DCC-GARCH : corrélations dynamiques pour les paires et le risque de portefeuille
Demandez à la plupart des desks crypto la corrélation entre BTC et ETH et vous obtenez un chiffre unique — 0.8, peut-être 0.75 — calculé sur une fenêtre dont personne ne se souvient avoir choisi la longueur. Ce chiffre est un mensonge, ou du moins une simplification dangereuse. La corrélation d'échantillon est une moyenne sur une période durant laquelle la véritable structure de dépendance ne cessait de bouger. En marché calme, BTC et ETH divergent suffisamment pour qu'une paire market-neutral paraisse attrayante. Dans une cascade de liquidations, ils se verrouillent l'un à l'autre et à tout le reste, et la diversification que vous avez payée s'évapore au moment précis où vous en avez besoin.
Ce n'est pas un effet subtil. Reprenez n'importe quel drawdown de 2022 — l'effondrement de LUNA en mai, la débâcle de 3AC en juin, l'implosion de FTX en novembre — et vous verrez la corrélation moyenne par paires sur les 20 premiers tokens grimper de la plage 0.4-0.6 vers 0.9+ en quelques jours. La corrélation n'est pas une constante qui se trouve occasionnellement mal estimée ; c'est une série temporelle avec sa propre dynamique, son propre clustering et ses propres régimes. La traiter comme un scalaire est l'équivalent multivarié de supposer une volatilité constante — une erreur que nous avons déjà passé la partie 1 de cette série à démonter pour un actif unique.
Cet article est la partie 3 d'une série de quatre volets sur la volatilité. La partie 1 a construit un GARCH(1,1) univarié avec la bibliothèque arch et montré comment la volatilité forme des clusters et revient vers sa moyenne. La partie 2 a ajouté l'asymétrie (GJR-GARCH, EGARCH) et des innovations de Student-t pour capturer l'effet de levier et les queues épaisses. Ici nous passons au multivarié : nous modélisons la matrice de covariance conditionnelle entière à mesure qu'elle évolue, en utilisant le modèle Dynamic Conditional Correlation (DCC) d'Engle. Cela nous donne deux choses qu'une corrélation scalaire ne pourra jamais offrir — un ratio de couverture dynamique pour le pairs trading, et une variance de portefeuille honnête, variable dans le temps, pour une allocation fondée sur le risque. La partie 4 clôt la série avec un backtest en volatility targeting qui relie les prévisions univariées et multivariées à une règle de dimensionnement des positions.
Nous supposons que vous avez lu les parties 1 et 2, aussi ne redériverons-nous pas le GARCH univarié. Si vous voulez le comportement de queue conjoint — la probabilité que deux actifs franchissent ensemble leur quantile de 1 % — c'est une question de copule, et nous la traitons dans Modèles de copules pour le risque conjoint. DCC et copules sont complémentaires : la copule vous donne une structure de dépendance de queue statique mais flexible, tandis que DCC vous donne une série temporelle traitable de toute la matrice de corrélation. Cet article porte sur cette dernière.
Pourquoi la corrélation statique échoue en crypto
Avant la machinerie, soyons précis sur ce qui échoue. Une corrélation d'échantillon unique sur une fenêtre estime
Cela porte trois hypothèses implicites, toutes fausses pour la crypto :
- Stationnarité de la dépendance. La fenêtre a un seul vrai . En réalité la dépendance a des régimes — un régime de marché calme autour de 0.5 et un régime de stress autour de 0.95 — et les mélange en un milieu dénué de sens.
- Volatilité marginale constante. La corrélation de Pearson est une covariance normalisée. Si et bougent elles-mêmes (elles le font — c'est toute la prémisse des parties 1 et 2), alors même une covariance constante produit une corrélation variable dans le temps, et vice versa. Vous ne pouvez pas séparer les deux sans un modèle de volatilité en dessous.
- Symétrie selon la direction du marché. La corrélation monte plus dans les drawdowns que dans les rallyes. C'est le cousin multivarié de l'effet de levier. Une fenêtre glissante ne peut l'exprimer sans devenir si courte qu'elle n'est plus que du bruit pur.
Le correctif par fenêtre glissante — recalculer sur les 30 ou 60 derniers jours — échange un problème contre un autre. Les fenêtres courtes sont réactives mais bruitées et retardent la rupture réelle ; les fenêtres longues sont stables mais périmées. Pire, une matrice de corrélation glissante sur actifs n'est pas garantie de rester semi-définie positive dès que vous commencez à la rétrécir ou à la rapiécer, ce qui casse tout optimiseur en aval. Nous voulons un modèle qui (a) soit piloté par un processus de volatilité propre à chaque actif, (b) produise une matrice de corrélation valide à chaque étape par construction, et (c) ait des paramètres que nous pouvons estimer par maximum de vraisemblance plutôt qu'en choisissant une longueur de fenêtre au hasard. Ce modèle est DCC-GARCH.
Le problème multivarié : la matrice de covariance conditionnelle
Soit le vecteur des rendements de actifs à l'instant , avec une moyenne conditionnelle (souvent juste une constante ou un petit terme AR) et un résidu . Nous supposons
où est la matrice de covariance conditionnelle étant donné l'ensemble d'information , et est une distribution conditionnelle quelconque (gaussienne ou, mieux pour la crypto, Student-t multivariée). Tout en modélisation de volatilité multivariée est une réponse différente à une seule question : comment paramétrer la dynamique de pour qu'elle reste symétrique définie positive à chaque étape sans explosion du nombre de paramètres ?
Deux réponses classiques montrent pourquoi le problème est difficile.
VECH
Le modèle VECH (Bollerslev, Engle, Wooldridge 1988) écrit la demi-vectorisation de comme une fonction linéaire des carrés des résidus passés et des covariances passées :
où empile le triangle inférieur d'une matrice symétrique dans un vecteur de longueur . C'est maximalement général — chaque variance et covariance dépend de toutes les variances et covariances passées — et maximalement inutile au-delà de . Pour actifs, et sont chacun . À cela fait deux matrices , soit environ 450 paramètres, plus des contraintes de définie-positivité pénibles ne serait-ce qu'à exprimer. La surface de vraisemblance est un marécage.
BEKK
Le modèle BEKK (Engle & Kroner 1995) garantit la définie-positivité par construction à l'aide d'une forme quadratique :
avec triangulaire supérieure. Parce que chaque terme est une forme quadratique, automatiquement tant que . BEKK est plus parcimonieux que VECH mais évolue toujours en paramètres — les matrices et sont chacune . Pour vous estimez de l'ordre de 200+ paramètres conjointement par MLE, sur des données crypto quotidiennes bruitées, sans garantie que l'optimiseur converge vers quoi que ce soit de sensé. En pratique le BEKK complet est confiné à , et même là les gens utilisent les restrictions « diagonale » ou « scalaire » qui jettent la majeure partie des dynamiques croisées.
C'est la malédiction de la dimensionnalité du GARCH multivarié : le nombre de paramètres croît quadratiquement, mais la quantité d'information dans les données non. Vous êtes à court de degrés de liberté bien avant d'être à court d'actifs qui vous intéressent. Tout book crypto de 10 à 30 tokens est complètement hors de portée pour VECH ou BEKK.
La sortie, due à Engle, consiste à cesser d'essayer de modéliser directement et plutôt à le factoriser en morceaux que nous savons déjà estimer à bas coût.
Le DCC d'Engle (2002) : la décomposition en deux étapes
Le modèle Constant Conditional Correlation (CCC) de Bollerslev (1990) fut la première factorisation parcimonieuse. Il écrit
où est la matrice diagonale des écarts-types conditionnels — un GARCH univarié par actif — et est une matrice de corrélation constante. C'est une énorme simplification : vous ajustez modèles GARCH univariés indépendants, puis estimez une seule matrice de corrélation d'échantillon des résidus standardisés. La définie-positivité est automatique tant que est une matrice de corrélation valide et que tous les .
Le problème de CCC est là dans son nom même — la corrélation est constante, ce qui est exactement l'hypothèse par laquelle nous avons ouvert cet article en la rejetant. La Dynamic Conditional Correlation (2002) d'Engle conserve la belle factorisation de CCC mais laisse respirer la matrice de corrélation :
Maintenant varie dans le temps. Le trait de génie est que les volatilités et les corrélations sont estimées en deux étapes séparées, de sorte que nous n'affrontons jamais l'optimisation conjointe complète en .
Étape 1 : GARCH univarié par actif
Pour chaque actif , ajustez un modèle GARCH univarié exactement comme dans les parties 1 et 2 — GARCH(1,1), GJR-GARCH, ou EGARCH avec innovations de Student-t, selon ce qui convenait le mieux à cette série. Cela donne les variances conditionnelles et donc .
Des modèles ajustés nous extrayons les résidus standardisés :
Par construction chaque a (approximativement) une variance conditionnelle unitaire. Empilez-les dans un vecteur . Ces résidus standardisés sont la matière première de l'étape de corrélation — leur dynamique de volatilité individuelle en a été retirée, de sorte que tout co-mouvement restant est de la dépendance pure, non un artefact de volatilité. (C'est la même logique de type PIT que l'article sur les copules emploie avant d'ajuster les marges ; ici nous nous arrêtons à la standardisation plutôt que d'aller jusqu'aux uniformes.)
Étape 2 : la récursion de corrélation DCC
Nous modélisons un processus auxiliaire , une matrice symétrique définie positive, avec une récursion de type GARCH pilotée par les produits externes des résidus standardisés :
où :
- est la matrice de corrélation inconditionnelle des résidus standardisés (c'est le correlation targeting — plus de détails ci-dessous),
- régit avec quelle force le choc du jour tire la corrélation,
- régit la persistance — quelle part du de la veille se reporte,
- et la contrainte de retour à la moyenne est (avec ), directement analogue à dans le GARCH univarié.
Notez que la structure est identique à une récursion GARCH(1,1) scalaire, mais sur des matrices : un ancrage de long terme , un terme de choc et un terme de persistance. Parce qu'il s'agit d'une combinaison convexe de matrices semi-définies positives (, le produit externe de rang 1 et le précédent), reste défini positif tant que et que les poids sont non négatifs. C'est ce qui nous procure gratuitement des matrices de covariance valides garanties.
est presque une matrice de corrélation mais pas tout à fait — sa diagonale n'est pas exactement 1. Nous la normalisons donc :
Élément par élément, la corrélation conditionnelle entre les actifs et est
Ce est une matrice de corrélation en bonne et due forme — diagonale unitaire, hors-diagonale dans , définie positive — à chaque pas de temps, par construction. Réassemblez la covariance conditionnelle complète :
Cette dernière forme élément par élément est celle que vous utiliserez sans cesse : la covariance conditionnelle de deux actifs est leur corrélation dynamique multipliée par chacune de leurs volatilités dynamiques. Chaque ingrédient du membre de droite varie dans le temps et provient d'un modèle que vous pouvez estimer.
Le modèle entier n'a que deux paramètres de corrélation, et , que ou . Le côté volatilité évolue linéairement (un GARCH univarié par actif, chacun avec ~4-5 paramètres, tous ajustés indépendamment et de façon embarrassingly parallel). C'est pourquoi DCC passe à l'échelle là où BEKK et VECH ne le peuvent pas : la malédiction de la dimensionnalité est confinée à , qui est ciblé (injecté comme estimation d'échantillon) plutôt qu'optimisé.
La restriction scalaire et son coût
Les scalaires signifient que chaque paire d'actifs partage la même dynamique de corrélation — la même vitesse d'ajustement et la même persistance. La corrélation BTC-ETH et la corrélation DOGE-SHIB bougent au même rythme même si leur économie diffère. C'est le prix de la traitabilité, et c'est généralement un prix acceptable. Des généralisations (DCC généralisé avec matrices ; le DCC asymétrique de Cappiello-Engle-Sheppard) l'assouplissent au prix de paramètres et de stabilité d'estimation. Nous mentionnons l'aDCC plus bas.
La quasi-log-vraisemblance DCC
Pour estimer et il nous faut la vraisemblance. Le résultat clé d'Engle est que la log-vraisemblance gaussienne se sépare en une partie volatilité et une partie corrélation, ce qui justifie l'estimateur en deux étapes. En supposant , la contribution à la log-vraisemblance à l'instant est
Substituez . Alors et , et en utilisant :
Maintenant scindez-la en ajoutant et retranchant :
La partie volatilité ne dépend que des paramètres GARCH univariés (via ) — la maximiser revient exactement à ajuster modèles GARCH univariés indépendants, ce que nous avons fait à l'étape 1. La partie corrélation dépend de et (via ), étant donné les résidus standardisés de l'étape 1. Donc à l'étape 2 nous maximisons seulement
(le terme ne dépend pas de , nous le supprimons donc). C'est une optimisation à deux paramètres quel que soit le nombre d'actifs — c'est là tout l'intérêt. On l'appelle quasi-vraisemblance parce que l'estimateur en deux étapes est convergent mais pas pleinement efficace ; les erreurs-types nécessitent une correction (Engle & Sheppard 2001), mais pour la génération de signaux ce sont les estimations ponctuelles qui comptent.
Pour la crypto, les innovations gaussiennes sous-estiment le risque de queue. Basculer vers la vraisemblance de Student-t multivariée est un changement direct de (remplacer le noyau gaussien par la densité multivariée et ajouter un paramètre de degrés de liberté ). Nous gardons la quasi-vraisemblance gaussienne dans l'estimateur ci-dessous par souci de clarté et notons où intervient — la standardisation des parties 1-2 utilisait déjà des innovations t sur les marges, ce qui capture l'essentiel du bénéfice de queue.
Implémentation Python
Un fait brutal mais important : la bibliothèque arch ne fait pas de GARCH multivarié ni de DCC. arch est un superbe moteur univarié (nous nous appuyons dessus pour exactement cela), mais il n'y a pas de dcc_model dedans. Vos options pratiques sont :
- Codez votre propre DCC par-dessus
arch— ajustez les modèles univariés avecarch, extrayez les résidus standardisés, implémentez la récursion et la quasi-vraisemblance de corrélation en NumPy/SciPy, et optimisez les deux scalaires. C'est ce que nous faisons ci-dessous. Cela fait environ 60 lignes et c'est totalement transparent. - Le paquet PyPI
mgarch— une implémentation DCC-GARCH légère en pur Python. Pratique pour un ajustement rapide, moins flexible si vous voulez câbler précisément des marges GJR ou des innovations t. - Le
rmgarchde R (Alexios Galanos) — l'implémentation de référence.dccspec/dccfitprennent en charge DCC, aDCC, GARCH-copule, Student-t et de vraies erreurs-types. Si vous faites de la recherche sérieuse en volatilité multivariée,rmgarch(appelé depuis Python viarpy2si nécessaire) est l'étalon-or.
Nous construisons l'option 1 parce qu'elle rend chaque rouage explicite et réutilise les compétences univariées des parties 1-2.
Étape 1 : ajuster les marges GARCH univariées avec arch
import numpy as np
import pandas as pd
from arch import arch_model
from scipy.optimize import minimize
def fetch_returns(symbols, start="2022-01-01", end="2025-12-31"):
"""
Daily log returns for a list of crypto symbols.
Replace with your data source (ccxt for 24/7 exchange data,
yfinance for a quick sketch).
"""
import yfinance as yf
px = yf.download([f"{s}-USD" for s in symbols],
start=start, end=end)["Close"]
px.columns = symbols
rets = np.log(px / px.shift(1)).dropna()
return rets
symbols = ["BTC", "ETH", "SOL", "BNB"]
returns = fetch_returns(symbols)
def fit_univariate(series, dist="t"):
"""
Fit GJR-GARCH(1,1) with Student-t innovations (Part 2 model).
Returns the fitted result and the *scaled* series it was fit on.
We scale returns by 100 for the optimizer's numerical health,
exactly as in Parts 1 and 2.
"""
scaled = series * 100.0
model = arch_model(scaled, mean="Constant",
vol="GARCH", p=1, o=1, q=1, dist=dist)
res = model.fit(disp="off")
return res
fits = {s: fit_univariate(returns[s]) for s in symbols}
Z = pd.DataFrame({s: fits[s].std_resid for s in symbols}).dropna()
Sigma = pd.DataFrame({s: fits[s].conditional_volatility
for s in symbols}).loc[Z.index]
print(Z.describe())
Une vérification rapide de bon sens sur les résidus standardisés compte. Si une colonne a un écart-type très éloigné de 1, ou une forte autocorrélation résiduelle dans son carré (Ljung-Box sur ), la marge univariée est mal spécifiée et l'étape DCC héritera de cette erreur. Corrigez d'abord la marge — c'est à cela que servait la partie 2.
Étape 2 : la récursion DCC et la quasi-log-vraisemblance
def dcc_negloglik(params, Z):
"""
Negative DCC quasi-log-likelihood (Gaussian) in (a, b).
Z : (T, d) array of standardized residuals from Step 1.
Implements:
Q_t = (1-a-b) Qbar + a z_{t-1} z_{t-1}' + b Q_{t-1}
R_t = diag(Q_t)^{-1/2} Q_t diag(Q_t)^{-1/2}
LL = -1/2 sum_t ( log|R_t| + z_t' R_t^{-1} z_t )
with correlation targeting: Qbar = sample corr of Z.
"""
a, b = params
if a <= 0 or b <= 0 or a + b >= 1.0:
return 1e10
Z = np.asarray(Z)
T, d = Z.shape
Qbar = np.cov(Z, rowvar=False, bias=True) # unconditional (targeted)
dinv = np.diag(1.0 / np.sqrt(np.diag(Qbar)))
Qbar = dinv @ Qbar @ dinv
Q = Qbar.copy() # initialize Q_1 at the target
ll = 0.0
for t in range(T):
q_diag = np.sqrt(np.diag(Q))
R = Q / np.outer(q_diag, q_diag)
sign, logdet = np.linalg.slogdet(R)
if sign <= 0:
return 1e10
z = Z[t]
Rinv_z = np.linalg.solve(R, z)
ll += -0.5 * (logdet + z @ Rinv_z)
Q = (1 - a - b) * Qbar + a * np.outer(z, z) + b * Q
return -ll
def fit_dcc(Z):
"""Estimate (a, b) by maximizing the DCC quasi-log-likelihood."""
res = minimize(
dcc_negloglik, x0=[0.03, 0.94], args=(np.asarray(Z),),
method="L-BFGS-B",
bounds=[(1e-6, 0.5), (1e-6, 0.999)],
)
a, b = res.x
print(f"DCC estimates: a = {a:.4f}, b = {b:.4f}, a+b = {a+b:.4f}")
print(f"log-likelihood: {-res.fun:.2f}")
return a, b
a_hat, b_hat = fit_dcc(Z)
Exécuter ceci sur un book BTC/ETH/SOL/BNB sur quelques années de données quotidiennes produit une sortie de la forme suivante (les nombres ci-dessous sont illustratifs, ils ne proviennent pas d'une expérience datée précise — exécutez-le sur vos propres données) :
DCC estimates: a = 0.0287, b = 0.9401, a+b = 0.9688
log-likelihood: -3812.44
Comment le lire :
- est petit — la matrice de corrélation ne bondit pas sous le choc d'une seule journée. Chaque jour pousse vers le produit externe de seulement ~3 %.
- est grand — les corrélations sont fortement persistantes. Une fois que le book se couple lors d'un événement de stress, il reste couplé un moment, décroissant lentement vers . Cela correspond à l'expérience vécue des drawdowns crypto : les corrélations ne rebondissent pas dès que le prix se stabilise.
- confirme le retour à la moyenne. Le processus de corrélation a un niveau de long terme stationnaire () vers lequel il revient, avec une demi-vie d'environ jours. Si vous estimez un jour essentiellement égal à 1, le processus de corrélation est intégré — il n'a pas d'ancrage de long terme, généralement le symptôme d'une rupture structurelle dans votre échantillon que le modèle absorbe comme une persistance infinie.
La persistance quasi unitaire et le minuscule chargement du choc sont l'empreinte canonique du DCC à travers les classes d'actifs, et la crypto ne fait pas exception. C'est aussi pourquoi une corrélation glissante à 30 jours est un si mauvais substitut : une fenêtre glissante suppose implicitement des et qui ne correspondent pas du tout à cette structure de décroissance.
Quelques notes d'implémentation qui font gagner un vrai temps de débogage :
- Initialisation. Démarrer à
[0.03, 0.94]reflète l'estimation crypto typique : petit (les corrélations réagissent aux chocs mais pas violemment), grand (les corrélations sont persistantes). Si votre optimiseur dérive vers , le processus de corrélation est intégré — généralement le signe d'une rupture structurelle dans l'échantillon (un changement de régime que le modèle s'efforce d'ajuster comme de la persistance). - Convention temporelle. À l'intérieur de la boucle nous évaluons contre puis mettons à jour avec pour l'étape suivante. Cela garde fonction de l'information jusqu'à seulement — pas de look-ahead. Se tromper de ce décalage d'un cran est le bug DCC le plus courant, et il gonfle silencieusement l'ajustement in-sample.
- Correlation targeting. Nous injectons comme corrélation d'échantillon plutôt que de l'estimer. C'est ce qui rend l'optimisation bidimensionnelle. Le coût est que utilise l'échantillon complet, donc dans un walk-forward strict vous devez le réestimer sur la seule fenêtre d'entraînement (voir ci-dessous).
Étape 3 : reconstruire les trajectoires de corrélation et de covariance
Une fois fixés, exécutez la récursion une fois de plus, cette fois en stockant toute la trajectoire (et ) pour que les stratégies en aval puissent l'utiliser.
def dcc_filter(Z, Sigma, a, b):
"""
Run the DCC recursion with fixed (a,b) and return the full paths:
R_path : (T, d, d) conditional correlation matrices
H_path : (T, d, d) conditional covariance matrices (scaled units)
Sigma : (T, d) conditional volatilities aligned with Z.
"""
Z = np.asarray(Z); Sig = np.asarray(Sigma)
T, d = Z.shape
Qbar = np.cov(Z, rowvar=False, bias=True)
dinv = np.diag(1.0 / np.sqrt(np.diag(Qbar)))
Qbar = dinv @ Qbar @ dinv
Q = Qbar.copy()
R_path = np.empty((T, d, d))
H_path = np.empty((T, d, d))
for t in range(T):
q_diag = np.sqrt(np.diag(Q))
R = Q / np.outer(q_diag, q_diag)
R_path[t] = R
Dt = np.diag(Sig[t]) # D_t = diag(sigma_i,t)
H_path[t] = Dt @ R @ Dt # H_t = D_t R_t D_t
z = Z[t]
Q = (1 - a - b) * Qbar + a * np.outer(z, z) + b * Q
return R_path, H_path
R_path, H_path = dcc_filter(Z, Sigma, a_hat, b_hat)
i, j = symbols.index("BTC"), symbols.index("ETH")
rho_btc_eth = pd.Series(R_path[:, i, j], index=Z.index, name="rho_BTC_ETH")
print(rho_btc_eth.describe())
print("min/max correlation:", rho_btc_eth.min().round(3),
rho_btc_eth.max().round(3))
La série rho_btc_eth est le fruit de tout l'exercice : au lieu d'un chiffre unique, vous disposez maintenant d'une corrélation quotidienne que vous pouvez tracer, seuiller ou injecter dans une stratégie. Sur de vraies données crypto, vous la verrez typiquement varier d'environ 0.5 en périodes calmes à plus de 0.9 en périodes de stress — l'écart exact qu'une corrélation d'échantillon unique moyenne et fait disparaître.
Prévision à un pas
Pour le trading en direct, vous avez besoin du de la période suivante à partir de l'information disponible maintenant. Le côté volatilité provient de la prévision à un pas de chaque modèle arch ; le côté corrélation est un tour de plus de la récursion :
def dcc_forecast_next(Z, a, b, fits, symbols):
"""One-step-ahead H_{t+1} using info through the last observation."""
Z = np.asarray(Z); T, d = Z.shape
Qbar = np.cov(Z, rowvar=False, bias=True)
dinv = np.diag(1.0 / np.sqrt(np.diag(Qbar)))
Qbar = dinv @ Qbar @ dinv
Q = Qbar.copy()
for t in range(T):
z = Z[t]
Q = (1 - a - b) * Qbar + a * np.outer(z, z) + b * Q
q_diag = np.sqrt(np.diag(Q))
R_next = Q / np.outer(q_diag, q_diag)
sig_next = np.array([
np.sqrt(fits[s].forecast(horizon=1, reindex=False)
.variance.iloc[-1, 0])
for s in symbols
])
Dt = np.diag(sig_next)
H_next = Dt @ R_next @ Dt
return R_next, H_next, sig_next
R_next, H_next, sig_next = dcc_forecast_next(Z, a_hat, b_hat, fits, symbols)
Rappelez-vous que tout est en unités mises à l'échelle (×100) parce que nous avons ajusté arch sur series * 100. Divisez les volatilités par 100 (et les covariances par ) pour revenir aux unités de rendement brut avant d'alimenter une stratégie. Garder la mise à l'échelle en ordre est fastidieux mais c'est une source fréquente de bugs silencieux.
Application 1 : un ratio de couverture dynamique pour le pairs trading
La paire market-neutral classique — long sur un actif, short sur un montant pondéré par le bêta d'un autre — vit ou meurt selon le ratio de couverture . Estimez-le par OLS statique sur une fenêtre d'entraînement et vous héritez exactement du problème de corrélation périmée dont traite tout cet article : la couverture qui neutralisait l'exposition au marché le trimestre dernier est fausse ce trimestre.
DCC vous donne le ratio de couverture sous forme de série temporelle. La couverture à variance minimale de l'exposition ETH à l'aide de BTC est le coefficient de régression conditionnel
Chaque terme de droite est une sortie DCC. Le ratio de couverture bouge pour deux raisons distinctes, et DCC les sépare proprement : la corrélation change (les actifs se couplent ou se découplent), et le ratio de volatilité change (un actif devient relativement plus volatil). Un bêta par OLS glissant mélange les deux effets avec un retard ; DCC les attribue.
def dynamic_hedge_ratio(R_path, Sigma, base="BTC", target="ETH",
symbols=symbols, index=Z.index):
"""
beta_t to hedge `target` exposure with `base`:
beta_t = rho_t * sigma_target,t / sigma_base,t
(Scaling cancels in the ratio, so scaled units are fine here.)
"""
i = symbols.index(target)
j = symbols.index(base)
rho = R_path[:, i, j]
sig = np.asarray(Sigma)
beta = rho * sig[:, i] / sig[:, j]
return pd.Series(beta, index=index, name=f"beta_{target}_{base}")
beta_t = dynamic_hedge_ratio(R_path, Sigma)
print(beta_t.describe())
spread = (returns["ETH"] - beta_t.shift(1) * returns["BTC"]).dropna()
Alimentez spread dans le moteur de paires que vous exploitez. La couverture dynamique ne crée pas en soi un edge — elle rend le spread que vous tradez véritablement market-neutral dans le temps, de sorte que votre signal de retour à la moyenne n'est pas contaminé par une exposition directionnelle qui dérive. Si vous construisez des stratégies de paires, cela s'insère directement dans les cadres de Arbitrage statistique et pairs trading en crypto et de l'approche par distance des paires, en remplaçant leur ratio de couverture fixe. La série de corrélation elle-même est aussi une entrée plus propre pour un signal de paire fondé sur la corrélation que n'importe quelle fenêtre glissante — vous obtenez un lissé et cohérent avec le modèle au lieu d'une estimation par fenêtre bruitée.
Deux mises en garde spécifiques à l'utilisation de en direct. Premièrement, décalez-le — tradez sur , jamais sur le contemporain, sinon vous trichez. Deuxièmement, un ratio de couverture qui fouette dans tous les sens chaque jour génère du turnover et des frais ; sur le marché crypto 24/7 avec des coûts de funding sur la jambe short, une couverture sur-réactive peut saigner plus que la dérive qu'elle corrige. Lissez (un EWMA, ou ne rééquilibrez la couverture que lorsqu'elle sort d'une bande) et dimensionnez le tout sainement — le dimensionnement de position à partir d'un signal bruité est une discipline à part entière, couverte dans le dimensionnement par le critère de Kelly.
Application 2 : variance de portefeuille variable dans le temps
Pour un portefeuille de vecteur de poids , la variance conditionnelle est
Avec une matrice de covariance statique — le défaut de Markowitz — ce nombre est une constante que vous avez calculée une fois et que vous faites semblant de croire encore vraie. Elle ne l'est pas. Le risque de portefeuille respire avec le marché, et il respire le plus fort exactement quand les corrélations bondissent, car dans un drawdown les termes et les termes montent ensemble et se multiplient. Un portefeuille qui semblait à 40 % de vol annualisée en marché calme peut tourner à 80 %+ lors d'une semaine de stress, et une matrice de covariance statique vous dira que rien n'a changé.
def portfolio_vol_path(H_path, weights, index=Z.index, unscale=1e4):
"""
Conditional portfolio volatility sigma_{p,t} = sqrt(w' H_t w).
H_path is in scaled (x100) units, so covariances carry a 100^2
factor: divide by unscale=1e4 to return to raw-return variance.
"""
w = np.asarray(weights)
var_t = np.einsum("i,tij,j->t", w, H_path, w) / unscale
return pd.Series(np.sqrt(var_t), index=index, name="port_vol")
w = np.array([0.4, 0.3, 0.2, 0.1]) # BTC, ETH, SOL, BNB
pv = portfolio_vol_path(H_path, w)
pv_annual = pv * np.sqrt(365)
print(pv_annual.describe())
Ce variable dans le temps est l'entrée honnête dont l'allocation fondée sur le risque a besoin. L'optimisation moyenne-variance (Markowitz pour la crypto) avec une covariance d'échantillon statique optimise contre une fiction ; l'alimenter avec (ou sa prévision à court horizon) rend la frontière efficiente elle-même variable dans le temps et force l'optimiseur à réduire le risque à l'entrée des régimes de corrélation croissante plutôt qu'après. Les approches en risk-parity et hiérarchiques — le pipeline HRP + CVaR — sont encore plus sensibles à l'entrée de covariance, puisque toute l'allocation est une fonction de la matrice de risque. Et si vous comparez des allocateurs face à face, comme dans comparaison des algorithmes d'optimisation de portefeuille, le fait qu'ils consomment une covariance statique ou dynamique est souvent un moteur plus important du risque réalisé que le choix de l'algorithme.
L'application directe est le volatility targeting de tout le portefeuille : choisissez une vol annualisée cible , et mettez à l'échelle l'exposition brute par à chaque période pour que le risque réalisé reste à peu près constant au lieu d'exploser en crise. Cela boucle la boucle avec la partie 4, qui construit et backteste exactement cette règle.
Application 3 : la corrélation comme signal de régime
Au-delà de la couverture et du dimensionnement, la matrice de corrélation porte un signal macro. Le scalaire unique le plus utile que vous puissiez en extraire est la corrélation moyenne par paires :
Quand monte à travers le book, le marché entre dans un régime risk-off — les histoires idiosyncratiques cessent de compter et tout se trade comme un seul bêta macro. C'est l'empreinte quantitative de « les corrélations vont à 1 en crise ». Elle tend à précéder les drawdowns ou à coïncider avec eux, ce qui en fait un indicateur de régime utilisable plutôt qu'une autopsie retardée.
def avg_pairwise_corr(R_path, index=Z.index):
T, d, _ = R_path.shape
iu = np.triu_indices(d, k=1) # upper-triangle off-diagonals
avg = R_path[:, iu[0], iu[1]].mean(axis=1)
return pd.Series(avg, index=index, name="avg_corr")
avg_corr = avg_pairwise_corr(R_path)
roll_q = avg_corr.rolling(365, min_periods=90).quantile(0.80)
risk_off = (avg_corr > roll_q)
Vous pouvez utiliser risk_off comme frein autonome (réduire l'exposition brute, élargir les stops, mettre en pause les stratégies de retour à la moyenne qui se font écraser quand tout tend ensemble) ou comme feature dans un modèle de régime plus formel. Cela se marie naturellement avec l'approche par chaîne de Markov cachée dans détection de régime par HMM : la corrélation DCC moyenne est l'une des variables d'observation les plus informatives que vous puissiez fournir à un HMM, parce qu'elle est prospective à l'égard du stress systémique d'une manière que les rendements passés ne sont pas. La mise en garde honnête : une corrélation qui monte vous dit que la diversification échoue, pas dans quelle direction va le marché. C'est un signal de risque, pas un signal d'alpha, et il devrait être dimensionné comme tel — voir l'asymétrie des pertes et des profits pour comprendre pourquoi traiter un régime de risque comme un pari directionnel finit mal.
Considérations pratiques
Stabilité d'estimation et nombre d'actifs
DCC passe bien mieux à l'échelle que BEKK, mais « passe à l'échelle » n'est pas « gratuit ». La matrice de correlation targeting est une corrélation d'échantillon , et les matrices de corrélation d'échantillon deviennent mal conditionnées à mesure que approche le nombre d'observations. Avec 4 actifs et 1000 jours, tout va bien. Avec 60 actifs et 400 jours, est presque singulière, son inverse dans la vraisemblance explose, et peut dériver hors du domaine défini positif à cause du bruit numérique. Atténuations, à peu près dans l'ordre de fréquence où vous en aurez besoin :
- Rétrécissez vers une cible structurée (Ledoit-Wolf, ou vers l'identité / une matrice à corrélation constante) avant d'exécuter la récursion. C'est le correctif à plus fort effet de levier pour les grands books.
- Regroupez les actifs en une poignée de secteurs (majors, L1, DeFi, memes), modélisez à l'intérieur et entre secteurs au niveau sectoriel, ou exécutez DCC sur des facteurs en composantes principales plutôt que sur les actifs bruts.
- Préférez plus de données à plus d'actifs. DCC a un appétit insatiable pour un historique long, propre et contemporain — ce qui est exactement ce que les jeunes tokens n'ont pas.
De façon réaliste, gardez le DCC direct à quelques dizaines d'actifs au plus. Pour un grand univers, le DCC sur rendements de facteurs plus résidus idiosyncratiques est le contournement standard.
Le correlation targeting est un raccourci à un coût
Cibler rend l'estimation traitable mais inscrit la corrélation inconditionnelle sur échantillon complet dans chaque . Dans un backtest strict c'est une fuite de look-ahead : votre matrice de corrélation au jour « connaît » la corrélation moyenne de tout l'échantillon, y compris le futur. Pour une évaluation honnête vous devez réestimer sur la seule fenêtre d'entraînement et la garder fixe hors échantillon, ou la faire rouler en avant. C'est la même discipline que tout le cadre d'optimisation walk-forward impose, et il est facile de la violer accidentellement avec un pratique np.cov(Z) sur tout le tableau — comme le fait notre code pédagogique ci-dessus. Corrigez-le avant de faire confiance à un seul chiffre de P&L.
Cadence de réajustement et discipline anti-look-ahead
Vous n'avez pas besoin de réoptimiser chaque jour — ce sont des paramètres stables. Une cadence de production sensée :
- Réestimez et les paramètres GARCH univariés chaque semaine ou chaque mois.
- Exécutez le filtre (mise à jour de , ) à chaque période avec les paramètres gelés pour obtenir des et frais. Le filtrage est bon marché ; l'ajustement ne l'est pas.
- Toujours prévoir, jamais lisser. Utilisez construit à partir de l'information jusqu'à pour trader à . La structure en deux passes (ajuster sur une fenêtre, puis filtrer en avant) est ce qui vous garde honnête.
L'écart entre un backtest DCC et la performance en direct est presque toujours une fuite de look-ahead — sur échantillon complet, contemporain, ou réajustement sur des données qui incluent le trade que vous évaluez. La discipline d'aligner le backtest sur les conditions réelles est un sujet à part entière dans parité backtest-live, et DCC est un modèle qui punit ici la négligence plus que la plupart. Si, après une évaluation walk-forward propre, la corrélation dynamique n'apporte rien de plus qu'une simple estimation glissante pour votre stratégie, c'est un résultat négatif réel et publiable — l'état d'esprit de résultats négatifs honnêtes s'applique directement.
DCC asymétrique (aDCC)
Tout comme l'effet de levier univarié (partie 2) signifie que les mauvaises nouvelles font monter la volatilité plus que les bonnes, les corrélations montent plus après des chocs négatifs conjoints qu'après des chocs positifs conjoints. Cappiello, Engle & Sheppard (2006) capturent cela avec le DCC asymétrique, ajoutant un terme piloté par le produit externe des résidus standardisés à partie négative :
où et mesure le surcroît de corrélation issu des mouvements baissiers conjoints. Pour la crypto, où la corrélation de crash est le risque dominant, le terme d'asymétrie est généralement significatif et vaut le paramètre supplémentaire. rmgarch ajuste l'aDCC d'office (model="aDCC") ; ajouter le terme à notre estimateur NumPy est un exercice simple.
Comparaison : DCC face aux alternatives
Où se situe DCC parmi les façons dont vous pourriez obtenir une matrice de covariance pour un book crypto ? Le résumé honnête :
| Approche | Paramètres | Passe à l'échelle jusqu'à | variable dans le temps ? | PD garantie ? | Dépendance de queue ? |
|---|---|---|---|---|---|
| Covariance d'échantillon / glissante | 0 (longueur de fenêtre) | tout | grossièrement (retardée, bruitée) | non (nécessite rapiéçage) | non |
| EWMA (RiskMetrics) | 1 () | tout | oui (décroissance unique) | oui | non |
| CCC-GARCH | marges + | dizaines | non ( constant) | oui | non |
| DCC-GARCH | marges + 2 | dizaines | oui | oui | non |
| aDCC-GARCH | marges + 3 | dizaines | oui, asymétrique | oui | partielle |
| BEKK | oui (riche) | oui | non | ||
| VECH | oui (la plus riche) | pénible | non | ||
| GARCH-copule | marges + copule | dizaines (vines) | copule statique | oui | oui |
Quelques lectures de ce tableau :
- EWMA est la référence bon marché que tout le monde devrait battre avant de prétendre que DCC aide. C'est un cas particulier à un paramètre dans l'esprit — une décroissance exponentielle unique appliquée directement à la covariance — et pour beaucoup de books il est étonnamment difficile de faire mieux hors échantillon. Si DCC ne bat pas EWMA en walk-forward propre, utilisez EWMA.
- CCC vs DCC est tout le propos de cet article : même factorisation, mais CCC fige et DCC le laisse bouger. Les deux paramètres supplémentaires () sont toute la différence, et en crypto ils gagnent leur place.
- BEKK/VECH achètent des dynamiques plus riches — chaque covariance peut répondre à chaque choc passé — mais le coût en paramètres les confine à de tout petits books. Pour tout ce qui dépasse 4 actifs, ce ne sont pas de vraies options.
- GARCH-copule est la seule ligne avec un « oui » sous dépendance de queue. C'est encore la complémentarité : DCC modélise le centre dynamique de la distribution conjointe, les copules modélisent ses queues statiques. Si votre question de risque est « que se passe-t-il quand tout casse en même temps », tournez-vous vers le pipeline de copules ; si c'est « quel est mon ratio de couverture / ma variance de portefeuille en ce moment », tournez-vous vers DCC.
Le défaut pratique pour un desk crypto systématique : DCC (ou aDCC) pour les ratios de couverture et la covariance dynamique dans le corps, une surcouche de copule pour le risque de queue et la CVaR, et EWMA comme référence de bon sens qui vous garde honnête sur le fait de savoir si la machinerie supplémentaire se paie elle-même.
Limites
- Dynamique scalaire. Un seul et un seul pour toutes les paires est une forte restriction. BTC-ETH et deux alts obscurs partagent la même vitesse d'ajustement. Le DCC généralisé assouplit cela mais réintroduit l'explosion de paramètres que DCC était conçu pour éviter.
- Perte d'efficacité en deux étapes. L'estimateur par quasi-vraisemblance est convergent mais pas pleinement efficace, et les erreurs-types naïves sont fausses. Utilisez la correction d'Engle-Sheppard si l'inférence vous importe ; pour la génération de signaux les estimations ponctuelles suffisent.
- Queues gaussiennes par défaut. La quasi-vraisemblance gaussienne pure sous-estime le risque de queue conjoint. Les innovations de Student-t aident ; pour une véritable dépendance de queue (la probabilité de mouvements extrêmes simultanés), DCC est le mauvais outil et un modèle de copule est le bon. DCC vous donne le corps dynamique de la corrélation ; les copules vous donnent la queue statique. Les desks sérieux utilisent les deux.
- Corrélation n'est pas causalité, ni direction. Une croissante avertit que la diversification échoue ; elle ne dit rien de la direction du marché. Ne surchargez pas un signal de risque avec des attentes directionnelles.
- Faim de données. Tout ce qui précède suppose des historiques longs, propres et synchronisés. Les tokens les plus récents et les plus intéressants de la crypto violent les trois.
Résumé
- La corrélation statique est un mensonge en crypto. Les corrélations forment des clusters, persistent et bondissent vers 1 dans les drawdowns — exactement quand la diversification est censée aider. Un seul d'échantillon moyenne un processus à changement de régime en un milieu dénué de sens.
- Le GARCH multivarié complet (VECH, BEKK) ne passe pas à l'échelle. Le nombre de paramètres croît en ; les deux sont confinés à une poignée d'actifs en pratique.
- DCC (Engle 2002) factorise le problème : , avec issu d'ajustements GARCH univariés indépendants (réutilisez les parties 1-2) et issu d'une récursion à deux paramètres. Il passe à l'échelle jusqu'à des dizaines d'actifs parce que seuls sont optimisés.
- La récursion , normalisée en , produit une matrice de corrélation définie positive valide à chaque étape, avec , .
archne fait pas de DCC. Ajustez les marges avecarch, puis implémentez l'estimateur NumPy/SciPy d'environ 60 lignes ici, ou utilisezmgarch(Python) ourmgarch(R, la référence).- Trois bénéfices concrets : un ratio de couverture dynamique pour le pairs trading ; une variance de portefeuille honnête et variable dans le temps pour l'allocation fondée sur le risque ; et la corrélation moyenne par paires comme signal de régime risk-off.
- La discipline est primordiale. Le correlation targeting fait fuir la moyenne sur échantillon complet, donc réestimez sur les seules données d'entraînement ; décalez chaque ratio de couverture ; filtrez en avant, ne lissez jamais. L'évaluation walk-forward n'est pas négociable.
- L'aDCC ajoute un terme d'asymétrie baissière et en vaut généralement la peine en crypto, où la corrélation de crash domine.
- La partie 4 utilise ces prévisions pour construire et backtester une stratégie en volatility targeting.
Références :
- Engle, R. (2002). Dynamic Conditional Correlation: A Simple Class of Multivariate Generalized Autoregressive Conditional Heteroskedasticity Models. Journal of Business & Economic Statistics, 20(3), 339-350. DOI
- Engle, R. & Sheppard, K. (2001). Theoretical and Empirical Properties of Dynamic Conditional Correlation Multivariate GARCH. NBER Working Paper 8554. DOI
- Bollerslev, T. (1990). Modelling the Coherence in Short-Run Nominal Exchange Rates: A Multivariate Generalized ARCH Model. Review of Economics and Statistics, 72(3), 498-505. DOI
- Cappiello, L., Engle, R., & Sheppard, K. (2006). Asymmetric Dynamics in the Correlations of Global Equity and Bond Returns. Journal of Financial Econometrics, 4(4), 537-572. DOI
- Engle, R. & Kroner, K. (1995). Multivariate Simultaneous Generalized ARCH. Econometric Theory, 11(1), 122-150. DOI
- Bollerslev, T., Engle, R., & Wooldridge, J. (1988). A Capital Asset Pricing Model with Time-Varying Covariances. Journal of Political Economy, 96(1), 116-131. DOI
- Galanos, A. (2022). rmgarch: Multivariate GARCH Models. R package. CRAN.
MarketMaker.cc Team
Recherche quantitative et stratégie