Publié sous forme d'Idée TradingView sur ETHUSDT. Voir le graphique sur TradingView
Si vous avez déjà porté un indicateur vers Python pour le backtester, ou fait confiance à un script parce qu'il était livré avec une vérification, il est ici question de l'écart que ces deux choses laissent ouvert.
Nous avons reconstruit trois de nos propres indicateurs pour qu'un backtest puisse tourner dessus. Les trois reconstructions contenaient une constante erronée. Les trois ont réussi leur validation. Pas une seule n'a été rattrapée par un test.
Pourquoi reconstruire un indicateur, au fond
Un script Pine s'exécute sur votre graphique. Il ne parcourt pas six ans d'historique en boucle, et il ne répond pas à la question de savoir si un signal rapporte réellement. Pour poser cette question, on le reconstruit dans quelque chose que l'on peut exécuter des milliers de fois.
C'est dans cette reconstruction qu'un indicateur cesse discrètement d'être le même indicateur.
Le bug qui a coûté le plus cher, et c'est une bougie
L'une des longueurs à l'intérieur de Volume Oracle est dérivée de l'unité de temps :
target bars = 36 hours × 60 / 240 minutes = 9
regime length = round(9 × 2.5) = round(22.5)
Le math.round de Pine tranche les égalités en s'éloignant de zéro. Il donne 23. Le round() de Python 3 utilise l'arrondi bancaire, qui tranche les égalités vers le nombre pair. Il donne 22.
Les deux sont des implémentations correctes de l'arrondi. Ce sont simplement des conventions différentes, et rien ne vous avertit que vous êtes passé de l'une à l'autre.
Chaque rétrospection dérivée de ce nombre s'est déplacée avec lui. Le résultat sur la ligne la mieux étayée d'un balayage de 36 signaux :
| version | avantage à 48 h | taux de réussite | n |
|---|---|---|---|
| reconstruit, longueur 22 | +0,423 % | 51 | 1216 |
| correct, longueur 23 | +0,260 % | 50 | 1223 |
Une décote de 38 % due à une convention d'arrondi.
Nous avons ensuite consigné la leçon de travers
La note que nous avons rédigée disait que cela ne se produisait qu'en 4H. Elle a été écrite depuis la seule unité de temps qui se trouvait alors sous test, et elle est restée ainsi pendant deux mois.
En faisant tourner le même calcul sur toutes les unités de temps :
| unité de temps | longueur de régime | longueur de flux | résultat |
|---|---|---|---|
| 5m | 45 contre 45 | 23 contre 22 | diverge |
| 2H | 45 contre 45 | 23 contre 22 | diverge |
| 4H | 23 contre 22 | 12 contre 11 | diverge |
| 8H | 53 contre 52 | 27 contre 26 | diverge |
| 1D | 18 contre 18 | 9 contre 9 | égalité, concorde |
| 3D, 1W | 15 contre 15 | 8 contre 8 | égalité, concorde |
Quatre sur seize, pas une.
De ce tableau tombent deux choses qu'aucun raisonnement n'avait produites.
Une égalité ne suffit pas. 1D, 3D et 1W tombent toutes exactement sur ,5 et concordent. L'arrondi bancaire ne déplace qu'une égalité dont la réponse arrondie au supérieur est impaire, donc round(17.5) vaut 18 dans les deux cas. Environ la moitié de toutes les égalités sont inoffensives, et c'est précisément pour cela qu'en vérifier une au hasard ne prouve rien.
La divergence peut arriver une étape plus tard. En 5m et en 2H, la première longueur concorde à 45. La ligne suivante, round(45 × 0.5), vaut de nouveau 22,5, et celle-là se scinde. Si vous vérifiez la première constante puis passez à autre chose, vous validez à tort les deux unités de temps.
Les deux autres, brièvement
Harmonic normalise un oscillateur en le bornant à une bande glissante. Dans le code source, chaque bougie est bornée par sa propre bande. La reconstruction bornait une fenêtre entière de 200 bougies avec la bande de la bougie courante, ce qui est une autre fonction. Écart mesuré sur 11 497 bougies : 0,47 point en moyenne. Une véritable erreur de transcription sans effet matériel.
Celui-là, nous l'avons aussi gonflé avant de le mesurer. Il a été annoncé, à la simple lecture du code, comme étant presque certainement la dérive de 9 %. Le chiffre était 0,47. Un vrai bug et un bug matériel sont deux affirmations différentes, et l'on mesure l'ampleur avant de nommer l'impact.
Plutus Flow moyenne le volume sur 50 bougies pour détecter les pics. La reconstruction en utilisait 20, parce que c'était le nombre que nous croyions connaître. Effet : moins d'un dixième de point de pourcentage, aucun changement de signe. Le verdict n'a pas bougé, mais le nombre était faux et il est désormais juste, ce qui est le seul critère qui compte.
La partie qui constitue vraiment la leçon
Voici ce que la validation de chaque reconstruction rapportait à l'époque.
| reconstruction | sa propre vérification | ce que cela signifiait |
|---|---|---|
| Volume Oracle | 3 sur 3 avant, 3 sur 3 après | n'a pas su les distinguer |
| Harmonic | 6 sur 8 avant, 5 sur 8 après | a mieux noté la version buguée |
| Plutus Flow | jamais exécutée | — |
C'est sur la ligne du milieu qu'il faut s'arrêter. La validation a préféré le mauvais code. Si nous avions laissé la vérification décider, elle nous aurait ramenés par le raisonnement dans le bug.
Une validation qui compare une seule valeur de sortie ne peut pas détecter une constante d'entrée erronée. La sortie est un tamis beaucoup trop grossier. Quantité de jeux de paramètres erronés et différents produisent aujourd'hui la même étiquette de régime, le même état de croisement, la même couleur sur le graphique. Ils diffèrent sur des bougies que vous ne regardez pas en ce moment, et c'est exactement là que vit un backtest.
Que faire à la place
Affichez chaque paramètre dérivé. Chaque longueur, chaque seuil, chaque multiplicateur. Comparez-les au code source ligne par ligne, avant de noter quoi que ce soit.
Gardez la vérification de sortie. Elle attrape une autre classe d'erreurs et elle est nécessaire. Elle est simplement loin d'être suffisante, et ces trois-là sont passées devant elle sans être inquiétées.
La portée honnête
Le code Pine était correct dans les trois cas. Ce qui était faux, c'est l'appareil construit pour le tester, et c'est la version la plus inconfortable du problème : l'instrument a dérivé, et l'autocontrôle de l'instrument affirmait le contraire.
Le chiffre de 38 % correspond à une ligne, un balayage, une unité de temps, dans l'échantillon, et il est cité depuis notre propre relevé plutôt que recalculé. Le résultat de l'arrondi est reproductible en une seule commande, et c'est en le corrigeant qu'est apparu le fait que notre propre note à ce sujet était fausse depuis deux mois.
Publié sous forme d'Idée TradingView sur ETHUSDT. Voir le graphique sur TradingView
Mesure passée de notre propre outillage, décrite après coup. Rien ici n'est une recommandation ni une indication de résultats futurs.