Systems Architecture · Published
Designing an Event-Driven Backtesting Engine
How to separate data, strategy, risk, execution, and accounting so a simulator stays deterministic, testable, and honest about fills.
01
Start with the question the engine must answer
A backtesting engine is not automatically better because it is event-driven. The architecture is valuable when the research question depends on ordering, state, path-dependent rules, or execution assumptions that a single vectorized calculation would hide.
Before choosing components, define the required fidelity. A daily allocation study may need only a clear rebalance clock and conservative close-to-next-open assumptions. An intraday execution study needs order lifecycles, latency, queue assumptions, and much richer data. Building the second system for the first question adds complexity without adding evidence.
02
Use a small event vocabulary
Events should describe changes in domain state, not mirror every method call. A compact vocabulary might include market data, timer, signal intent, approved order, execution report, and corporate action. Each event carries an event timestamp, a processing sequence, a source, and a schema version.
The processing sequence is essential when timestamps tie. Determinism requires the engine to make the same choice every time, whether a timer fires before a bar update or an order cancellation arrives before a fill. That ordering rule belongs in the simulation contract.
03
Keep domain responsibilities separate
The data adapter advances the information set. The strategy reads state and emits intent. The risk layer applies constraints. The execution model turns orders into fills under explicit assumptions. The portfolio ledger records transactions and derives holdings, cash, and exposure.
This separation prevents a strategy from quietly changing portfolio state or assuming a fill. It also lets the same strategy be evaluated under multiple cost and execution policies without rewriting signal logic.
- Data is responsible for availability, calendars, and corporate-action inputs.
- Strategy is responsible for decisions from the permitted information set.
- Risk is responsible for pre-trade constraints and portfolio-aware resizing.
- Execution is responsible for latency, fill eligibility, costs, and partial fills.
- Accounting is responsible for an auditable transaction and valuation history.
04
Make execution assumptions first-class
A fill at the same close that created a signal is not a neutral default. It is an assumption about observation, decision, order transmission, liquidity, and price formation. The engine should make that policy visible in configuration and in every result manifest.
Start with the simplest conservative model that answers the question. Then add spread, slippage, volume participation, latency, or partial fills only when the available data can support them. A detailed formula fed by coarse bars can create false precision rather than realism.
05
Treat accounting as a ledger
Mutable position summaries are convenient, but a transaction ledger provides a stronger source of truth. Fills, fees, dividends, splits, transfers, and financing events are recorded as immutable entries. Positions and cash become derived views that can be rebuilt and reconciled.
This design supports invariants. Cash changes must reconcile with fills and charges. A position cannot appear without a transaction. Realized and unrealized P&L must bridge between valuation points. These checks catch errors that a plausible equity curve can conceal.
06
Build confidence from small deterministic tests
The most valuable tests are often tiny. Use a two-asset, five-event scenario with hand-calculated outcomes. Check a split, a partial fill, a rejected order, a fee, and a missing price independently before running years of data.
Golden event traces help review full behavior: given an input stream and configuration, the engine should produce the same ordered output. Property tests can assert conservation and boundary rules across many generated sequences. Integration tests should then cover dataset adapters and run manifests.
07
Scale at the experiment boundary
A deterministic engine is easiest to reason about when one simulation owns its state. Parallelism can be introduced by running independent parameter sets, universes, or folds in separate workers. Shared mutable state inside one run should be a last resort.
An event-driven engine earns its complexity when it exposes the assumptions that affect a decision. Its quality is measured less by how many events it processes than by whether another person can reproduce the run, inspect the ordering, challenge the fill model, and reconcile the result.