Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

look-ahead bias backtest toekomstige data

Look-ahead bias is toekomstige data die in een backtest lekt. Lees hoe same bar beslissingen, survivorship en herziene cijfers resultaten opblazen, en hoe u erop test.

Look-ahead bias is het gebruik van informatie in een backtest die niemand op het moment van de gesimuleerde transactie had kunnen bezitten. De vermogenscurve stijgt, de statistieken zien er uitstekend uit, en geen ervan overleeft het contact met een live order. Deze pagina definieert de bias, bespreekt drie mechanismen die haar veroorzaken, en meet elk mechanisme op echte marktdata.

Wat look-ahead bias is

Een backtest is een bewering over een beslissing: op dit tijdstip, met deze informatie, zou de regel dit hebben gedaan. De bewering is alleen geldig wanneer elke input die de regel leest al bestond, in zijn definitieve vorm, vóór dat tijdstip. Look-ahead bias is elke schending van die voorwaarde, en ze is stil. Niets in een spreadsheet of script klaagt wanneer een rij een waarde teruggeeft die later is gestempeld dan de beslissing die ze voedt.

Drie schendingen dekken het grootste deel van wat in de praktijk voorkomt. Een beslissing die een prijsbalk leest waarin ze ook handelt. Een universum samengesteld uit de namen die vandaag nog genoteerd staan. Een dataveld dat is herzien na de datum die het draagt.

Mechanisme één: beslissen op een balk waarin u ook handelt

Begin met een regel die onschuldig klinkt. Koop op kracht, dat wil zeggen op sessies waarin het fonds hoger sluit dan waar het opende. Onzorgvuldig geprogrammeerd gaat de test in op de opening van die sessie en uit op de slotkoers van die sessie, terwijl de voorwaarde waarop wordt gescreend de slotkoers van dezelfde balk is. Niemand kent een slotkoers bij de openingsbel.

Het paneel hieronder draait beide versies op SPY, de S&P 500-tracker, over tien kalenderjaren. De eerste kolom is de onmogelijke. De tweede vertraagt de identieke voorwaarde met één sessie, wat het vroegste moment is waarop een gefinancierde rekening erop zou kunnen handelen.

QueryBeslissing in dezelfde sessie versus een sessie vertraging: SPY, gemiddelde sessiewinst, 2016-2025
De exacte SQL achter elk getal
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

Het verschil is niet subtiel. In 2016 leverde dezelfde-balk-versie gemiddeld 0.447% per kwalificerende sessie op, terwijl de vertraagde versie gemiddeld -0.008% opleverde, een spread van 0.455 procentpunt per dag. In 2025 lazen de twee 0.636% en -0.03%. Met ruwweg 140 kwalificerende sessies per jaar stapelt een spread van een half procentpunt per sessie zich op tot een vermogenscurve die geen enkele live rekening ooit heeft afgedrukt.

Het hele voordeel is definitie. Het selecteren van sessies die boven hun opening sloten, en vervolgens de open-to-close op diezelfde sessies meten, meet een grootheid die gegarandeerd positief is. De regel ontdekte zijn eigen filter.

Varianten hiervan zitten altijd in dagdata verborgen: een voortschrijdend gemiddelde dat de slotkoers van vandaag bevat en wordt gebruikt om vandaag te handelen, een signaal berekend op prijzen die al zijn aangepast voor een split die nog niet had plaatsgevonden, een intraday-stop geplaatst op een sessiehoogtepunt dat nog niet was afgedrukt. De gemeenschappelijke vorm is een beslissingstijdstip dat eerder ligt dan een van zijn eigen inputs.

Mechanisme twee: een universum getrokken uit de overlevenden van vandaag

Het tweede mechanisme raakt de regel nooit. Het zit in de lijst met namen die de regel mag zien.

Haal een tickerlijst van een huidige dataprovider, draai deze tien jaar terug, en de steekproef sluit stilletjes elk bedrijf uit dat onderweg failliet ging, werd overgenomen of van de beurs werd gehaald. De regel kreeg de verliezers nooit aangeboden. Het paneel meet de omvang van die weglating: elke in de VS genoteerde naam die in een bepaald kalenderjaar ten minste 200 sessies boven de $1 handelde, het aandeel van dat cohort dat in juli 2026 nog prijzen drukt, en het mediane kalenderjaarrendement van het hele cohort naast het mediane rendement van alleen de overlevenden.

