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-execution-realism.md directly for raw markdown (easy copy/paste into an LLM).

Quick definition: execution realism is the part of an options backtest that decides whether the selected contract could actually be entered and exited at the modeled prices.

Execution realism is the difference between a signal backtest and a tradable options backtest. Options do not trade continuously across every strike. Last price can be stale, midpoint can be optimistic, and a few cents of spread can dominate a short-dated trade.

Why this matters

The strategy can be directionally right and still fail as an options trade. A call can move in the expected direction while the spread, quote age, DTE, or size makes the simulated entry unrealistic. A fill model that ignores those details is not merely optimistic; it changes what the backtest claims to measure.

A useful execution model records both the price and the reason that price was allowed. If the simulator buys near the ask, exits near the bid, uses midpoint with a haircut, or rejects the trade entirely, the trade log should say so.

Bars, quotes, and trades

Use each data object for the job it answers:

DataBest useDo not use it for
Underlying barsSignal features and underlying stop/target logic.Option fill prices.
Option barsOption price path and fallback valuation.Proof that a specific bid/ask fill existed.
Option quotesExecutable bid/ask context and spread quality.Long-term signal features without freshness checks.
Option tradesActivity evidence and last-sale context.Universal fill proxy.

A serious framework often uses bars for path and quotes for fills. For a long option, entry near the ask and exit near the bid is more conservative than assuming midpoint both ways. More advanced models can allow midpoint or price improvement only when quote freshness, spread width, and trade evidence support it.

Fill policy

Make the fill model explicit:

bash
def long_option_entry_fill(bid: float, ask: float, mode: str) -> float:
    if ask <= 0 or bid < 0 or ask < bid:
        raise ValueError("invalid_quote")
    if mode == "marketable_limit":
        return ask
    if mode == "mid_with_haircut":
        return (bid + ask) / 2 + 0.25 * (ask - bid)
    raise ValueError(f"unsupported_fill_mode: {mode}")

The important part is not this exact formula. It is that the simulator records what side of the market was used and rejects quotes that do not pass the policy.

Fill model table

Fill modelEntry assumptionExit assumptionBest use
Midpoint referenceMidpoint only as a diagnostic value.Midpoint only as a diagnostic value.Comparing theoretical edge before execution costs.
Midpoint with haircutMidpoint plus part of spread.Midpoint minus part of spread.Conservative research when quotes are fresh and tight.
Marketable long optionBuy at or near ask.Sell at or near bid.Stress-testing whether a long-premium idea survives crossing spreads.
Quote rejectNo fill when quote is stale, crossed, missing, or too wide.No exit fill until observable.Keeping weak liquidity from becoming fake PnL.

Use one model across a comparison. Do not let the winning profile use midpoint while a later profile is judged with marketable assumptions.

Stops and exits

Stops and targets must be observable. If a premium stop uses option quotes, the framework should check quotes after entry and apply the stop only when a quote pair proves the stop level was reachable. If an underlying stop uses underlying bars, the option exit still needs a corresponding option quote or option bar at the exit time.

Common exit reasons:

  • profit target
  • premium stop
  • underlying stop
  • time stop
  • end-of-day exit
  • max-hold exit
  • invalid or missing quote
  • forced rejection because entry would occur after exit

Every exit should preserve the timestamp and price source.

Spread and slippage

Track cost in dollars, not only percentages. A 0.10 option spread is 10 dollars per contract before commissions. For short-dated options, that may be larger than the expected edge.

Useful execution metrics include entry spread percentage, exit spread percentage, spread as a share of premium, expected move to spread ratio, entry quote age, exit quote age, rejected trade count by reason, and fill source. These metrics should sit beside PnL, Sharpe, drawdown, and win rate so a reader can see whether returns came from tradable markets or optimistic assumptions.

Rejecting trades is success

A realistic simulator should reject trades when the market is not good enough. That can reduce trade count and make performance less exciting, but it improves the scientific value of the result.

Good rejection reasons include:

  • no quote near entry
  • quote crossed or invalid
  • spread too wide
  • ask below minimum premium
  • entry after effective exit
  • stop or target cannot be priced
  • quantity below one after risk caps

Specific rejects make no-go research useful. If a strategy fails because the signal is wrong, the next experiment is different from a strategy that fails because the option market is too wide.

Read next

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.