CuteMarkets Docs

Backtesting Framework

Framework guides for engineers building realistic options backtests with causal data, quote-aware fills, and robust validation.

Tip: open /docs/backtesting-engine-loop.md directly for raw markdown (easy copy/paste into an LLM).

Backtesting Engine Loop

The engine loop is the causality contract of the framework. It decides what data the strategy can see, when a signal exists, when an order can be filled, and how the exit is resolved.

Daily replay shape

For intraday options strategies, a clear loop usually looks like this:

bash
for day in trading_days:
    session = load_underlying_session(ticker, day)
    daily_context = load_daily_context(ticker, day)
    option_context = load_contract_context(ticker, day)

    setup = strategy.find_setup(session, daily_context)
    if setup is None:
        record_skip(day, "no_setup")
        continue

    exit_plan = strategy.resolve_exit(session, setup)
    trade = option_engine.try_express_setup(
        ticker=ticker,
        day=day,
        setup=setup,
        exit_plan=exit_plan,
        option_context=option_context,
    )
    record_trade_or_rejection(trade)

The exact implementation can be more complex, but the direction should stay the same: load the causal state, find a setup, choose an instrument, simulate execution, then log the result.

Signal time and entry time

The most important rule is that a completed bar can create a signal, but it cannot also give the strategy an intrabar fill before the signal existed. If a signal is generated on bar t, the conservative default is entry on the next observable bar or quote after t.

For example, an opening-range breakout can:

  1. Build the opening range from completed bars.
  2. Detect a breakout after the range is known.
  3. Set signal_ts to the completed signal bar.
  4. Set entry_ts to the next bar open or the next quote that passes the execution policy.

This rule is not cosmetic. Same-bar entry is one of the easiest ways to overstate a momentum strategy.

Engine boundaries

Keep these records separate in the code:

RecordShould contain
ConfigStrategy parameters, option filters, risk limits, and fill rules.
Session stateUnderlying bars and context available for the day.
SetupDirection, signal timestamp, entry timestamp, underlying entry price, and feature metadata.
Exit planExit timestamp, stop/target result, and reason.
Option attemptSelected or rejected contract, structure, quote evidence, and rejection counters.
TradeFinal quantity, entry price, exit price, PnL, return, and audit metadata.

When a skip happens, log it. No-trade days are part of the result set because they explain coverage, density, and feasibility.

Strategy families

The public cutebacktests runtime can support different signal families as long as they share the same replay contract:

  • opening-range breakout and continuation
  • VWAP and z-score mean reversion
  • relative-strength or proxy-relative logic
  • daily forecast profiles expressed through options

The strategy family changes the setup. It should not change the framework's causal discipline.

Failure modes to test

Engine tests should cover:

  • no bars for a day
  • setup exists but no eligible contract exists
  • contract exists but no quote window exists near entry
  • signal timestamp after allowed entry cutoff
  • entry timestamp at or after exit timestamp
  • exit target observable only after the relevant timestamp
  • multiple symbols trading on the same calendar day

Read next: Backtesting Execution Realism and Backtesting Test Plan.

Next steps

Move from the docs into the product workflow

If you are evaluating the API rather than implementing a specific endpoint right now, the product pages map the live, historical, and chain workflows directly.