← Retour aux articles
May 25, 2026
5 min de lecture

Dans notre algorithme maison : HRP + Long/Short + CVaR avec Hull-White

Dans notre algorithme maison : HRP + Long/Short + CVaR avec Hull-White
#portfolio optimization
#HRP
#hierarchical risk parity
#CVaR
#Hull-White
#EWMA
#long/short
#risk management
#Rust
#quantitative finance
📊
Part 3 of 5 · Collection
Portfolio Construction & Risk

📄 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

prices  I  log returns  II  HRP weights  III  long/short  IV  CVaR budget\text{prices} \;\xrightarrow{\text{I}}\; \text{log returns} \;\xrightarrow{\text{II}}\; \text{HRP weights} \;\xrightarrow{\text{III}}\; \text{long/short} \;\xrightarrow{\text{IV}}\; \text{CVaR budget}
  • 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 :

ri,t=ln ⁣(Si,tSi,t1)r_{i,t} = \ln\!\left(\frac{S_{i,t}}{S_{i,t-1}}\right)

ii est l'actif et tt 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 Σ\Sigma et la normalisons en matrice de corrélation CC :

Σi,j=Cov(ri,rj),Ci,j=ρi,j=Cov(ri,rj)σiσj\Sigma_{i,j} = \mathrm{Cov}(r_i, r_j), \qquad C_{i,j} = \rho_{i,j} = \frac{\mathrm{Cov}(r_i, r_j)}{\sigma_i \sigma_j}

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 :

di,j=1ρi,j2d_{i,j} = \sqrt{\frac{1 - \rho_{i,j}}{2}}

Plus ρi,j\rho_{i,j} est proche de 1, plus di,jd_{i,j} 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 π=(π1,,πN)\pi = (\pi_1, \ldots, \pi_N) — 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 si=biaimax(bi,ai)s_i = \dfrac{b_i - a_i}{\max(b_i, a_i)}, où aia_i est la distance moyenne à l'intérieur d'un cluster et bib_i 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 Σ\Sigma selon π\pi, en regroupant les grandes valeurs le long de la diagonale :

Σi,jq=Σπi,πj\Sigma^{q}_{i,j} = \Sigma_{\pi_i, \pi_j}

Bissection récursive

Puis la récursion se déroule de haut en bas. À chaque étape, un cluster est scindé en deux moitiés LL et RR, et le capital est alloué entre les moitiés inversement proportionnellement à leurs variances :

wL=1/σL21σL2+1σR2,wR=1wLw_L = \frac{1/\sigma_L^2}{\dfrac{1}{\sigma_L^2} + \dfrac{1}{\sigma_R^2}}, \qquad w_R = 1 - w_L

La variance d'un cluster est calculée sur son sous-bloc de covariance comme σC2=1m2i,jCΣi,jq\sigma_C^2 = \tfrac{1}{m^2}\sum_{i,j \in C}\Sigma^{q}_{i,j}. 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.

  1. Les actifs sont répartis en paniers long et short selon le signal.
  2. 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.
  3. Si l'agent émet également une confiance pip_i, les parts de risque entre les côtés sont fixées par la confiance totale :

ξL=iLpi,ξS=iSpi,λL=ξLξL+ξS,λS=ξSξL+ξS\xi_L = \sum_{i \in L} p_i, \quad \xi_S = \sum_{i \in S} p_i, \qquad \lambda_L = \frac{\xi_L}{\xi_L + \xi_S}, \quad \lambda_S = \frac{\xi_S}{\xi_L + \xi_S}

Sans confiance, les parts retombent sur le nombre d'actifs dans chaque panier. Le poids signé final est wi=λLwiHRPw_i = \lambda_L \cdot w_i^{\text{HRP}} pour les longs et wi=λSwiHRPw_i = -\lambda_S \cdot w_i^{\text{HRP}} 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 αL=λL/σL\alpha_L = \sqrt{\lambda_L}/\sigma_L, αS=λS/σS\alpha_S = \sqrt{\lambda_S}/\sigma_S — 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 λ\lambda, 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 :

rp,t=i=1nwiri,t,σp,t2=λσp,t12+(1λ)rp,t12r_{p,t} = \sum_{i=1}^{n} w_i\, r_{i,t}, \qquad \sigma_{p,t}^2 = \lambda\, \sigma_{p,t-1}^2 + (1 - \lambda)\, r_{p,t-1}^2

avec λ=0.94\lambda = 0.94 (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 :

r~p,s=σp,t+1σp,srp,s,s=tN+1,,t\widetilde{r}_{p,s} = \frac{\sigma_{p,t+1}}{\sigma_{p,s}}\, r_{p,s}, \qquad s = t - N + 1, \ldots, t

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 :

VaRαHW=q1α(r~p)VaR_\alpha^{HW} = -q_{1-\alpha}(\widetilde{r}_p)

CVaRαHW=E ⁣[r~pr~pq1α(r~p)]CVaR_\alpha^{HW} = -\mathbb{E}\!\left[\widetilde{r}_p \mid \widetilde{r}_p \le q_{1-\alpha}(\widetilde{r}_p)\right]

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 α\alpha 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 :

winew=γwi,γ=CVaRmaxCVaRαHW,wcash=1i=1nwineww_i^{new} = \gamma\, w_i, \quad \gamma = \frac{CVaR_{\max}}{CVaR_\alpha^{HW}}, \qquad w_{cash} = 1 - \sum_{i=1}^{n} |w_i^{new}|

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 NN 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 O(N3)O(N^3) 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 O(N2)O(N^2) (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 : à N=2000N = 2000, c'est ~15 ms sur une passe de ~0,5 s. Le coût est désormais dominé par la matrice de covariance, O(N2T)O(N^2 \cdot T) — 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. À N100N \le 100, 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 NN 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 O(N2)O(N^2), une passe complète prend ~0,5 s à N=2000N = 2000 et quelques secondes à N=5000N = 5000 dans les langages compilés ; même Node interprété franchit N=2000N = 2000 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

  1. López de Prado, M. (2016). Building Diversified Portfolios that Outperform Out of Sample. The Journal of Portfolio Management.
  2. López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley.
  3. Hull, J., & White, A. (1998). Incorporating Volatility Updating into the Historical Simulation Method for Value at Risk. Journal of Risk.
  4. Rockafellar, R. T., & Uryasev, S. (2000). Optimization of Conditional Value-at-Risk. Journal of Risk.
  5. RiskMetrics Group (1996). RiskMetrics — Technical Document. J.P. Morgan.
  6. 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.}
}
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

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.

Marina Zhuravleva
Marina Zhuravleva

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.

Kirill Kiselev
Kirill Kiselev

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.

Newsletter

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.