← Retour aux articles
July 12, 2026
5 min de lecture

DCC-GARCH : corrélations dynamiques pour les paires et le risque de portefeuille

#volatility
#GARCH
#DCC
#correlation
#portfolio
#pairs-trading
#crypto

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 HtH_t à 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 ρ^\hat{\rho} sur une fenêtre [tw,t][t-w, t] estime

ρ^ij=s(ri,srˉi)(rj,srˉj)s(ri,srˉi)2s(rj,srˉj)2\hat{\rho}_{ij} = \frac{\sum_{s} (r_{i,s} - \bar{r}_i)(r_{j,s} - \bar{r}_j)}{\sqrt{\sum_s (r_{i,s}-\bar{r}_i)^2}\sqrt{\sum_s (r_{j,s}-\bar{r}_j)^2}}

Cela porte trois hypothèses implicites, toutes fausses pour la crypto :

  1. Stationnarité de la dépendance. La fenêtre a un seul vrai ρ\rho. 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 ρ^\hat{\rho} les mélange en un milieu dénué de sens.
  2. Volatilité marginale constante. La corrélation de Pearson est une covariance normalisée. Si σi,t\sigma_{i,t} et σj,t\sigma_{j,t} 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.
  3. 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 ρ^\hat{\rho} 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 dd 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 rtRdr_t \in \mathbb{R}^d le vecteur des rendements de dd actifs à l'instant tt, avec une moyenne conditionnelle μt\mu_t (souvent juste une constante ou un petit terme AR) et un résidu ϵt=rtμt\epsilon_t = r_t - \mu_t. Nous supposons

ϵtFt1D(0,Ht)\epsilon_t \mid \mathcal{F}_{t-1} \sim \mathcal{D}(0, H_t)

HtH_t est la matrice de covariance conditionnelle d×dd \times d étant donné l'ensemble d'information Ft1\mathcal{F}_{t-1}, et D\mathcal{D} 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 HtH_t 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 HtH_t comme une fonction linéaire des carrés des résidus passés et des covariances passées :

vech(Ht)=c+Avech(ϵt1ϵt1)+Bvech(Ht1)\mathrm{vech}(H_t) = c + A\,\mathrm{vech}(\epsilon_{t-1}\epsilon_{t-1}') + B\,\mathrm{vech}(H_{t-1})

vech()\mathrm{vech}(\cdot) empile le triangle inférieur d'une matrice symétrique dans un vecteur de longueur d(d+1)/2d(d+1)/2. 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 d=3d=3. Pour dd actifs, AA et BB sont chacun d(d+1)2×d(d+1)2\frac{d(d+1)}{2} \times \frac{d(d+1)}{2}. À d=5d=5 cela fait deux matrices 15×1515\times 15, 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 :

Ht=CC+Aϵt1ϵt1A+BHt1BH_t = C'C + A'\,\epsilon_{t-1}\epsilon_{t-1}'\,A + B'\,H_{t-1}\,B

avec CC triangulaire supérieure. Parce que chaque terme est une forme quadratique, Ht0H_t \succ 0 automatiquement tant que CC0C'C \succ 0. BEKK est plus parcimonieux que VECH mais évolue toujours en O(d2)O(d^2) paramètres — les matrices AA et BB sont chacune d×dd \times d. Pour d=10d=10 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é à d4d \le 4, 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 HtH_t 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

Ht=DtRDtH_t = D_t\, R\, D_t

Dt=diag(σ1,t,,σd,t)D_t = \mathrm{diag}(\sigma_{1,t}, \ldots, \sigma_{d,t}) est la matrice diagonale des écarts-types conditionnels — un GARCH univarié par actif — et RR est une matrice de corrélation constante. C'est une énorme simplification : vous ajustez dd 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 RR est une matrice de corrélation valide et que tous les σi,t>0\sigma_{i,t} > 0.

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 :

Ht=DtRtDtH_t = D_t\, R_t\, D_t

Maintenant RtR_t 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 O(d2)O(d^2).

Étape 1 : GARCH univarié par actif

Pour chaque actif ii, 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 σi,t2\sigma_{i,t}^2 et donc Dt=diag(σ1,t,,σd,t)D_t = \mathrm{diag}(\sigma_{1,t}, \ldots, \sigma_{d,t}).

Des modèles ajustés nous extrayons les résidus standardisés :

zi,t=ϵi,tσi,tz_{i,t} = \frac{\epsilon_{i,t}}{\sigma_{i,t}}

Par construction chaque zi,tz_{i,t} a (approximativement) une variance conditionnelle unitaire. Empilez-les dans un vecteur zt=(z1,t,,zd,t)z_t = (z_{1,t}, \ldots, z_{d,t})'. 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 QtQ_t, une matrice d×dd \times d symétrique définie positive, avec une récursion de type GARCH pilotée par les produits externes des résidus standardisés :

Qt=(1ab)Qˉ+azt1zt1+bQt1Q_t = (1 - a - b)\,\bar{Q} + a\, z_{t-1} z_{t-1}' + b\, Q_{t-1}

où :

  • Qˉ=1Tt=1Tztzt\bar{Q} = \frac{1}{T}\sum_{t=1}^{T} z_t z_t' est la matrice de corrélation inconditionnelle des résidus standardisés (c'est le correlation targeting — plus de détails ci-dessous),
  • a0a \ge 0 régit avec quelle force le choc du jour zt1zt1z_{t-1}z_{t-1}' tire la corrélation,
  • b0b \ge 0 régit la persistance — quelle part du Qt1Q_{t-1} de la veille se reporte,
  • et la contrainte de retour à la moyenne est a+b<1a + b < 1 (avec a,b>0a, b > 0), directement analogue à α+β<1\alpha + \beta < 1 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 Qˉ\bar{Q}, un terme de choc et un terme de persistance. Parce qu'il s'agit d'une combinaison convexe de matrices semi-définies positives (Qˉ\bar{Q}, le produit externe de rang 1 et le Qt1Q_{t-1} précédent), QtQ_t reste défini positif tant que Qˉ0\bar{Q} \succ 0 et que les poids sont non négatifs. C'est ce qui nous procure gratuitement des matrices de covariance valides garanties.

