Backtesting Execution Realism
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.
Bars, quotes, and trades
Use each data object for the job it answers:
| Data | Best use |
|---|---|
| Underlying bars | Signal features and underlying stop/target logic. |
| Option bars | Option price path and fallback valuation. |
| Option quotes | Executable bid/ask context and spread quality. |
| Option trades | Activity evidence and last-sale context. |
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:
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.
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:
- 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
- fill source: quote, bar fallback, or proxy
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/target cannot be priced
- quantity below one after risk caps
Read next: Quotes, Trades, Backtesting Robustness, and Options Backtest Realism Checker.