← Terug naar artikelen
March 30, 2026
5 min leestijd

Copulamodellen voor gezamenlijke risicomodellering in cryptoportefeuilles

Copulamodellen voor gezamenlijke risicomodellering in cryptoportefeuilles
#risk
#copula
#portfolio
#tail-dependence
#VaR
📊
Part 4 of 5 · Collection
Portfolio Construction & Risk

Copulamodellen — gezamenlijke risicoverdeling

Correlatie is het eerste instrument waar de meeste portefeuillebeheerders naar grijpen bij het beoordelen van diversificatie. Maar in cryptomarkten is correlatie gevaarlijk misleidend. Twee tokens kunnen tijdens rustige markten een Pearson-correlatie van 0,3 vertonen en tijdens een crash plotseling naar 0,95 stijgen. Lineaire correlatie gaat uit van elliptische verdelingen — een aanname die instort onder de zware staarten en asymmetrische afhankelijkheidsstructuren die inherent zijn aan cryptocurrency-rendementen.

Copulamodellen lossen dit op door marginaal gedrag (hoe elk actief individueel presteert) te scheiden van afhankelijkheidsstructuur (hoe activa samen bewegen). Deze scheiding, geworteld in de stelling van Sklar, geeft ons een flexibel raamwerk om de volledige gezamenlijke verdeling van portefeuillerendementen te modelleren — inclusief de staarten waar het risico daadwerkelijk zit.

Waarom lineaire correlatie faalt voor crypto

Beschouw een portefeuille van BTC, ETH, SOL en AVAX. Tijdens de instorting van Terra/Luna in mei 2022 convergeerden de correlaties tussen deze activa naar 1,0, precies op het moment dat diversificatie het meest nodig was. Een mean-variance-optimalisator die stabiele correlaties veronderstelde, zou het portefeuillerisico drastisch hebben onderschat.

De kernproblemen met Pearson-correlatie voor crypto:

  1. Niet-elliptische verdelingen. Crypto-rendementen vertonen aanzienlijke scheefheid en kurtosis. Dagelijkse BTC-rendementen vertonen regelmatig kurtosiswaarden boven 10 (normale verdeling: 3).
  2. Asymmetrische afhankelijkheid. Activa zijn tijdens neergangen doorgaans sterker gecorreleerd dan tijdens rally's. Dit "correlatie-instortings"-fenomeen is goed gedocumenteerd in aandelenmarkten en is nog uitgesprokener in crypto.
  3. Staartafhankelijkheid. De kans dat twee activa gelijktijdig extreme verliezen ondervinden, wordt niet vastgelegd door lineaire correlatie. Je kunt twee activa hebben met identieke correlatie maar sterk verschillende staartafhankelijkheid.

De stelling van Sklar: de basis

De stelling van Sklar (1959) stelt dat elke multivariate gezamenlijke verdeling F(x1,x2,,xd)F(x_1, x_2, \ldots, x_d) kan worden ontbonden in:

F(x1,x2,,xd)=C(F1(x1),F2(x2),,Fd(xd))F(x_1, x_2, \ldots, x_d) = C\bigl(F_1(x_1), F_2(x_2), \ldots, F_d(x_d)\bigr)

waarbij FiF_i de marginale verdelingsfuncties zijn en C:[0,1]d[0,1]C: [0,1]^d \to [0,1] de copula is — een functie die de volledige afhankelijkheidsstructuur tussen variabelen codeert.

Omgekeerd geldt: als de marginalen continu zijn, is de copula CC uniek.

Deze ontbinding is krachtig omdat ze ons in staat stelt om:

  • De marginale verdeling van elk actief afzonderlijk te modelleren (met GARCH, EVT, of elke andere geschikte verdeling)
  • De afhankelijkheidsstructuur onafhankelijk te modelleren via de copula
  • Ze te combineren om de volledige gezamenlijke verdeling te verkrijgen

De dichtheid van de gezamenlijke verdeling ontbindt als volgt:

f(x1,,xd)=c(F1(x1),,Fd(xd))i=1dfi(xi)f(x_1, \ldots, x_d) = c\bigl(F_1(x_1), \ldots, F_d(x_d)\bigr) \cdot \prod_{i=1}^{d} f_i(x_i)

