Position longue à 84,120. Stop à 84,036, objectif à 84,271. La bougie de la minute suivante arrive : ouverture à 84,118, plus haut à 84,290, plus bas à 84,010, clôture à 84,240.
Les deux niveaux sont dans cette bougie. Le stop a été touché et l’objectif aussi, et les quatre valeurs OHLC ne contiennent absolument aucune information sur l’ordre dans lequel cela s’est produit. Votre backtest a quand même renvoyé un chiffre. Quelque part dans la boucle, une ligne a tranché — probablement une ligne que vous ne considériez pas comme une hypothèse de modélisation au moment de l’écrire.
C’est la plus grande source de performance fictive que je rencontre dans les stratégies à horizon court, avant les frais et le slippage, parce que cela ne ressemble pas à une hypothèse. Cela ressemble à de la plomberie logicielle.
Mauvaise méthode 1 : laisser la chaîne de if décider
La structure habituelle :
if bar.high >= target:
exit(target, "tp")
elif bar.low <= stop:
exit(stop, "sl")
Personne n’a choisi « les objectifs sont exécutés avant les stops ». La vérification de l’objectif a simplement été écrite en premier parce que c’est le scénario favorable, celui auquel vous pensiez. Inversez les deux branches et la courbe de capital change ; cela devrait suffire à montrer que le P&L de la stratégie dépend en partie de votre éditeur de code.
J’ai testé un scalper de retour à la moyenne volontairement ordinaire sur le perp BTCUSDT, avec trois mois de bougies 1-minute et 4,812 trades, un stop à 0.10% et un objectif à 0.18% du prix d’entrée. Mêmes signaux, mêmes frais ; seule la convention de départage a changé.
Un huitième des trades porte tout le résultat. C’est l’arithmétique d’une paire stop-objectif serrée : les trades ambigus sont ceux où le prix est parti dans les deux sens, autrement dit la plupart des cas intéressants, et chacun vaut toute la distance entre le stop et l’objectif selon le pile ou face. 12.6% des trades × 0.28% d’écart, cela représente 3.5% du notionnel brut échangé par unité d’échantillon, bien plus que le véritable avantage de la stratégie.
Le taux dépend de la taille de vos bougies par rapport à l’écart entre les niveaux, et se dégrade rapidement quand les bougies couvrent une période plus longue. Mêmes stop et objectif, mêmes signaux, avec un rééchantillonnage :
| Intervalle des bougies | Trades avec les deux niveaux dans une même bougie | Sharpe (objectif en premier) |
|---|---|---|
| 1s | 0.3% | 0.91 |
| 1m | 12.6% | 2.31 |
| 5m | 34% | 3.60 |
| 15m | 49% | 4.42 |
| 1h | 71% | 5.88 |
Regardez ce que dit vraiment ce tableau. Des bougies plus larges ont amélioré le backtest. L’instinct de tout chercheur est de penser que les bougies horaires sont le choix prudent : moins de bruit, moins de surapprentissage à la microstructure. Mais avec une convention intrabar qui vous avantage, une bougie plus large est simplement une boîte plus grande dans laquelle vous pouvez supposer que vous avez eu de la chance. Sur les bougies 1h, 7 trades sur 10 reposent entièrement sur la convention. Ce backtest ne teste pas une stratégie : il teste l’ordre de 2 instructions `if` répétées 3,400 fois.
Mauvaise méthode 2 : supposer que le stop a été exécuté au prix du stop
Supposons que vous fixiez l’ordre. Le stop est exécuté en premier, vous comptabilisez une perte exactement égale à 0.10% plus les frais taker, et vous vous sentez rigoureux. Deux autres choses ne vont toujours pas.
La première, c’est qu’un stop est un déclencheur, pas une exécution. Sur Binance USDⓈ-M, un ordre STOP_MARKET devient un ordre au marché dès que la condition de déclenchement est remplie, puis consomme les ordres disponibles dans le carnet. Pendant une minute calme, cela représente 1 ou 2 ticks de slippage. Pendant la minute qui a réellement déclenché votre stop — celle avec une bougie de 40 points et une cascade de liquidations en dessous — le carnet est précisément peu fourni du côté que vous traversez. Dans mon échantillon, en comparant les déclenchements de stop au flux des ticks, l’exécution médiane était 1.4 bps au-delà du déclencheur et le 95e centile était à 11 bps. Avec un stop de 10 bps, la queue de distribution vous coûte un dixième supplémentaire du risque que vous pensiez avoir défini.
La deuxième est plus subtile et propre aux perps : quel prix le déclenche. Par défaut, Binance déclenche les ordres stop au prix mark, calculé à partir de l’indice et d’une base lissée, et non à partir de la dernière transaction sur cette plateforme. Votre série OHLC est basée sur le dernier prix. Ce sont deux séries différentes, et elles divergent surtout lors des événements précis qui déclenchent les stops.
| Dernier prix (vos klines) | Prix mark (déclenchement par défaut) | |
|---|---|---|
| Source | transactions sur cette plateforme | indice de plusieurs plateformes + base |
| Comportement des mèches | amplitude complète | fortement amorties |
| Écart typique | 1–3 bps au calme, 20–35 bps pendant une minute de cascade | |
| Conséquence pour le backtest | stops déclenchés à tort, et inversement | |
Ainsi, une mèche de 25 bps sur le flux du dernier prix vous fait sortir dans le backtest, alors que le prix mark en direct n’a jamais été à moins de 10 bps de votre seuil de déclenchement. Ou l’inverse, le jour où l’indice bouge et où votre plateforme accuse un retard. Si vous réglez workingType sur CONTRACT_PRICE, vous alignez au moins le comportement en direct sur vos données, ce qui est généralement le bon choix pour un chercheur, car simuler honnêtement un déclenchement au prix mark signifie faire transiter une deuxième série dans tout votre moteur d’exécution.
Voici le cas dont je me souviens le mieux : quelqu’un de notre équipe a « amélioré » une stratégie en faisant passer l’objectif de 0.18% à 0.21%. Le Sharpe est passé de 2.3 à 3.1. Aucun nouvel avantage. L’objectif avait simplement quitté la partie dense de la distribution des mèches 1-minute : moins de trades entraient donc dans la catégorie ambiguë, où le code leur accordait discrètement la victoire. La personne avait optimisé le départage.
Mauvaise méthode 3 : toujours supposer le pire et appeler cela de la prudence
Le réflexe est de choisir le pessimisme. Si les deux niveaux sont touchés, on prend le stop. C’est réglé, plus d’optimisme, on publie.
Je faisais ça autrefois. C’est mieux que l’autre option, et c’est quand même faux, pour 2 raisons.
Cela élimine des stratégies pourtant valables. Une résolution pessimiste sur 12.6% des trades a coûté à celle-ci 2.1 points de Sharpe par rapport au 0.94 obtenu avec une résolution sur les ticks. Si le vrai chiffre est 0.94 et que votre convention affiche 0.18, vous abandonnez l’idée pour travailler sur quelque chose de moins bon. Une prudence qui se trompe de 2 points de Sharpe, ce n’est pas de la prudence : c’est du bruit avec une posture morale.
Pire encore, cela fausse l’optimisation. Donnez à un balayage de paramètres un départage pessimiste et l’optimiseur apprendra à éviter l’ambiguïté, puisqu’elle constitue désormais une pénalité pure. Il ira vers des stops larges et des objectifs proches, ou vers des bougies lentes où les deux niveaux coexistent rarement, puis vous proposera des paramètres choisis en fonction de votre convention d’exécution plutôt que du marché. C’est le même échec que la version généreuse, avec le signe opposé, tout aussi invisible dans le tearsheet.
Règle empirique à appliquer avant toute autre chose : si stop_distance + target_distance est inférieur à l’amplitude au 75e centile de votre intervalle de bougie, votre hypothèse intrabar pèse davantage dans le P&L que votre signal. Calculez ces deux chiffres. Cela prend 4 lignes et a mis fin à plus de revues de stratégies que n’importe quelle autre vérification isolée.
Ce qui fonctionne vraiment
Le parcours à l’intérieur de la bougie est une donnée. Récupérez-la, ou encadrez ce que vous ne pouvez pas obtenir.
- Résolvez avec la série la plus fine dont vous disposez. Les aggTrades Binance pour les minutes concernées représentent quelques centaines de lignes et tranchent la question : quel niveau a été touché en premier, et à quel prix la traversée a été exécutée. Pas besoin de données de ticks pour tout le backtest, uniquement pour les bougies ambiguës. Dans mon échantillon, il s’agissait de 606 minutes sur 129,600. C’est un petit téléchargement, pas un projet d’infrastructure.
- Si les ticks ne sont pas disponibles, descendez d’un ou 2 intervalles de temps uniquement pour la résolution. Signaux en 15m, sorties résolues avec des bougies 1s ou 1m. L’ambiguïté passe de 49% à une fraction de pourcent, et le reliquat est assez faible pour qu’on puisse honnêtement l’ignorer.
- Présentez toujours la fourchette. Exécutez chaque backtest 2 fois, avec une résolution optimiste puis pessimiste, et affichez les 2 Sharpes à côté de celui obtenu après résolution. Cet écart mesure votre incertitude intrabar et doit figurer sur le tearsheet, à côté de l’intervalle de confiance que vous indiqueriez pour le Sharpe lui-même. Quand la fourchette va de 0.2 à 2.3, aucune conclusion dans cet intervalle n’est réelle.
- Suivez le taux d’ambiguïté comme un indicateur à part entière. Il figure en haut de chaque fiche de stratégie, à côté du nombre de trades et du turnover. Un taux supérieur à environ 5% signifie que le test porte sur la logique de sortie, pas sur celle d’entrée.
- Modélisez séparément le déclenchement et l’exécution. Déclenchez sur la série de prix réellement utilisée par la plateforme ; simulez l’exécution au prix de déclenchement plus un slippage tiré d’une distribution calibrée sur le flux, et non au prix de déclenchement lui-même.
Les actions connaissent le même problème, sous une autre forme. Un stop à 62.00 sur un titre qui ouvre à 58.40 après un gap nocturne ne s’exécute pas à 62.00 : il s’exécute à un prix inférieur au cours d’ouverture. Un backtest sur des bougies journalières qui comptabilise un slippage de −$0.00 lors des gaps au-delà du stop vous dira volontiers qu’une couche de stop-loss a amélioré votre drawdown. C’est faux. Le stop n’a simplement jamais été testé les jours qui comptent. Les suspensions de cotation posent le même problème : votre stop est réellement exécuté à la réouverture, à un prix que le plus bas de la bougie ne montre jamais.
Rien de tout cela n’a d’exotique. Il s’agit de reconnaître qu’une bougie est un résumé, et qu’une stratégie stop-objectif parie sur l’ordre des événements écartés par ce résumé. Quand le moteur de paper trading finit par exécuter la stratégie sur un flux en direct, le flux donne son avis sur cet ordre, sans jamais se soucier de la branche de l’instruction if que vous avez écrite en premier.
← Tous les articles


