02 · Connexion Rithmic en Python¶
Ce chapitre décrit la mise en place concrète de la couche d'accès aux données et à l'exécution.
Sur le code de ce chapitre
Les exemples sont illustratifs. Ils reflètent l'architecture générale de
async_rithmic telle que documentée par le projet, mais les noms exacts
de méthodes et de paramètres doivent être vérifiés dans la documentation
officielle en vigueur :
async-rithmic.readthedocs.io et
github.com/rundef/async_rithmic.
Les APIs évoluent ; ce rapport n'est pas la documentation de la
bibliothèque.
Le code qui est testé et exécutable se trouve en annexe : il ne dépend d'aucune bibliothèque externe.
Prérequis¶
python3 -m venv .venv
source .venv/bin/activate
pip install async_rithmic
Identifiants nécessaires, obtenus auprès de Rithmic ou de votre intermédiaire :
| Variable | Rôle |
|---|---|
RITHMIC_APP_NAME |
Nom d'application déclaré et approuvé |
RITHMIC_USER |
Identifiant utilisateur |
RITHMIC_PASSWORD |
Mot de passe |
RITHMIC_SYSTEM |
Système cible : Rithmic Test, Rithmic Paper Trading, Rithmic 01 |
RITHMIC_URL |
URL de la passerelle WebSocket |
Commencez sur Rithmic Test
Le système de test est accessible sans revue de conformance et fournit des données CME réelles (Ironbeam). Tout le développement, la collecte de données et la validation du moteur de signaux peuvent s'y faire gratuitement. N'engagez la conformance qu'une fois le bot fonctionnel.
Structure minimale¶
import asyncio, os
from async_rithmic import RithmicClient # vérifier le nom exact dans la doc
async def main():
client = RithmicClient(
user=os.environ["RITHMIC_USER"],
password=os.environ["RITHMIC_PASSWORD"],
system_name=os.environ["RITHMIC_SYSTEM"],
app_name=os.environ["RITHMIC_APP_NAME"],
app_version="1.0.0",
)
await client.connect()
try:
await boucle(client)
finally:
await client.disconnect()
asyncio.run(main())
Le bloc try/finally n'est pas décoratif : sans déconnexion propre, la session
reste ouverte côté Rithmic et le prochain démarrage peut être refusé.
Souscription aux données¶
async_rithmic documente le streaming de ticks, de meilleur bid/ask, de barres
temporelles et de profondeur L2 (tous les bids/asks sur plusieurs niveaux de
prix).
async def souscrire(client, symbole="MESU6", exchange="CME"):
await client.subscribe_to_market_data(symbole, exchange) # trades + bbo
await client.subscribe_to_order_book(symbole, exchange) # profondeur L2
Le traitement se fait par callbacks ou par itération asynchrone selon la version de la bibliothèque. Le point important est ce que vous en faites :
def on_tick(tick):
# 1. horodater localement, immédiatement
ts_local = time.time_ns()
# 2. journaliser AVANT tout traitement
journal.write({"type": "tick", "ts_local": ts_local, "data": tick})
# 3. normaliser vers vos propres types
t = Trade(
ts_exchange=tick.timestamp_ns,
ts_local=ts_local,
symbol=tick.symbol,
price=tick.price,
size=tick.size,
aggressor=normaliser_agresseur(tick),
)
# 4. alimenter l'agrégation
file_evenements.put_nowait(t)
Ne jamais calculer dans le callback
Un callback qui fait du travail bloque la réception des messages suivants. Sous charge — c'est-à-dire précisément à l'ouverture, quand tout se joue — le bot accumule un retard croissant et se met à réagir à un marché qui n'existe plus.
Le callback fait trois choses et rien d'autre : horodater, journaliser, empiler dans une file. Tout le calcul se fait dans une tâche séparée qui consomme cette file.
La normalisation de l'agresseur¶
C'est le point technique le plus important de ce chapitre, car il conditionne la validité de tout le footprint.
def normaliser_agresseur(tick, dernier_quote):
"""Retourne +1, -1 ou 0.
Si le flux fournit le côté agresseur, l'utiliser. Sinon, classifier
contre le DERNIER quote reçu AVANT ce trade.
"""
if getattr(tick, "aggressor", None) is not None:
return 1 if tick.aggressor == "B" else -1
if dernier_quote is None:
return 0
if tick.price >= dernier_quote.ask:
return 1
if tick.price <= dernier_quote.bid:
return -1
return 0
Vérifiez la convention de votre flux
Les conventions d'encodage du côté agresseur varient. Une inversion de signe produit un footprint parfaitement cohérent et entièrement faux : toutes vos absorptions seront des continuations et réciproquement.
Test de validation simple : sur une barre fortement haussière, le delta doit être majoritairement positif. Si ce n'est pas le cas, votre convention est inversée. Faites ce test avant tout backtest.
L'envoi d'ordres¶
async def envoyer_bracket(client, compte, symbole, exchange, side, qte,
entree, stop, cible):
"""Ordre d'entrée avec stop et cible attachés.
Le stop et la cible attachés sont obligatoires chez certaines prop firms
(Apex depuis mars 2026) et souhaitables partout : ils survivent à une
déconnexion du client.
"""
return await client.submit_bracket_order(
account_id=compte,
symbol=symbole,
exchange=exchange,
side=side,
quantity=qte,
order_type="MKT",
stop_loss_price=stop,
take_profit_price=cible,
)
L'idempotence¶
Le scénario redouté : vous envoyez un ordre, la connexion tombe avant la confirmation, vous reconnectez — l'ordre est-il passé ?
class ExecuteurIdempotent:
def __init__(self, client):
self.client = client
self.envoyes = {} # cle_intent -> order_id
def cle(self, intent):
"""Clé déterministe : deux intents identiques dans la même seconde
et sur le même signal ont la même clé."""
return f"{intent.symbol}:{intent.side}:{intent.reason}:{intent.ts // 1_000_000_000}"
async def envoyer(self, intent):
k = self.cle(intent)
if k in self.envoyes:
journal.write({"type": "doublon_ignore", "cle": k})
return self.envoyes[k]
order_id = await self.envoyer_bracket(intent)
self.envoyes[k] = order_id
return order_id
Sans idempotence, une reconnexion double les positions
C'est le bug le plus classique et le plus coûteux d'un bot débutant : le signal se redéclenche après reconnexion, l'ordre part une seconde fois, et la position est double avec un risque double — souvent au pire moment, puisque les reconnexions surviennent dans les phases de charge.
La gestion des rejets¶
Tout ordre peut être refusé. Causes fréquentes en contexte prop :
| Cause | Réaction du bot |
|---|---|
| Marge insuffisante | Arrêt, alerte : l'état de risque local est faux |
| Instrument non autorisé | Arrêt, erreur de configuration |
| Taille supérieure au maximum | Réduire et réessayer une seule fois |
| Stop/TP manquant | Erreur de code, arrêt |
| Marché fermé | Ignorer, journaliser |
| Drawdown atteint | Arrêt définitif de la séance |
Un rejet n'est jamais à réessayer en boucle
Une boucle de réessai sur rejet produit des centaines d'ordres refusés en quelques secondes. Certaines firmes considèrent cela comme un comportement abusif et ferment le compte. Une seule tentative, puis arrêt et alerte.
Multi-comptes¶
async_rithmic gère plusieurs comptes. C'est utile pour la copie interne — vos
propres comptes uniquement, ce qui est généralement autorisé, avec un plafond
(20 chez Apex, 5 chez Topstep selon les comparateurs).
async def repliquer(client, comptes, intent):
"""Réplique un intent sur plusieurs comptes.
Attention : les comptes n'ont pas nécessairement le même drawdown
disponible. La taille doit être recalculée par compte.
"""
for compte in comptes:
taille = dimensionner(compte.dd_disponible, intent)
if taille > 0:
await envoyer_bracket(client, compte.id, ..., qte=taille, ...)
La réplication multiplie le risque, pas seulement le gain
Dix comptes trading le même signal, c'est dix fois le gain et dix fois la perte, avec une corrélation de 1. Ce n'est pas une diversification : la variance du portefeuille est multipliée par cent, pas par dix.
Le conseil relayé par les comparateurs — obtenir deux ou trois cycles de versement sur un seul compte avant d'en ajouter (Copilink) — est raisonnable et à suivre.
Résumé¶
- Développez sur Rithmic Test : gratuit, données réelles, sans conformance.
- Le callback horodate, journalise et empile — il ne calcule rien.
- Vérifiez la convention d'agresseur par un test sur barre haussière.
- Stop et cible attachés systématiquement : obligatoires chez certaines firmes, utiles partout.
- Idempotence obligatoire : sans elle, une reconnexion double les positions.
- Un rejet ne se réessaie pas en boucle.
- La réplication multi-comptes multiplie le risque avec une corrélation de 1.
Chapitre précédent : Architecture du bot · Chapitre suivant : Construire le footprint