Sommaire (6 parties)
Ta stratégie, rejouée sur les prix du passé, affiche un ratio de Sharpe de 2,8, un excellent score rendement sur risque. Tu lances le robot en réel et il perd dès la première semaine. Valeurs d'illustration, pas un souvenir : tu as connu ça, ou tu le connaîtras. Dans les deux cas, le chiffre qui t'avait convaincu venait d'un endroit que tu n'avais pas regardé.
Un backtest rejoue une stratégie sur les prix du passé pour voir ce qu'elle aurait rapporté. Il ne vaut que si chaque prix qu'il utilise existait vraiment au moment où la stratégie aurait agi.
Le ratio de Sharpe note le résultat : le rendement moyen, divisé par la volatilité. Le rendement se compte au-dessus du taux sans risque, ce que rapporte un placement sans danger. La volatilité, c'est l'ampleur des variations.
J'ai deux expériences de ce métier, côté plateforme centralisée, c'est-à-dire tenue par une entreprise. En freelance, j'écris en Python des robots de trading crypto qui tournent 24 h sur 24. Ils gèrent un stop-loss, un ordre qui coupe une position perdante. Ils gèrent aussi un trailing stop, un seuil de sortie qui suit le prix à la hausse, et la taille des positions.
Chez Goin Invest, j'ai écrit en Go des algorithmes de trading pour des mandats de gestion : gérer le portefeuille d'un client selon un contrat. Ils se connectaient à cinq exchanges (des plateformes d'échange de cryptomonnaies), dont Binance, Kraken, Bybit et OKEx.
L'autre côté m'échappait : la DeFi, la finance décentralisée, où le programme est lui-même la place de marché. J'ai donc lu deux livres de l'abonnement ExpertEdge de Packt : Quantitative Trading d'Ernest P. Chan (Wiley, 2e édition, 2021) et Building Full Stack DeFi Applications de Samuel Zhou (Packt, 2024).
Le premier apprend à vérifier qu'un prix de backtest est honnête. Le second montre ce qui arrive quand un programme croit un prix qu'on lui a fabriqué.
Look-ahead : quand ton backtest utilise un prix qui n'existe pas encore
Une comparaison : tu joues à pile ou face, mais on te laisse lire le résultat avant de parier. Tu gagnes à tous les coups, et tu n'as aucun talent.
Le look-ahead bias est cette triche, quand elle se glisse dans un backtest sans que tu le veuilles. C'est utiliser une information qui n'existait pas encore à l'instant du trade.
Chan donne un exemple. Une règle « acheter à moins de 1 % du plus bas du jour » est impossible à appliquer en direct. Ce plus bas n'est connu qu'à la clôture. Le backtest, lui, a tout l'historique sous la main et ne se plaint de rien.
Chan propose un test de troncature (leçon 15). L'idée : refaire tourner la stratégie avec les derniers jours masqués, et vérifier qu'elle décide pareil.
- Tu calcules les positions de ta stratégie, ce qu'elle détient à chaque date, sur tout l'historique. J'appelle ce résultat A.
- Tu retires les N derniers jours (il parle de 10 à 100), tu relances, ce qui donne B.
- Tu coupes les N dernières lignes de A et tu compares avec B.
Pour Chan, un écart prouve que le programme se sert du futur : « les écarts de positions signifient que vous utilisez, sans le vouloir, la partie tronquée de l'historique ».
Voici ma version en Python, pas celle du livre. Elle est de moi, à toi de l'adapter à ta stratégie. C'est le test le moins cher du métier, et presque personne ne l'écrit :
import numpy as np
# prix et positions sont des Series ou DataFrame pandas
def test_troncature(strategie, prix, n=60):
"""Échoue si la stratégie utilise des données postérieures au jour où elle décide."""
positions_a = strategie(prix) # historique complet
positions_b = strategie(prix.iloc[:-n]) # sans les n derniers jours
# Sur la période commune, les positions doivent être identiques.
commun = positions_a.iloc[:-n]
assert len(commun) == len(positions_b)
# equal_nan : une fenêtre glissante laisse des NaN au début, aux mêmes endroits
assert np.allclose(commun.values, positions_b.values, equal_nan=True), \
"look-ahead : le futur a fuité dans les positions"
# Le piège classique que ce test attrape (un exemple de moi) :
# un signal normalisé par la moyenne et l'écart-type de l'historique entier.
# Remplace-le par une fenêtre glissante, par exemple prix.rolling(50),
# et le test passe.
(Le commentaire sur la normalisation globale est ma propre illustration, il n'est pas dans le livre.)
Branche ce test dans ta chaîne d'intégration continue, celle qui lance tes tests automatiquement à chaque modification. Chaque fois que quelqu'un touche à la stratégie, le test relit l'histoire avec un voile devant les derniers jours.
Chan ne parle pas de crypto, mais le principe se transpose. Un marché qui ne ferme jamais n'a pas de « clôture du jour ». La tentation est de fabriquer une bougie journalière : l'ouverture, le plus haut, le plus bas et la clôture d'une journée. Puis de décider à l'intérieur.
Attention, le test de troncature ne voit pas ce piège-là. La bougie du jour est la même dans les deux passes, puisqu'on ne coupe que des jours entiers. La parade est de décider sur la bougie d'hier et d'agir aujourd'hui. Tu décales le signal d'un jour (signal.shift(1) avec pandas, la bibliothèque Python de tableaux de données).
Trop de réglages, trop peu de données, trop de levier
Un backtest honnête a encore deux ennemis : trop de réglages et trop peu de données.
Un réglage (ou paramètre) est un nombre que tu choisis, comme un seuil ou une durée. Chan donne une règle nette : « je n'emploierais pas plus de cinq paramètres », seuils d'entrée et de sortie, durée de détention et fenêtre de moyenne mobile compris (leçon 15).
Le mal s'appelle le data-snooping : à force d'optimiser sur le passé, tu règles ta stratégie sur du bruit. Pour te représenter la chose : apprendre par cœur les réponses d'un ancien examen. Tu as 20 sur 20 à celui-là, et rien n'est prouvé pour le prochain.
Le remède du livre est de couper l'historique en deux. Tu règles sur la première partie, l'entraînement. Puis tu testes sur la seconde, que tu n'as jamais regardée.
Sur la quantité de données, Chan s'appuie sur les calculs de Bailey et de ses coauteurs (2012) sur la longueur minimale d'un backtest. Le Sharpe de backtest est celui que tu mesures sur le passé. Le vrai Sharpe est celui que la stratégie aura en réel. Un point de donnée est un cours de clôture, un par jour :
- Un Sharpe de backtest de 1 demande 681 points pour être confiant que le vrai Sharpe est au moins 0. Cela fait 2,71 ans de cours quotidiens.
- Un Sharpe de backtest de 2 n'en demande que 174.
- Pour être confiant que le vrai Sharpe dépasse 1, il faut un Sharpe de backtest d'au moins 1,5. Cela demande 2 739 points, près de 11 ans de données quotidiennes.
Je ne transposerais pas ces chiffres à d'autres fréquences sans refaire le calcul.
Reste le levier, la taille de ton portefeuille divisée par ton capital propre. Chan expose le critère de Kelly : le levier qui maximise la croissance à long terme. Sa formule est le rendement moyen au-dessus du taux sans risque, divisé par la variance, le carré de la volatilité.
Pour SPY, le fonds qui suit le S&P 500, Kelly donne 2,528. Dans la pratique, la plupart des traders prennent la moitié, le demi-Kelly. La raison : « il est toujours plus sûr d'avoir un levier inférieur à celui que recommande la formule de Kelly » (leçon 28).
Le garde-fou qui me plaît le plus est la leçon 29 : « le levier à utiliser est toujours le plus petit entre le demi-Kelly et le levier maximal obtenu avec la pire perte historique ». Kelly suppose des rendements en courbe en cloche, alors que les krachs sont bien plus fréquents.
Le S&P 500 a perdu 20,47 % en une journée, le 19 octobre 1987. Si tu tolères 20 % de perte en un jour, le levier maximal est d'environ 1, contre 1,26 pour le demi-Kelly. Tu retiens 1.
Sur un robot crypto, je ne peux pas en déduire de chiffre, Chan n'en fournit aucun. Le principe se transpose : la pire journée est à calculer sur tes données.
DeFi : comment truquer un prix, et comment s'en protéger
Passons côté contrat. Un smart contract est un programme déployé sur une blockchain, et personne ne peut le modifier ensuite. Une blockchain est un registre partagé, tenu par un réseau d'ordinateurs plutôt que par une entreprise.
Le pool : une balance qui se déséquilibre
Une bourse classique tient un carnet d'ordres : la liste des offres d'achat et de vente, qu'elle apparie. Un AMM s'en passe. Autre façon de voir : une balance à deux plateaux. Des ETH (la monnaie d'Ethereum) d'un côté, de l'USDT (un jeton indexé sur le dollar) de l'autre. Le prix se lit sur le déséquilibre : plus un plateau se vide, plus ce qu'il contenait vaut cher.
Un AMM (Automated Market Maker, teneur de marché automatique) est cette balance écrite en code. C'est un smart contract qui garde deux jetons, deux monnaies numériques, dans une réserve commune : le pool.
La règle d'Uniswap v2, l'AMM le plus connu, s'appelle le produit constant. On multiplie la réserve de A par la réserve de B, et le résultat, noté K, ne change pas avec les échanges. Mon exemple, sans frais :
J'achète 1 ETH : il n'en reste que 4 dans le pool. K doit rester à 50 000, donc la réserve d'USDT monte à 50 000 / 4 = 12 500. J'ai payé la différence, 2 500 USDT, et chaque ETH se vend maintenant 12 500 / 4 = 3 125.
Un seul achat a fait passer le prix de 2 000 à 3 125. C'est l'impact de prix. Le livre donne le cas extrême : avec des réserves égales, acheter 90 % d'un jeton multiplie son prix par 100 (leçon 25). Garde ça en tête, parce que c'est exactement ce qui rend le prix d'un pool manipulable.
L'oracle : un prix lu dans un seul pool
Un contrat ne connaît aucun prix. Il le demande à un oracle, le service qui lui fournit un prix. Un cas concret, imaginé pour l'occasion : tu estimes ta maison en demandant l'avis d'un seul voisin. Si quelqu'un a réussi à le convaincre d'un prix absurde, ton estimation est absurde aussi.
La solution paresseuse est la même : lire le ratio des réserves d'un seul pool. Zhou la juge sans détour : « cette façon est extrêmement dangereuse » (leçon 84).
L'attaque : gonfler le prix, puis emprunter dessus
L'attaque tourne autour d'un prêt. Pour emprunter, tu déposes un bien en collatéral, c'est-à-dire en garantie. Le plafond d'emprunt dépend de la valeur de ce dépôt. Avec un taux de collatéral de 80 %, tu empruntes au plus 80 % de la valeur du dépôt.
Il faut aussi des fonds pour acheter. Une transaction est un ordre envoyé à la chaîne. Un flash loan est un prêt sans collatéral, à rembourser dans la même transaction. Si le remboursement échoue, la transaction entière est annulée.
Zhou détaille l'attaque en quatre temps (leçon 98). Le flash loan fournit les fonds au départ et se rembourse à la fin, le tout dans une seule transaction :
- L'attaquant achète le jeton, ce qui gonfle son prix dans le pool.
- Il en dépose un peu en collatéral. L'oracle lit le prix gonflé, donc il croit ce dépôt précieux.
- Il emprunte d'autres actifs, bien au-delà de la vraie valeur du dépôt.
- Il revend le jeton.
De combien faut-il gonfler le prix ? Avec un taux de collatéral de 80 %, un prix gonflé de 25 % suffit. Le plafond d'emprunt égale alors la vraie valeur du dépôt. En effet, 80 % de 125 % font 100 %.
Le livre cite l'attaque d'Euler Finance, en mars 2023 : 197 millions de dollars. C'est la plus grosse attaque par flash loan connue au moment où il est écrit.
La parade : une moyenne sur 720 secondes
Pour t'en faire une idée : pour connaître le prix de l'essence, le prix moyen de la semaine est plus fiable que celui d'une minute. Fausser une minute est facile. Fausser toute une semaine coûte bien plus cher.
La défense du livre suit cette idée : un oracle moyenné. Un bloc est le lot de transactions que la chaîne valide d'un coup, environ toutes les 12 secondes. À chaque bloc, chaque paire de jetons ajoute à un cumul son prix multiplié par le temps écoulé.
L'oracle en tire un TWAP (time-weighted average price, prix moyen pondéré par le temps) sur une fenêtre, ici 720 secondes. Cela fait 60 blocs de 12 secondes. Une manipulation tient dans un seul bloc, elle pèse donc peu dans la moyenne. Pour Zhou, cette approche « ne dépend pas de plusieurs sources d'oracle tout en résistant à la manipulation » (leçon 100).
Le test du livre rejoue l'attaque. Après la manipulation, l'oracle moyenné estime BAR, un jeton de test du livre, à 2,16 ETH. L'ancien oracle, lui, l'estime à 723,47 ETH. L'emprunt de l'attaquant est refusé avec le motif ACCOUNT_UNHEALTHY (leçon 105). Contre l'ancien oracle, le même scénario rapportait plus de 100 ETH à l'attaquant (leçon 99).
C'est la meilleure section du livre : on voit le prix truqué et le prix honnête côte à côte, sortis du même pool.
Une liste de contrôle « d'où vient ce prix ? »
Voici ce que je tire des deux livres mis face à face. La liste est de moi, elle n'est ni chez Chan ni chez Zhou, mais chaque ligne s'appuie sur un cas qu'ils décrivent :
- À quel instant ce prix existait-il vraiment ? Si ta stratégie ou ton contrat le lit avant ce moment, tu as un look-ahead (le plus bas du jour connu à la clôture).
- Qui peut le déplacer, et avec quel capital ? Si une seule commande suffit, ce n'est pas un prix. C'est l'opinion de celui qui paie (le pool qui passe de 2 000 à 3 125).
- Combien de temps doit-il tenir pour qu'on s'y fie ? Une moyenne sur une fenêtre coûte plus cher à fausser qu'un instantané (12 secondes contre 720).
- Combien de sources indépendantes ? Une source qu'on peut déplacer seule est une faille, même propre.
- Est-ce le prix que je paierai vraiment ? Le prix du backtest n'est pas celui de l'exécution, et l'impact de prix du pool le montre en trois lignes.
- Sur quel jeu de données ai-je réglé et sur lequel ai-je testé ? Si c'est le même, le prix n'a rien prouvé.
Ce que les deux livres ne disent pas sur la crypto réelle
Chan ne parle quasiment pas de crypto. Dans mon extraction, le mot n'apparaît qu'une fois, dans une liste de classes d'actifs que gère une plateforme de backtest.
Ses exemples sont des actions, des ETF et des futures, avec un cours de clôture par jour. Un ETF est un fonds coté qui suit un indice. Un future est un contrat d'achat ou de vente à date fixe. Rien sur un marché continu ni sur la connexion à plusieurs exchanges. Son industrialisation vise un particulier, avec des liens Excel et des ordres envoyés par lots chez un courtier.
Sur le stop-loss, la thèse de Chan est que l'outil ne sert que dans une tendance. Premier cas : le gap, un prix qui saute d'un niveau à un autre sans passer entre les deux. L'ordre est alors exécuté bien plus bas, et le stop ne fait que réaliser la perte. Second cas : le retour à la moyenne, quand un prix s'écarte puis revient vers sa moyenne. Là, le stop te fait sortir « au pire moment » (leçon 37).
Je ne suis pas converti pour la crypto : un marché continu a moins de gaps qu'une action fermée la nuit. Seul un test sur des données crypto tranchera, et le livre n'en fournit pas.
Zhou a le défaut inverse : il est plein de crypto, mais sa DeFi s'arrête aux services de base : échange, staking, prêt et oracle. Son protocole de prêt reprend Aave v2, l'un des grands protocoles de prêt de la DeFi. La v3 est seulement présentée, avec le mode d'isolation et l'E-mode, des fonctions « que nous n'implémenterons pas dans ce cours ».
La liquidation est la saisie de la garantie d'un emprunteur dont la dette dépasse son plafond. Le livre l'explique avec des chiffres. Un liquidateur qui verse 1 000 USDT pour un compte en défaut repart avec 1 100 USDT de garantie, prime comprise (leçon 86). Un close factor limite la part de la dette remboursable en une transaction (leçon 94). Le code de la fonction de liquidation, lui, n'est pas publié dans le cours : l'auteur le juge trop long. Le health factor, l'indicateur de santé d'une position de prêt chez Aave, n'apparaît nulle part dans les leçons extraites.
Côté sécurité, le livre montre le bon test d'oracle, mais l'attaque s'y joue avec un compte de test, pas avec un vrai flash loan. Dans les leçons extraites, il manque deux choses :
- le fuzzing, des tests à entrées aléatoires ;
- le fork du mainnet, un test contre une copie de la vraie chaîne.
Le code a vieilli aussi. Il parle à la chaîne avec Ethers v5, une bibliothèque JavaScript passée depuis en v6. Il démarre le front avec create-react-app, un outil de démarrage React déprécié depuis.
Aucun des deux livres, dans les parties lues, ne relie ses deux mondes. Personne n'y dit comment backtester une stratégie dont les prix sont ceux d'un pool. L'impact de prix y dépend de l'ordre que tu passes. Cette question est ouverte, et ces livres ne la traitent pas.
Lequel lire selon ton profil
Quantitative Trading : 6,0 sur 10 sur ma fiche. À lire si tu écris des robots de trading et que tu veux savoir s'ils gagnent vraiment. À lire aussi si tu règles la taille de tes positions au feeling. Lis les leçons 15, 28 à 31 et 37, survole le reste. Le code est écrit trois fois, en MATLAB, Python et R, et l'univers (actions américaines, liens Excel) a vieilli. Passe ton chemin si tu cherches du trading crypto ou du 24 h sur 24 : le livre n'en parle quasiment pas.
Building Full Stack DeFi Applications : 5,8 sur 10 sur ma fiche. À lire si tu viens de la finance ou du back-end et que tu veux voir de l'intérieur un AMM, un prêt et un oracle. Lis l'AMM et l'oracle, parcours le prêt, saute le front React. Ce n'est pas un livre pour déployer en production : aucun audit, et le code de la liquidation n'est pas publié dans le cours.
Si tu n'as le temps que pour un des deux et que tes robots tournent sur des exchanges centralisés, prends Chan. Si tu dois comprendre pourquoi un protocole que tu intègres lit tel prix, prends la partie oracle de Zhou, leçons 98 à 105.
Conclusion
Je cherchais deux livres sur deux métiers, et je suis tombé sur une seule question posée deux fois. Chez Chan, le prix trompeur vient de ton propre code, qui a regardé trop loin devant lui. Chez Zhou, il vient d'un tiers, qui a payé juste ce qu'il fallait pour le déplacer. Dans les deux cas, personne n'a menti : le chiffre était exact, il était simplement sorti d'un endroit qu'on n'avait pas interrogé.
Ce qui me frappe, c'est que les remèdes se ressemblent. Le test de troncature retire le futur pour voir si les décisions bougent. Le TWAP dilue l'instant pour voir si le prix bouge. Dans les deux cas, on ne croit pas un chiffre avant de lui avoir retiré la chose qui pouvait le tricher. Je n'ai pas trouvé de livre qui applique cette discipline à un robot qui trade sur un pool DeFi. J'aimerais lire quelqu'un qui l'ait fait avec des données.
ODILON50, l'abonnement annuel est à -50 % : Building Full Stack DeFi Applications et Quantitative Trading. Je touche une commission si tu t'abonnes avec ce code. Mes notes n'en dépendent pas.