Look-ahead bias: the backtest that lies to you
The most expensive bug in trading code never shows up as an error. It shows up as a beautiful equity curve that quietly used information it couldn’t have had. Here’s how it sneaks in.
The worst bug in a trading strategy doesn’t crash. It doesn’t throw an error or paint a warning. It produces a gorgeous equity curve, a smooth climb that makes you feel like you’ve finally cracked it. And it’s completely fake, because somewhere in the logic the strategy used information it could not possibly have had at the time. That’s look-ahead bias, and it’s the most expensive mistake in backtesting precisely because it disguises itself as success.
What it actually is
Look-ahead bias is when your backtest, running over history, peeks at the future. Not on purpose. It happens because in historical data the future is just sitting there, fully known, and it’s frighteningly easy to let it leak into a decision the strategy makes in the past.
The classic version: your code makes a decision using a candle’s closing price, but acts as if the decision was made at the candle’s open. In hindsight the close is known. Live, at the open, it hasn’t happened yet. So the backtest gets to decide using a number that, in real time, wouldn’t exist for another few minutes or hours. Every one of those decisions is made with a glimpse of the answer.
Why the curve looks so good
A strategy that can see a little way into the future is, unsurprisingly, brilliant. It buys right before the up-move because it already knows the up-move happened. It dodges the loss because it already saw the loss coming. The equity curve is beautiful, and it’s beautiful for the one reason that guarantees it’ll never work live: it cheated, and you can’t cheat in real time.
This is what makes look-ahead bias so dangerous. Other bugs make results worse, so they get noticed and fixed. This one makes results better, so it gets celebrated, and the trader ships a strategy that has never once made a decision under real conditions.
Where it hides
It creeps in through a few familiar doors. Using a bar’s close to make a decision that’s supposed to happen earlier in that same bar. Pulling a higher-timeframe value that hasn’t actually finished forming yet, so the strategy “knows” where the daily candle closed before the day is over. Calculating something off the full dataset, like a level based on a range that includes future bars, and then using it in the past. In every case the tell is the same: a number is available to the decision that, in live trading, wouldn’t exist yet.
How to catch it
The mental test is simple and you should run it on every rule. Stand at the exact moment the decision fires and ask: would I genuinely have this number right now, or am I borrowing it from a future that hasn’t happened? If there’s any doubt, force the strategy to act on the previous fully-completed bar rather than the one still forming. It feels like giving up an edge. It isn’t. It’s removing an edge you were never actually going to have.
The honest test is whether the strategy can make each decision using only what was knowable at that instant. A curve that survives that constraint might be real. A curve that needed the future to look good was always going to leave its money in the backtest, where the future is free.
The useful version
The fastest way to find look-ahead bias is to narrate the decision from the candle’s point of view.
At the exact bar where the entry fires, ask: what values exist right now? The current close may not exist yet. A future pivot certainly does not. A higher-timeframe candle may still be forming. If the strategy uses any of those as if they are final, the backtest is being paid with future information.
A common Pine smell
Be suspicious when code mixes higher-timeframe requests, offsets, and historical plotting without an explicit timing rule:
htfClose = request.security(syminfo.tickerid, "D", close)
safeDailyClose = request.security(syminfo.tickerid, "D", close[1])
The second form is boring because it waits for the prior completed daily close. Boring is often correct. If you want the current developing daily value, label it as developing and do not backtest it like a final value.