On a Mesuré Python vs Rust sur 1 Million de Bougies : Voici les Résultats

Benchmark Python vs Rust sur 1 million de bougies : appels API, indicateurs techniques et surprise Polars. Résultats et guide pratique.

On a Mesuré Python vs Rust sur 1 Million de Bougies : Voici les Résultats

Quand une opération dans un bot de trading prend une seconde, ce n'est pas grave. Quand on veut la répéter des milliers de fois pour tester une stratégie sur plusieurs années de données, cette seconde finit par peser très lourd. C'est pourquoi certains développeurs s'intéressent à des langages conçus pour la vitesse brute. Rust est l'un d'entre eux : un langage qui produit des programmes aussi rapides que le C ou le C++, tout en éliminant certaines catégories de bugs grâce à un système de gestion mémoire unique.

On entend de plus en plus son nom dans le monde du trading algorithmique. Des market makers crypto comme Keyrock, des hedge funds comme Capula, ou encore Hyperliquid, la plus grande plateforme décentralisée de futures, l'utilisent en production. On a voulu vérifier par nous-mêmes : sur les opérations qu'un bot de trading fait au quotidien, est-ce que Rust fait vraiment la différence ?

Benchmark API : Python vs Rust à Égalité

On commence par ce que fait tout bot de trading : télécharger des données de marché. On a chronométré exactement la même opération en Python et en Rust — envoyer une requête à l'API Binance, récupérer 1 000 bougies BTCUSDT, lire la réponse et extraire les prix de clôture. 100 exécutions de chaque côté, médiane retenue.

Étape Python Rust
Requête réseau ~630 ms ~835 ms
Lecture du JSON ~1,2 ms ~1,2 ms
Extraction des prix ~0,1 ms ~0,01 ms
Total ~631 ms ~836 ms

Les temps réseau varient d'une exécution à l'autre (~575 à ~890 ms selon les runs), mais le calcul est identique : ~1,2 ms de chaque côté. La requête réseau représente 99,8 % du temps total. Que ce soit en Python ou en Rust, c'est le réseau qui dicte le rythme.

Ce que ça veut dire concrètement : toute la partie d'un bot qui communique avec l'extérieur — placer des ordres, récupérer des données, écouter un flux — n'a rien à gagner d'un changement de langage. Python fait parfaitement l'affaire pour ça.

La question intéressante, c'est ce qui se passe après : quand le bot doit calculer.

Benchmark Calcul : Python vs Rust sur 1 Million de Bougies

Pour comparer les performances de calcul, on a pris un cas classique : calculer une moyenne mobile simple (SMA) sur 20 périodes — la moyenne des 20 derniers prix, recalculée à chaque nouvelle bougie. Même algorithme pour tout le monde, sur 1 million de points de données. 200 exécutions par méthode, médiane retenue.

Calcul en Python Pur : la Baseline de Performance

La première approche est la plus naturelle : une boucle for qui parcourt les données et calcule chaque moyenne à la main. C'est lisible, c'est simple, et c'est lent.

Résultat : ~80 ms.

Pourquoi ? Python traite chaque nombre un par un, et chaque nombre en Python est un objet complet en mémoire (~28 octets, avec son type, son compteur de références, sa valeur). Sur un million d'éléments, ça s'accumule.

Pandas, NumPy et Polars : Accélérer le Calcul en Python

En pratique, on ne code presque jamais une boucle for sur des données financières en Python. On utilise des bibliothèques spécialisées qui font le gros du travail en coulisses, dans du code compilé beaucoup plus rapide.

Pandas est la plus connue : c'est la bibliothèque de référence pour manipuler des tableaux de données en Python. DataFrames, séries temporelles, analyse financière — la majorité des développeurs Python en trading l'utilisent au quotidien. Sous le capot, ses calculs sont écrits en C, ce qui les rend bien plus rapides qu'une boucle Python.

NumPy est la base du calcul numérique en Python. Là où Python stocke chaque nombre comme un objet séparé en mémoire, NumPy les range côte à côte dans un bloc continu, comme une ligne de cases dans un tableur. Cette organisation permet de faire des calculs sur des millions de nombres d'un coup, sans boucle. Pandas et la plupart des bibliothèques scientifiques Python sont construites sur NumPy.

Polars est une alternative plus récente à Pandas, réputée plus rapide. On va voir pourquoi dans un instant.

Avec Pandas, le calcul de la SMA se fait en une ligne (rolling(20).mean()) et tourne en ~19 ms — 4 fois plus rapide que Python pur. Avec Polars, même idée (rolling_mean(20)) : ~7 ms, soit 11 fois plus rapide.

Rust via PyO3 : Écrire du Rust, Appeler Depuis Python

Et si au lieu d'utiliser une bibliothèque existante, on écrivait le calcul directement en Rust ? C'est là qu'intervient PyO3, un outil qui permet d'écrire une fonction en Rust et de l'appeler depuis Python comme n'importe quel module.

Concrètement, voici ce que ça donne. Côté Rust, on écrit la fonction de calcul :

#[pyfunction]
fn sma(prices: Vec<f64>, period: usize) -> Vec<f64> {
    let mut result = vec![f64::NAN; period - 1];
    for i in (period - 1)..prices.len() {
        let sum: f64 = prices[i + 1 - period..=i].iter().sum();
        result.push(sum / period as f64);
    }
    result
}

Côté Python, on l'utilise exactement comme une bibliothèque classique :

import rust_sma

prices = [42000.0, 42050.0, 42030.0, ...]
result = rust_sma.sma(prices, 20)

Pas de subprocess, pas de fichier intermédiaire. Python importe le module Rust comme si c'était du Python. C'est d'ailleurs exactement la même technologie qui fait tourner Polars sous le capot.