QtQ_t est presque une matrice de corrélation mais pas tout à fait — sa diagonale n'est pas exactement 1. Nous la normalisons donc :

Rt=(diag(Qt))1/2Qt(diag(Qt))1/2R_t = \left(\mathrm{diag}(Q_t)\right)^{-1/2}\, Q_t\, \left(\mathrm{diag}(Q_t)\right)^{-1/2}

Élément par élément, la corrélation conditionnelle entre les actifs ii et jj est

ρij,t=qij,tqii,tqjj,t\rho_{ij,t} = \frac{q_{ij,t}}{\sqrt{q_{ii,t}\, q_{jj,t}}}

Ce RtR_t est une matrice de corrélation en bonne et due forme — diagonale unitaire, hors-diagonale dans [1,1][-1,1], définie positive — à chaque pas de temps, par construction. Réassemblez la covariance conditionnelle complète :

Ht=DtRtDt,hij,t=ρij,tσi,tσj,tH_t = D_t\, R_t\, D_t, \qquad h_{ij,t} = \rho_{ij,t}\,\sigma_{i,t}\,\sigma_{j,t}

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, aa et bb, que d=2d = 2 ou d=50d = 50. 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 à Qˉ\bar{Q}, qui est ciblé (injecté comme estimation d'échantillon) plutôt qu'optimisé.

La restriction scalaire et son coût

Les scalaires a,ba, b 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 A,BA, B ; 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 aa et bb 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 ϵtFt1N(0,Ht)\epsilon_t \mid \mathcal{F}_{t-1} \sim \mathcal{N}(0, H_t), la contribution à la log-vraisemblance à l'instant tt est

t=12(dlog(2π)+logHt+ϵtHt1ϵt)\ell_t = -\frac{1}{2}\left( d\log(2\pi) + \log|H_t| + \epsilon_t' H_t^{-1} \epsilon_t \right)

Substituez Ht=DtRtDtH_t = D_t R_t D_t. Alors Ht=Dt2Rt|H_t| = |D_t|^2 |R_t| et Ht1=Dt1Rt1Dt1H_t^{-1} = D_t^{-1} R_t^{-1} D_t^{-1}, et en utilisant zt=Dt1ϵtz_t = D_t^{-1}\epsilon_t :

t=12(dlog(2π)+2logDt+logRt+ztRt1zt)\ell_t = -\frac{1}{2}\left( d\log(2\pi) + 2\log|D_t| + \log|R_t| + z_t' R_t^{-1} z_t \right)

Maintenant scindez-la en ajoutant et retranchant ztztz_t'z_t :

t=12(dlog(2π)+2logDt+ϵtDt2ϵt)partie volatiliteˊ   tV  12(logRt+ztRt1ztztzt)partie correˊlation   tC\ell_t = \underbrace{-\frac{1}{2}\left( d\log(2\pi) + 2\log|D_t| + \epsilon_t' D_t^{-2}\epsilon_t \right)}_{\text{partie volatilité }\;\ell_t^{V}} \;\underbrace{-\frac{1}{2}\left( \log|R_t| + z_t' R_t^{-1} z_t - z_t' z_t \right)}_{\text{partie corrélation }\;\ell_t^{C}}

La partie volatilité tV\ell_t^V ne dépend que des paramètres GARCH univariés (via DtD_t) — la maximiser revient exactement à ajuster dd modèles GARCH univariés indépendants, ce que nous avons fait à l'étape 1. La partie corrélation tC\ell_t^C dépend de aa et bb (via RtR_t), étant donné les résidus standardisés de l'étape 1. Donc à l'étape 2 nous maximisons seulement

LC(a,b)=12t=1T(logRt+ztRt1zt)\mathcal{L}^C(a, b) = -\frac{1}{2}\sum_{t=1}^{T}\left( \log|R_t| + z_t' R_t^{-1} z_t \right)

(le terme ztztz_t'z_t ne dépend pas de a,ba, b, 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 t\ell_t (remplacer le noyau gaussien par la densité tt multivariée et ajouter un paramètre de degrés de liberté ν\nu). Nous gardons la quasi-vraisemblance gaussienne dans l'estimateur ci-dessous par souci de clarté et notons où ν\nu 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 :

  1. Codez votre propre DCC par-dessus arch — ajustez les modèles univariés avec arch, extrayez les résidus standardisés, implémentez la récursion QQ 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.
  2. 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.
  3. Le rmgarch de R (Alexios Galanos) — l'implémentation de référence. dccspec / dccfit prennent 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 via rpy2 si 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 zi,t2z_{i,t}^2), 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 :

  • a=0.029a = 0.029 est petit — la matrice de corrélation ne bondit pas sous le choc d'une seule journée. Chaque jour pousse RtR_t vers le produit externe zt1zt1z_{t-1}z_{t-1}' de seulement ~3 %.
  • b=0.940b = 0.940 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 Qˉ\bar{Q}. Cela correspond à l'expérience vécue des drawdowns crypto : les corrélations ne rebondissent pas dès que le prix se stabilise.
  • a+b=0.969<1a + b = 0.969 < 1 confirme le retour à la moyenne. Le processus de corrélation a un niveau de long terme stationnaire (Qˉ\bar{Q}) vers lequel il revient, avec une demi-vie d'environ log(0.5)/log(a+b)22\log(0.5)/\log(a+b) \approx 22 jours. Si vous estimez un jour a+ba + b 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 aa et bb 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 aa (les corrélations réagissent aux chocs mais pas violemment), grand bb (les corrélations sont persistantes). Si votre optimiseur dérive vers a+b1a+b \to 1, 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 RtR_t contre ztz_t puis mettons à jour QQ avec ztztz_t z_t' pour l'étape suivante. Cela garde RtR_t fonction de l'information jusqu'à t1t-1 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 Qˉ\bar{Q} comme corrélation d'échantillon plutôt que de l'estimer. C'est ce qui rend l'optimisation bidimensionnelle. Le coût est que Qˉ\bar{Q} 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 a,ba, b fixés, exécutez la récursion une fois de plus, cette fois en stockant toute la trajectoire RtR_t (et HtH_t) 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 Ht+1H_{t+1} 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 1002=10,000100^2 = 10{,}000) 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 β\beta. 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

βt=Covt(rETH,rBTC)Vart(rBTC)=hETH,BTC,tσBTC,t2=ρETH,BTC,tσETH,tσBTC,t\beta_t = \frac{\mathrm{Cov}_t(r_{\text{ETH}}, r_{\text{BTC}})}{\mathrm{Var}_t(r_{\text{BTC}})} = \frac{h_{\text{ETH,BTC},t}}{\sigma_{\text{BTC},t}^2} = \rho_{\text{ETH,BTC},t}\,\frac{\sigma_{\text{ETH},t}}{\sigma_{\text{BTC},t}}

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 ρt\rho_t change (les actifs se couplent ou se découplent), et le ratio de volatilité σETH,t/σBTC,t\sigma_{\text{ETH},t}/\sigma_{\text{BTC},t} 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 ρt\rho_t 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 βt\beta_t en direct. Premièrement, décalez-le — tradez sur βt1\beta_{t-1}, jamais sur le βt\beta_t 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 βt\beta_t (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 ww, la variance conditionnelle est

σp,t2=wHtw=ijwiwjρij,tσi,tσj,t\sigma_{p,t}^2 = w'\, H_t\, w = \sum_{i}\sum_{j} w_i w_j\, \rho_{ij,t}\,\sigma_{i,t}\,\sigma_{j,t}

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 σi,t\sigma_{i,t} et les termes ρij,t\rho_{ij,t} 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 σp,t\sigma_{p,t} 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 HtH_t (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 σ\sigma^{*}, et mettez à l'échelle l'exposition brute par σ/σp,t\sigma^{*} / \sigma_{p,t} à 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 :

ρˉt=2d(d1)i<jρij,t\bar{\rho}_t = \frac{2}{d(d-1)}\sum_{i < j} \rho_{ij,t}

Quand ρˉt\bar{\rho}_t 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 Qˉ\bar{Q} est une corrélation d'échantillon d×dd \times d, et les matrices de corrélation d'échantillon deviennent mal conditionnées à mesure que dd approche le nombre d'observations. Avec 4 actifs et 1000 jours, tout va bien. Avec 60 actifs et 400 jours, Qˉ\bar{Q} est presque singulière, son inverse dans la vraisemblance explose, et RtR_t 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 Qˉ\bar{Q} 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 Qˉ\bar{Q} rend l'estimation traitable mais inscrit la corrélation inconditionnelle sur échantillon complet dans chaque RtR_t. Dans un backtest strict c'est une fuite de look-ahead : votre matrice de corrélation au jour tt « connaît » la corrélation moyenne de tout l'échantillon, y compris le futur. Pour une évaluation honnête vous devez réestimer Qˉ\bar{Q} 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 a,ba, b chaque jour — ce sont des paramètres stables. Une cadence de production sensée :

  • Réestimez a,ba, b et les paramètres GARCH univariés chaque semaine ou chaque mois.
  • Exécutez le filtre (mise à jour de QtQ_t, σi,t\sigma_{i,t}) à chaque période avec les paramètres gelés pour obtenir des RtR_t et HtH_t frais. Le filtrage est bon marché ; l'ajustement ne l'est pas.
  • Toujours prévoir, jamais lisser. Utilisez RtR_t construit à partir de l'information jusqu'à t1t-1 pour trader à tt. 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 — Qˉ\bar{Q} sur échantillon complet, βt\beta_t 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 zt=min(zt,0)z_t^- = \min(z_t, 0) :

Qt=(QˉaQˉbQˉgNˉ)+azt1zt1+gzt1zt1+bQt1Q_t = (\bar{Q} - a\bar{Q} - b\bar{Q} - g\bar{N}) + a\, z_{t-1} z_{t-1}' + g\, z_{t-1}^- z_{t-1}^{-\prime} + b\, Q_{t-1}

Nˉ=1Ttztzt\bar{N} = \frac{1}{T}\sum_t z_t^- z_t^{-\prime} et g0g \ge 0 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 ztz_t^- à 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'à ρ\rho variable dans le temps ? PD garantie ? Dépendance de queue ?
Covariance d'échantillon / glissante 0 (longueur de fenêtre) tout dd grossièrement (retardée, bruitée) non (nécessite rapiéçage) non
EWMA (RiskMetrics) 1 (λ\lambda) tout dd oui (décroissance unique) oui non
CCC-GARCH dd marges + Qˉ\bar{Q} dizaines non (RR constant) oui non
DCC-GARCH dd marges + 2 dizaines oui oui non
aDCC-GARCH dd marges + 3 dizaines oui, asymétrique oui partielle
BEKK O(d2)O(d^2) 4\le 4 oui (riche) oui non
VECH O(d4)O(d^4) 3\le 3 oui (la plus riche) pénible non
GARCH-copule dd 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 RR et DCC le laisse bouger. Les deux paramètres supplémentaires (a,ba, b) 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 aa et un seul bb 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 ρˉt\bar{\rho}_t 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 ρ^\hat{\rho} 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 O(d2)O(d^2) ; les deux sont confinés à une poignée d'actifs en pratique.
  • DCC (Engle 2002) factorise le problème : Ht=DtRtDtH_t = D_t R_t D_t, avec DtD_t issu d'ajustements GARCH univariés indépendants (réutilisez les parties 1-2) et RtR_t issu d'une récursion à deux paramètres. Il passe à l'échelle jusqu'à des dizaines d'actifs parce que seuls a,ba, b sont optimisés.
  • La récursion Qt=(1ab)Qˉ+azt1zt1+bQt1Q_t = (1-a-b)\bar{Q} + a\,z_{t-1}z_{t-1}' + b\,Q_{t-1}, normalisée en RtR_t, produit une matrice de corrélation définie positive valide à chaque étape, avec a,b>0a,b>0, a+b<1a+b<1.
  • arch ne fait pas de DCC. Ajustez les marges avec arch, puis implémentez l'estimateur NumPy/SciPy d'environ 60 lignes ici, ou utilisez mgarch (Python) ou rmgarch (R, la référence).
  • Trois bénéfices concrets : un ratio de couverture dynamique βt=ρtσETH,t/σBTC,t\beta_t = \rho_t\,\sigma_{\text{ETH},t}/\sigma_{\text{BTC},t} pour le pairs trading ; une variance de portefeuille honnête et variable dans le temps wHtww'H_t w 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 Qˉ\bar{Q} 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.
blog.disclaimer

MarketMaker.cc Team

Recherche quantitative et stratégie

Discuter sur Telegram
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.