Aller au contenu

Trois bugs, trois tests réussis, aucun détecté

Graphique 4 heures du perpétuel ETHUSDT avec deux fenêtres de rétrospection tracées côte à côte, l'une de 23 bougies et l'autre de 22, soit la longueur que calcule le code source Pine et la longueur qu'a calculée la reconstruction Python à partir de la même formule.
La même rétrospection, calculée deux fois. Pine arrondit 22,5 en s'éloignant de zéro et obtient 23. Python 3 l'arrondit au nombre pair et obtient 22. Rien, dans aucun des deux langages, ne vous avertit que la convention a changé.

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 :

versionavantage à 48 htaux de réussiten
reconstruit, longueur 22+0,423 %511216
correct, longueur 23+0,260 %501223

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 tempslongueur de régimelongueur de fluxrésultat
5m45 contre 4523 contre 22diverge
2H45 contre 4523 contre 22diverge
4H23 contre 2212 contre 11diverge
8H53 contre 5227 contre 26diverge
1D18 contre 189 contre 9égalité, concorde
3D, 1W15 contre 158 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.

reconstructionsa propre vérificationce que cela signifiait
Volume Oracle3 sur 3 avant, 3 sur 3 aprèsn'a pas su les distinguer
Harmonic6 sur 8 avant, 5 sur 8 aprèsa mieux noté la version buguée
Plutus Flowjamais 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.


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.