waarbij cc de copuladichtheid is en fif_i de marginale dichtheden.

Copulafamilies en hun eigenschappen

Vergelijking van staartafhankelijkheid tussen copulafamilies

Gaussische copula

De Gaussische copula wordt geparametriseerd door een correlatiematrix Σ\Sigma:

CΣGauss(u1,,ud)=ΦΣ(Φ1(u1),,Φ1(ud))C_{\Sigma}^{\text{Gauss}}(u_1, \ldots, u_d) = \Phi_{\Sigma}\bigl(\Phi^{-1}(u_1), \ldots, \Phi^{-1}(u_d)\bigr)

waarbij ΦΣ\Phi_{\Sigma} de multivariate normale CDF is en Φ1\Phi^{-1} de univariate normale kwantielfunctie.

Staartafhankelijkheid: λL=λU=0\lambda_L = \lambda_U = 0 (voor ρ<1\rho < 1).

De Gaussische copula heeft geen staartafhankelijkheid — ze onderschat systematisch de kans op gezamenlijke extreme gebeurtenissen. Dit was een belangrijke factor bij de verkeerde prijsstelling van CDO's vóór 2008, en het is even gevaarlijk voor crypto-risicomodellering.

Student's t-copula

De t-copula introduceert symmetrische staartafhankelijkheid via een vrijheidsgraden-parameter ν\nu:

Cν,Σt(u1,,ud)=tν,Σ(tν1(u1),,tν1(ud))C_{\nu, \Sigma}^{t}(u_1, \ldots, u_d) = t_{\nu, \Sigma}\bigl(t_{\nu}^{-1}(u_1), \ldots, t_{\nu}^{-1}(u_d)\bigr)

Staartafhankelijkheid:

λL=λU=2tν+1((ν+1)(1ρ)1+ρ)\lambda_L = \lambda_U = 2 \cdot t_{\nu+1}\left(-\sqrt{\frac{(\nu+1)(1-\rho)}{1+\rho}}\right)

Voor ν=4\nu = 4 en ρ=0,5\rho = 0,5 geeft dit λ0,18\lambda \approx 0,18 — een kans van 18% dat beide activa zich tegelijkertijd in hun slechtste kwantiel bevinden. Lagere ν\nu (zwaardere staarten) verhoogt deze kans. Cryptomarkten, met hun dikstaartige rendementen, vereisen doorgaans een ν\nu in het bereik van 3-8.

De t-copula is een aanzienlijke verbetering ten opzichte van Gaussisch, maar legt symmetrische staartafhankelijkheid op (λL=λU\lambda_L = \lambda_U). In de praktijk vertonen crypto-activa vaak sterkere afhankelijkheid in de onderste staart (samen crashen) dan in de bovenste staart (samen stijgen).

Clayton-copula

De Clayton-copula legt afhankelijkheid in de onderste staart vast — precies het soort asymmetrisch crash-clusteringgedrag dat we in crypto zien:

CθClayton(u1,u2)=(u1θ+u2θ1)1/θ,θ>0C_{\theta}^{\text{Clayton}}(u_1, u_2) = \left(u_1^{-\theta} + u_2^{-\theta} - 1\right)^{-1/\theta}, \quad \theta > 0

Staartafhankelijkheid: λL=21/θ\lambda_L = 2^{-1/\theta}, λU=0\lambda_U = 0.

Naarmate θ\theta toeneemt, wordt de afhankelijkheid in de onderste staart sterker. Voor θ=2\theta = 2 is λL0,71\lambda_L \approx 0,71 — een zeer hoge kans op gezamenlijke extreme verliezen.

Gumbel-copula

De Gumbel-copula is het spiegelbeeld — ze legt afhankelijkheid in de bovenste staart vast:

CθGumbel(u1,u2)=exp([(lnu1)θ+(lnu2)θ]1/θ),θ1C_{\theta}^{\text{Gumbel}}(u_1, u_2) = \exp\left(-\left[(-\ln u_1)^{\theta} + (-\ln u_2)^{\theta}\right]^{1/\theta}\right), \quad \theta \geq 1

Staartafhankelijkheid: λL=0\lambda_L = 0, λU=221/θ\lambda_U = 2 - 2^{1/\theta}.

Frank-copula

