Backtesting Framework
This knowledge base explains how to design a realistic options backtesting framework. It is written for engineers who want to build or audit a simulator, not for readers looking for a trading recommendation.
The public reference implementation is cutebacktests. It packages an intraday/options runtime, opening-range profile helpers, provider adapters, persistence, and robustness utilities. cute-intraday-option-strats is a narrower strategy package on top of that runtime.
Architecture spine
A credible framework keeps five responsibilities separate:
| Layer | Job |
|---|---|
| Data model | Store point-in-time underlyings, contracts, quotes, trades, bars, and coverage metadata. |
| Replay engine | Walk the market forward using only information available at each simulated timestamp. |
| Strategy layer | Turn historical state into a setup, signal timestamp, direction, and exit policy. |
| Instrument expression | Convert an underlying setup into an option contract or structure. |
| Research layer | Run folds, holdouts, diagnostics, and portfolio aggregation without changing simulator rules. |
This separation is the main design rule. A strategy should not know how the option provider paginates. A contract selector should not compute signal features. A research sweep should not rewrite fill semantics mid-run. When those boundaries blur, it becomes hard to tell whether a result came from edge, leakage, or a convenient simulator shortcut.
Minimum viable framework
The smallest useful options framework needs:
- Historical contract discovery by simulated date, not today's chain.
- Underlying bars for signal generation and option quotes or bars for execution.
- A replay loop that makes signal decisions before entry decisions.
- A contract selector with explicit DTE, moneyness, spread, open-interest, and volume rules.
- A fill model that records the side of the market used and rejects untradable conditions.
- A trade log with enough metadata to explain skipped trades and filled trades.
- Walk-forward or holdout tests that run the same engine rules outside the selection window.
In cutebacktests, the public shape is intentionally explicit:
from datetime import datetime
from cutebacktests import (
IntradayOptionsBacktestConfig,
IntradayOptionsBacktester,
get_opening_range_profile,
)
profile = get_opening_range_profile("c4_long_only_rr15")
config = IntradayOptionsBacktestConfig(
ticker="SPY",
start=datetime(2025, 1, 1),
end=datetime(2025, 1, 31),
return_trade_log=True,
**profile.to_intraday_strategy_kwargs(),
)
The concrete profile can change. The framework standards should not.
Private-to-public naming
Some early internal research code used legacy names tied to its original project history. The public extraction uses behavior-based names instead: IntradayOptionsBacktester, IntradayOptionsBacktestConfig, IntradayStrategyConfig, cutebacktests.backtest.intraday_options, and cutebacktests.strategies.intraday. Public docs should use the public names.