Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

biais de regard rétrospectif backtest

Le biais de regard rétrospectif fait fuiter des données futures dans un backtest. Décisions intra-barre, survivorship et chiffres révisés gonflent les résultats. Comment le détecter.

Le biais de regard rétrospectif (look-ahead bias) consiste à utiliser dans un backtest une information que personne ne pouvait détenir au moment où la transaction simulée a été placée. La courbe d'equity grimpe, les statistiques sont excellentes, et rien ne survit au contact d'un ordre réel. Cette page définit le biais, détaille trois mécanismes qui le produisent, et mesure chacun d'eux sur des données de marché réelles.

Ce qu'est le biais de regard rétrospectif

Un backtest est une affirmation sur une décision : à cet horodatage, avec cette information, la règle aurait fait ceci. L'affirmation n'est valable que si chaque donnée d'entrée que la règle lit existait déjà, sous sa forme définitive, avant cet horodatage. Le biais de regard rétrospectif est toute violation de cette condition, et il est silencieux. Rien dans un tableur ou un script ne se plaint quand une ligne renvoie une valeur horodatée plus tard que la décision qu'elle alimente.

Trois violations couvrent l'essentiel de ce qu'on rencontre en pratique. Une décision qui lit une barre de prix qu'elle négocie également. Un univers constitué à partir des noms encore cotés aujourd'hui. Un champ de données qui a été révisé après la date qu'il porte.

Mécanisme un : décider sur une barre que l'on négocie aussi

Commençons par une règle qui semble anodine. Achetez sur la force, c'est-à-dire sur les séances où le fonds clôture au-dessus de son ouverture. Codée négligemment, le test entre à l'ouverture de cette séance et sort à la clôture de cette même séance, alors que la condition qu'il filtre est le cours de clôture de la même barre. Personne ne connaît un cours de clôture à la cloche d'ouverture.

Le panneau ci-dessous exécute les deux versions sur SPY, le tracker du S&P 500, sur dix années civiles. La première colonne est la version impossible. La seconde décale la condition identique d'une seule séance, ce qui est le plus tôt qu'un compte financé pourrait agir dessus.

RequêteDécision même barre vs décalage d’une séance : SPY, gain moyen par séance, 2016-2025
Le SQL exact derrière chaque chiffre
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

La séparation n'est pas subtile. En 2016, la version sur la même barre a rapporté en moyenne 0.447% par séance éligible, tandis que la version décalée a rapporté -0.008%, soit un spread de 0.455 points de pourcentage par jour. En 2025, les deux lisent 0.636% et -0.03%. Avec environ 140 séances éligibles par an, un spread d'un demi-point de pourcentage par séance se compose en une courbe d'equity qu'aucun compte réel n'a jamais imprimée.

L'avantage est définitionnel. Sélectionner des séances qui ont clôturé au-dessus de leur ouverture, puis mesurer l'ouverture-à-clôture sur ces mêmes séances, mesure une quantité garantie positive. La règle a découvert son propre filtre.

Des variantes de cela se cachent tout le temps dans les données quotidiennes : une moyenne mobile qui inclut la clôture d'aujourd'hui et est utilisée pour négocier aujourd'hui, un signal calculé sur des prix déjà ajustés pour un split qui n'avait pas encore eu lieu, un stop intraday placé à un plus haut de séance qui ne s'était pas encore imprimé. La forme commune est un horodatage de décision antérieur à l'une de ses propres entrées.

Mécanisme deux : un univers tiré des survivants d'aujourd'hui

Le second mécanisme ne touche jamais à la règle. Il se trouve dans la liste des noms que la règle est autorisée à voir.

Tirez une liste de tickers d'un fournisseur de données actuel, exécutez-la sur dix ans en arrière, et l'échantillon exclut silencieusement toutes les entreprises qui ont fait faillite, ont été acquises ou ont été radiées en cours de route. La règle ne s'est jamais vu proposer les perdants. Le panneau mesure l'ampleur de cette omission : pour chaque nom coté aux États-Unis qui a négocié au moins 200 séances au-dessus de 1 $ au cours d'une année civile donnée, la part de cette cohorte qui imprime encore des prix en juillet 2026, et le rendement médian sur l'année civile de la cohorte entière à côté du rendement médian des seuls survivants.

