Skip to main content
All articles
Systems Architecture

How a Backtesting Engine Works: Event Loop, Fills and Costs

Inside a backtest: the event loop, how fills are simulated, why lookahead bias appears, and what separates a useful engine from a plausible one.

By Stretus Research3 min read

What does a backtesting engine actually do?

It replays historical market data in order, presenting each observation to the strategy exactly as if it had just arrived, collecting the resulting intents and simulating their execution, then reports the trades and the equity curve that would have followed.

The word "exactly" carries the design. Every property that makes a backtest trustworthy comes from the engine's discipline about what the strategy can see at each step.

The event loop, reduced to its essentials

text
for candle in ordered(history):
    # 1. advance the clock. the strategy cannot read beyond it
    clock = candle.timestamp

    # 2. resolve orders placed on PREVIOUS iterations.
    #    this ordering is the point: an order decided at t
    #    cannot fill at t's close, only at t+1's open or later.
    fills = simulate_fills(open_orders, candle)
    portfolio.apply(fills, costs=cost_model(fills))

    # 3. NOW evaluate. the strategy sees candles up to and
    #    including this one, and nothing after it.
    intent = strategy.evaluate(portfolio.state, view_until(clock))

    # 4. gate and queue. not filled in this iteration.
    if intent and risk_gate.approve(intent, portfolio):
        open_orders.append(to_order(intent, clock))

Why is the order of steps 2 and 3 so important?

Because resolving fills before evaluating the strategy is what prevents an order from being filled at a price the strategy used to decide on it. Reverse the two and you get an engine that reports edge no live system can reproduce.

This is lookahead bias in its most common form, and it is easy to introduce accidentally when the loop is written for convenience rather than correctness. Making it structural, the strategy is handed a view of data that physically ends at the current timestamp, removes an entire class of bug.

How should fills be simulated?

Conservatively, and transparently. A market order decided on candle N should fill at candle N+1's open, adjusted for slippage. Filling at candle N's close assumes you traded at a price you only knew after the fact.

Limit orders need more care: a fill is only plausible if the candle's range actually traded through your price, and even then a touch is not a guarantee in a thin market. Engines that fill any limit whose price falls inside the candle range are generous in a way that shows up as a backtest edge and a live loss.

Fill assumptions, ranked by how much they overstate results

AssumptionRealismEffect on results
Fill at signal candle closeNone, uses unknowable dataLarge phantom edge
Fill at next open, no slippagePartialOptimistic, scales with trade frequency
Fill at next open + fixed slippageReasonable baselineUsable
Fill at next open + volatility-scaled slippageBetterPenalises trading in fast markets, correctly
Limit filled if price touchedWeak in thin booksOverstates limit-order strategies

What is the difference between vectorised and event-driven backtests?

A vectorised backtest computes signals across the whole series at once, fast, but unable to represent state that depends on the path taken. An event-driven backtest steps candle by candle and can model trailing stops, partial fills and position-dependent sizing.

Vectorised runs are excellent for screening many parameter sets quickly. They cannot correctly model a trailing stop, because the stop's level depends on the highest price reached since entry, a path-dependent quantity. If a strategy uses one and the backtest is vectorised, the result is not describing that strategy.

What should an engine disclose?

Its fill model, its cost model, the data resolution, and the exact window tested. Without those four, a return figure is not interpretable and cannot be compared with anything.

This is a reasonable test to apply to any platform, including this one: if you cannot find out how fills were simulated, you cannot know what the number means.

References

  1. Advances in Financial Machine Learning, on backtest overfittingMarcos López de Prado, Wiley
  2. The Probability of Backtest OverfittingBailey, Borwein, López de Prado, Zhu, SSRN
  3. NSE, historical data and trading holidaysNational Stock Exchange of India
  • backtesting
  • architecture
  • engineering
  • validation

Topics

Written by

Stretus Research
Platform & Markets Team

The Stretus team writing on algorithmic trading, market structure and the systems that sit between a strategy and an exchange. Every claim about the platform links to the documentation that specifies it.

See these ideas running

The platform implements the workflow described here: strategies expressed as rules, backtested against historical data, paper traded, then executed through authorized broker APIs under platform-level risk controls.