Un watchdog pour les API wallets d’Hyperliquid Bot V10
Le 22 août 2026, à 8 h 10 UTC, mon bot de trading sur Hyperliquid est mort, et personne n’a été prévenu. Le conteneur hébergé chez Fly.io avait 512 Mo de mémoire et une politique de relance à zéro tentative : après deux échecs, l’orchestrateur a cessé de le redémarrer. Le watchdog qui aurait dû le voir tournait sur une machine retirée du réseau le même jour, au milieu d’une migration. La collecte est restée arrêtée quatorze jours. Le moniteur embarqué dans le bot ne pouvait rien dire : il meurt avec le processus qu’il surveille.
En remettant tout en place, j’ai trouvé une seconde panne, silencieuse celle-là. Sur Hyperliquid, un bot ne signe pas avec la clé du compte : il utilise un API wallet, une clé déléguée qui porte une date d’expiration. Passée cette date, le bot continue de collecter, d’évaluer ses signaux et de battre ses heartbeats, mais chaque ordre est rejeté par l’exchange. Tout ce que le watchdog regardait, la fraîcheur des données, restait vert. Une clé expirée ne se voyait qu’au moment où les ordres tombaient.
L’architecture retenue
Le bot lui-même a quitté Fly.io pour un VPS sous systemd, avec relance automatique et notification en cas d’échec. La surveillance, elle, a été sortie du bot et de sa machine : un watchdog externe, lancé par un timer systemd toutes les 5 minutes sur une petite machine distincte du VPS. Il lit dans MongoDB les heartbeats que chaque collecteur écrit par flux et par coin, plus le document d’état du bot, et alerte sur Telegram quand un flux est muet depuis plus de cinq minutes. Une unité d’alerte séparée couvre le cas où le surveillant lui-même échoue.
À ce watchdog, j’ai ajouté le contrôle des API wallets. Hyperliquid expose un point d’entrée public qui liste les agents autorisés d’un compte, avec leur date de validité. Le watchdog l’interroge avec la seule adresse publique du compte maître, ne signe rien et ne détient aucune clé privée. Pour chaque agent, il calcule le temps restant : expiré, ou moins de 30 jours, et le problème part dans l’alerte. Le premier passage a trouvé l’agent de la version précédente du bot à quatorze jours de l’expiration, ce que je n’avais vu nulle part.
Le troisième angle mort était la sauvegarde de l’archive, tirée chaque nuit depuis une autre machine. Si cette machine s’éteint, elle ne peut pas signaler sa propre panne. Le service de sauvegarde écrit donc un marqueur horodaté après chaque passage réussi, et le watchdog le lit par SSH avec une clé verrouillée sur une seule commande de lecture. Au-delà de 48 heures, soit deux passages nocturnes manqués, l’alerte part.
Ce que ça change en pratique
Trois pannes qui étaient invisibles ont désormais chacune un observateur extérieur : le processus qui meurt, la clé qui expire, la sauvegarde qui cesse. Le watchdog ne dépend d’aucun secret de trading, son fichier d’environnement est réduit à quatre variables, et son installation n’embarque que quatre bibliothèques.
Un défaut est apparu en chemin, et il est instructif. L’anti-spam du watchdog reposait sur un booléen : une fois l’état « en panne » mémorisé, plus aucune alerte jusqu’au retour au vert. Or un agent qui expire dans quatorze jours maintient cet état pendant quatorze jours. Une panne de collecte survenant entre-temps aurait été muette, exactement ce que le watchdog existe pour éviter. L’anti-spam porte maintenant sur le contenu : une alerte part dès qu’un problème nouveau apparaît, et les problèmes déjà signalés ne se répètent pas. Limite connue : le message de rétablissement n’est envoyé que lorsque tous les problèmes ont disparu.
Le contrôle a été vérifié en cherchant à le casser : appel réel au point d’entrée public depuis deux machines, résultat conforme au calcul manuel ; marqueur de sauvegarde vieilli artificiellement de 72 heures, détecté au bon seuil ; clé de lecture sollicitée avec une autre commande, qui renvoie le marqueur et rien d’autre. Onze tests couvrent le contrôle des agents et l’anti-spam, six la sauvegarde.
Les tradeoffs, assumés
Un observateur externe, c’est une machine de plus à tenir à jour, et une chaîne d’alerte de plus à tester. J’ai accepté ce coût parce que le moniteur embarqué avait prouvé sa limite : il ne peut pas signaler la mort de ce qui le porte.
La piste la plus tentante sur la stratégie elle-même a été l’inversion du signal. Avec un gain apparent de 4,3 points et une p-valeur de 0,002, elle semblait sauver le bot. Elle est tombée sur trois attaques : deux coins sur dix portaient tout l’effet ; un bootstrap par journées, plutôt que par setup, faisait enjamber zéro à l’intervalle de confiance ; et la meilleure variante restait sous le seuil de rentabilité frais inclus. Verdict : falsifiée.
Côté outil, Fly.io a été écarté après l’incident du 22 août, remplacé par un VPS et systemd. Ce n’est pas une critique de la plateforme : c’est le constat qu’une relance à zéro tentative et un plafond de 512 Mo ne conviennent pas à un processus qui doit tourner sans interruption.
Quand ça vaut le coup, et quand non
Ça vaut le coup pour qui veut apprendre à falsifier proprement sa propre hypothèse, et à bâtir une infrastructure qui ne ment pas sur son état de santé. Les deux vont ensemble : sans collecte fiable, il n’y a rien à falsifier.
Ça ne vaut pas le coup pour qui espère qu’un signal momentum sur perpétuels finira par payer. La non-rentabilité est le résultat le mieux établi du projet : marge de −10,2 points par rapport au seuil de rentabilité, intervalle de confiance à 95 % entre −16,0 et −4,1, probabilité d’être perdant de 1,00. Le seuil lui-même, frais aller-retour compris, se situe à 44,5 % de trades gagnants.
Ce que je changerais si je recommençais
L’ordre était inversé. Le protocole pré-enregistré et le script de falsification sont arrivés en septembre, après six semaines de collecte et toute la plomberie industrielle : VPS, watchdogs, tableaux de bord, découpage en modules. Or les 1 074 setups rejoués depuis l’archive auraient pu répondre « pas d’edge » dès les premiers jours.
Les deux corrections de méthode auraient dû cadrer la mesure avant de la faire, pas la corriger après. La première : l’étalon n’est pas le hasard mais la rentabilité frais inclus. La seconde : des setups déclenchés la même journée par le même mouvement de marché ne sont pas indépendants, et les traiter comme tels gonfle la significativité d’un facteur 40. Une fois ces deux corrections appliquées, l’inversion du signal qui affichait une p-valeur de 0,002 est remontée à 0,71.
La stack
Python pour l’ensemble : moteur, collecteurs et journaux. Les indicateurs techniques sont dans strategy/indicators.py, le gestionnaire de risque, drawdown journalier et pertes consécutives, dans risk/risk_manager.py. Les données sont ingérées dans MongoDB, exportées en Parquet partitionné par coin et par jour par scripts/export_parquet.py, puis analysées avec DuckDB et Polars ; scripts/coverage_report.py mesure les trous de collecte.
Le MarketContextStore de collector/context_store.py garde la dernière valeur connue de chaque flux et son âge, la stratégie lit et décide. Le SignalLogger enregistre une ligne par évaluation, y compris celles bloquées par les garde-fous, avec le régime de marché du moment ; le TradeLogger lie chaque position à son signal et à un instantané des features. Les heartbeats vivent dans collector/heartbeat.py, le moniteur embarqué dans monitor/health.py, le watchdog externe dans scripts/external_watchdog.py, et la falsification dans scripts/falsification.py. Les alertes passent par Telegram ; déploiement et unités systemd sont dans deploy/.