On a testé deux variantes de cette approche :

  • Rust (liste) : on passe les données sous forme de liste Python classique
  • Rust (NumPy) : on passe les données sous forme de tableau NumPy

La différence entre les deux est révélatrice.

Résultats du Benchmark Python vs Rust

Méthode Temps médian vs Python pur
Python pur ~80 ms (base)
Pandas ~19 ms 4x plus rapide
Polars ~7 ms 11x plus rapide
Rust (liste) ~20 ms 4x plus rapide
Rust (NumPy) ~1,9 ms 42x plus rapide

Deux surprises dans ce tableau. D'abord, Rust avec une liste Python est plus lent que Polars. Ensuite, Rust avec NumPy est le plus rapide de tous, et de loin.

Transfert de Données Python-Rust : le Facteur Clé de Performance

Pourquoi un tel écart entre les deux versions Rust ? Le calcul est pourtant identique. La différence tient entièrement au format des données qu'on envoie à Rust.

Une liste Python, c'est une collection d'objets séparés, dispersés dans la mémoire de l'ordinateur. Avant de pouvoir calculer quoi que ce soit, Rust doit parcourir chaque objet un par un, extraire le nombre qu'il contient, et recopier le tout dans un format qu'il comprend. Sur un million d'éléments, cette conversion prend ~20 ms à elle seule. Le calcul Rust proprement dit ? Quelques fractions de milliseconde.

Un tableau NumPy, c'est une série de nombres rangés côte à côte en mémoire, sans emballage. C'est exactement le format natif de Rust. Pas de conversion, pas de copie — Rust lit les données directement sur place. D'où le passage de ~20 ms à ~1,9 ms.

La leçon : ce n'est pas seulement le langage qui détermine la vitesse, c'est aussi la façon dont les données circulent entre les outils.

Polars : une Bibliothèque Python Écrite en Rust

Revenons au résultat surprenant de Polars (~7 ms), étonnamment proche du Rust via NumPy (~1,9 ms). Ce n'est pas un hasard.

Polars est écrit intégralement en Rust. Le moteur de calcul est un programme Rust compilé. Le package Python polars qu'on installe avec pip install polars n'est qu'une fine interface autour de ce moteur, construite avec PyO3 — exactement le même outil qu'on a utilisé pour notre benchmark.

Autrement dit : quiconque utilise déjà Polars profite de la vitesse de Rust sans le savoir. La différence résiduelle avec notre code Rust vient du fait que Polars fait plus de choses en coulisses (vérifications de cohérence, gestion des valeurs manquantes, construction de sa structure de données interne).

Avant d'écrire du Rust sur mesure, ça vaut le coup de vérifier si une bibliothèque Python existante ne fait pas déjà le travail.

Installer Rust et PyO3 pour le Trading : Ce Que Ça Demande

Écrire une fonction en Rust et l'appeler depuis Python demande un peu de mise en place, mais le processus est plus simple qu'on pourrait le croire.

L'installation tient en deux commandes : installer le compilateur Rust (via un outil appelé rustup, disponible sur tous les systèmes) et installer maturin, un utilitaire Python qui s'occupe de transformer le code Rust en module importable depuis Python. Après ça, le cycle de travail est toujours le même : écrire la fonction Rust, lancer une commande de compilation, et import depuis Python.

La difficulté n'est pas dans l'outillage, elle est dans le langage. Rust a une courbe d'apprentissage réputée raide. Là où Python fait confiance au développeur et s'occupe automatiquement de la mémoire via un ramasse-miettes, Rust exige des garanties à la compilation. Le programme ne compile pas tant que le développeur n'a pas prouvé au compilateur que la mémoire est gérée correctement. C'est ce qui rend Rust à la fois plus sûr et plus exigeant à écrire.

Cela dit, pour des fonctions de calcul appelées depuis Python — le cas d'usage qu'on a testé ici — le code Rust reste relativement simple. On manipule des tableaux de nombres, on fait des boucles, on retourne un résultat. Pas besoin de maîtriser toutes les subtilités du langage pour commencer.

Quand Rust Change la Donne en Trading Algorithmique

Tout dépend d'où le bot passe son temps.

Un bot qui trade sur des timeframes de quelques minutes ou plus, avec des calculs simples, passe l'essentiel de son temps à communiquer avec l'exchange : envoyer des requêtes, attendre les réponses, respecter les limites de débit. On l'a vu dans le benchmark API — le langage ne change rien ici. Python fait parfaitement l'affaire.

En revanche, un bot qui embarque une vraie logique de calcul — backtests sur plusieurs années, optimisation de paramètres, traitement de carnets d'ordres tick par tick, risk management dynamique — passe bien plus de temps à calculer qu'à communiquer. C'est là que Rust fait une vraie différence. Un facteur 10x à 40x sur le calcul, multiplié par des milliers d'itérations dans une boucle d'optimisation, ça se ressent concrètement.

L'approche la plus courante dans l'industrie aujourd'hui :

  • Python pour l'orchestration, la recherche de stratégies, la visualisation et le prototypage
  • Rust (ou C++) pour les composants critiques en performance — et uniquement ceux-là

C'est ce que font Keyrock (market maker crypto, membre de la Rust Foundation, connecté à 80+ exchanges), Hyperliquid (200 000 ordres par seconde, plateforme écrite intégralement en Rust), et la plupart des firmes quantitatives.

La première étape n'est pas d'apprendre Rust. C'est d'identifier, dans son propre code, ce qui prend du temps. Si c'est le réseau → pas de gain possible. Si c'est du calcul pur sur de gros volumes → c'est un candidat sérieux pour Rust. Et entre les deux, des bibliothèques comme Polars couvrent déjà une grande partie du besoin.