De Frank-copula heeft geen staartafhankelijkheid in beide staarten (λL=λU=0\lambda_L = \lambda_U = 0), wat haar geschikt maakt voor het modelleren van afhankelijkheid in het centrale gedeelte van de verdeling zonder staarteffecten:

CθFrank(u1,u2)=1θln(1+(eθu11)(eθu21)eθ1)C_{\theta}^{\text{Frank}}(u_1, u_2) = -\frac{1}{\theta}\ln\left(1 + \frac{(e^{-\theta u_1}-1)(e^{-\theta u_2}-1)}{e^{-\theta}-1}\right)

De juiste copula kiezen voor crypto

Voor cryptoportefeuilles wijst het empirisch bewijs op:

  • Clayton of geroteerde Gumbel (survival Gumbel) voor afhankelijkheid in de onderste staart — het vastleggen van crash-besmetting
  • t-copula als een robuuste algemene keuze wanneer symmetrische staartafhankelijkheid aanvaardbaar is
  • Joe-copula voor het vastleggen van sterke afhankelijkheid in de bovenste staart tijdens rallyfases

Onderzoek van Bruhn en Jeleskovic (2024) toonde aan dat GARCH-copulamodellen, met name die welke Student's t-marginalen met t-copula's gebruiken, consistent beter presteerden dan mean-variance- en historische CVaR-benaderingen tijdens neergang (2022), herstel (2023) en stabiliteit (2024) marktomstandigheden in crypto.

De vloek van de dimensionaliteit: vine-copula's komen in beeld

Vine-copula boomstructuur voor een cryptoportefeuille

Standaard multivariate copula's (Gaussisch, t-copula) schalen naar hoge dimensies maar leggen restrictieve aannames op. Archimedische copula's (Clayton, Gumbel, Frank) zijn van nature bivariaat — ze uitbreiden naar d>2d > 2 dimensies vereist dat alle paren dezelfde afhankelijkheidsparameter delen, wat onrealistisch is.

Vine-copula's lossen dit op door een dd-dimensionale copula te ontbinden in een cascade van bivariate copula's gerangschikt in een boomstructuur. Elk paar variabelen (voorwaardelijk op andere) krijgt zijn eigen bivariate copulafamilie en parameter.

De pair-copulaconstructie

Voor een dd-dimensionale dichtheid is de vine-copulafactorisatie:

f(x1,,xd)=i=1dfi(xi)j=1d1i=1djci,i+ji+1,,i+j1f(x_1, \ldots, x_d) = \prod_{i=1}^{d} f_i(x_i) \cdot \prod_{j=1}^{d-1}\prod_{i=1}^{d-j} c_{i,i+j|i+1,\ldots,i+j-1}

waarbij ci,jSc_{i,j|S} een bivariate copuladichtheid is voor variabelen ii en jj voorwaardelijk op de verzameling SS.

Een dd-dimensionale vine-copula vereist (d2)=d(d1)/2\binom{d}{2} = d(d-1)/2 bivariate copula's. Voor een cryptoportefeuille met 10 activa zijn dat 45 paarcopula's — elk mogelijk uit een andere familie.

Vine-structuren: C-vine, D-vine, R-vine

C-vine (canonieke vine): Elke boom heeft een enkele wortelknoop verbonden met alle andere knopen. Het beste wanneer één variabele domineert — bijv. BTC als de marktdrijver.

Tree 1:    BTC --- ETH
           BTC --- SOL
           BTC --- AVAX
           BTC --- DOT

Tree 2:    ETH|BTC --- SOL|BTC
           ETH|BTC --- AVAX|BTC
           ETH|BTC --- DOT|BTC

D-vine (tekenbare vine): Een sequentiële padstructuur. Het beste wanneer variabelen een natuurlijke ordening hebben (bijv. naar marktkapitalisatie of sector).

R-vine (reguliere vine): De meest algemene structuur — elke geldige boomvolgorde. R-vines omvatten zowel C-vines als D-vines.

Onderzoek naar cryptocurrency-portefeuilles suggereert dat D-vine-structuren vaak superieure VaR-voorspellingen opleveren in vergelijking met C-vine en R-vine voor crypto-activa, hoewel dit afhangt van de specifieke portefeuillesamenstelling.

Waarom vine-copula's belangrijk zijn voor crypto

