Look-ahead bias in backtesting: five ways a strategy can use the future
Suppose I let you buy a stock at its opening price, but tell you its closing price before you decide.
Buy only on the days when the close is above the open. Sit in cash on the others.
The resulting chart looks excellent. The decision also requires information you could not have had at the stated execution time.
1. Choose today's trade using today's outcome
For session t, let O be its open and C its close. A cheating long-or-cash target is one when C exceeds O, and zero otherwise.
Before costs its intraday return is:
Every losing intraday return is removed after observing it. This is an outcome-selection rule.
On our seeded synthetic fixture, compounding those selected intraday returns over sessions 80–1511 turns one dollar into approximately $315.07 before costs. The total gain is about 31,406.63%. This calculation excludes overnight exposure and creates a fresh intraday position each selected session.
It is intentionally impossible. It is also different from the fixed moving-average strategy's execution process, so comparing the two does not estimate a pure “bias premium”. It demonstrates how easily future information creates an attractive chart.

Consider four invented intraday returns: +2%, −1%, +1%, −2%. Always holding intraday produces approximately −0.05% compounded. Selecting only the positive days produces +3.02% before costs. You did not improve a forecast; you supplied the answer.
2. Use a centred indicator
A centred moving average places observations from both sides of a date around that date. It is often useful for retrospective description. It is unavailable in that form for a decision made at the date itself.
For an odd window of five, a centred mean at session 100 includes sessions 98–102. Sessions 101 and 102 are future observations relative to session 100.
rolling(5) to rolling(5, center=True) can create this problem without an obvious error message.The repair is more precise than “shift everything once”. First use an indicator whose required observations are available. Then align the signal with the intended execution time. A one-row shift does not repair a feature that still looks two rows ahead.
import pandas as pd
close = pd.Series([100., 101., 102., 103., 104.])
trailing = close.rolling(3).mean()
centred = close.rolling(3, center=True).mean()
print(trailing.iloc[2], centred.iloc[2])
# 101.0 102.0The centred value at index two uses the $103 observation at index three. The trailing value uses only indices zero through two. Neither should control an opening fill that happened before those closes became known.

3. Fit a transformation on the whole dataset
Suppose you standardise a feature by subtracting its mean and dividing by its standard deviation. If you estimate those quantities using the full dataset, early transformed values depend on later observations.
The same issue applies to imputation, feature selection, dimension reduction and learned thresholds. A chronological train/test split does not repair preprocessing performed before the split.
Fit each learned transformation using only the permitted training data. Apply the fitted transformation to the evaluation observations without refitting on them. If your live process updates a transformation, define when those updates happen and reproduce that schedule.
There is no rule that every statistic must remain frozen forever. A trailing mean can update with newly completed observations. The constraint is that each update uses information available at its decision time.
4. Give old dates a modern universe or a revised fact
Imagine testing a stock strategy from ten years ago using today's surviving index members. You know which businesses lasted and which joined later. That can alter the opportunity set in a way the historical investor could not reproduce.
A similar problem appears in fundamental and economic data. The period a number describes is not necessarily its publication time. A December earnings figure may be announced months later. A revised economic series may differ from the first value available to traders.
Keep separate fields for the observation period, publication time and revision vintage when the source supports them. Join a fact to a trading decision only after its release is available under your latency assumptions.
5. Select the winner after inspecting the test
You fit five moving-average windows on earlier data. Choose one and run it on later data. Then it disappoints, so you try another threshold, add a volatility filter and repeat.
Those later observations now influence development. Calling them “out of sample” does not erase that influence.
This is a research-selection problem, even if every individual trade has a causal signal. The distinction matters: correct event timing and independent evaluation are separate requirements.
Make the future-change test routine
Choose a cutoff. Compute your signals using the full data, then compute them again with everything after the cutoff removed. The signals before the cutoff should agree for our trailing-window rule.
import pandas as pd
from backtest import signals
data = pd.read_csv('synthetic-prices.csv')
full = signals(data, 20).iloc[:800]
prefix = signals(data.iloc[:800], 20)
print(full.equals(prefix))
# TrueIt is a necessary check for this causal indicator, not a complete certificate. It does not prove point-in-time data quality, realistic fills or an untouched research holdout. A causally computed signal can still trade at an impossible price. An historical dataset can already contain revised values before your function sees it.

An out-of-sample chart can still leak
Chronological partitioning protects a particular boundary. Leakage can cross it through full-sample transformations, labels that span the boundary or decisions made after viewing evaluation outcomes.
Draw the dependency chain for one order: source observation, release, feature, model fit, signal, submission and execution. Then draw the dependency chain for the model choice. Both should be consistent with the story you tell about the experiment.
Until then, peace.
Go deeper with lessons, derivations and worked exercises behind this article.