QuantLib en Rust : analyse du projet libitofin
Découvrez libitofin, le portage Rust de QuantLib accessible via Python. Apprenez pourquoi une bibliothèque de pricing surpasse un tableur et l'importance de fixer une version.
« QuantLib en Rust » est la description en une phrase de libitofin : un portage en Rust de QuantLib, la bibliothèque C++ qui fait référence en matière de pricing de dérivés en open source depuis le début des années 2000, accompagnée d’un package Python nommé itofin. En août 2026, le projet se définit comme étant en phase pré-1.0, un qualificatif qui régit tous les aspects pratiques de son utilisation. Une bibliothèque de pricing gagne sa légitimité grâce aux mécanismes qui entourent ses formules, et ces mécanismes représentent le même travail, quel que soit le langage.
Ce qu’une bibliothèque de pricing apporte par rapport à une formule sur tableur
Une cellule Black-Scholes dans un tableur prend cinq entrées et renvoie un prix. La formule est la partie la plus simple. Quatre couches l’entourent, et ces couches constituent la bibliothèque.
Structures de taux. Un taux d’actualisation est une courbe sur différentes maturités, avec une règle d’interpolation pour combler les écarts entre les points réellement cotés. Le panneau ci-dessous présente la matière première : les maturités des bons du Trésor cotées à une date donnée.
Le SQL exact derrière chaque chiffre
SELECT
arrayElement(tenors, i) AS tenor,
round(arrayElement(rates, i), 2) AS yield_pct,
formatDateTime(curve_date, '%b %e, %Y') AS as_of
FROM
(
SELECT
date AS curve_date,
['1 month', '3 months', '6 months', '1 year', '2 years', '3 years',
'5 years', '7 years', '10 years', '20 years', '30 years'] AS tenors,
[toFloat64(yield_1_month), toFloat64(yield_3_month), toFloat64(yield_6_month),
toFloat64(yield_1_year), toFloat64(yield_2_year), toFloat64(yield_3_year),
toFloat64(yield_5_year), toFloat64(yield_7_year), toFloat64(yield_10_year),
toFloat64(yield_20_year), toFloat64(yield_30_year)] AS rates,
arrayJoin(range(1, 12)) AS i
FROM global_markets.treasury_yields
WHERE date = (SELECT max(date) FROM global_markets.treasury_yields)
)
WHERE yield_pct > 0
ORDER BY iÀ la date du Aug 10, 2026, la courbe cotée comportait 7 maturités, allant de 3.79% à 1 month jusqu’à 5.25% à 30 years. Un tableur traite cela avec une recherche et un taux de 4 % codé en dur. Une bibliothèque le traite avec un objet courbe sur lequel chaque instrument s’appuie pour son pricing, selon une interpolation définie (linéaire sur les taux zéro, log-linéaire sur les facteurs d’actualisation, splines monotones) et une politique d’extrapolation définie au-delà du dernier point. Déplacez cet objet d’un point de base et chaque sensibilité du portefeuille s’ajuste de manière cohérente.
Conventions de calcul de jours (day-count). Les intérêts courent sur une fraction d’année, et la définition de cette fraction est une convention liée à l’instrument. Actual/360 divise les jours écoulés par 360. Actual/365 divise par 365. La famille 30/360 considère que chaque mois compte 30 jours. La convention Business/252 compte les séances de trading par rapport à une année de 252 jours, ce qui nécessite un calendrier boursier réel avec les jours fériés déjà intégrés. Prenons un million de dollars empruntés à 5 % sur 90 jours : la convention actual/360 génère 12 500 dollars d’intérêts, tandis que l’actual/365 en génère 12 329 sur la même opération. Le panneau ci-dessous montre pourquoi la base des jours ouvrés nécessite un calendrier plutôt qu’un simple diviseur.
Le SQL exact derrière chaque chiffre
SELECT
formatDateTime(toStartOfMonth(date), '%Y-%m') AS month,
countDistinct(date) AS trading_days,
toUInt8(toDayOfMonth(toLastDayOfMonth(max(date)))) AS calendar_days
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= toStartOfMonth(today() - 365)
AND date < toStartOfMonth(today())
GROUP BY month
ORDER BY monthSur les 12 mois observés, 2026-07 a compté 22 séances contre 31 jours calendaires. Aucune règle arithmétique ne permet d’obtenir ce premier chiffre. Il provient d’un calendrier des jours fériés, et chaque mois a sa propre réponse. Une bibliothèque fournit ces calendriers par place boursière et par pays ; un tableur vous oblige à les maintenir vous-même.
Mécanismes de calibration. Les paramètres des modèles ne sont cotés nulle part. Vous les choisissez en ajustant les prix du modèle aux prix de marché observés sur toute une surface de cotations, puis en réajustant à mesure que la surface évolue. Il s’agit d’un problème de moindres carrés bornés, et la bibliothèque fournit la boucle de calcul associée : un optimiseur Levenberg-Marquardt, des transformations de paramètres qui maintiennent une variance positive sans contraintes rigides, une fonction de coût sur l’ensemble des cotations, et des critères de convergence qui échouent de manière explicite plutôt que de renvoyer silencieusement l’estimation initiale.
Une couche numérique. Sous tout cela se trouvent l’algèbre linéaire et l’intégration. Les décompositions QR et SVD résolvent les systèmes produits par un ajustement, la quadrature et l’intégration de Fourier permettent le pricing des modèles dont les formules sont des intégrales, et les solveurs de racines extraient la volatilité implicite. Cette couche est ingrate, et c’est précisément celle qui est souvent mal réimplémentée. La version classique est un solveur de Newton fait maison qui converge sur les cotations liquides « at-the-money » mais s’égare sur les options « deep out-of-the-money ». Une erreur fréquente est une inversion de matrice qui perd en précision sur un ajustement quasi singulier et renvoie des paramètres qui semblent tout à fait plausibles. Si vous construisez votre modèle mental à partir de zéro, un ouvrage de trading quantitatif open source est un meilleur point de départ que la référence API de n’importe quelle bibliothèque.
Qu’est-ce que libitofin, et est-ce vraiment QuantLib en Rust ?
Oui, dans ce qui compte. Il s’agit d’un portage de la conception de QuantLib en Rust, et le projet indique qu’il est testé par rapport à la suite de tests de QuantLib elle-même. C’est la manière honnête de porter une bibliothèque numérique : les résultats sont vérifiés par rapport à l’implémentation de référence plutôt que par rapport à une attente écrite à la main de ce que la réponse devrait être. Le volet Python est un package nommé itofin, permettant à un processus Python d’accéder au moteur Rust sans avoir besoin d’une chaîne d’outils C++.
Le qualificatif qui importe le plus est « pré-1.0 ». Selon la convention de versioning de Rust, une version 0.x n’offre aucune garantie de compatibilité entre les versions mineures : le passage de 0.4 à 0.5 autorise le renommage, le déplacement ou la suppression de n’importe quel élément. Considérez l’API comme une cible mouvante, et quelques habitudes s’imposent :
- Fixez une version taguée exacte dans votre fichier de verrouillage (lockfile) et mettez à jour volontairement, en utilisant votre propre suite de tests comme garde-fou.
- Maintenez une fine couche d’abstraction autour des types de la bibliothèque, afin qu’un renommage ne nécessite qu’une modification dans un seul fichier au lieu de quarante.
- Enregistrez la version de la bibliothèque à côté des chiffres qu’elle a produits, afin qu’une réexécution divergente pointe vers la mise à jour plutôt que vers le marché.
- Maintenez les chiffres de marge de production, de collatéral et de risque réglementaire sur un outil qui offre une garantie de compatibilité jusqu’à l’arrivée d’une version 1.0.
Rien de tout cela n’est une critique du projet. « Pré-1.0 » est une auto-description précise et la place appropriée pour un jeune portage d’une bibliothèque très vaste. Le risque est qu’un utilisateur le traite comme un remplacement direct de QuantLib et se heurte à une signature modifiée lors d’une version mineure, au milieu d’un trimestre. Les instructions d’installation évoluent également avec la version, et tant cargo que pip nécessitent un accès réseau pour résoudre les dépendances ; lisez donc le README du projet au tag que vous avez l’intention de fixer plutôt qu’un extrait copié dans un article de blog, celui-ci inclus.
Python, un moteur compilé ou Rust ?
Deux questions tranchent ce choix, et aucune ne concerne les préférences personnelles. Combien de fois par seconde effectuez-vous un repricing, et vos chiffres doivent-ils correspondre entre des processus distincts ? La taille de la chaîne d’options définit l’échelle de la première question.
Le SQL exact derrière chaque chiffre
WITH (SELECT max(date) FROM global_markets.options_greeks) AS last_session
SELECT
underlying_symbol AS symbol,
countDistinct(ticker) AS contracts_priced,
formatDateTime(max(date), '%b %e, %Y') AS as_of
FROM global_markets.options_greeks
WHERE date = last_session
AND underlying_symbol IN ('SPY', 'AAPL', 'NVDA', 'MSFT', 'KO')
AND volume > 0
GROUP BY symbol
ORDER BY contracts_priced DESCLe Aug 11, 2026, SPY a vu 5336 contrats distincts s’échanger en une seule séance, contre 360 pour KO. Pricer cette large chaîne une fois ne représente rien. La pricer avec cinq sensibilités par contrat, à chaque mise à jour de cotation, sur tout un portefeuille de sous-jacents, est un programme différent avec des contraintes différentes.
Restez en Python avec une bibliothèque établie lorsque la boucle de calcul se mesure en milliers de valorisations par minute et que le travail environnant est de la recherche, de l’analyse ou des valorisations de fin de journée. Les bindings Python de QuantLib sont le choix mature : le même moteur C++, la plus large couverture d’instruments et des années d’utilisation en production. La vitesse est rarement la contrainte limitante dans le code de recherche ; la couverture et l’exactitude le sont.
Appelez un moteur compilé depuis Python lorsque la boucle est intensive et que le code qui l’entoure ne l’est pas. Le coût à surveiller est la frontière elle-même. Un appel par contrat depuis Python génère une surcharge à chaque passage, et la solution consiste à transmettre un tableau au moteur et à en récupérer un. C’est la place qu’itofin vise, aux côtés de QuantLib-Python.
Écrivez en Rust lorsque la boucle de pricing est le produit lui-même : un pricer au sein d’un service de cotation, un calcul de risque sur un calendrier strict, ou un binaire déployé sur une machine sans Python. La seconde question s’applique également ici. Les résultats en virgule flottante dépendent de l’ordre des opérations ; ainsi, un même modèle implémenté deux fois peut diverger sur les derniers chiffres, et un notebook de recherche qui contredit un service de production représente une semaine d’investigations. Un moteur unique utilisé des deux côtés élimine toute cette catégorie de divergences, ce qui constitue l’argument durable en faveur d’un cœur compilé avec des bindings, quel que soit le langage utilisé. Le même instinct s’applique au travail sur les stratégies, comme le montre un backtest reproductible.
Où se situe réellement la cible de calibration
La calibration nécessite une base d’ajustement, et cette base est une surface de volatilités implicites de marché. L’aspect résolution de racines est couvert dans comment la volatilité implicite est calculée. La forme ci-dessous est ce qu’un modèle doit égaler.
Le SQL exact derrière chaque chiffre
SELECT
multiIf(days_to_expiry <= 7, '0 to 7 days',
days_to_expiry <= 30, '8 to 30 days',
days_to_expiry <= 60, '31 to 60 days',
days_to_expiry <= 120, '61 to 120 days',
days_to_expiry <= 240, '121 to 240 days',
'241 days or more') AS dte_bucket,
round(avg(implied_volatility) * 100, 1) AS iv_pct,
countDistinct(ticker) AS contracts
FROM global_markets.options_greeks
WHERE underlying_symbol = 'AAPL'
AND date >= today() - 10
AND days_to_expiry >= 0
AND iv_converged = 1
AND volume > 0
AND abs(toFloat64(strike_price) / toFloat64(underlying_close) - 1) < 0.05
GROUP BY dte_bucket
ORDER BY min(days_to_expiry)Près de la monnaie, les contrats AAPL dans le compartiment 0 to 7 days ont affiché une volatilité implicite moyenne de 29.6% sur les séances observées, contre 29.3% à 241 days or more. Un modèle portant un seul paramètre de volatilité ne peut pas se situer sur les deux points à la fois, ce qui explique pourquoi les modèles avec une structure par terme de volatilité existent. L’ajustement de l’un à une surface comme celle-ci est l’étape de calibration, et les sensibilités qui découlent du modèle ajusté sont les grecques, couvertes dans les grecques d’options expliquées.
Comment ces panneaux ont été construits
Le panneau des courbes lit la date cotée la plus récente de la série des bons du Trésor et déroule ses onze colonnes de maturités en lignes, en excluant toute maturité sans cotation ce jour-là. Le panneau des séances compte les dates distinctes sur le flux SPY par mois, ce qui est un indicateur fiable du nombre total de séances boursières. Le panneau des chaînes compte les codes de contrats distincts avec un volume non nul lors de la dernière séance disponible, regroupés par sous-jacent. Le panneau de volatilité ne conserve que les résolutions convergées avec un volume non nul et des prix d’exercice à moins de 5 % de la clôture du sous-jacent, ce qui constitue la bande standard « near-the-money » ; le compartiment frontal inclut les expirations du jour même.
FAQ
libitofin est-il un remplacement direct de QuantLib ?
Non. En août 2026, il s’agit d’un portage pré-1.0 qui couvre une partie de la surface de QuantLib et valide ses résultats par rapport à la suite de tests de QuantLib. QuantLib lui-même, via ses bindings Python, reste l’option la plus large et la plus stable pour les travaux de production.
Que signifie « pré-1.0 » pour une bibliothèque de pricing ?
Selon la convention de versioning de Rust, une version 0.x n’offre aucune promesse de compatibilité : la version mineure suivante est libre de renommer ou de supprimer n’importe quel élément. En pratique, cela signifie fixer une version taguée exacte et réexécuter votre propre suite de tests à chaque mise à jour.
Dois-je écrire en Rust pour utiliser libitofin ?
Non. Le projet publie un package Python nommé itofin, le moteur est donc appelable depuis un processus Python standard. Écrire en Rust devient pertinent lorsque la boucle de pricing elle-même est le produit que vous déployez.
Qu’est-ce qu’une bibliothèque de pricing m’apporte qu’une formule sur tableur n’offre pas ?
Des courbes au lieu de taux uniques, des conventions de calcul de jours avec de véritables calendriers boursiers, une boucle de calibration qui ajuste les paramètres du modèle aux prix cotés, et une couche numérique testée sous-jacente. La formule fermée n’est qu’une petite partie du travail.
Le langage de programmation change-t-il le prix de l’option ?
Pas mathématiquement. Il change la reproductibilité : les résultats en virgule flottante dépendent de l’ordre des opérations ; deux implémentations d’un même modèle peuvent donc diverger sur les derniers chiffres. Utiliser le même moteur pour la recherche et la production élimine cet écart.
Chaque panneau ici contient sa requête SQL exacte ; développez-en un pour voir comment le calcul a été effectué. Les mêmes questions sur les courbes, les calendriers et les chaînes peuvent être posées en langage naturel sur le terminal Strasmore.