Een portefeuille van 8 crypto-activa gemodelleerd met één enkele Clayton-copula dwingt alle 28 paren om dezelfde θ\theta te delen. Maar BTC-ETH zou θClayton=3,5\theta_{\text{Clayton}} = 3,5 (sterke crashafhankelijkheid) kunnen hebben, terwijl SOL-AVAX θ=1,2\theta = 1,2 (gematigd) zou kunnen hebben. Vine-copula's laten elk paar zijn eigen afhankelijkheidsstructuur uitdrukken:

  • BTC-ETH: t-copula (ν=4\nu=4, ρ=0,72\rho=0,72)
  • BTC-SOL: Clayton (θ=2,1\theta=2,1)
  • ETH-AVAX: Frank (θ=5,3\theta=5,3)
  • SOL-DOT | BTC: Gumbel (θ=1,8\theta=1,8)

Deze flexibiliteit is cruciaal voor nauwkeurige portefeuillerisicoschatting.

De marginalen modelleren: GARCH-EVT

Voordat we de copula fitten, moeten we de rendementsreeks van elk actief transformeren naar uniforme [0,1][0,1] variabelen (de "probability integral transform"). De standaardpijplijn:

  1. Fit een GARCH-model op de rendementsreeks van elk actief om tijdsvariërende volatiliteit vast te leggen
  2. Extraheer gestandaardiseerde residuen zt=(rtμt)/σtz_t = (r_t - \mu_t) / \sigma_t
  3. Fit de staarten met behulp van Extreme Value Theory (EVT) — specifiek, de Generalized Pareto Distribution (GPD) voor de bovenste en onderste staarten voorbij een drempel (doorgaans het 5e en 95e percentiel)
  4. Gebruik de empirische CDF voor het centrale gedeelte van de verdeling
  5. Pas de probability integral transform toe om pseudo-uniforme observaties te verkrijgen ui,t=F^i(zi,t)u_{i,t} = \hat{F}_i(z_{i,t})

Deze GARCH-EVT-aanpak wordt vaak de "semi-parametrische" methode genoemd. Ze legt correct vast:

  • Volatiliteitsclustering (GARCH)
  • Zware staarten (GPD van EVT)
  • De algehele vorm van de verdeling (empirische CDF voor het centrale gedeelte)

Voor crypto-activa werkt een EGARCH(1,1)- of GJR-GARCH(1,1)-model met Student's t-innovaties doorgaans goed, omdat het de asymmetrische volatiliteitsrespons vastlegt (slecht nieuws verhoogt de volatiliteit meer dan goed nieuws).

Portefeuille-VaR en -CVaR met copula's

Value-at-Risk (VaR)

Portefeuille-VaR op betrouwbaarheidsniveau α\alpha is:

VaRα=inf{l:P(Ll)α}\text{VaR}_\alpha = \inf\{ l : P(L \leq l) \geq \alpha \}

waarbij LL het portefeuilleverlies is. Met copula's schatten we VaR via Monte Carlo:

  1. Simuleer NN steekproeven uit de gefitte vine-copula (in uniforme ruimte)
  2. Transformeer terug naar rendementsruimte met behulp van de inverse marginale CDF's
  3. Bereken portefeuillerendementen: rp=iwirir_p = \sum_i w_i \cdot r_i
  4. VaR is het α\alpha-kwantiel van de gesimuleerde portefeuilleverliesverdeling

Conditional Value-at-Risk (CVaR / Expected Shortfall)

CVaR is het verwachte verlies gegeven dat het verlies groter is dan VaR:

CVaRα=E[LLVaRα]=11αα1VaRudu\text{CVaR}_\alpha = E[L \mid L \geq \text{VaR}_\alpha] = \frac{1}{1-\alpha}\int_{\alpha}^{1}\text{VaR}_u\, du

CVaR is coherent (voldoet aan subadditiviteit), waardoor het superieur is aan VaR voor portefeuille-optimalisatie. Monte Carlo-schatting is eenvoudig — bereken het gemiddelde van de verliezen die VaR overschrijden.

Waarom copula-gebaseerd risico beter is dan correlatie-gebaseerd risico

