27 août 2026 · ingénierie des données

Votre univers de symboles est une machine à remonter le temps : biais du survivant dans les backtests de contrats perpétuels crypto

Votre univers de symboles est une machine à remonter le temps : biais du survivant dans les backtests de contrats perpétuels crypto

Vous m'avez envoyé le notebook dimanche soir, et je l'ai gardé ouvert sur mon deuxième écran depuis. Momentum transversal, les 150 contrats perpétuels Binance USDⓈ-M au plus fort volume en dollars sur 30 jours, rééquilibrage hebdomadaire, achat du décile supérieur et vente à découvert du décile inférieur, de 2021-01 à 2026-06. Sharpe de 2.31, drawdown maximal de 14.2 %, et un modèle de coûts qui me semblait honnête : taker à l'entrée comme à la sortie, funding comptabilisé à chaque intervalle, et un terme de slippage qui évolue selon la taille de votre ordre par rapport à la profondeur du carnet. Vous avez bien fait les choses difficiles. Puis vous m'avez demandé pourquoi six semaines de trading simulé n'avaient rien donné, et si l'avantage s'était érodé.

Il ne s'est pas érodé. Il n'a jamais été dans le backtest. Regardez la cellule 4 :

info = requests.get(BASE + "/fapi/v1/exchangeInfo").json()
symbols = [s["symbol"] for s in info["symbols"]
           if s["status"] == "TRADING" and s["quoteAsset"] == "USDT"]

Vous avez appelé cet endpoint en juin 2026 et utilisé sa réponse pour définir ce qui était négociable en mars 2021. Par construction, chaque symbole de cette liste avait survécu jusqu'en juin 2026. Voilà tout le bug, et son impact dépasse celui de tout le reste de votre modèle de coûts réuni.

2.31 → 0.74Sharpe, liste des survivants contre données ponctuelles
~1 sur 5de votre univers de 2021 n'existe plus
380 KBun instantané quotidien d'exchangeInfo, compressé avec gzip

Ce que l'endpoint ne vous dira pas

exchangeInfo ne contient aucun historique. Pas de paramètre asOf, pas d'archive, pas de journal des modifications. C'est une photo de l'instant présent, et Binance ne vous a jamais promis autre chose. Lorsqu'un contrat est retiré de la cote, son entrée est supprimée de la réponse et, du point de vue de l'API, son ticker cesse d'exister. Binance a coté plus de 600 contrats perpétuels USDⓈ-M depuis 2019 et en a retiré bien plus d'une centaine. BTS, COCOS, TOMO, RAY, FTT, SC, et toute une longue traîne de contrats de la saison des altcoins de 2021 qui ont connu un trimestre glorieux avant de perdre peu à peu leur liquidité, jusqu'à ce que la plateforme les retire.

Demandez-vous quels noms un filtre de momentum sur 30 jours apprécie. Pas BTC. Il aime ce qui vient de tripler après une flambée à la cotation et une vague sur Twitter. Cette population recoupe largement celle des actifs qui finiront retirés de la cote, et votre filtre d'univers a éliminé ce chevauchement.

J'ai relancé votre stratégie sur notre archive d'instantanés, avec un univers ponctuel, le même signal et les mêmes coûts. Le Sharpe obtenu était de 0.74, avec un drawdown de 31 %. Environ deux cinquièmes de l'écart viennent des actifs retirés de la cote que vous n'auriez jamais pu détenir. Un autre quart vient du problème inverse, plus subtil, et qui, je le crains, vous plaira moins.

L'autre bout de la machine à remonter le temps : les symboles qui n'existaient pas encore

Votre indicateur mesure le rendement sur 90 jours. Votre fenêtre glissante a min_periods=20, parce que vous l'avez réglée une fois pour éviter que la période de chauffe ne sabote le premier trimestre de l'échantillon, puis ne l'avez jamais revue. Un contrat coté depuis 21 jours reçoit donc un score de momentum calculé sur trois semaines d'évolution des prix après sa cotation, se classe dans le décile supérieur à peu près chaque fois que sa cotation s'est bien passée, puis entre dans votre portefeuille.

Cette partie peut se défendre. Voici ce qui ne va pas : votre fournisseur a complété rétrospectivement certains de ces symboles avec des données au comptant ou d'indice antérieures à l'existence du contrat perpétuel. Résultat : quelques contrats ont un historique de klines antérieur à leur propre onboardDate. Vous pouvez le vérifier en une minute environ. Faites une jointure entre votre table de prix et les dates de cotation, puis comptez les lignes antérieures à la cotation. Vos données contiennent 41 symboles avec des barres antérieures à leur cotation, et l'un d'eux, à l'été 2023, apporte un gain de 6 % en une seule semaine à la courbe de capital, grâce à une position sur un contrat qui n'existerait que neuf jours plus tard.

Une chaîne de ticker n'est pas un identifiant. C'est une étiquette que la plateforme loue, parfois deux fois.

Ce qui nous amène aux changements de nom. MATICUSDT est devenu POLUSDT. FTMUSDT est devenu SUSDT après une conversion de 1:1. L'affaire LUNA, en mai 2022, a donné LUNCUSDT, puis un tout nouveau LUNAUSDT qui partage une racine de ticker avec un actif qui a perdu presque toute sa valeur. Si votre chargeur utilise la chaîne du symbole comme clé et concatène tous les fichiers trouvés, vous avez au moins une série avec une discontinuité qui ne correspond pas à un mouvement de prix. Votre indicateur de momentum interprétera cette discontinuité comme le signal le plus fort de l'ensemble transversal.

Tout le reste qui évolue sous vos pieds