QueryOverleving in het universum: namen die elk jaar verhandelen, aandeel nog genoteerd in juli 2026, en mediaanrendement
De exacte SQL achter elk getal
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

Van de 7063 namen die de lat haalden in 2015, handelden 52.5% nog in juli 2026. Voor het 2024-cohort is het cijfer 87.5%, grotendeels een kwestie van verstreken tijd: een naam uit 2024 heeft twee jaar gehad om te verdwijnen en een naam uit 2015 heeft er elf gehad.

De rendementskolommen dragen de bias zelf. In 2017 keerde de mediane naam 7.51% terug, terwijl de mediane overlevende 11.32% terugkeerde, een gat van 3.81 procentpunt dat gratis werd weggegeven. Het gat is positief in elk van de 10 weergegeven jaren. Een strategie die op overlevenden wordt getest, erft dit voordeel voordat ze een enkele beslissing neemt, en regels die screenen op zwakte erven meer dan de mediaan, omdat de namen die verdwenen zijn onevenredig in die categorie vallen.

De correctie is een point-in-time-universum: de lijst met namen zoals die op elke herbalanceringsdatum stond, inclusief delistings, waarbij een van de beurs gehaalde positie wordt gesloten op de laatste notering in plaats van uit het bestand te worden verwijderd.

Mechanisme drie: data die is herzien na de datumstempel

Prijzen worden één keer gestempeld. Fundamentele gegevens niet. Een kwartaalcijfer draagt de einddatum van het kwartaal, wordt gewijzigd door een latere indiening, en een database die alleen de huidige versie opslaat, zal het gewijzigde cijfer gewillig tegen de oorspronkelijke datum serveren. Een regel die op dat cijfer screent, leest een correctie die maanden nodig had om aan te komen.

Hetzelfde patroon loopt door indexlidmaatschapslijsten die zijn herbouwd naar huidige samenstellingen, analistenschattingen die zijn overschreven met de uiteindelijke consensus, en corporate-action-aanpassingen die achterwaarts over een hele prijsreeks zijn toegepast. In elk geval is de opgeslagen registratie een momentopname in de tegenwoordige tijd met een datum in de verleden tijd.

Twee praktijken bevatten het. Gebruik een bron die jaargangen bijhoudt, dat wil zeggen de waarde zoals die op elke datum bekend was in plaats van de waarde zoals die nu bekend is. Waar jaargangen niet beschikbaar zijn, pas dan een rapportagevertraging toe die ruim genoeg is om de echte te dekken, en behandel de lengte van die vertraging als een parameter die het testen waard is in plaats van een detail dat het raden waard is.

Hoe de curve eruitziet wanneer de toekomst naar binnen lekt

Look-ahead bias heeft een handtekening: prestaties die ver boven liggen wat de onderliggende markt bood. Een nuttig referentiepunt is het hindsight-plafond, de groei die een perfecte één-dag-vooruit-voorspelling op één enkel instrument zou hebben opgeleverd.

QueryHet plafond van terugblik: SPY buy and hold, hetzelfde jaar zonder de grootste stijgingsdagen, en perfecte eendaagse vooruitziendheid
De exacte SQL achter elk getal
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

Het plafond is enorm. In 2020 vermenigvuldigde perfect één-dag-vooruitzicht op SPY het kapitaal 27.7 keer over 253 sessies, tegenover 16.2% voor het simpelweg aanhouden van het fonds. In het rustigste jaar van de tien, 2017, leverde dezelfde perfecte voorspelling 2.2 keer het kapitaal op.

De middelste kolom laat zien hoe geconcentreerd dat plafond is. Verwijder de sessies die meer dan 2% wonnen, en 2020 verandert van 16.2% in -44.9%. Een lek dat slechts een handvol van die sessies per jaar waard is, verplaatst een backtest een heel eind. Een curve met ondiepe drawdowns, een hoog hitpercentage, en de beste dagen geclusterd op de meest gewelddadige sessies van de markt, verdient een audit.