Beschouw twee portefeuilles met identieke paarsgewijze correlaties van 0,5:

  • Portefeuille A: Gaussische copula-afhankelijkheid (geen staartafhankelijkheid)
  • Portefeuille B: Clayton copula-afhankelijkheid (θ=2\theta = 2, λL=0,71\lambda_L = 0,71)

Op het 99%-betrouwbaarheidsniveau zal portefeuille B een aanzienlijk hogere VaR en CVaR hebben omdat de Clayton-copula de neiging van activa om samen te crashen correct modelleert. De Gaussische copula onderschat dit risico door aan te nemen dat extreme gezamenlijke bewegingen verwaarloosbaar zeldzaam zijn.

In empirische studies naar cryptoportefeuilles kan het verschil in 99%-CVaR tussen Gaussische en vine-copulamodellen meer dan 30-40% bedragen, wat betekent dat correlatie-gebaseerde modellen het staartrisico met een derde of meer kunnen onderschatten.

Implementatie: Python met pyvinecopulib

Hier is een volledige pijplijn voor het fitten van een vine-copula op crypto-rendementen en het schatten van portefeuille-VaR/CVaR.

Stap 1: Datavoorbereiding en marginale fitting

import numpy as np
import pandas as pd
from arch import arch_model
from scipy import stats
import pyvinecopulib as pv

def fetch_crypto_returns(symbols, start="2023-01-01", end="2025-12-31"):
    """
    Fetch daily returns for a list of crypto symbols.
    Replace with your data source (ccxt, yfinance, etc.)
    """
    import yfinance as yf
    prices = yf.download(
        [f"{s}-USD" for s in symbols],
        start=start, end=end
    )["Close"]
    prices.columns = symbols
    returns = np.log(prices / prices.shift(1)).dropna()
    return returns

symbols = ["BTC", "ETH", "SOL", "AVAX", "DOT", "LINK", "MATIC", "ATOM"]
returns = fetch_crypto_returns(symbols)

def fit_garch_marginal(series, dist="t"):
    """
    Fit GJR-GARCH(1,1) with Student-t innovations.
    Returns standardized residuals and the fitted model.
    """
    model = arch_model(
        series * 100,  # scale for numerical stability
        vol="GARCH",
        p=1, o=1, q=1,  # GJR-GARCH
        dist=dist,
        mean="AR",
        lags=1
    )
    result = model.fit(disp="off")
    std_resid = result.std_resid.dropna()
    return std_resid, result

residuals = {}
garch_models = {}
for sym in symbols:
    std_resid, model = fit_garch_marginal(returns[sym])
    residuals[sym] = std_resid
    garch_models[sym] = model

residuals_df = pd.DataFrame(residuals).dropna()

Stap 2: Probability integral transform

def semi_parametric_pit(residuals, tail_threshold=0.05):
    """
    Semi-parametric probability integral transform:
    - GPD for tails beyond threshold
    - Empirical CDF for the body
    Returns pseudo-uniform observations in [0, 1].
    """
    n = len(residuals)
    u = np.zeros(n)
    sorted_resid = np.sort(residuals)

    lower_thresh = np.quantile(residuals, tail_threshold)
    upper_thresh = np.quantile(residuals, 1 - tail_threshold)

    for i, x in enumerate(residuals):
        if x <= lower_thresh:
            lower_exceedances = -(residuals[residuals <= lower_thresh] - lower_thresh)
            shape, _, scale = stats.genpareto.fit(lower_exceedances, floc=0)
            u[i] = tail_threshold * (
                1 - stats.genpareto.cdf(-(x - lower_thresh), shape, scale=scale)
            )
        elif x >= upper_thresh:
            upper_exceedances = residuals[residuals >= upper_thresh] - upper_thresh
            shape, _, scale = stats.genpareto.fit(upper_exceedances, floc=0)
            u[i] = 1 - tail_threshold * (
                1 - stats.genpareto.cdf(x - upper_thresh, shape, scale=scale)
            )
        else:
            u[i] = np.mean(residuals <= x)

    u = np.clip(u, 1e-6, 1 - 1e-6)
    return u

U = np.column_stack([
    semi_parametric_pit(residuals_df[sym].values)
    for sym in symbols
])

Stap 3: Fit de vine-copula

