- Speed & Reliability: Build an ultra-fast, robust, event-based options backtester.
- Memory Efficiency: Handle large tick/minute parquet datasets using DuckDB lazy fetches without preloading all strikes.
- No Look-Ahead Bias: The engine clock controls all data access. Future data is in memory but inaccessible via API.
- Standardized Interfaces: Strict protocols for
DataSource,Strategy, andBacktestEngine. - Parallelization-Ready: Isolate strategy state to allow
concurrent.futuresparameter sweeps.
Full architecture spec: See
ARCHITECTURE_FINAL.md(artifact) for the complete Phase 1 design. Deferred items: SeeBACKTEST_TODO.md(artifact) for Phase 2+ improvements.
| Decision | Resolution |
|---|---|
| Clock | Pure List[Any] — timestamps, integers, anything. Auto or user-defined |
| Cache | Full-day fetch always. Self-healing (lazy fetch on miss). Never exposed raw |
prefetch() |
Optional performance hint. Docstring: "cache fetches lazily anyway if skipped" |
| Price returns | (price, lag) tuple. lag=0 live, lag>0 stale, None/-1 = no data |
| Leg factory | Single add(strike, opt_type, side, qty=1) on Strategy base |
try_fill |
All-or-nothing. Accepts List[Leg] (auto-key) or Dict[str, Leg] (user-key) |
| Positions | Dict[str, Trade] — keyed open trades. Key freed on close. Collision → ValueError |
| State machine | IDLE → ACTIVE → DONE. try_fill owns IDLE→ACTIVE. Per-tick: adjust → exit_check → exit |
max_cycles |
Default 1. Cycle resets on on_exit. closed_trades accumulates across cycles |
| EOD force close | Always fires on last tick. on_day_end fires after |
on_day_start |
Gets DayStartContext (restricted: spot, atm, prefetch only). Returns False to skip day |
BarContext |
Created once/day, mutated per tick. Tick-bound accessors only. No raw cache |
- No Numba / preloaded NumPy arrays in the event engine.
- Pure Python dicts for engine state and data points at any bar.
- Lazy evaluation: DuckDB queries only when needed (or voluntarily prefetched).
- Never fetch the same instrument twice for the same day (cache is the guard).
- Prototype (
dummy_engine.py) validated: DuckDB lazy fetches <20ms; cache lookups nanoseconds. - Old design used underlying price dict as the loop clock — now separated (clock ≠ data).
preloadrenamed toprefetchto prevent misunderstanding it as a required step.FilledEntrywrapper class was removed —try_fillreturnsOptional[Dict[str, Trade]]directly.