Kalibratie is belangrijker dan een enkele drempelwaarde. Een regel die een gestage 40% per jaar over een decennium claimt, claimt een groot deel van het hindsight-plafond, en de eerlijke vraag is welke input hem vertelde wat er ging gebeuren.

Hoe een backtest te testen op look-ahead bias

  • Vertraag alles met één balk. Verschuif elk signaal één volledige periode later en voer opnieuw uit. Echte voordelen nemen iets af. Lekken storten in, vaak tot niets.
  • Verstoor de toekomst. Neem de data na elk beslissingstijdstip, vervang deze door ruis of een herschikking, en voer de beslissingslogica opnieuw uit. Elke beslissing moet identiek uitkomen. Elke beslissing die verandert, heeft iets gelezen wat het niet had kunnen weten.
  • Behandel tijdstempels als data. Sla voor elke input het moment op waarop een waarde beschikbaar werd, naast het moment dat ze beschrijft. Een join die die twee kolommen nooit vergelijkt, kan de regel niet handhaven.
  • Herbouw het universum zoals het was op de datum. Neem van de beurs gehaalde en overgenomen namen op, sluit ze op hun laatste notering, en voer dezelfde regel uit op beide universums om de bijdrage van survivorship te meten.
  • Meet tegen het plafond. Zet het rendement van de strategie naast een buy-and-hold-cijfer en een perfect-vooruitzicht-cijfer voor hetzelfde venster. Afstand tot het plafond is context die een Sharpe ratio alleen niet geeft.

Positiebepaling ligt stroomafwaarts van dit alles. Een opgeblazen winstpercentage voedt een opgeblazen inzetgrootte, en dat is waar Kelly criterion positiebepaling een boekhoudkundige fout omzet in een drawdown. Dezelfde discipline is van toepassing voordat echt kapitaal beweegt: papierhandel voor echt geld test ordermechanica, en geautomatiseerde stacks dragen hetzelfde gevaar op machinesnelheid, behandeld in multi agent AI handelssystemen. Effecten die zorgvuldige point-in-time-testen overleven, zoals de lage volatiliteit anomalie, zijn juist daarom interessant.

FAQ

Wat is look-ahead bias in backtesting?

Het is het gebruik van data in een gesimuleerde transactie die niet beschikbaar was toen de transactie zou zijn geplaatst. Veelvoorkomende vormen zijn: beslissen op een prijsbalk waarin de transactie ook wordt uitgevoerd, screenen van een universum van bedrijven die vandaag nog genoteerd staan, en het lezen van een financieel cijfer dat is herzien na de datum die eraan is gekoppeld.

Hoe kunt u look-ahead bias detecteren?

Vertraag elk signaal met één volledige periode en voer opnieuw uit: een lek stort meestal in terwijl een echt voordeel zachtjes afneemt. Een sterkere test vervangt alle data na elk beslissingstijdstip door ruis en bevestigt dat elke beslissing onveranderd blijft.

Is survivorship bias hetzelfde als look-ahead bias?

Ze zijn verschillend maar gaan samen. Survivorship bias is een steekproef opgebouwd uit de namen die bleven bestaan, en weten welke namen bleven bestaan is zelf informatie uit de toekomst, dus een alleen-overlevenden-universum is een look-ahead-lek dat zich in de data bevindt in plaats van in de regel.

Waarom zien backtests met look-ahead bias er zo goed uit?

Het lek selecteert uitkomsten in plaats van ze te voorspellen. In het paneel hierboven leverde het screenen van sessies op hun eigen slotkoers een gemiddelde sessiewinst op van ongeveer 0.455 procentpunt boven de vertraagde versie in 2016, een voordeel dat definitie is in plaats van verdiend.

Wat is point-in-time data?

Point-in-time-data slaat elke waarde op zoals deze bekend was op elke historische datum, inclusief het oorspronkelijke cijfer en elke latere herziening, samen met het universum van effecten zoals het die dag bestond. Het is de referentie die een backtest nodig heeft om aan te tonen dat zijn inputs bestonden vóór zijn beslissingen.


Elk cijfer hierboven is een opgeslagen, versiebeheerde query over echte dagelijkse balken. Vouw het SQL van een paneel uit, of voer de vertragingstest uit op uw eigen regel op de Strasmore-terminal.

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