controls = pv.FitControlsVinecop(
    family_set=[
        pv.BicopFamily.student,
        pv.BicopFamily.clayton,
        pv.BicopFamily.gumbel,
        pv.BicopFamily.frank,
        pv.BicopFamily.joe,
        pv.BicopFamily.bb1,       # Clayton-Gumbel mixture
        pv.BicopFamily.bb7,       # Joe-Clayton mixture
        pv.BicopFamily.gaussian,
    ],
    selection_criterion="bic",    # BIC for model selection
    tree_criterion="tau",          # Kendall's tau for tree structure
    nonparametric_method="constant",
    trunc_lvl=5,                   # Truncate after 5 trees
)

vine = pv.Vinecop(U, controls=controls)

print(f"Log-likelihood: {vine.loglik(U):.2f}")
print(f"AIC: {vine.aic(U):.2f}")
print(f"BIC: {vine.bic(U):.2f}")

for i in range(vine.order.shape[0] - 1):
    pair = vine.get_pair_copula(0, i)
    print(f"Tree 1, Edge {i}: {pair.family} "
          f"(params: {pair.parameters})")

Stap 4: Monte Carlo VaR en CVaR

def estimate_var_cvar(vine, garch_models, symbols, weights,
                       n_sim=50_000, alpha=0.99, seed=42):
    """
    Estimate portfolio VaR and CVaR using Monte Carlo simulation
    from the fitted vine copula.
    """
    U_sim = vine.simulate(n=n_sim, seeds=[seed])

    returns_sim = np.zeros((n_sim, len(symbols)))
    for j, sym in enumerate(symbols):
        model = garch_models[sym]
        forecasts = model.forecast(horizon=1)
        mu = forecasts.mean.iloc[-1, 0] / 100  # unscale
        sigma = np.sqrt(forecasts.variance.iloc[-1, 0]) / 100

        nu = model.params.get("nu", 5)
        z_sim = stats.t.ppf(U_sim[:, j], df=nu)
        returns_sim[:, j] = mu + sigma * z_sim

    weights = np.array(weights)
    portfolio_returns = returns_sim @ weights

    losses = -portfolio_returns

    var = np.quantile(losses, alpha)

    cvar = np.mean(losses[losses >= var])

    return var, cvar, portfolio_returns

weights = [1.0 / len(symbols)] * len(symbols)

var_99, cvar_99, sim_returns = estimate_var_cvar(
    vine, garch_models, symbols, weights,
    n_sim=100_000, alpha=0.99
)

print(f"1-day 99% VaR:  {var_99*100:.2f}%")
print(f"1-day 99% CVaR: {cvar_99*100:.2f}%")

from scipy.stats import norm
mu_p = sim_returns.mean()
sigma_p = sim_returns.std()
var_gauss = -(mu_p + sigma_p * norm.ppf(0.01))
print(f"\nGaussian VaR:   {var_gauss*100:.2f}%")
print(f"Copula/Gaussian ratio: {var_99/var_gauss:.2f}x")

Stap 5: Staartafhankelijkheidsanalyse

def compute_tail_dependence(vine, symbols):
    """
    Extract lower and upper tail dependence coefficients
    from the first tree of the vine copula.
    """
    results = []
    order = vine.order
    n_edges = order.shape[0] - 1

    for i in range(n_edges):
        pair = vine.get_pair_copula(0, i)
        u_pair = pair.simulate(n=100_000, seeds=[42])
        q = 0.01  # 1st percentile

        mask_lower = (u_pair[:, 0] <= q)
        lambda_L = np.mean(u_pair[mask_lower, 1] <= q) if mask_lower.sum() > 0 else 0

        mask_upper = (u_pair[:, 0] >= 1 - q)
        lambda_U = np.mean(u_pair[mask_upper, 1] >= 1 - q) if mask_upper.sum() > 0 else 0

        i_idx = order[0]
        j_idx = order[i + 1]
        results.append({
            "pair": f"{symbols[i_idx]}-{symbols[j_idx]}",
            "family": str(pair.family),
            "lambda_L": round(lambda_L, 4),
            "lambda_U": round(lambda_U, 4),
        })

    return pd.DataFrame(results)

tail_dep = compute_tail_dependence(vine, symbols)
print(tail_dep.to_string(index=False))

Een typische output voor een cryptoportefeuille zou er zo uit kunnen zien:

