If you have ever built a strategy with a gorgeous equity curve that fell apart the moment you traded it live, you probably did not have a bad strategy. You had a leak.
Look-ahead bias is the bug that makes a backtest lie. Future information sneaks into a decision the strategy could not have known at the time, the historical results look incredible, and live trading is where reality collects the bill. ML people call it data leakage. Traders call it repainting. Same disease.
I build a backtesting engine, so I have stepped on every one of these. Here are the six ways future data leaks in, from the obvious to the ones that quietly destroy you.
1. Signaling on the bar that has not closed yet
The classic. You evaluate a condition on close[0], the current bar, and act on it. But in real time that bar is not finished. Its close is still moving. You are effectively deciding with information from the future of that candle.
The fix is boring and absolute: evaluate on close[1], the last confirmed bar. Every signal, every indicator input. If your backtest uses the forming bar anywhere, the numbers are fiction.
2. Filling inside the bar you used to decide
Subtler. Say your rule is "enter when price breaks the high of the previous range." You detect the break using a bar's high, then you fill the entry at a price inside that same bar. But you only knew the high existed because the bar already printed it. In live trading you would have been filled on the way up, at a worse price, or not at all.
A candle gives you O, H, L, C and nothing about the path between them. If your entry and your detection both depend on the same candle's extremes, you are reading the future. Decide on the closed bar, fill on the next bar's open, and the lie disappears.
3. Repainting indicators
Some indicators revise their own history. A value that showed X two bars ago now shows Y because new data arrived. ZigZag, certain pivot detectors, anything that anchors to a later confirmed point, and a lot of the flashier "smart money" tools.
If you backtest against the repainted (final) values, you are testing on data that did not exist at decision time. The indicator looks psychic because it is. Test only against the value the indicator would have shown live, on the bar it would have shown it.
4. Normalizing with statistics from the whole series
This is the one that catches the ML crowd. You z-score your feature: subtract the mean, divide by the standard deviation. Easy. Except you computed the mean and standard deviation over the entire dataset, including data that comes after each point.
Every normalized value before today now carries a whisper of the future. Your model trains on it and looks brilliant in backtest. The fix is rolling, point-in-time statistics: at each bar, use only what was known up to that bar. Slower, correct.
5. Optimizing on the same data you report
You try 500 parameter combinations on 2016 to 2026, pick the best Sharpe, and report that Sharpe. You did not find an edge. You found the combination that best fit the noise of that exact decade. Run it forward and it reverts to mediocre.
This is just overfitting wearing a backtest costume. Walk-forward analysis is the honest answer: optimize on a window, test on the next unseen window, roll forward, and report only the out-of-sample results.
6. Survivorship bias in the universe
You backtest a stock strategy on the instruments that exist today. But the ones that delisted, went to zero, or got acquired are missing. Your universe is pre-filtered for survivors, so your results skew up. You need point-in-time universe data, the list as it was on each date, not the list as it is now.
Why you cannot fix this with discipline
The tempting conclusion is "just be careful." That fails, because look-ahead bias is the default, not the exception. Every shortcut leaks. The current bar is right there. The full-series mean is one function call away. Careful breaks down the moment you are tired or moving fast.
The only thing that holds is architecture. In the engine I work on, a strategy literally cannot reference the forming bar. The evaluation step is causal by construction: it hands a block only the data that existed at that timestamp, so there is nothing future to leak. Anti-repainting is not a checkbox you tick, it is a property of the system that you make impossible to violate. That is also why I built the no-code backtesting tool I am building to enforce close[1] at the engine level rather than trusting the user to remember.
If you take one thing from this: a backtest is only as honest as the information boundary it respects. Make that boundary impossible to cross, and the curve you see is the curve you can actually trade.
What is the worst leak you have shipped to production? Mine was the z-score one. Looked like genius for a week.
Top comments (0)