CuteMarkets Docs

Backtesting Framework

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

Docs

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

Read this page with Backtesting Framework, Backtesting Data Model, Backtesting Execution Realism, Historical Options Replay Runbook, and Backtesting Test Plan.

Quick definition: the engine loop is the causality contract that decides what data the strategy can see, when a signal exists, when an order can be filled, and how the exit is resolved.

The engine loop is where a backtest either becomes a replay or becomes a story about the future. A useful loop walks the market forward, loads only the state available at each decision, records skips, and refuses to fill trades that cannot be supported by the configured market data.

Why this matters

Most attractive backtest bugs are timing bugs. A completed bar creates a signal, but the simulator fills at a price that existed before the signal. A stop is marked hit before the option quote used to prove it. A daily filter uses the current session close. These are small implementation details, but they change the scientific meaning of the result.

The engine loop should make those errors hard to express. It should separate setup detection, entry timing, contract selection, execution, exit resolution, and logging.

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. If a framework intentionally supports standing orders, that state should be explicit before the bar completes.

Engine boundaries

Keep these records separate in the code:

RecordShould containShould not contain
ConfigStrategy parameters, option filters, risk limits, and fill rules.Results from the current run.
Session stateUnderlying bars and context available for the day.Future bars or final day summary.
SetupDirection, signal timestamp, entry timestamp, underlying entry price, and feature metadata.Contract-specific fill assumptions.
Exit planExit timestamp, stop/target result, and reason.Final option exit price before quote validation.
Option attemptSelected or rejected contract, structure, quote evidence, and rejection counters.Strategy feature computation.
TradeFinal quantity, entry price, exit price, PnL, return, and audit metadata.Hidden fallback decisions.

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

Implementation checklist

StepRequired behaviorReject or skip reason
Load barsVerify session coverage before signal generation.missing_underlying_bars
Build setupUse completed bars and predeclared context only.no_setup
Resolve entryEnter after signal time, not before it.entry_after_cutoff
Discover contractsUse point-in-time contracts and DTE filters.no_contracts_in_dte_window
Validate quoteCheck bid, ask, spread, size, and quote age.quote_missing_near_entry
Resolve exitPrice target, stop, or time exit with observable data.exit_quote_missing
Log resultPreserve fill source, timestamps, and rejects.artifact_write_failed

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. If one family needs a special entry rule, make that rule visible in the profile and test it directly.

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, and multiple symbols trading on the same calendar day.

The test names should read like a checklist for future developers. A failing test should tell them whether the engine leaked time, selected the wrong universe, filled without evidence, or computed the wrong portfolio unit.

For production research, the loop should emit artifacts that can be inspected with the Backtesting Data Model, checked against the Backtesting Data Quality Checklist, and regression-tested with the Backtesting Test Plan. That keeps point-in-time universe selection, contract eligibility, quote-aware fills, reject counters, and portfolio attribution connected.

Read next

Engine loop implementation notes

The engine loop is easiest to audit when each phase writes a separate record. Signal generation writes the underlying ticker, indicator window, and signal timestamp. Contract selection writes the Contracts request with as_of, expiration, strike, side, DTE, and moneyness. Market evidence writes Quotes, Trades, and Aggregates with their own timestamps. Execution writes the fill rule and rejects.

Keep the loop causal. A bar that closes at 10:35 cannot create a 10:34 decision. A chain snapshot from today cannot define a 2024 contract universe. A trade print cannot become an executable fill unless the quote window supports it. The engine loop should make those checks routine, not special cases reserved for final review.

Terminology

Terms to keep straight on this page

Market-data APIs use similar words for different objects. These links keep the docs page connected to the precise CuteMarkets workflow and related reference material.

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 live and historical workflows for stocks, options, and WebSockets.