Pair Family λL\lambda_L λU\lambda_U
BTC-ETH student 0.22 0.22
BTC-SOL clayton 0.35 0.00
BTC-AVAX bb7 0.28 0.12
BTC-DOT student 0.18 0.18
BTC-LINK clayton 0.31 0.00
BTC-MATIC frank 0.00 0.00
BTC-ATOM gumbel 0.00 0.15

Merk op hoe elk paar een compleet andere afhankelijkheidsstructuur kan hebben. BTC-SOL vertoont sterke afhankelijkheid in de onderste staart (Clayton) met nul afhankelijkheid in de bovenste staart — ze crashen samen maar stijgen niet noodzakelijkerwijs samen. BTC-MATIC vertoont helemaal geen staartafhankelijkheid (Frank), wat wijst op enig diversificatievoordeel zelfs in extreme situaties.

Backtesten van het copula-VaR-model

Een VaR-model is alleen bruikbaar als het goed gekalibreerd is. De standaard backtest berekent VaR-overschrijdingen — dagen waarop het werkelijke verlies groter was dan de voorspelde VaR — en test of het overschrijdingspercentage overeenkomt met het verwachte percentage.

def backtest_var(returns, symbols, weights, window=500,
                 alpha=0.99, n_sim=20_000):
    """
    Rolling-window VaR backtest using vine copula.
    """
    violations = []
    var_series = []
    T = len(returns)

    for t in range(window, T):
        window_returns = returns.iloc[t-window:t]

        U_window = np.zeros((window, len(symbols)))
        models_t = {}
        for j, sym in enumerate(symbols):
            std_resid, model = fit_garch_marginal(window_returns[sym])
            models_t[sym] = model
            u = pv.to_pseudo_obs(std_resid.values.reshape(-1, 1))
            U_window[:len(u), j] = u.ravel()

        U_clean = U_window[~np.any(U_window == 0, axis=1)]
        vine_t = pv.Vinecop(U_clean, controls=controls)

        var_t, _, _ = estimate_var_cvar(
            vine_t, models_t, symbols, weights,
            n_sim=n_sim, alpha=alpha
        )
        var_series.append(var_t)

        actual_return = (returns.iloc[t][symbols].values
                         * np.array(weights)).sum()
        violations.append(-actual_return > var_t)

    violation_rate = np.mean(violations)
    expected_rate = 1 - alpha
    print(f"Expected violation rate: {expected_rate:.4f}")
    print(f"Actual violation rate:   {violation_rate:.4f}")
    print(f"Number of violations:    {sum(violations)} / {len(violations)}")

    return violations, var_series

Een goed gekalibreerd 99%-VaR-model zou een overschrijdingspercentage dicht bij 1% moeten hebben. Als het percentage aanzienlijk hoger is, onderschat het model het risico. Als het aanzienlijk lager is, is het te conservatief.

Praktische overwegingen

Rekenkosten

Het fitten van vine-copula's is O(d2n)O(d^2 \cdot n) per boomniveau. Voor een portefeuille met 10 activa met een rollend venster van 500 dagen kan een volledige backtest met 50.000 Monte Carlo-simulaties per stap uren duren. Strategieën om dit te beheersen:

  • Afgekapte vines: Stel trunc_lvl=3 of trunc_lvl=4 in — hogere bomen leggen zwakkere voorwaardelijke afhankelijkheden vast die minder bijdragen aan het risico
  • Verminderd aantal simulaties: 10.000-20.000 simulaties volstaan vaak voor 99% VaR
  • Parallelle berekening: De GARCH-fits voor elk actief zijn onafhankelijk en kunnen parallel worden uitgevoerd
  • Modelcaching: Fit de copula wekelijks opnieuw in plaats van dagelijks, en werk alleen de GARCH-voorspellingen bij

Regimebewustzijn

Cryptomarkten vertonen duidelijke regimes (bull, bear, zijwaarts, hoge-volatiliteitsgebeurtenissen). Een enkele vine-copula gefit over de hele steekproef legt mogelijk geen regime-afhankelijke afhankelijkheid vast. Overweeg:

  • Rollende vensters van 250-500 dagen
  • Regime-switching copula's waarbij de copulaparameters afhangen van een verborgen Markov-toestand
  • Exponentieel gewogen observaties die meer gewicht geven aan recente data

