How to check a backtest for lookahead bias
Short answer
Run the strategy twice: once on the full data, and once with everything after a chosen moment removed or changed. Every decision made before that moment must be identical in both runs. If one decision moves, the strategy used data it could not have had.
What lookahead bias looks like
Lookahead bias means a decision in the backtest depends on something that did not exist yet. It rarely looks like cheating in the code. It is usually one innocent line, and it makes the equity curve look better than any live result could be.
We learned this the hard way. In our own research, some of the best-looking intraday results turned out to be leaks of this kind, which is why these checks now run on every strategy.
These are the usual places it hides:
- A signal computed from a bar’s close and filled at that same close, or anywhere inside that same bar.
- A higher-timeframe value, such as a 15-minute bar or the daily high, read before that bar has finished.
- A session level, such as the day’s VWAP or volume profile, taken from the finished session instead of the session so far.
- An indicator normalised with the mean or range of the whole dataset.
- A front-month contract picked with volume from later in the same day.
Four tests that find it
- Cut the future. Pick several moments across the history. For each one, run the strategy again on the data up to that moment only, and compare the decision logs. Anything before the cut must match exactly.
- Change the future. Keep the data up to a chosen moment and scramble or reshape what comes after it. Earlier decisions must not move. This catches code that treats missing data differently from real data, which can slip past the first test.
- Fill after the signal. A signal from a completed bar can fill at the next bar’s open at the earliest. If your backtester fills at the signal price, every result is too good.
- Replay bar by bar. Feed the strategy one completed bar at a time through an event loop, the way a live engine would, and compare trades, fills, exits and profit with the fast backtest. Any difference means the fast version knew something a live engine would not.
What these tests do not catch
They find leaks in the code and the data pipeline. They do not find selection bias: a strategy can be perfectly causal and still be the luckiest of a thousand variants. That takes the overfitting checks.
Test more than one moment, too. A leak in a rarely used branch only shows up at the moments that reach that branch.
How BenchAlgo handles it
- Strategies only receive completed bars, one event at a time. Reading at or past the current moment raises an error that voids the run, even if the code catches it.
- The forming session profile and the session VWAP are computed through the current moment only.
- The harness owns fills and the choice of contract. A strategy cannot pick either one.
- Every run is checked for leaks of later data. A failed check blocks an idea from becoming a Lead or Validated.