Dès que vous admettez que la liste des symboles varie dans le temps, le même raisonnement s'applique à tous les autres champs de cette réponse. Or vous utilisez leurs valeurs d'aujourd'hui pour toute la période.

ChampÉvolutionConséquence
statusTRADING → SETTLING → disparuBiais du survivant ; sorties fantômes à un cours de clôture auquel personne n'a pu négocier
onboardDateNouvelles cotations chaque semaine ; champ absent pour les symboles disparusNégociation de contrats avant leur existence
tickSize / stepSizeAjustés à mesure que les cours évoluentArrondi des ordres et prix limites qui auraient été rejetés
minNotionalRelevé au fil du temps sur les carnets peu profondsPetites positions que votre routeur en production refuserait
fundingIntervalHours8h pendant des années, puis 4h ou 1h sur de nombreux symbolesCoût de portage erroné d'un facteur de 2–3× précisément sur les altcoins détenus par votre filtre
tranches de levierPaliers et marge de maintenance révisésModélisation de la liquidation et capacité de marge

Dans votre cas, c'est le funding qui fait le plus mal. Votre boucle de comptabilisation part du principe qu'il y a trois paiements par jour sur toute la période. Une part importante du portefeuille d'altcoins est passée à un funding toutes les quatre heures, et les positions vendeuses sur les actifs à funding élevé représentent une bonne part de votre PnL simulé. L'erreur ne se limite pas à un arrondi. Elle est d'un tout autre ordre de grandeur.

Le retrait de la cote est un événement, et vous ne le modélisez pas

Dans votre nouvelle simulation avec données ponctuelles, j'ai accordé à la stratégie une sortie généreuse. En réalité, les retraits suivent un scénario : une annonce, généralement sept à quatorze jours à l'avance, puis une période en mode reduce-only, puis un règlement forcé au prix de mark. L'annonce est une information publique dont vous pouvez tenir compte. Une simulation réaliste prévoit donc une sortie au cours de clôture du jour de l'annonce. Mais sur un retrait typique d'un altcoin, ce cours est déjà inférieur de 10-20 % à celui de la semaine précédente, le carnet est peu profond, et votre terme de slippage doit tenir compte du changement de régime. Si votre moteur d'exécution vous donne le prix de règlement sans impact, vous avez discrètement rendu négociables des actifs en train de mourir à leur juste valeur.

Si vous n'avez jamais archivé exchangeInfo, vous n'êtes pas fichu. Le dépôt public à data.binance.vision/data/futures/um/monthly/klines/ conserve les répertoires des symboles retirés de la cote longtemps après que l'API les a oubliés. Parcourez les répertoires : pour chaque symbole, le premier et le dernier fichier mensuel donnent une fenêtre de cotation et de retrait exploitable sans fournisseur de données. C'est une reconstruction, pas un registre, et vous n'y retrouverez ni les tick sizes ni les intervalles de funding. Mais vous saurez quels actifs existaient à quelle période, soit 80 % de ce qu'il vous faut pour lundi.

Ce que je vous ferais construire avant de retoucher au signal

  1. Un cron qui récupère chaque jour exchangeInfo sur toutes les plateformes que vous étudiez et l'écrit dans un stockage objet, avec une clé par date. Le fichier compressé avec gzip fait moins d'un demi-mégaoctet. Dix ans ne vous coûteront qu'une erreur d'arrondi sur S3 et vous donneront une capacité de recherche que vous ne pourrez pas acheter plus tard.
  2. Un référentiel d'actifs dérivé de ces instantanés : une ligne par (venue, symbol, valid_from, valid_to), avec tous les champs. Comparez les instantanés consécutifs pour le générer et traitez chaque changement de champ comme une nouvelle ligne.
  3. Une fonction d'univers qui exige un horodatage. universe(ts), jamais universe(). Faites en sorte que le choix par défaut soit impossible, pour que personne, vous y compris à 1h du matin, ne puisse reprendre par accident la liste des survivants.
  4. Une assertion dans le chargeur de données : aucune barre ne peut précéder onboard_ts d'un jour. Faites échouer l'exécution, ne vous contentez pas d'un avertissement.
  5. Un identifiant d'instrument interne et stable qui résiste aux changements de nom, le ticker devenant un simple attribut. Associez POL et MATIC au même ID, et signalez les redenominations pour que la logique de continuité refuse de les relier.

Faites cela, puis relancez la stratégie. À mon avis, vous obtiendrez un résultat proche du 0.74 que j'ai trouvé. La vraie question sera alors de savoir si un 0.74 qui inclut les actifs disparus vaut la peine d'être testé en simulation. Peut-être. Le momentum transversal sur les contrats perpétuels a du potentiel, et une bonne partie de ce qui reste après la correction de l'univers correspond à un véritable rendement de portage côté vendeur. Vous verrez aussi vos résultats en simulation et votre backtest commencer à converger, car le trading simulé a toujours utilisé un univers ponctuel. Il n'a jamais eu le choix.

P.-S. Ce n'est pas une bizarrerie des cryptos, le phénomène est simplement plus marqué ici. Les analystes actions se battent contre les rendements des titres radiés et les tickers recyclés depuis que CRSP a commencé à les fournir. Les marchés prédictifs poussent le phénomène à l'extrême : chaque contrat expire par conception, l'univers n'est donc composé que de cotations et de retraits. Si vous transposez un jour ce filtre à Kalshi, construisez d'abord le référentiel d'actifs. Là-bas, il n'existe pas d'autre forme d'historique.

biais du survivantdonnées ponctuellescontrats à terme cryptobacktestingingénierie des données
← Tous les articles