Veelvoorkomende valkuilen

  1. De PIT vergeten. Ruwe rendementen rechtstreeks in de copula invoeren in plaats van pseudo-uniforme observaties levert zinloze resultaten op. Transformeer altijd eerst naar uniforme marginalen.
  2. Overfitting met te veel families. Het opnemen van elke mogelijke bivariate familie in de selectieset kan tot overfitting leiden, vooral bij korte steekproeven. Gebruik BIC voor modelselectie en overweeg beperking tot 4-5 families.
  3. Seriële afhankelijkheid negeren. Copula's modelleren cross-sectionele afhankelijkheid op één enkel tijdstip. Als je de GARCH-stap overslaat en autogecorreleerde rendementen in de copula invoert, wordt de geschatte afhankelijkheid vervuild door seriële effecten.
  4. Statische copula's in dynamische markten. Een copula gefit op bullmarktdata uit 2021 zal slecht gekalibreerd zijn voor een crash in 2022. Gebruik altijd rollende of uitbreidende vensters.

Conclusie

Copulamodellen — met name vine-copula's — bieden een wiskundig rigoureus raamwerk voor het modelleren van het gezamenlijke risico van cryptoportefeuilles dat veel verder gaat dan wat lineaire correlatie kan vastleggen. De belangrijkste voordelen:

  • Gescheiden marginale en afhankelijkheidsmodellering via de stelling van Sklar
  • Flexibele staartafhankelijkheid door geschikte copulafamilie-selectie (Clayton voor crashbesmetting, Gumbel voor gezamenlijke rallybewegingen, t-copula voor symmetrische staarten)
  • Hoogdimensionale schaalbaarheid door vine-copuladecompositie, waarbij elk paar activa zijn eigen bivariate copula krijgt
  • Nauwkeurige VaR/CVaR-schatting die rekening houdt met niet-lineaire, asymmetrische afhankelijkheid — cruciaal voor risicobeheer in een markt waar "alles crasht samen" de norm is, niet de uitzondering

De GARCH-EVT-copula-pijplijn is nu de standaardaanpak bij kwantitatieve hedgefondsen en op crypto gerichte risicodesks. Met bibliotheken zoals pyvinecopulib is de implementatiedrempel laag genoeg dat elke systematische trader copula-gebaseerde risicomodellering kan integreren in zijn portefeuillebeheerworkflow.

De code in dit artikel biedt een werkend startpunt. Voor productiegebruik zou je correcte kruisvalidatie voor de GARCH-ordeselectie toevoegen, geavanceerdere marginale modellen (bijv. EGARCH met hefboomeffecten, of realized-volatility-maatstaven met intraday-data), en stresstesten onder hypothetische copulaparameters gekalibreerd op historische crisisepisodes.


Referenties

  • Sklar, A. (1959). Fonctions de repartition a n dimensions et leurs marges. Publications de l'Institut de Statistique de l'Universite de Paris, 8, 229-231.
  • Joe, H. (2014). Dependence Modeling with Copulas. Chapman and Hall/CRC.
  • Aas, K., Czado, C., Frigessi, A., & Bakken, H. (2009). Pair-copula constructions of multiple dependence. Insurance: Mathematics and Economics, 44(2), 182-198.
  • Jeleskovic, V. & Bruhn, L. (2024). Cryptocurrency portfolio optimization: Utilizing a GARCH-Copula model within the Markowitz framework. Journal of Corporate Accounting & Finance.
  • Nagler, T. & Vatter, T. (2023). pyvinecopulib: A Python library for vine copula models. GitHub.
  • Tiwari, A. K., et al. (2020). Modeling risk dependence and portfolio VaR forecast through vine copula for cryptocurrencies. PLOS ONE, 15(1), e0242102.
Disclaimer: De informatie in dit artikel is uitsluitend bedoeld voor educatieve en informatieve doeleinden en vormt geen financieel, beleggings- of handelsadvies. Het handelen in cryptovaluta brengt een aanzienlijk risico op verlies met zich mee.

Auteurs

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Blijf de markt voor

Abonneer je op onze nieuwsbrief voor exclusieve AI-handelsinzichten, marktanalyses en platformupdates.

We respecteren je privacy. Je kunt je op elk moment afmelden.