Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor · · Updated 2026-08-01

Look-ahead bias for backtest wey dey kill am

Look-ahead bias na when future data enter your backtest. See how same bar decisions, survivorship, and revised figures dey inflate results, and how to test for am.

Look-ahead bias na di use of information inside a backtest wey nobody fit hold for hand when dem simulate di trade. Di equity curve go climb, di statistics go look correct, but e no go survive when real money enter di market. Dis page dey explain wetin dis bias be, show three ways wey e dey happen, and measure each one using real market data.

Wetin look-ahead bias be

A backtest na claim about a decision: for dis time, with dis information, di rule go do dis. Di claim dey correct only when every input wey di rule dey read don dey, for im final form, before dat time. Look-ahead bias na any break of dat condition, and e dey silent. Nothing for a spreadsheet or a script go complain when a row give back a value wey dem stamp later than di decision wey e feed.

Three breaches dey cover most of wetin dey happen for practice. A decision wey dey read a price bar wey e also trade inside. A universe wey dem assemble from names wey still dey listed today. A data field wey dem revise after di date wey e carry.

Mechanism one: deciding on a bar wey you also trade

Start with a rule wey sound harmless. Buy on strength, meaning on sessions wey di fund finish above where e open. If you code am carelessly, di test go enter for dat session's open and exit for dat session's close, while di condition wey e dey screen on na di closing price of dat same bar. Nobody know a closing price for the opening bell.

Di panel below dey run both versions on SPY, di S&P 500 tracker, across ten calendar years. Di first column na di impossible one. Di second one dey lag di same condition by one session, wey na di earliest wey a funded account fit act on am.

QuerySame bar decision vs one session lag: SPY, average session gain, 2016-2025
The exact SQL behind every number
WITH bars AS (
    SELECT date,
           toFloat64(open) AS o,
           toFloat64(close) AS c,
           toFloat64(close) > toFloat64(open) AS up_day
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
lagged AS (
    SELECT date, o, c, up_day,
           any(up_day) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prior_up
    FROM bars
)
SELECT toYear(date) AS year,
       countIf(up_day) AS signal_days,
       round(100 * avgIf(c / o - 1, up_day), 3) AS same_bar_avg_pct,
       round(100 * avgIf(c / o - 1, prior_up), 3) AS lagged_avg_pct,
       round(100 * (avgIf(c / o - 1, up_day) - avgIf(c / o - 1, prior_up)), 3) AS gap_pp
FROM lagged
WHERE date >= toDate('2016-01-01')
GROUP BY year
ORDER BY year
Run this yourself

Di difference no dey subtle. For 2016 di same-bar version average 0.447% per qualifying session while di lagged version average -0.008%, a spread of 0.455 percentage points a day. For 2025 di two read 0.636% and -0.03%. With roughly 140 qualifying sessions for a year, a spread of half a percentage point a session go compound into an equity curve wey no live account don ever print.

Di whole edge na for definition. Selecting sessions wey close above their open, then measuring open-to-close on those same sessions, dey measure a quantity wey guaranteed positive. Di rule don discover im own filter.

Variants of dis dey hide for daily data all di time: a moving average wey include today's close and dey use to trade today, a signal wey dem compute on prices wey dem don already adjust for a split wey never happen, an intraday stop wey dem place at a session high wey never print. Di common shape na a decision timestamp wey dey earlier than one of im own inputs.

Mechanism two: a universe drawn from today's survivors

Di second mechanism no dey touch di rule at all. E dey for di list of names wey di rule dey allowed to see.

Pull a ticker list from a current data provider, run am back ten years, and di sample go quietly remove every company wey bankrupt, wey dem acquire, or wey dem delist along di way. Di rule no ever see di losers. Di panel dey measure di size of dat omission: every US-listed name wey trade at least 200 sessions above $1 for a given calendar year, di share of dat cohort wey still dey print prices for July 2026, and di median calendar-year return of di whole cohort beside di median of di survivors alone.

QuerySurvivorship for the universe: names wey dey trade every year, share still listed July 2026, and median return
The exact SQL behind every number
WITH recent AS (
    SELECT DISTINCT ticker
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2026-06-15')
      AND date <= toDate('2026-07-28')
),
per_name AS (
    SELECT toYear(date) AS year,
           ticker,
           argMin(toFloat64(close), date) AS first_close,
           argMax(toFloat64(close), date) AS last_close,
           count() AS sessions
    FROM global_markets.stocks_daily_aggs
    WHERE date >= toDate('2015-01-01')
      AND date <= toDate('2024-12-31')
      AND close > 1
    GROUP BY year, ticker
    HAVING sessions >= 200
)
SELECT year,
       uniqExact(ticker) AS names_trading,
       round(100 * uniqExactIf(ticker, ticker IN (SELECT ticker FROM recent)) / uniqExact(ticker), 1) AS still_listed_pct,
       round(100 * quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker)), 2) AS median_return_all_pct,
       round(100 * quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent)), 2) AS median_return_survivors_pct,
       round(100 * (quantileDeterministicIf(0.5)(last_close / first_close - 1, cityHash64(ticker), ticker IN (SELECT ticker FROM recent))
                    - quantileDeterministic(0.5)(last_close / first_close - 1, cityHash64(ticker))), 2) AS survivor_gap_pp
