Nous avons redémarré une stratégie paper au milieu d'une position et obtenu une séquence d'ordres différente de celle produite avant le redémarrage. Mêmes données de marché, même version de stratégie, même solde de compte. Le calcul du signal correspondait. En revanche, la stratégie ne se souvenait pas de la même façon de sa position et de ses ordres en attente.
Nous avons retracé l'écart au fil d'une journée de replay. La leçon à retenir est simple : pour votre logiciel, un redémarrage est un événement de marché. Si vous ne testez pas comment la stratégie reconstitue son état, un backtest propre peut masquer un système paper qui oublie ce qu'il détient.
09:10 — Nous avons choisi une position sans histoire
La stratégie de test négociait un contrat perpétuel liquide : elle entrait en position longue quand une moyenne mobile courte croisait à la hausse une moyenne plus longue, puis sortait au croisement inverse. Nous avons lancé le replay avec une petite position déjà ouverte et un ordre limite reduce-only en attente pour la réduire. La reprise devait donc reconstituer 2 éléments : ce que nous détenions et ce que nous avions déjà demandé à la plateforme d'exécuter.
Au point de contrôle, le compte détenait 0.04 contrat. L'ordre en attente portait sur 0.01. Le processus de la stratégie avait mis ces 2 valeurs en cache, mais son démarrage ne récupérait que la position. Il a considéré que l'ordre en cache n'existait plus.
09:25 — Le premier ordre en double est apparu
Au redémarrage, la stratégie a détecté la position longue existante, exécuté sa logique de signal et soumis un nouvel ordre de réduction de 0.01. La plateforme paper avait alors 2 ordres actifs. Aucun n'était problématique en soi. Ensemble, ils pouvaient vendre 2 fois la quantité prévue s'ils étaient tous les 2 exécutés.
Nous avons d'abord soupçonné la boucle de signal. Ce n'était pas la boucle, mais l'état incomplet. La stratégie demandait : « Quelle est ma position ? », sans jamais demander : « Quels ordres sont encore actifs ? »
| État après le redémarrage | Ce que le processus croyait | Ce que détenait le compte |
|---|---|---|
| Position | Longue de 0.04 | Longue de 0.04 |
| Ordres de réduction ouverts | Aucun | 2 ordres de 0.01 chacun |
| Exposition prévue après une exécution | Longue de 0.03 | Pourrait tomber à 0.02 |
10:00 — Nous avons corrigé la reprise, puis découvert un problème de timing
Nous avons modifié le démarrage pour reconstituer l'état à partir des positions et des ordres ouverts du compte avant d'autoriser de nouvelles décisions. Le doublon a disparu. Puis nous avons compliqué la déconnexion : un ordre a été exécuté pendant que la stratégie était hors ligne, et la notification d'exécution est arrivée après la reconnexion.
L'instantané du compte tenait déjà compte de l'exécution. La notification tardive a ensuite réduit une deuxième fois la position locale. Pendant quelques secondes, la stratégie a cru détenir 0.02 contrat alors que le compte en détenait 0.03. Son rééquilibrage suivant s'est fondé sur un manque fictif.
Nous avons ajouté des règles de rapprochement : prendre l'instantané du compte comme point de départ, utiliser les identifiants d'événement pour ignorer les exécutions déjà prises en compte et ne soumettre aucun ordre avant la fin de la synchronisation initiale. Une notification peut arriver en retard ou en double. La reprise doit gérer les 2 cas.
13:40 — Le replay a révélé un écart discret
Nous avons rejoué le même parcours de prix sur l'exécution initiale et celle après redémarrage. Comparer le P&L final n'aurait pas révélé le problème : les 2 versions ont fini avec la même position après le retournement du marché. La comparaison des événements d'ordre, elle, l'a mis au jour.
Nous avons journalisé chaque décision avec l'état qu'elle avait consulté : position, ordres ouverts, identifiant de la dernière exécution traitée, valeur du signal et version de la stratégie. Le premier événement divergent s'est alors expliqué. Une exécution voyait un ordre actif ; l'autre, une liste vide. Plus tard, l'une a appliqué une exécution 2 fois.
Un solde final identique ne prouve pas que le comportement a été identique. Comparez la séquence de décisions et d'ordres, surtout autour des phases de reprise.
16:20 — Ce que nous ferions autrement la prochaine fois
Nous avons passé trop de temps à rejouer les données de prix avant de vérifier les transitions d'état du compte. La prochaine fois, nous injecterions d'abord les cas de panne et garderions le parcours de marché presque plat. L'erreur logicielle serait ainsi facile à repérer, sans qu'un mouvement volatile brouille le diagnostic.
- Redémarrer avec une position et un ordre partiellement exécuté.
- Se déconnecter après la soumission, puis se reconnecter avant l'arrivée de la notification d'exécution.
- Envoyer 2 fois le même événement d'exécution et vérifier qu'il ne modifie l'état qu'une seule fois.
- Bloquer les nouveaux ordres jusqu'au rapprochement des positions et des ordres ouverts.
- Comparer les journaux de décisions et d'ordres des exécutions continues et redémarrées.
Nous avons aussi appris à enregistrer un instantané de reprise avec la version de la stratégie et le journal des événements. L'échec est ainsi devenu reproductible en quelques minutes, sans dépendre de la mémoire de quelqu'un pour retrouver la séquence exacte de reconnexion.
Une stratégie paper qui ne se comporte correctement que tant que son processus reste actif n'a pas réussi sa répétition générale. Redémarrez-la en pleine position, faites arriver les exécutions en retard et examinez chaque ordre qu'elle envoie ensuite. Le but n'est pas de prouver qu'elle ne tombera jamais en panne. C'est de rendre sa reprise visible avant que le compte paper ne vous donne la même leçon par surprise.
← Tous les articles


