Basic Prerequisites: 04 · Vectorized vs Event-Driven (the headline numbers) and Backtesting Hygiene (the statistical layer).


1. Intuition & Practical Objective

An event-driven engine removes the mechanical bugs of a vectorized backtest but does not, by itself, make a backtest true. It only moves the dishonesty from invisible arithmetic into explicit modelling choices that are still wrong by default. This page names the three that matter most - look-ahead inside the loop, optimistic fills, and survivorship - states each as a first-principles violation, and measures the money each one silently invents.

The objective is a practitioner’s rule: every number a backtest cannot refuse to make an assumption about is a number you must choose deliberately. The engine forces the choice; this page tells you which way is honest.


2. Mathematical Ground Truth & Derivations

Failure 1 - look-ahead inside the event loop. The loop’s invariant (page 02) is that an event processed at may depend only on events with timestamp . Two standard violations:

  • Compute-then-fill on the same event. The order emitted while processing bar fills against bar ‘s own quote. Formally it sets , i.e. , violating .
  • Index-forward in the data handler. Any code path that reads bars[t+1] (to normalise a feature, to compute a “realised” label, to set a stop) is reading the future. A rolling statistic centred on () does this invisibly.

The measurable signature is the one from page 01: on a pure random walk the look-ahead backtest reports where the truth is . The correct conclusion is not “the strategy is weak”; it is “the engine is broken”.

Failure 2 - optimistic fills. The default fill assumptions and their true costs, for an order of shares at decision mid :

with realised execution shortfall in basis points

The capacity fiction is the sibling: a fill of shares against a bar of volume under participation cap can only ever be

A vectorized backtest sets and implicitly. For a -share order into a -share bar at , - the naive fill understates the working time by , and with it the market impact and the timing risk.

Failure 3 - survivorship. A replay over today’s universe holds only names that survived to today. If a fraction of the universe delists each year with loss , the survivor-only series omits a drag of roughly per year. Over years,

so the inflation compounds - it is not a flat haircut.

The practice checklist (an engine is only trustworthy if all hold):

  1. Signals use data with timestamp ; the engine’s clock equals the latest processed timestamp (assert it).
  2. for every fill (assert it - the loop guarantees it only if you do not shortcut it).
  3. Fills cross the spread; passive fills require the queue-ahead condition of Queue Position & Fill Probability.
  4. with stated and defended.
  5. The universe is point-in-time: delisted and added names present as of each date.
  6. Costs (commission, borrow, financing) are charged as events, not applied at the end.
  7. The run is bit-for-bit reproducible from the seed and the data file.

3. Computational Implementation - the three failures, in dollars

One script, three experiments: the fill-assumption cost, the capacity requirement, and the survivorship inflation. Standard library only.

Read the three results.

  1. Fills. A mid-price assumption is free; crossing a 2-cent spread is bps; realistic slippage is bps. On a strategy trading daily, 2 bps 252 round trips is 5% per year of pure friction - larger than most “edges.” The engine must be told to cross.
  2. Capacity. The order is 25% of ADV. The engine’s 5% cap turns it into bars of work. A vectorized backtest fills it in one bar and thereby reports a strategy that could never have been executed at that size. (The market impact of working the order is developed in Execution, Backtesting & Simulation.)
  3. Survivorship. Erasing 5% of names per year, all of which went to zero, inflates terminal wealth from to - a overstatement, purely from which names appear in the data file. This is the failure that lives in the DataHandler’s universe definition, not in the loop, which is why an event-driven architecture alone does not fix it.

4. Failure Modes & First-Principles Breakdowns (numbered)

  1. Filling on the generating event. Sets ; the most direct look-ahead in the loop. Assert at runtime.
  2. Centred rolling windows. A rolling(..., center=True) feature, or any statistic that sees both sides of , leaks the future into the signal - inside a perfectly correct event loop. Audit every data derivation, not just the engine.
  3. Mid-price fills. bps is not a conservative default; it is an assumption of a free option. Fill against the side you would actually cross.
  4. Infinite capacity. Every engine must cap fills by bar volume, and the cap must be stated; a backtest whose has no upper bound is not simulating a market.
  5. Point-in-time universe blindness. Survivorship is a data bug that no loop can detect. Use point-in-time index membership and include delisted names (see Tick-Level Databases & Time-Series for as-of joins).
  6. Costs applied at the end instead of per event. Commission/financing applied as a terminal adjustment misses that costs reduce the capital available to compound. Charge them as events, in the loop.
  7. Non-reproducible runs. A backtest that cannot be re-run bit-for-bit is a rumour. Fix the seed, fix the tie-break rule, freeze the data snapshot.
  8. Fixing the mechanics and stopping. With all of the above correct, the reported Sharpe is still the maximum of a search - deflate it (04 · The Deflated Sharpe Ratio).

5. References

  • López de Prado, Marcos, Advances in Financial Machine Learning (Wiley, 2018)
  • Halls-Moore, Michael, QuantStart event-driven series
  • Almgren & Chriss, Optimal Execution of Portfolio Transactions (2001)
  • Elton, Gruber & Blake, Survivorship Bias and Mutual Fund Performance (1996)
  • NautilusTrader Docs

6. Connected Graph Bridges