FROM per_name
GROUP BY year
ORDER BY year
Run this yourself

Of di 7063 names wey clear di bar for 2015, 52.5% still dey trading for July 2026. For di 2024 cohort di figure na 87.5%, mostly na matter of time wey don pass: a 2024 name don get two years to disappear and a 2015 name don get eleven.

Di return columns dey carry di bias itself. For 2017 di median name return 7.51% while di median survivor return 11.32%, a gap of 3.81 percentage points wey dem give for free. Di gap dey positive for every one of di 10 years wey dem chart. A strategy wey dem test on survivors go inherit am before e make even one decision, and rules wey dey screen for weakness go inherit more than di median, because di names wey vanish dey disproportionately for dat bucket.

Di correction na a point-in-time universe: di list of names as e stand on each rebalance date, delistings included, with a delisted position wey dem close at im final print rather than drop from di record.

Mechanism three: data wey dem revise after im date stamp

Prices dey stamp once. Fundamentals no dey. A quarterly figure dey carry di quarter's end date, dem go amend am with a later filing, and a database wey dey store only di current version go happily serve di amended number against di original date. A rule wey dey screen on dat number dey read a correction wey take months to arrive.

Di same pattern dey run through index membership lists wey dem rebuild to current constituents, analyst estimate histories wey dem overwrite with di final consensus, and corporate action adjustments wey dem apply backward across an entire price series. For each case di stored record na a present-tense snapshot wey dey wear a past-tense date.

Two practices dey contain am. Use a source wey dey keep vintages, meaning di value as e was known on each date rather than di value as e is known now. Where vintages no dey available, apply a reporting lag wey generous enough to cover di real one, and treat di length of dat lag as a parameter wey worth testing rather than a detail wey worth guessing.

Wetin di curve look like when di future dey leak in

Look-ahead bias get a signature: performance wey dey far above wetin di underlying market offer. A useful reference point na di hindsight ceiling, di growth wey a perfect one-day-ahead forecast go don produce on a single instrument.

QueryHindsight ceiling: SPY buy and hold, same year without its biggest up days, and perfect one-day foresight
The exact SQL behind every number
WITH bars AS (
    SELECT date,
           toFloat64(close) AS c,
           any(toFloat64(close)) OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_c
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= toDate('2015-12-01')
      AND date <= toDate('2025-12-31')
),
rets AS (
    SELECT toYear(date) AS year,
           c / prev_c - 1 AS r
    FROM bars
    WHERE date >= toDate('2016-01-01')
      AND prev_c > 0
)
SELECT year,
       count() AS sessions,
       round(100 * (exp(sum(log(1 + r))) - 1), 1) AS buy_and_hold_pct,
       round(100 * (exp(sumIf(log(1 + r), r < 0.02)) - 1), 1) AS without_big_up_days_pct,
       round(exp(sum(log(1 + abs(r)))), 1) AS hindsight_ceiling_multiple