RequêteBiais de survie dans l’univers : noms négociés chaque année, part encore cotée en juillet 2026, et rendement médian
Le SQL exact derrière chaque chiffre
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

Sur les 7063 noms qui ont passé la barre en 2015, 52.5% étaient encore négociés en juillet 2026. Pour la cohorte 2024, le chiffre est de 87.5%, principalement une question de temps écoulé : un nom de 2024 a eu deux ans pour disparaître et un nom de 2015 en a eu onze.

Les colonnes de rendement portent le biais lui-même. En 2017, le nom médian a rapporté 7.51% tandis que le survivant médian a rapporté 11.32%, soit un écart de 3.81 points de pourcentage offerts gratuitement. L'écart est positif dans chacune des 10 années représentées. Une stratégie testée sur les survivants hérite de cet écart avant même de prendre une seule décision, et les règles qui filtrent sur la faiblesse en héritent plus que la médiane, car les noms qui ont disparu se situent de manière disproportionnée dans ce compartiment.

La correction est un univers point-in-time : la liste des noms telle qu'elle se présentait à chaque date de rééquilibrage, radiations incluses, avec une position radiée clôturée à son dernier cours plutôt que supprimée de l'historique.

Mécanisme trois : des données révisées après leur horodatage

Les prix sont horodatés une fois. Les fondamentaux ne le sont pas. Un chiffre trimestriel porte la date de fin du trimestre, est modifié par un dépôt ultérieur, et une base de données qui ne stocke que la version actuelle servira volontiers le chiffre modifié avec la date d'origine. Une règle qui filtre sur ce chiffre lit une correction qui a mis des mois à arriver.

Le même schéma traverse les listes de composition d'indices reconstruites avec les constituants actuels, les historiques d'estimations d'analystes écrasés par le consensus final, et les ajustements d'opérations sur le capital appliqués rétroactivement sur une série de prix entière. Dans chaque cas, l'enregistrement stocké est un instantané au présent portant une date au passé.

Deux pratiques le contiennent. Utilisez une source qui conserve les millésimes, c'est-à-dire la valeur telle qu'elle était connue à chaque date plutôt que la valeur telle qu'elle est connue aujourd'hui. Lorsque les millésimes ne sont pas disponibles, appliquez un décalage de publication suffisamment généreux pour couvrir le décalage réel, et traitez la longueur de ce décalage comme un paramètre qui mérite d'être testé plutôt qu'un détail à deviner.

À quoi ressemble la courbe quand le futur s'infiltre

Le biais de regard rétrospectif a une signature : une performance bien supérieure à ce que le marché sous-jacent a offert. Un point de référence utile est le plafond de la rétrospection (hindsight ceiling), la croissance qu'une prévision parfaite à un jour d'avance aurait produite sur un seul instrument.

RequêtePlafond du recul : SPY buy and hold, la même année sans ses plus gros jours haussiers, et prévision parfaite à un jour
Le SQL exact derrière chaque chiffre
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

Le plafond est énorme. En 2020, une prévision parfaite à un jour sur SPY a multiplié le capital 27.7 fois sur 253 séances, contre 16.2% pour la simple détention du fonds. Dans l'année la plus calme des dix, 2017, la même prévision parfaite a rapporté 2.2 fois le capital.

La colonne du milieu montre à quel point ce plafond est concentré. Retirez les séances qui ont gagné plus de 2 %, et 2020 passe de 16.2% à -44.9%. Une fuite qui ne vaut que quelques-unes de ces séances par an déplace un backtest très loin. Une courbe avec des drawdowns peu profonds, un taux de réussite élevé, et ses meilleurs jours concentrés sur les séances les plus violentes du marché mérite un audit.

L'étalonnage importe plus que n'importe quel seuil unique. Une règle qui revendique un lisse 40 % par an sur une décennie revendique une grande fraction du plafond de rétrospection, et la question honnête est de savoir quelle donnée d'entrée lui a dit ce qui allait se passer.

