DCC-GARCH: Dynamische correlaties voor pairs en portefeuillerisico
Vraag de meeste crypto-desks naar de correlatie tussen BTC en ETH en je krijgt een enkel getal — 0,8, misschien 0,75 — berekend over een venster waarvan niemand zich herinnert het gekozen te hebben. Dat getal is een leugen, of op zijn minst een gevaarlijke versimpeling. Steekproefcorrelatie is een gemiddelde over een periode waarin de werkelijke afhankelijkheidsstructuur voortdurend in beweging was. In rustige markten drijven BTC en ETH ver genoeg uit elkaar om een marktneutraal paar aantrekkelijk te laten lijken. In een liquidatiecascade koppelen ze aan elkaar en aan al het andere, en de diversificatie waarvoor je betaalde verdampt op precies het moment dat je haar nodig hebt.
Dit is geen subtiel effect. Neem eender welke drawdown uit 2022 — de LUNA-ineenstorting in mei, de 3AC-ontrafeling in juni, de FTX-implosie in november — en je ziet de gemiddelde paarsgewijze correlatie over de top-20 tokens binnen enkele dagen oplopen van de range 0,4-0,6 richting 0,9+. Correlatie is geen constante die af en toe slecht wordt geschat; het is een tijdreeks met haar eigen dynamiek, haar eigen clustering en haar eigen regimes. Haar als een scalair behandelen is het multivariate equivalent van uitgaan van constante volatiliteit — een fout die we in Deel 1 van deze serie al voor een enkel activum hebben ontkracht.
Dit artikel is Deel 3 van een vierdelige volatiliteitsserie. Deel 1 bouwde univariate GARCH(1,1) met de arch-bibliotheek en liet zien hoe volatiliteit clustert en terugkeert naar het gemiddelde. Deel 2 voegde asymmetrie (GJR-GARCH, EGARCH) en Student-t-innovaties toe om het leverage-effect en de dikke staarten te vangen. Hier gaan we multivariaat: we modelleren de volledige conditionele covariantiematrix terwijl deze evolueert, met behulp van Engle's Dynamic Conditional Correlation (DCC) model. Dat geeft ons twee dingen die een scalaire correlatie nooit kan — een dynamische hedgeratio voor pairs trading, en een eerlijke, tijdsvariërende portefeuillevariantie voor risicogebaseerde allocatie. Deel 4 sluit de serie af met een op volatiliteit gerichte backtest die de univariate en multivariate voorspellingen samenbindt tot een positiegroottebepaling.
We gaan ervan uit dat je Deel 1 en 2 hebt gelezen, dus we leiden univariate GARCH niet opnieuw af. Als je het gezamenlijke staartgedrag wilt — de kans dat twee activa samen hun 1%-kwantiel doorbreken — dan is dat een copula-vraag, en die behandelen we in Copula-modellen voor gezamenlijk risico. DCC en copula's zijn complementair: de copula geeft je een statische-maar-flexibele staartafhankelijkheidsstructuur, terwijl DCC je een hanteerbare tijdreeks van de volledige correlatiematrix geeft. Dit artikel gaat over die laatste.
Waarom statische correlatie faalt in crypto
Voor de machinerie, wees precies over wat er faalt. Een enkele steekproefcorrelatie over een venster schat
Dit draagt drie impliciete aannames, allemaal onwaar voor crypto:
- Stationariteit van afhankelijkheid. Het venster heeft één ware . In werkelijkheid heeft afhankelijkheid regimes — een rustig-marktregime rond 0,5 en een stressregime rond 0,95 — en mengt ze tot een betekenisloos midden.
- Constante marginale volatiliteit. Pearson-correlatie is een genormaliseerde covariantie. Als en zelf bewegen (dat doen ze — dat is de hele premisse van Deel 1 en 2), dan produceert zelfs een constante covariantie een tijdsvariërende correlatie, en vice versa. Je kunt de twee niet scheiden zonder een volatiliteitsmodel eronder.
- Symmetrie over marktrichting. Correlatie stijgt in drawdowns meer dan in rally's. Dit is de multivariate neef van het leverage-effect. Een voortschrijdend venster kan dit niet uitdrukken zonder zo kort te worden dat het pure ruis is.
De voortschrijdend-vensteroplossing — herbereken over de laatste 30 of 60 dagen — ruilt het ene probleem voor het andere. Korte vensters zijn responsief maar rumoerig en lopen achter op de werkelijke breuk; lange vensters zijn stabiel maar verouderd. Erger nog, een voortschrijdende correlatiematrix over activa blijft niet gegarandeerd positief semidefiniet zodra je begint met krimpen of oplappen, wat elke stroomafwaartse optimizer breekt. We willen een model dat (a) wordt aangedreven door een deugdelijk volatiliteitsproces per activum, (b) per constructie bij elke stap een geldige correlatiematrix produceert, en (c) parameters heeft die we via maximum likelihood kunnen schatten in plaats van door een vensterlengte uit de hoge hoed te toveren. Dat model is DCC-GARCH.
Het multivariate probleem: de conditionele covariantiematrix
Laat de vector van rendementen zijn voor activa op tijdstip , met conditioneel gemiddelde (vaak gewoon een constante of een kleine AR-term) en residu . We nemen aan
waarbij de conditionele covariantiematrix is gegeven de informatieset , en een conditionele verdeling is (Gaussisch of, beter voor crypto, multivariaat Student-t). Alles in multivariate volatiliteitsmodellering is een ander antwoord op één vraag: hoe parametriseer je de dynamiek van zodat deze bij elke stap symmetrisch positief definiet blijft zonder een explosie van parameters?
Twee klassieke antwoorden laten zien waarom het probleem lastig is.
VECH
Het VECH-model (Bollerslev, Engle, Wooldridge 1988) schrijft de half-vectorisatie van als een lineaire functie van voorbije gekwadrateerde residuen en voorbije covarianties:
waarbij de onderste driehoek van een symmetrische matrix stapelt in een vector van lengte . Dit is maximaal algemeen — elke variantie en covariantie hangt af van elke voorbije variantie en covariantie — en maximaal nutteloos voorbij . Voor activa zijn en elk . Bij zijn dat twee matrices, ruwweg 450 parameters, plus positief-definietheidsbeperkingen die pijnlijk zijn om zelfs maar uit te drukken. Het likelihood-oppervlak is een moeras.
BEKK
Het BEKK-model (Engle & Kroner 1995) garandeert positieve definietheid per constructie met behulp van een kwadratische vorm:
met bovendriehoekig. Omdat elke term een kwadratische vorm is, geldt automatisch zolang . BEKK is spaarzamer dan VECH maar schaalt nog steeds als parameters — de - en -matrices zijn elk . Voor schat je in de orde van 200+ parameters gezamenlijk via MLE, op rumoerige dagelijkse cryptodata, zonder garantie dat de optimizer naar iets zinvols convergeert. In de praktijk blijft volledig BEKK beperkt tot , en zelfs dan gebruiken mensen de "diagonale" of "scalaire" beperkingen die het merendeel van de kruisdynamiek weggooien.
Dit is de vloek van de dimensionaliteit voor multivariate GARCH: het aantal parameters groeit kwadratisch, maar de hoeveelheid informatie in de data niet. Je bent je vrijheidsgraden kwijt lang voordat je zonder activa zit waar je om geeft. Elk cryptoboek met 10-30 tokens is volledig buiten bereik voor VECH of BEKK.
De uitweg, te danken aan Engle, is om te stoppen met proberen direct te modelleren en het in plaats daarvan te factoriseren in stukken die we al goedkoop weten te schatten.
Engle's DCC (2002): de tweestapsdecompositie
Bollerslev's Constant Conditional Correlation (CCC) model (1990) was de eerste spaarzame factorisatie. Het schrijft
waarbij de diagonaalmatrix van conditionele standaarddeviaties is — één univariate GARCH per activum — en een constante correlatiematrix is. Dit is een enorme versimpeling: je fit onafhankelijke univariate GARCH-modellen en schat vervolgens een enkele steekproefcorrelatiematrix van de gestandaardiseerde residuen. Positieve definietheid is automatisch zolang een geldige correlatiematrix is en alle .
Het probleem van CCC zit precies in de naam — de correlatie is constant, wat exact de aanname is waarmee we dit artikel openden om haar te verwerpen. Engle's Dynamic Conditional Correlation (2002) behoudt CCC's prachtige factorisatie maar laat de correlatiematrix ademen:
Nu is tijdsvariërend. De genialiteit is dat de volatiliteiten en de correlaties in twee afzonderlijke stappen worden geschat, zodat we nooit met de volledige gezamenlijke optimalisatie worden geconfronteerd.
Stap 1: Univariate GARCH per activum
Fit voor elk activum een univariate GARCH-model precies zoals in Deel 1 en 2 — GARCH(1,1), GJR-GARCH of EGARCH met Student-t-innovaties, wat het beste bij die reeks paste. Dit geeft conditionele varianties en dus .
Uit de gefitte modellen halen we de gestandaardiseerde residuen:
Per constructie heeft elke (bij benadering) eenheidsconditionele variantie. Stapel ze in een vector . Deze gestandaardiseerde residuen zijn het ruwe materiaal voor de correlatiestap — hun individuele volatiliteitsdynamiek is eruit gestript, dus wat er aan comovement overblijft is pure afhankelijkheid, geen volatiliteitsartefact. (Dit is dezelfde PIT-achtige logica die het copula-artikel gebruikt voordat de marges worden gefit; hier stoppen we bij standaardisatie in plaats van helemaal door te gaan naar uniformen.)
Stap 2: De DCC-correlatierecursie
We modelleren een hulpproces , een symmetrische positief-definiete matrix, met een GARCH-achtige recursie aangedreven door de uitproducten van gestandaardiseerde residuen:
waarbij:
- de onconditionele correlatiematrix van de gestandaardiseerde residuen is (dit is correlation targeting — meer hierover hieronder),
- bepaalt hoe sterk de schok van vandaag de correlatie trekt,
- bepaalt persistentie — hoeveel van de van gisteren wordt meegedragen,
- en de mean-reversion-beperking is (met ), direct analoog aan in univariate GARCH.
Merk op dat de structuur identiek is aan een scalaire GARCH(1,1)-recursie, maar op matrices: een langetermijnanker , een schokterm en een persistentieterm. Omdat het een convexe combinatie is van positief-semidefiniete matrices (, het rang-1 uitproduct en het vorige ), blijft positief definiet zolang en de gewichten niet-negatief zijn. Dit is wat ons gratis gegarandeerd geldige covariantiematrices oplevert.
is bijna een correlatiematrix maar niet helemaal — zijn diagonaal is niet exact 1. Dus normaliseren we het:
Elementsgewijs is de conditionele correlatie tussen activa en
Deze is een deugdelijke correlatiematrix — eenheidsdiagonaal, off-diagonalen in , positief definiet — bij elke afzonderlijke tijdstap, per constructie. Stel de volledige conditionele covariantie weer samen:
Die laatste elementsgewijze vorm is degene die je voortdurend zult gebruiken: de conditionele covariantie van twee activa is hun dynamische correlatie maal elk van hun dynamische volatiliteiten. Elk ingrediënt aan de rechterkant is tijdsvariërend en komt uit een model dat je kunt schatten.
Het hele model heeft slechts twee correlatieparameters, en , ongeacht of of . De volatiliteitszijde schaalt lineair (één univariate GARCH per activum, elk met ~4-5 parameters, allemaal onafhankelijk gefit en gênant parallel). Dit is waarom DCC schaalt waar BEKK en VECH dat niet kunnen: de dimensionaliteitsvloek is beperkt tot , die getarget wordt (ingeplugd als een steekproefschatting) in plaats van geoptimaliseerd.
De scalaire beperking en haar kosten
De scalairen betekenen dat elk paar activa dezelfde correlatiedynamiek deelt — dezelfde aanpassingssnelheid en dezelfde persistentie. De BTC-ETH-correlatie en de DOGE-SHIB-correlatie bewegen op hetzelfde ritme, ook al verschilt hun economie. Dit is de prijs van hanteerbaarheid, en het is meestal een aanvaardbare prijs. Generalisaties (Generalized DCC met matrix ; de Cappiello-Engle-Sheppard asymmetrische DCC) versoepelen dit ten koste van parameters en schattingsstabiliteit. We noemen aDCC hieronder.
De DCC quasi-log-likelihood
Om en te schatten hebben we de likelihood nodig. Engle's belangrijkste resultaat is dat de Gaussische log-likelihood scheidt in een volatiliteitsdeel en een correlatiedeel, en dat is wat de tweestapsschatter rechtvaardigt. Uitgaande van is de log-likelihood-bijdrage op tijdstip
Substitueer . Dan geldt en , en met :
Splits het nu op door toe te voegen en af te trekken:
Het volatiliteitsdeel hangt alleen af van de univariate GARCH-parameters (via ) — het maximaliseren ervan is exact het fitten van onafhankelijke univariate GARCH-modellen, wat we in Stap 1 deden. Het correlatiedeel hangt af van en (via ), gegeven de gestandaardiseerde residuen uit Stap 1. Dus in Stap 2 maximaliseren we alleen
(de term hangt niet af van , dus die laten we vallen). Dit is een tweeparameter-optimalisatie ongeacht hoeveel activa — dat is het hele punt. Het wordt een quasi-likelihood genoemd omdat de tweestapsschatter consistent maar niet volledig efficiënt is; de standaardfouten hebben een correctie nodig (Engle & Sheppard 2001), maar voor signaalgeneratie zijn de puntschattingen wat telt.
Voor crypto onderschatten Gaussische innovaties het staartrisico. Het inwisselen van de multivariate Student-t-likelihood is een drop-in wijziging aan (vervang de Gaussische kernel door de multivariaat--dichtheid en voeg een vrijheidsgradenparameter toe). We houden de Gaussische quasi-likelihood in de schatter hieronder voor de duidelijkheid en geven aan waar binnenkomt — de standaardisatie uit Deel 1-2 gebruikte al t-innovaties op de marges, wat het merendeel van het staartvoordeel vangt.
Python-implementatie
Een botte maar belangrijke feit: de arch-bibliotheek doet geen multivariate GARCH of DCC. arch is een superieure univariate engine (we leunen er precies daarvoor op), maar er zit geen dcc_model in. Je praktische opties zijn:
- Bouw je eigen DCC bovenop
arch— fit univariate modellen metarch, extraheer gestandaardiseerde residuen, implementeer de -recursie en de correlatie-quasi-likelihood in NumPy/SciPy, en optimaliseer de twee scalairen. Dit is wat we hieronder doen. Het is ongeveer 60 regels en volledig transparant. - Het
mgarchPyPI-pakket — een lichtgewicht pure-Python DCC-GARCH-implementatie. Handig voor een snelle fit, minder flexibel als je GJR-marges of t-innovaties precies bedraad wilt hebben. - R's
rmgarch(Alexios Galanos) — de referentie-implementatie.dccspec/dccfitondersteunen DCC, aDCC, GARCH-copula, Student-t en deugdelijke standaardfouten. Als je serieus multivariaat volatiliteitsonderzoek doet, isrmgarch(indien nodig aangeroepen vanuit Python viarpy2) de gouden standaard.
We bouwen optie 1 omdat die elk bewegend onderdeel expliciet maakt en de univariate vaardigheden uit Deel 1-2 hergebruikt.
Stap 1: Fit univariate GARCH-marges met 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())
Een snelle sanity check op de gestandaardiseerde residuen is van belang. Als een kolom een standaarddeviatie ver van 1 heeft, of zware resterende autocorrelatie in zijn kwadraat (Ljung-Box op ), dan is de univariate marge verkeerd gespecificeerd en zal de DCC-stap die fout overerven. Fix eerst de marge — daar was Deel 2 voor.
Stap 2: De DCC-recursie en quasi-log-likelihood
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)
Dit uitvoeren op een BTC/ETH/SOL/BNB-boek over een paar jaar dagelijkse data produceert output in de volgende vorm (onderstaande getallen zijn illustratief, niet uit een specifiek gedateerd experiment — draai het op je eigen data):
DCC estimates: a = 0.0287, b = 0.9401, a+b = 0.9688
log-likelihood: -3812.44
Hoe je het leest:
- is klein — de correlatiematrix schokt niet op de schok van een enkele dag. Elke dag duwt slechts ~3% richting het uitproduct .
- is groot — correlaties zijn zeer persistent. Zodra het boek in een stressgebeurtenis aan elkaar koppelt, blijft het een tijdje gekoppeld en keert langzaam terug richting . Dit komt overeen met de doorleefde ervaring van cryptodrawdowns: correlaties springen niet terug op het moment dat de prijs stabiliseert.
- bevestigt mean reversion. Het correlatieproces heeft een stationair langetermijnniveau () waar het naar terugkeert, met een halfwaardetijd van ruwweg dagen. Als je ooit in wezen gelijk aan 1 schat, is het correlatieproces geïntegreerd — het heeft geen langetermijnanker, meestal een symptoom van een structurele breuk binnen je steekproef die het model absorbeert als oneindige persistentie.
De bijna-eenheidspersistentie en de kleine schokbelasting is de canonieke DCC-vingerafdruk over activaklassen heen, en crypto is geen uitzondering. Het is ook waarom een 30-daagse voortschrijdende correlatie zo'n slechte vervanging is: een voortschrijdend venster gaat impliciet uit van een en die helemaal niet overeenkomen met deze vervalstructuur.
Een paar implementatienotities die echte debugtijd besparen:
- Initialisatie. Beginnen bij
[0.03, 0.94]weerspiegelt de typische cryptoschatting: kleine (correlaties reageren op schokken maar niet heftig), grote (correlaties zijn persistent). Als je optimizer afdwaalt naar is het correlatieproces geïntegreerd — meestal een teken van een structurele breuk in de steekproef (een regimeverandering die het model met moeite als persistentie probeert te fitten). - Timingconventie. Binnen de lus scoren we tegen en werken daarna bij met voor de volgende stap. Dit houdt een functie van informatie tot en met alleen — geen look-ahead. Deze off-by-one verkeerd krijgen is de allergrootste veelvoorkomende DCC-bug, en het inflateert de in-sample fit stilletjes.
- Correlation targeting. We pluggen in als de steekproefcorrelatie in plaats van het te schatten. Dit is wat de optimalisatie tweedimensionaal maakt. De kost is dat de volledige steekproef gebruikt, dus in een strikte walk-forward moet je het opnieuw schatten op alleen het trainingsvenster (zie hieronder).
Stap 3: Reconstrueer de correlatie- en covariantiepaden
Zodra vastliggen, draai de recursie nog een keer, dit keer met opslag van het volledige - (en -) pad zodat stroomafwaartse strategieën het kunnen gebruiken.
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))
De rho_btc_eth-reeks is de opbrengst van de hele oefening: in plaats van één getal heb je nu een dagelijkse correlatie die je kunt plotten, drempelen of in een strategie voeren. Op echte cryptodata zul je haar doorgaans zien variëren van ruwweg 0,5 in rustige stukken tot boven 0,9 tijdens stress — precies de spreiding die een enkele steekproefcorrelatie wegmiddelt.
One-step-ahead-voorspelling
Voor live trading heb je de volgende-periode nodig uit informatie die nu beschikbaar is. De volatiliteitszijde komt uit de one-step-voorspelling van elk arch-model; de correlatiezijde is nog één draai van de recursie:
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)
Onthoud dat alles in geschaalde (×100) eenheden is omdat we arch fitten op series * 100. Deel volatiliteiten door 100 (en covarianties door ) om terug te keren naar ruwe-rendementseenheden voordat je een strategie voedt. De schaling recht houden is vervelend maar een frequente bron van stille bugs.
Toepassing 1: Een dynamische hedgeratio voor pairs trading
Het klassieke marktneutrale paar — long het ene activum, short een beta-gewogen hoeveelheid van een ander — leeft of sterft op de hedgeratio . Schat het via statische OLS over een trainingsvenster en je erft precies het verouderde-correlatieprobleem waar dit hele artikel over gaat: de hedge die vorig kwartaal de marktblootstelling neutraliseerde, is dit kwartaal verkeerd.
DCC geeft je de hedgeratio als een tijdreeks. De minimum-variantie-hedge van ETH-blootstelling met behulp van BTC is de conditionele regressiecoëfficiënt
Elke term aan de rechterkant is een DCC-output. De hedgeratio beweegt om twee verschillende redenen, en DCC scheidt ze netjes: de correlatie verandert (de activa koppelen of ontkoppelen), en de volatiliteitsverhouding verandert (het ene activum wordt relatief volatieler). Een voortschrijdende-OLS-beta smeert beide effecten samen met een vertraging; DCC attribueert ze.
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()
Voed spread in welke pairs-engine je ook draait. De dynamische hedge creëert op zichzelf geen edge — het maakt de spread die je verhandelt door de tijd heen echt marktneutraal, zodat je mean-reversion-signaal niet wordt vervuild door driftende directionele blootstelling. Als je pairs-strategieën bouwt, past dit direct in de raamwerken in Statistical Arbitrage & Pairs Trading in Crypto en de distance approach to pairs, waarbij hun vaste hedgeratio wordt vervangen. De correlatiereeks zelf is ook een schonere input voor een op correlatie gebaseerd paarsignaal dan welk voortschrijdend venster ook — je krijgt een gladgestreken, modelconsistente in plaats van een rumoerige vensterschatting.
Twee waarschuwingen die specifiek zijn voor het live gebruiken van . Ten eerste, vertraag het — verhandel op , nooit op de gelijktijdige , anders gluur je vooruit. Ten tweede, een hedgeratio die elke dag heen en weer zwiept genereert omzet en kosten; in crypto's 24/7-markt met funding costs op het short-been kan een overreactieve hedge meer bloeden dan de drift die het corrigeert. Strijk glad (een EWMA, of herbalanceer de hedge alleen wanneer deze voorbij een band beweegt) en dimensioneer het geheel verstandig — positiegroottebepaling op basis van een rumoerig signaal is een eigen discipline, behandeld in Kelly-criterium-sizing.
Toepassing 2: Tijdsvariërende portefeuillevariantie
Voor een portefeuille met gewichtsvector is de conditionele variantie
Met een statische covariantiematrix — de Markowitz-standaard — is dit getal een constante die je eenmaal berekende en waarvan je doet alsof deze nog steeds waar is. Dat is niet zo. Portefeuillerisico ademt met de markt, en het ademt het hardst precies wanneer correlaties omhoogschieten, want in een drawdown stijgen zowel de -termen als de -termen samen en vermenigvuldigen ze. Een portefeuille die er in rustige markten uitzag als 40% geannualiseerde vol kan in een stressweek op 80%+ draaien, en een statische covariantiematrix zal je vertellen dat er niets veranderd is.
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())
Deze tijdsvariërende is de eerlijke input die risicogebaseerde allocatie nodig heeft. Mean-variance-optimalisatie (Markowitz voor crypto) met een statische steekproefcovariantie optimaliseert tegen een verzinsel; het voeden met (of zijn korte-horizonvoorspelling) maakt de efficiënte grens zelf tijdsvariërend en dwingt de optimizer om risico af te bouwen in stijgende-correlatieregimes in plaats van erna. Risk-parity- en hiërarchische benaderingen — de HRP + CVaR-pijplijn — zijn nog gevoeliger voor de covariantie-input, omdat de hele allocatie een functie is van de risicomatrix. En als je allocators onderling vergelijkt, zoals in portefeuille-optimalisatiealgoritmen vergeleken, is of ze statische of dynamische covariantie verbruiken vaak een grotere aanjager van het gerealiseerde risico dan de keuze van algoritme.
De directe toepassing is volatility targeting van de hele portefeuille: kies een doel-geannualiseerde vol , en schaal bruto blootstelling met elke periode zodat het gerealiseerde risico ongeveer constant blijft in plaats van op te blazen in crises. Dat sluit de lus met Deel 4, dat precies deze regel bouwt en backtest.
Toepassing 3: Correlatie als regimesignaal
Voorbij hedging en sizing draagt de correlatiematrix een macrosignaal. De enkele nuttigste scalair die je kunt extraheren is de gemiddelde paarsgewijze correlatie:
Wanneer over het boek stijgt, betreedt de markt een risk-off-regime — idiosyncratische verhalen doen er niet meer toe en alles verhandelt als één macrobeta. Dit is de kwantitatieve vingerafdruk van "correlaties gaan naar 1 in een crisis." Het neigt drawdowns voor te lopen of ermee samen te vallen, wat het een bruikbare regime-indicator maakt in plaats van een achterlopende postmortem.
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)
Je kunt risk_off gebruiken als een op zichzelf staande throttle (verminder bruto blootstelling, verbreed stops, sta mean-reversion-strategieën stil die worden overreden wanneer alles samen trendt) of als een kenmerk in een formeler regimemodel. Het past van nature bij de hidden-Markov-benadering in regimedetectie met HMM's: gemiddelde DCC-correlatie is een van de meer informatieve observatievariabelen die je een HMM kunt aanreiken, omdat het vooruitkijkend is over systemische stress op een manier waarop achterlopende rendementen dat niet zijn. De eerlijke kanttekening: stijgende correlatie vertelt je dat diversificatie faalt, niet welke kant de markt op gaat. Het is een risicosignaal, geen alfasignaal, en moet als zodanig gedimensioneerd worden — zie de asymmetrie van verliezen en winsten voor waarom het behandelen van een risicoregime als een directionele weddenschap slecht afloopt.
Praktische overwegingen
Schattingsstabiliteit en aantal activa
DCC schaalt veel beter dan BEKK, maar "schaalt" is niet "gratis." De correlation-targeting-matrix is een steekproefcorrelatie, en steekproefcorrelatiematrices worden slecht geconditioneerd naarmate het aantal observaties nadert. Met 4 activa en 1000 dagen zit je goed. Met 60 activa en 400 dagen is bijna singulier, explodeert zijn inverse in de likelihood, en kan door numerieke ruis niet-PD afdwalen. Mitigaties, ruwweg in volgorde van hoe vaak je ze nodig hebt:
- Krimp richting een gestructureerd doel (Ledoit-Wolf, of richting de identiteit / een constante-correlatiematrix) voordat je de recursie draait. Dit is de enkele fix met de meeste hefboomwerking voor grote boeken.
- Groepeer activa in een handvol sectoren (majors, L1's, DeFi, memes), modelleer binnen en over op sectorniveau, of draai DCC op principale-componentfactoren in plaats van ruwe activa.
- Verkies meer data boven meer activa. DCC heeft een onverzadigbare honger naar een lange, schone, gelijktijdige historie — precies wat jonge tokens niet hebben.
Realistisch gezien, houd directe DCC tot hooguit een paar dozijn activa. Voor een groot universum is DCC op factorrendementen plus idiosyncratische residuen de standaardomweg.
Correlation targeting is een shortcut met een kost
Het targeten van maakt de schatting hanteerbaar maar bakt de volledige-steekproef onconditionele correlatie in elke . In een strikte backtest is dit een look-ahead-lek: je dag--correlatiematrix "kent" de gemiddelde correlatie van de hele steekproef, inclusief de toekomst. Voor eerlijke evaluatie moet je opnieuw schatten op alleen het trainingsvenster en het out-of-sample vasthouden, of het naar voren rollen. Dit is dezelfde discipline die het hele walk-forward-optimalisatie-raamwerk afdwingt, en het is makkelijk om per ongeluk te overtreden met een handige np.cov(Z) over de volledige array — zoals onze onderwijscode hierboven doet. Fix het voordat je ook maar één P&L-getal vertrouwt.
Refit-cadans en look-ahead-discipline
Je hoeft niet elke dag opnieuw te optimaliseren — het zijn stabiele parameters. Een verstandige productiecadans:
- Herschat en de univariate GARCH-parameters wekelijks of maandelijks.
- Draai de filter (update , ) elke periode met de bevroren parameters om verse en te krijgen. Filteren is goedkoop; fitten niet.
- Voorspel altijd, strijk nooit glad. Gebruik gebouwd uit informatie tot en met om op te verhandelen. De twee-pass-structuur (fit op een venster, filter dan vooruit) is wat je eerlijk houdt.
De kloof tussen een DCC-backtest en live prestaties is bijna altijd een look-ahead-lek — volledige-steekproef , gelijktijdige , of herfitten op data die de trade bevat die je evalueert. De discipline van het matchen van backtest aan live condities is een eigen onderwerp in backtest-live-pariteit, en DCC is een model dat slordigheid hier meer bestraft dan de meeste. Als, na schone walk-forward-evaluatie, de dynamische correlatie niets toevoegt boven een simpele voortschrijdende schatting voor jouw strategie, dan is dat een echt en publiceerbaar negatief resultaat — de mindset in eerlijke negatieve resultaten is direct van toepassing.
Asymmetrische DCC (aDCC)
Net zoals het univariate leverage-effect (Deel 2) betekent dat slecht nieuws de volatiliteit meer verhoogt dan goed nieuws, stijgen correlaties meer na gezamenlijke negatieve schokken dan na gezamenlijke positieve. Cappiello, Engle & Sheppard (2006) vangen dit met asymmetrische DCC, waarbij een term wordt toegevoegd die wordt aangedreven door het uitproduct van de negatieve-deel gestandaardiseerde residuen :
waarbij en de extra correlatiekick meet van gezamenlijke neerwaartse bewegingen. Voor crypto, waar crash-correlatie het dominante risico is, is de asymmetrieterm meestal significant en de ene extra parameter waard. rmgarch fit aDCC out of the box (model="aDCC"); het toevoegen van de -term aan onze NumPy-schatter is een rechttoe rechtaan oefening.
Vergelijking: DCC tegen de alternatieven
Waar staat DCC te midden van de manieren waarop je een covariantiematrix voor een cryptoboek zou kunnen krijgen? De eerlijke samenvatting:
| Benadering | Params | Schaalt naar | Tijdsvariërende ? | PD gegarandeerd? | Staartafhankelijkheid? |
|---|---|---|---|---|---|
| Steekproef- / voortschrijdende covariantie | 0 (vensterlengte) | elke | grof (vertraagd, rumoerig) | nee (behoeft oplappen) | nee |
| EWMA (RiskMetrics) | 1 () | elke | ja (enkel verval) | ja | nee |
| CCC-GARCH | marges + | dozijnen | nee (constante ) | ja | nee |
| DCC-GARCH | marges + 2 | dozijnen | ja | ja | nee |
| aDCC-GARCH | marges + 3 | dozijnen | ja, asymmetrisch | ja | gedeeltelijk |
| BEKK | ja (rijk) | ja | nee | ||
| VECH | ja (rijkst) | pijnlijk | nee | ||
| GARCH-copula | marges + copula | dozijnen (vines) | statische copula | ja | ja |
Een paar lezingen van deze tabel:
- EWMA is de goedkope baseline die iedereen zou moeten verslaan voordat men beweert dat DCC helpt. Het is naar de geest een eenparameter-speciaalgeval — een enkel exponentieel verval direct toegepast op de covariantie — en voor veel boeken is het verbluffend moeilijk om er out-of-sample op te verbeteren. Als DCC EWMA niet verslaat in schone walk-forward, gebruik dan EWMA.
- CCC vs DCC is het hele punt van dit artikel: dezelfde factorisatie, maar CCC bevriest en DCC laat het bewegen. De twee extra parameters () zijn het hele verschil, en in crypto verdienen ze hun bestaan.
- BEKK/VECH kopen rijkere dynamiek — elke covariantie kan reageren op elke voorbije schok — maar de parameterkost beperkt ze tot piepkleine boeken. Voor alles voorbij 4 activa zijn ze geen echte optie.
- GARCH-copula is de enige rij met een "ja" onder staartafhankelijkheid. Dat is de complementariteit weer: DCC modelleert het dynamische centrum van de gezamenlijke verdeling, copula's modelleren zijn statische staarten. Als je risicovraag "wat gebeurt er wanneer alles tegelijk breekt" is, grijp dan naar de copula-pijplijn; als het "wat is mijn hedgeratio / portefeuillevariantie nu" is, grijp dan naar DCC.
De praktische standaard voor een systematische crypto-desk: DCC (of aDCC) voor hedgeratio's en dynamische covariantie in het lichaam, een copula-overlay voor staartrisico en CVaR, en EWMA als de sanity-check-baseline die je eerlijk houdt over de vraag of de extra machinerie zichzelf terugverdient.
Beperkingen
- Scalaire dynamiek. Eén en één voor alle paren is een sterke beperking. BTC-ETH en twee obscure alts delen dezelfde aanpassingssnelheid. Generalized DCC versoepelt dit maar herintroduceert de parameterexplosie die DCC was ontworpen om te vermijden.
- Tweestaps-efficiëntieverlies. De quasi-likelihood-schatter is consistent maar niet volledig efficiënt, en naïeve standaardfouten zijn verkeerd. Gebruik de Engle-Sheppard-correctie als je om inferentie geeft; voor signaalgeneratie volstaan de puntschattingen.
- Gaussische staarten standaard. De gewone Gaussische quasi-likelihood onderschat het gezamenlijke staartrisico. Student-t-innovaties helpen; voor echte staartafhankelijkheid (de kans op simultane extreme bewegingen) is DCC het verkeerde gereedschap en is een copula-model het juiste. DCC geeft je het dynamische lichaam van de correlatie; copula's geven je de statische staart. Serieuze desks gebruiken beide.
- Correlatie is geen causaliteit, en geen richting. Stijgende waarschuwt dat diversificatie faalt; het zegt niets over marktrichting. Overlaad een risicosignaal niet met directionele verwachtingen.
- Datahonger. Alles hierboven gaat uit van lange, schone, gesynchroniseerde histories. Crypto's nieuwste en meest interessante tokens overtreden alle drie.
Samenvatting
- Statische correlatie is een leugen in crypto. Correlaties clusteren, persisteren en schieten in drawdowns richting 1 — precies wanneer diversificatie zou moeten helpen. Een enkele steekproef middelt een regime-wisselend proces tot een betekenisloos midden.
- Volledige multivariate GARCH (VECH, BEKK) schaalt niet. Het parameteraantal groeit als ; beide zijn in de praktijk beperkt tot een handvol activa.
- DCC (Engle 2002) factoriseert het probleem: , met uit onafhankelijke univariate GARCH-fits (hergebruik Deel 1-2) en uit een tweeparameter-recursie. Het schaalt naar dozijnen activa omdat alleen worden geoptimaliseerd.
- De recursie , genormaliseerd naar , produceert bij elke stap een geldige positief-definiete correlatiematrix, met , .
archdoet geen DCC. Fit marges metarch, implementeer dan de ~60-regel NumPy/SciPy-schatter hier, of gebruikmgarch(Python) ofrmgarch(R, de referentie).- Drie concrete opbrengsten: een dynamische hedgeratio voor pairs trading; een eerlijke tijdsvariërende portefeuillevariantie voor risicogebaseerde allocatie; en gemiddelde paarsgewijze correlatie als een risk-off-regimesignaal.
- Discipline is alles. Correlation targeting lekt het volledige-steekproefgemiddelde, dus herschat op alleen trainingsdata; vertraag elke hedgeratio; filter vooruit, strijk nooit glad. Walk-forward-evaluatie is niet onderhandelbaar.
- aDCC voegt een neerwaartse-asymmetrieterm toe en is meestal de moeite waard in crypto, waar crash-correlatie domineert.
- Deel 4 gebruikt deze voorspellingen om een op volatiliteit gerichte strategie te bouwen en te backtesten.
Referenties:
- 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
Kwantitatief onderzoek en strategie