Dans notre algorithme maison : HRP + Long/Short + CVaR avec Hull-White
📄 Cet article s'est transformé en article de recherche. L'allocation HRP au cœur de ce pipeline est soumise à un test contrôlé face à Markowitz, au shrinkage de Ledoit-Wolf et à 1/N sous covariance connue (4 800 expériences). Lisez l'article en ligne (version interactive + PDF) sur hrp.marketmaker.cc, code et données sur github.com/suenot/hrp-validation.
Dans notre panorama «12 algorithmes d'optimisation de portefeuille, comparés», nous avons fait courir une douzaine de méthodes d'allocation côte à côte. Onze d'entre elles sont des classiques de manuel. La douzième, Pipeline, est la nôtre — et elle n'a eu droit qu'à une seule puce dans cet article. Cet article-ci est la plongée en profondeur : ce qu'il y a dedans, d'où vient chaque formule, et comment la spécification se transforme en Rust.
Pipeline n'invente pas une nouvelle façon de calculer les poids. Il prend la recette la plus robuste connue — Hierarchical Risk Parity (HRP) — et l'enveloppe des deux couches dont un compte de trading en direct a réellement besoin mais que le HRP pur n'a pas : la direction (long/short à partir des signaux de stratégie) et un budget de risque strict (CVaR ajusté au régime de volatilité actuel). Cela donne quatre étapes.
Les quatre étapes
- I — rendements logarithmiques de chaque actif.
- II — poids de base issus de HRP.
- III — une répartition long/short à partir des signaux d'agents, avec les parts de risque fixées par la confiance.
- IV — une correction CVaR avec volatilité Hull-White ; le risque excédentaire part en cash.
Parcourons-les dans l'ordre.
Étape I. Rendements logarithmiques
Tout commence par le passage des prix aux rendements logarithmiques :
où est l'actif et le pas de temps. Les rendements logarithmiques s'additionnent dans le temps et sont plus symétriques que les variations en pourcentage simples — l'entrée standard pour tout calcul de covariance.
Étape II. HRP comme fondation
HRP, proposé par Marcos López de Prado en 2016, contourne le mal central de l'optimisation moyenne-variance — l'inversion d'une matrice de covariance mal conditionnée. Il ne l'inverse jamais du tout. Il travaille plutôt avec la structure des corrélations.
Covariance et corrélation
À partir des rendements, nous construisons la matrice de covariance et la normalisons en matrice de corrélation :
Matrice de distance
Nous transformons la corrélation en une métrique de distance, de sorte que les actifs fortement corrélés se retrouvent "proches" les uns des autres :
Plus est proche de 1, plus est proche de 0 — et plus les actifs ont de chances de partager un cluster.
Dendrogramme et ordre des feuilles
À partir de la matrice de distance, nous construisons une hiérarchie de clusters via average linkage et relevons l'ordre des feuilles — une permutation des actifs dans laquelle les actifs similaires sont adjacents.
Étape optionnelle : le nombre optimal de clusters peut être choisi via le coefficient de silhouette , où est la distance moyenne à l'intérieur d'un cluster et la distance moyenne au voisin le plus proche. La passe de base n'en a pas besoin — la bissection récursive respecte déjà la hiérarchie.
Quasi-diagonalisation
Nous permutons les lignes et colonnes de selon , en regroupant les grandes valeurs le long de la diagonale :
Bissection récursive
Puis la récursion se déroule de haut en bas. À chaque étape, un cluster est scindé en deux moitiés et , et le capital est alloué entre les moitiés inversement proportionnellement à leurs variances :
La variance d'un cluster est calculée sur son sous-bloc de covariance comme . La descente se poursuit jusqu'à ce que chaque nœud contienne un seul actif. Les poids sont long-only, non négatifs, et somment à 1,0.
Dans notre implémentation, c'est la fonction hrp_from_cov(cov) -> Vec<f64> : corrélation → distance → average linkage → ordre des feuilles → quasi-diagonalisation → bissection récursive. Pipeline l'appelle comme sa base — et c'est aussi le optimize() public pour le cas sans signaux.
Étape III. La couche long/short
Le HRP pur est un portefeuille "achat seul". Mais une stratégie indique souvent non seulement combien mais dans quel sens. L'étape III prend des signaux par actif (Long/Short) de l'agent et construit deux sous-portefeuilles.
- Les actifs sont répartis en paniers long et short selon le signal.
- Au sein de chaque panier, les poids sont calculés avec le même HRP (sur le sous-bloc de covariance de ces actifs), sommant à 1 par panier.
- Si l'agent émet également une confiance , les parts de risque entre les côtés sont fixées par la confiance totale :
Sans confiance, les parts retombent sur le nombre d'actifs dans chaque panier. Le poids signé final est pour les longs et pour les shorts, après quoi toute l'exposition brute est normalisée à 1.
Une remarque honnête sur le code. La spécification originale comporte des facteurs de correction , — mais elle les marque aussi d'un "a-t-on vraiment besoin de cette étape ?". L'implémentation ne les applique pas : les deux côtés sont combinés directement via les parts de risque , ce qui maintient l'exposition brute exactement à 1 et ne crée aucun effet de levier caché. C'est une simplification délibérée de la spécification, pas un oubli.
Étape IV. CVaR avec un ajustement Hull-White
Le HRP équilibre le risque structurellement, mais ne sait rien du niveau absolu de risque en termes monétaires. L'étape finale impose un plafond strict sur le risque de queue — et le rend sensible à un changement de régime de marché.
Rendement du portefeuille et volatilité EWMA
D'abord, nous réduisons les poids à un rendement de portefeuille et estimons la volatilité conditionnelle avec l'EWMA :
avec (la valeur classique de RiskMetrics). L'EWMA donne la volatilité "d'aujourd'hui" plutôt qu'une moyenne sur tout l'historique.
Recalibrage Hull-White
L'idée clé : les rendements passés ne peuvent pas être pris tels quels — ils se sont produits sous une volatilité différente. La méthode Hull-White recalibre chaque rendement passé au niveau actuel :
Un mois calme est "étiré", un mois turbulent est "compressé", et la distribution est ramenée au régime actuel.
VaR et CVaR
Sur la distribution recalibrée, nous prenons le quantile de perte et la perte moyenne dans la queue :
Le CVaR (alias Expected Shortfall) ne répond pas à "à quel point une mauvaise journée typique est mauvaise" mais à "à quel point c'est mauvais en moyenne sur les pires pour cent" — il voit donc l'épaisseur de la queue, pas seulement son bord.
Budget de risque et cash
Si le CVaR dépasse le seuil acceptable, chaque position risquée est réduite par un facteur unique, et le capital libéré passe en cash :
Ainsi, le portefeuille se désensibilise au risque lorsque le risque de queue augmente et revient sur le marché lorsqu'il se calme.
De la spécification au code
L'ensemble de l'algorithme vit dans une seule crate Rust, portfolio-pipeline, et obéit au contrat uniforme du workspace :
pub fn optimize(prices: &[Vec<f64>]) -> Vec<f64>
C'est la projection long-only (étapes I, II, IV sans signaux) — exactement la même interface prices -> weights que les onze autres algorithmes, de sorte que Pipeline est un remplacement direct pour n'importe lequel d'entre eux. La version complète avec chaque étape est une fonction séparée :
pub fn run(
prices: &[Vec<f64>],
signals: Option<&[Side]>, // Long / Short per asset
confidence: Option<&[f64]>, // agent confidence → risk shares λ
cfg: &PipelineConfig, // CVaR / Hull-White parameters
) -> PipelineResult // signed weights + cash + cvar + σ
Les valeurs par défaut de la couche : queue cvar_alpha = 0.05, budget cvar_max = 0.05, EWMA ewma_lambda = 0.94, fenêtre Hull-White hw_window = 0 (tout l'historique). L'implémentation n'a aucune dépendance externe et est délibérément défensive : sur des historiques courts (moins de 4 points de prix), elle renvoie des poids égaux, et la couche CVaR ne s'active qu'à partir de ≥8 observations de rendement — sinon il n'y a rien à partir de quoi estimer une queue.
Pourquoi Rust : une base de code déterministe unique pour le backtest et la production, sans dérive du type "Python en recherche, autre chose en production", et assez rapide pour exécuter les douze algorithmes en une seule requête via le backend de comparaison.
Ce que cela coûte en temps
À quel point "assez rapide" est-il rapide ? Nous avons extrait le noyau HRP (rendements logarithmiques → covariance → average linkage → quasi-diagonalisation → poids récursifs) dans un benchmark autonome et exécuté exactement les mêmes mathématiques sur sept langages — C, C++, Rust, Zig, Python, Node.js et Bun — dans des conditions identiques : Apple Silicon, thread unique, 365 observations quotidiennes par actif, prix synthétiques, nombre d'actifs de 10 à 10 000.
Un mot sur la complexité, car elle détermine toute la forme. La version manuel de average linkage rebalaye la matrice de distance complète pour trouver la paire la plus proche à chaque fusion — c'est et cela devient le goulot d'étranglement à quelques milliers d'actifs. Le benchmark utilise à la place l'algorithme de chaîne de plus proches voisins (Müllner 2011) — le même que celui derrière linkage(method='average') de SciPy. Avec cela en place, le clustering n'est plus l'étape dominante : à , c'est ~15 ms sur une passe de ~0,5 s. Le coût est désormais dominé par la matrice de covariance, — la seule étape qu'aucune méthode de type HRP ne peut éviter.
Ce que montrent les exécutions (les tableaux complets par langage et un script de reproduction en une commande se trouvent dans le dépôt du projet) :
- Sur des portefeuilles réalistes, c'est gratuit. Un panier crypto compte des dizaines d'actifs, rarement plus d'une centaine. À , une passe HRP complète prend quelques millisecondes même en Node et des microsecondes en Rust/C. Recalculer les poids à chaque tick n'est pas un problème.
- Rust est dans un facteur ~1,0–1,3× de C — le même ordre de grandeur, tous deux compilés, et pratiquement à égalité une fois que atteint les milliers. C est un peu plus rapide sur l'arithmétique brute, mais Rust offre la même prévisibilité sans ramasse-miettes et sans comportement indéfini.
- Cela s'étend à des milliers d'actifs. Avec un linkage , une passe complète prend ~0,5 s à et quelques secondes à dans les langages compilés ; même Node interprété franchit en moins de deux secondes. Ce qui fixe désormais le plafond, c'est l'étape de covariance, pas le clustering.
Le constat pragmatique : à nos tailles de portefeuille, choisir Rust ne consiste pas à "battre C" (C est légèrement plus rapide ici) — il s'agit d'une base de code déterministe unique pour la recherche et la production, sans pauses GC et avec des années de marge de performance. Le benchmark complet en sept langages, avec résultats et script de reproduction, est ouvert dans le dépôt du projet.
Où Pipeline se situe parmi les douze
Dans notre comparaison sur un panier unique (délibérément truqué), Pipeline s'est comporté comme HRP — car via le point d'entrée long-only optimize(), il est HRP avec une couche CVaR. Sa machinerie directionnelle ne s'active que lorsqu'on lui fournit des signaux de stratégie. C'est tout l'intérêt : Pipeline n'est pas "encore un optimiseur de plus pour le backtest de poids" mais la couche d'exécution entre les signaux de stratégie et les ordres réels — il prend vos décisions d'achat/vente, répartit le capital via HRP au sein de chaque côté, équilibre les côtés par confiance, et écrête le risque de queue jusqu'à un budget fixé.
Pour le contexte complet — quelles sont les onze autres méthodes existantes et en quoi elles diffèrent — voir le panorama, «12 algorithmes d'optimisation de portefeuille, comparés». Et vous pouvez tout essayer en direct sur portfolio-optimizer.marketmaker.cc.
Références
- López de Prado, M. (2016). Building Diversified Portfolios that Outperform Out of Sample. The Journal of Portfolio Management.
- López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley.
- Hull, J., & White, A. (1998). Incorporating Volatility Updating into the Historical Simulation Method for Value at Risk. Journal of Risk.
- Rockafellar, R. T., & Uryasev, S. (2000). Optimization of Conditional Value-at-Risk. Journal of Risk.
- RiskMetrics Group (1996). RiskMetrics — Technical Document. J.P. Morgan.
- Marketmaker.cc: marketmaker.cc
Citation
@article{soloviov2026pipeline,
author = {Soloviov, Eugen and Zhuravleva, Marina and Kiselev, Kirill},
title = {Inside Our House Algorithm: HRP + Long/Short + CVaR with Hull-White Adjustment},
year = {2026},
url = {https://marketmaker.cc/fr/blog/post/portfolio-pipeline-hrp-cvar},
description = {A deep dive into Pipeline, a composite portfolio allocation algorithm built on Hierarchical Risk Parity with a signal-driven long/short overlay and a Hull-White CVaR risk-budget correction, with the full specification and its Rust implementation.}
}
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.
Financial mathematics
Fifth-year student at Bauman Moscow State Technical University (Automatic Control Systems), specializing in financial mathematics. Background in calibrating stochastic-volatility (Heston) and local-volatility (Dupire) models, fair pricing of options including exotics via both Monte-Carlo and analytic formulas, hedging-error reduction, and exposure to LSV models.
Portfolio optimization
Fourth-year student at the Faculty of Mechanics and Mathematics, Novosibirsk State University (NSU); thesis on Heston-model calibration and delta-hedging within the same model. Works on portfolio optimization.