Nous avons relancé un backtest enregistré et obtenu une courbe de capital différente. Même code de stratégie, même période, même symbole. Le solde final présentait un écart de 1.8%, et trois transactions avaient été décalées d’une bougie.
C’est suffisant pour rendre un résultat de recherche difficile à prendre au sérieux. Si un collègue, une future version de vous-même ou un service de trading simulé ne peut pas reproduire l’exécution, impossible de savoir si une modification a amélioré la stratégie ou simplement changé l’expérience. Voici les étapes que nous avons suivies pour trouver l’origine des écarts.
Jour 1 : nous avons défini ce que signifiait « même exécution »
Notre première erreur a été de considérer le fichier de stratégie comme l’expérience. Ce n’était pas le cas. L’exécution dépendait aussi des données d’entrée, de la version du moteur, du calendrier, des métadonnées de l’instrument et des paramètres d’exécution. Le code ne décrivait qu’une partie du calcul.
Nous avons établi un manifeste d’exécution avant de modifier quoi que ce soit. Il consignait le commit de la stratégie, les identifiants des instantanés de données, la période, la plateforme, le barème des frais, la source de financement, le modèle d’exécution des ordres et les versions logicielles. Nous avons aussi conservé les ordres et les exécutions obtenus, car une courbe de capital ne suffit pas à montrer à quel moment deux exécutions commencent à diverger.
| Élément | À consigner | Pourquoi c’est important |
|---|---|---|
| Données de marché | ID de l’instantané, version du schéma, ajustements | Les fournisseurs corrigent l’historique et révisent les opérations sur titres |
| Exécution | Niveau de frais, série de financement, paramètres d’exécution et d’impact | Les valeurs par défaut et les hypothèses sur le compte modifient les résultats |
| Environnement d’exécution | Commit du code, versions du moteur et des dépendances | Les bibliothèques peuvent modifier l’ordre des opérations, les arrondis ou les indicateurs |
| Résultat | Ordres, exécutions, positions et métriques | Indique où les exécutions commencent à diverger |
Jour 2 : nous avons comparé les transactions, pas le Sharpe
Les métriques récapitulatives nous ont induits en erreur. Les deux exécutions affichaient un Sharpe presque identique, mais leurs journaux d’exécution révélaient la première divergence au moment d’un règlement de financement. L’une imputait le taux à la position ouverte à l’horodatage du règlement ; l’autre, à la position après le rééquilibrage effectué à cet horodatage.
Le code de la stratégie n’avait pas changé. C’était l’ordre des événements du moteur. Une petite mise à jour de version avait rendu explicite une séquence qui dépendait auparavant du tri fortuit de deux événements.
Nous avons modifié le contrat d’exécution pour préciser l’ordre : appliquer le financement à la position détenue à l’arrivée du règlement, puis traiter les décisions de la stratégie pour cet horodatage. La convention exacte peut varier selon la plateforme et le moteur. La laisser implicite, voilà le bug.
Jour 3 : un fichier de données « identique » s’est révélé différent
Après avoir fixé l’ordre des événements, les divergences restantes se concentraient sur quelques transactions sur actions. Le fournisseur avait corrigé un ajustement historique lié à un fractionnement d’actions. Notre fichier portait le même nom et comptait le même nombre de lignes qu’avant, ce qui nous avait fait croire qu’il était inchangé.
Désormais, nous calculons l’empreinte de chaque instantané de données immuable et conservons avec lui la politique d’ajustement. Une empreinte indique si les octets ont changé ; elle n’explique pas pourquoi. Le manifeste contient donc aussi la source, l’heure de récupération et la version des transformations. Pour des données révisées au fil du temps, ces détails font partie du résultat.
Un backtest reproductible doit répondre à la question : « Quelle version du passé a-t-il consultée ? »
Jour 4 : nous avons trouvé un paramètre par défaut discret
La dernière différence venait des frais maker, fixés à 0 parce que le champ était absent de la configuration de la stratégie. Une version plus récente du moteur appliquait les frais par défaut du compte. Ce seul paramètre a suffisamment modifié les transactions marginales pour expliquer l’essentiel de l’écart de solde final.
Nous avons rendu explicites les paramètres ayant un effet économique et demandé au moteur d’inscrire la configuration résolue dans le dossier d’exécution. Les valeurs par défaut sont pratiques pendant l’exploration. Elles constituent de faibles éléments de preuve lorsqu’on compare des résultats dans le temps.
Ce que nous éviterions la prochaine fois
Nous avons passé une demi-journée à comparer les métriques agrégées avant d’examiner la première exécution divergente. Ne commencez pas par là. Triez les deux journaux d’événements par horodatage et comparez la première divergence ; les écarts ultérieurs découlent souvent de cette seule cause.
Nous renoncerions aussi à l’idée qu’une image de conteneur suffit à rendre une exécution reproductible. Elle fige une grande partie de l’environnement logiciel, mais pas un fichier de données externe, un barème de frais récupéré à l’exécution ni l’historique révisé d’un fournisseur.
Quand un backtest change, conservez les deux manifestes et les deux journaux d’exécution, puis corrigez une source d’écart à la fois. Le résultat utile n’est pas seulement une courbe que l’on peut recalculer. C’est un dossier qui explique quelles données et quelles hypothèses l’ont produite, et pourquoi la prochaine exécution pourrait être différente.
← Tous les articles