FROM rets
GROUP BY year
ORDER BY year
Run this yourself

Di ceiling dey enormous. For 2020, perfect one-day foresight on SPY multiply capital 27.7 times over 253 sessions, against 16.2% for simply holding di fund. For di calmest year of di ten, 2017, di same perfect forecast return 2.2 times capital.

Di middle column show how concentrated dat ceiling dey. Remove di sessions wey gain more than 2%, and 2020 change from 16.2% to -44.9%. A leak wey worth only a handful of those sessions a year dey move a backtest a very long way. A curve with shallow drawdowns, a high hit rate, and im best days wey dey clustered on di market's most violent sessions don earn an audit.

Calibration dey matter more than any single threshold. A rule wey dey claim a smooth 40% a year over a decade na claiming a large fraction of di hindsight ceiling, and di honest question na which input tell am wetin dey about to happen.

How to test a backtest for look-ahead bias

  • Lag everything by one bar. Shift every signal one full period later and re-run. Genuine edges go decay small. Leaks go collapse, often to nothing.
  • Perturb di future. Take di data after each decision timestamp, replace am with noise or a reshuffle, and re-run di decision logic. Every decision should come out identical. Any decision wey move don read something wey e no fit don know.
  • Treat timestamps as data. For each input, store di moment a value become available beside di moment e describe. A join wey never compare those two columns no fit enforce di rule.
  • Rebuild di universe as of di date. Include delisted and acquired names, close dem at their last print, and run di same rule on both universes to size di survivorship contribution.
  • Measure against di ceiling. Put di strategy's return beside a buy and hold figure and a perfect-foresight figure for di same window. Distance from di ceiling na context wey a Sharpe ratio alone no dey give.

Position sizing dey after all dis. An inflated win rate dey feed an inflated bet size, wey na where Kelly criterion position sizing turn an accounting error into a drawdown. Di same discipline dey apply before real capital move: paper trading before real money test order mechanics, and automated stacks carry di identical hazard at machine speed, wey dem cover for multi agent AI trading systems. Effects wey survive careful point-in-time testing, like di low volatility anomaly, dey interesting for exactly dat reason.

FAQ

Wetin be look-ahead bias for backtesting?

E be di use of data for a simulated trade wey never dey available when di trade would don be placed. Common forms na deciding on a price bar wey di trade also execute inside, screening a universe of companies wey still dey listed today, and reading a financial figure wey dem revise after di date wey dem attach to am.

How you fit detect look-ahead bias?

Lag every signal by one full period and re-run: a leak usually go collapse while a real edge go decay gently. A stronger test dey replace all data after each decision timestamp with noise and confirm say every decision come out unchanged.

Survivorship bias same as look-ahead bias?

Dem dey distinct but dem dey travel together. Survivorship bias na a sample wey dem build from di names wey lasted, and knowing which names lasted na information from di future, so a survivor-only universe na a look-ahead leak wey dey for di data rather than for di rule.

Why backtests with look-ahead bias look so good?

Di leak dey select outcomes rather than predict dem. For di panel above, screening sessions on their own closing price produce an average session gain about 0.455 percentage points above di lagged version for 2016, an advantage wey dey for definition rather than earned.

Wetin be point-in-time data?

Point-in-time data dey store each value as e was known on each historical date, including di original figure and every later revision, beside di universe of securities as e stood dat day. E be di reference wey a backtest need to show say im inputs dey before im decisions.


Every figure above na a stored, versioned query over real daily bars. Expand any panel's SQL, or run di lag test on your own rule on di Strasmore terminal.

#backtesting#look-ahead bias#survivorship bias#quant#research process