06 · Données historiques et backtest réaliste¶
Un backtest d'order flow honnête est difficile. Un backtest d'order flow malhonnête est trivial, et c'est celui que produisent la plupart des outils par défaut.
Les sources de données¶
| Source | Contenu | Coût | Fidélité |
|---|---|---|---|
| Enregistrement maison | ce que votre bot reçoit | gratuit | Maximale |
| Rithmic History Plant | ticks et barres depuis déc. 2011 | inclus | Bonne |
| Databento GLBX.MDP3 | MBO complet, PTP nanoseconde | 199 $/mois (Standard) | Excellente |
L'enregistrement maison¶
C'est la source la plus fidèle, et elle est gratuite.
import gzip, json
class Enregistreur:
"""Écrit chaque événement brut, avec l'horodatage de réception locale."""
def __init__(self, chemin):
self.f = gzip.open(chemin, "at", compresslevel=1)
def ecrire(self, event):
self.f.write(json.dumps({
"ts_local": time.time_ns(),
"ts_exchange": event.timestamp_ns,
"type": event.__class__.__name__,
"data": event.__dict__,
}) + "\n")
Pourquoi c'est supérieur aux données achetées
Vos enregistrements contiennent votre latence, vos pertes de paquets, vos reconnexions. Un backtest sur ces données mesure ce que votre bot aurait réellement vu — pas ce qu'un observateur idéal en colocation aurait vu.
L'écart entre les deux est exactement l'erreur que commettent la plupart des backtests d'order flow.
compresslevel=1 : la compression rapide suffit largement, et ne bloque pas
le processus temps réel.
Databento¶
Databento fournit CME Globex MDP 3.0 avec schémas MBO, MBP-10, TBBO, trades et OHLCV, horodatages PTP nanoseconde. Le plan Standard à 199 $/mois inclut les données live ; les données L2/L3 y sont limitées au dernier mois, l'historique complet de 16 ans exigeant les plans Plus (1 750 $/mois) ou Unlimited (4 500 $/mois) (Databento, GLBX.MDP3). Un crédit de 125 $ est offert aux nouveaux comptes.
Ne payez pas Databento en premier
Commencez par enregistrer votre flux pendant un mois. Vous aurez alors 20 séances de données parfaitement représentatives, gratuitement, et vous saurez si le projet mérite l'investissement. Beaucoup de bots meurent avant d'avoir besoin de données historiques payantes.
Les outils de backtest¶
| Outil | Approche | Pertinence pour l'order flow |
|---|---|---|
| NautilusTrader | Événementiel, Rust + Python, nanoseconde | Excellente. Adaptateur Databento intégré |
| hftbacktest | Rust + Python, modèles de file | Excellente pour les stratégies passives |
| Backtest maison | Rejeu du journal | Suffisant, et le plus simple |
NautilusTrader supporte le backtest multi-venues et multi-instruments à partir de quotes, trades, barres, carnet et données personnalisées, à résolution nanoseconde, avec un adaptateur Databento permettant de charger des fichiers DBN (documentation). Aucun adaptateur Rithmic n'apparaît dans la documentation consultée — à vérifier sur le dépôt.
hftbacktest modélise explicitement la position en file avec plusieurs
modèles (RiskAverseQueueModel, ProbQueueModel, L3FifoQueueModel) et
applique des latences distinctes à l'aller et au retour
(DeepWiki). C'est
l'outil de référence si votre stratégie repose sur des ordres passifs.
Le backtest maison est souvent le bon choix
Si votre moteur de signaux est une fonction pure et que vous avez un journal d'événements, le backtest se réduit à :
for event in relire_journal(chemin):
etat = agregat.update(event)
intents = moteur.signaux(etat, ctx, params)
simulateur.traiter(intents, event)
Quinze lignes, exactement le même code de signaux qu'en production, aucune dépendance. La sophistication d'un framework externe se paie en risque de divergence entre backtest et live — le pire des bugs possibles.
Les sept biais qui tuent un backtest d'order flow¶
1. Le regard vers l'avenir¶
Utiliser une information non disponible au moment de la décision. Sur l'order flow, la forme la plus insidieuse : utiliser le delta d'une barre avant sa clôture.
# FAUX : la barre n'est pas terminée au moment du signal
if barre.delta > seuil: entrer()
# JUSTE : on décide sur la barre précédente, close
if barres[-2].delta > seuil: entrer()
2. L'ordre des horodatages¶
Utiliser ts_exchange pour décider revient à réagir avant réception.
# Le bot ne peut décider qu'à ts_local
evenements.sort(key=lambda e: e.ts_local)
3. Le slippage constant¶
Le slippage est corrélé au signal : les motifs d'order flow se déclenchent dans les moments d'activité intense, ceux où le slippage est maximal.
def slippage_realiste(event, volatilite_courante, base=0.5):
"""En ticks. Modèle grossier mais orienté du bon côté."""
facteur = max(1.0, volatilite_courante / volatilite_mediane)
return base * facteur
Le biais le plus coûteux
Un slippage constant surestime d'autant plus la stratégie qu'elle est bonne — car les meilleurs signaux surviennent dans les pires conditions d'exécution. C'est un biais qui récompense exactement ce qu'il faudrait pénaliser.
4. L'exécution passive optimiste¶
Supposer que tout ordre limité au prix touché aurait été exécuté. Sans modèle de file, cette hypothèse est fausse dans une proportion inconnue mais élevée.
La règle conservatrice
Un ordre limité n'est réputé exécuté que si le prix a traversé son niveau, pas seulement touché. Cette règle sous-estime légèrement, ce qui est la bonne direction d'erreur. Si vous avez besoin de mieux, il vous faut du MBO et hftbacktest.
5. Le survivant des contrats¶
Backtester sur une série de prix continue reconstituée masque les rollovers et crée des sauts de prix artificiels. Backtestez contrat par contrat.
6. Le surajustement par les seuils¶
Chaque paramètre optimisé consomme du degré de liberté.
| Paramètres optimisés | Trades minimaux |
|---|---|
| 2 | 60 |
| 5 | 150 |
| 8 | 240 |
| 12 | 360 |
7. Le biais de sélection de période¶
Choisir la période de backtest après avoir vu les résultats. La protection : découper avant de commencer.
[--- développement 60 % ---][-- validation 20 % --][-- test 20 % --]
↑
touché UNE SEULE FOIS
La période de test se regarde une fois
Si vous regardez le résultat sur la période de test, ajustez, puis regardez à nouveau, elle est devenue une période de développement et vous n'avez plus de test. Il n'existe aucun moyen de réparer cela hors attendre de nouvelles données.
La validation contre le hasard¶
L'étape la plus importante et la plus omise.
import random
def test_hasard(signaux_reels, evenements, n_essais=1000, seed=0):
"""Le signal bat-il un tirage aléatoire de même fréquence
et de même distribution horaire ?"""
rng = random.Random(seed)
reel = pnl_de(signaux_reels)
par_heure = distribution_horaire(signaux_reels)
meilleurs = 0
for _ in range(n_essais):
faux = tirer_signaux_aleatoires(evenements, par_heure, rng)
if pnl_de(faux) >= reel:
meilleurs += 1
return meilleurs / n_essais # p-value empirique
Une p-value de 0,30 signifie que 30 % des tirages aléatoires font aussi bien que votre stratégie. Vous n'avez pas de stratégie.
Conserver la distribution horaire est essentiel
Un tirage aléatoire uniforme sur la journée n'est pas une bonne référence : votre stratégie ne trade qu'à l'ouverture, où la volatilité est plus élevée. Le tirage doit reproduire la même distribution horaire pour que la comparaison isole le signal et non le créneau.
C'est aussi le moyen de mesurer ce que le filtre horaire apporte à lui seul.
Le paper trading¶
Entre backtest et production, une phase obligatoire.
| Phase | Durée minimale | Critère de passage |
|---|---|---|
| Backtest | — | p-value < 0,05 vs hasard, espérance positive coûts inclus |
| Paper (Rithmic Paper Trading) | 20 séances | Fréquence et P&L cohérents avec le backtest |
| Production taille 1 | 20 séances | Idem, sur argent réel |
| Production taille normale | — | — |
Le paper trading révèle les bugs, pas la performance
Son but n'est pas de confirmer que la stratégie gagne — 20 séances ne prouvent rien statistiquement. Son but est de révéler les écarts entre backtest et réalité : symboles, fuseaux horaires, fills partiels, reconnexions, rollover, tailles.
Critère de passage : la fréquence des signaux en paper doit correspondre à celle du backtest à ±20 % près. Si elle diffère davantage, quelque chose de fondamental diverge, et ce n'est pas la peine de regarder le P&L.
Résumé¶
- Enregistrez votre propre flux : source la plus fidèle, et gratuite.
- Le backtest maison sur journal rejoué évite la divergence backtest/live.
- Sept biais : regard vers l'avenir, horodatages, slippage constant, exécution passive optimiste, contrats continus, surajustement, sélection de période.
- Le slippage constant est le biais le plus coûteux, car il récompense les bons signaux.
- Validez contre un tirage aléatoire à distribution horaire identique.
- 20 séances de paper minimum, jugées sur la fréquence, pas sur le P&L.
Chapitre précédent : Exécution et gestion des ordres · Chapitre suivant : Mise en production et supervision