Comment tester un backtest pour le biais de regard rétrospectif

  • Décalez tout d'une barre. Déplacez chaque signal d'une période entière plus tard et relancez. Les véritables edges se dégradent un peu. Les fuites s'effondrent, souvent jusqu'à zéro.
  • Perturbez le futur. Prenez les données après chaque horodatage de décision, remplacez-les par du bruit ou un réarrangement, et relancez la logique de décision. Chaque décision doit rester identique. Toute décision qui bouge a lu quelque chose qu'elle ne pouvait pas savoir.
  • Traitez les horodatages comme des données. Pour chaque entrée, stockez le moment où une valeur est devenue disponible à côté du moment qu'elle décrit. Une jointure qui ne compare jamais ces deux colonnes ne peut pas appliquer la règle.
  • Reconstruisez l'univers à la date. Incluez les noms radiés et acquis, clôturez-les à leur dernier cours, et exécutez la même règle sur les deux univers pour dimensionner la contribution de la survie.
  • Mesurez par rapport au plafond. Placez le rendement de la stratégie à côté d'un chiffre de buy and hold et d'un chiffre de prévision parfaite pour la même fenêtre. La distance par rapport au plafond est un contexte qu'un seul ratio de Sharpe ne donne pas.

Le dimensionnement des positions est en aval de tout cela. Un taux de réussite gonflé alimente une taille de mise gonflée, ce qui est l'endroit où le dimensionnement des positions selon le critère de Kelly transforme une erreur comptable en drawdown. La même discipline s'applique avant que le capital réel ne bouge : le paper trading avant l'argent réel teste la mécanique des ordres, et les piles automatisées portent le même danger à la vitesse de la machine, couvert dans les systèmes de trading multi-agents par IA. Les effets qui survivent à des tests point-in-time minutieux, comme l'anomalie de faible volatilité, sont intéressants précisément pour cette raison.

FAQ

Qu'est-ce que le biais de regard rétrospectif dans le backtesting ?

C'est l'utilisation dans une transaction simulée de données qui n'étaient pas disponibles au moment où la transaction aurait été placée. Les formes courantes sont : décider sur une barre de prix que la transaction exécute également à l'intérieur, filtrer un univers d'entreprises encore cotées aujourd'hui, et lire un chiffre financier qui a été révisé après la date qui lui est attachée.

Comment détecter le biais de regard rétrospectif ?

Décalez chaque signal d'une période entière et relancez : une fuite s'effondre généralement tandis qu'un véritable edge se dégrade doucement. Un test plus fort remplace toutes les données après chaque horodatage de décision par du bruit et confirme que chaque décision reste inchangée.

Le biais de survie est-il la même chose que le biais de regard rétrospectif ?

Ils sont distincts mais ils voyagent ensemble. Le biais de survie est un échantillon construit à partir des noms qui ont duré, et savoir quels noms ont duré est en soi une information provenant du futur, donc un univers de seuls survivants est une fuite de regard rétrospectif située dans les données plutôt que dans la règle.

Pourquoi les backtests avec biais de regard rétrospectif ont-ils l'air si bons ?

La fuite sélectionne les résultats plutôt que de les prédire. Dans le panneau ci-dessus, filtrer les séances sur leur propre cours de clôture a produit un gain moyen par séance d'environ 0.455 points de pourcentage au-dessus de la version décalée en 2016, un avantage qui est définitionnel plutôt que gagné.

Qu'est-ce qu'une donnée point-in-time ?

Une donnée point-in-time stocke chaque valeur telle qu'elle était connue à chaque date historique, y compris le chiffre original et chaque révision ultérieure, aux côtés de l'univers des titres tel qu'il se présentait ce jour-là. C'est la référence dont un backtest a besoin pour montrer que ses entrées existaient avant ses décisions.


Chaque chiffre ci-dessus est une requête stockée et versionnée sur de véritables barres quotidiennes. Développez le SQL de n'importe quel panneau, ou exécutez le test de décalage sur votre propre règle sur le terminal Strasmore.

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