← Torna agli articoli
March 30, 2026
5 min di lettura

Modelli Copula per la Modellazione del Rischio Congiunto nei Portafogli Crypto

Modelli Copula per la Modellazione del Rischio Congiunto nei Portafogli Crypto
#rischio
#copula
#portafoglio
#dipendenza-code
#VaR

Modelli copula — distribuzione del rischio congiunto

La correlazione è il primo strumento a cui la maggior parte dei gestori di portafoglio ricorre quando valuta la diversificazione. Ma nei mercati crypto, la correlazione è pericolosamente fuorviante. Due token potrebbero mostrare una correlazione di Pearson di 0,3 durante i mercati calmi, per poi salire a 0,95 durante un crollo. La correlazione lineare assume distribuzioni ellittiche — un'assunzione che crolla sotto le code pesanti e le strutture di dipendenza asimmetrica endemiche nei rendimenti delle criptovalute.

I modelli copula risolvono questo problema separando il comportamento marginale (come si comporta ogni asset individualmente) dalla struttura di dipendenza (come si muovono insieme gli asset). Questa separazione, radicata nel teorema di Sklar, ci fornisce un framework flessibile per modellare l'intera distribuzione congiunta dei rendimenti del portafoglio — incluse le code dove il rischio vive realmente.

Perché la Correlazione Lineare Fallisce per le Crypto

Si consideri un portafoglio di BTC, ETH, SOL e AVAX. Durante il crollo Terra/Luna nel maggio 2022, le correlazioni tra questi asset convergevano verso 1,0, esattamente quando la diversificazione era più necessaria. Un ottimizzatore media-varianza che assumeva correlazioni stabili avrebbe drasticamente sottostimato il rischio del portafoglio.

I problemi fondamentali della correlazione di Pearson per le crypto:

  1. Distribuzioni non ellittiche. I rendimenti delle crypto mostrano asimmetria e curtosi significative. I rendimenti giornalieri di BTC mostrano regolarmente valori di curtosi superiori a 10 (distribuzione normale: 3).
  2. Dipendenza asimmetrica. Gli asset tendono ad essere più correlati durante i ribassi che durante i rialzi. Questo fenomeno di "rottura della correlazione" è ben documentato nei mercati azionari ed è ancora più pronunciato nelle crypto.
  3. Dipendenza nelle code. La probabilità che due asset subiscano simultaneamente perdite estreme non è catturata dalla correlazione lineare. Si possono avere due asset con correlazione identica ma dipendenza nelle code radicalmente diversa.

Il Teorema di Sklar: Il Fondamento

Il teorema di Sklar (1959) afferma che qualsiasi distribuzione congiunta multivariata F(x1,x2,,xd)F(x_1, x_2, \ldots, x_d) può essere decomposta 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)

dove FiF_i sono le funzioni di distribuzione marginale e C:[0,1]d[0,1]C: [0,1]^d \to [0,1] è la copula — una funzione che codifica l'intera struttura di dipendenza tra le variabili.

Al contrario, se le marginali sono continue, la copula CC è unica.

Questa decomposizione è potente perché ci permette di:

  • Modellare la distribuzione marginale di ogni asset separatamente (usando GARCH, EVT o qualsiasi distribuzione che si adatti)
  • Modellare la struttura di dipendenza indipendentemente tramite la copula
  • Combinarle per ottenere la distribuzione congiunta completa

La densità della distribuzione congiunta si fattorizza come:

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)

dove cc è la densità della copula e fif_i sono le densità marginali.

Famiglie di Copule e Loro Proprietà

Confronto della dipendenza nelle code tra famiglie di copule

Copula Gaussiana

La copula gaussiana è parametrizzata da una matrice di correlazione Σ\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)

dove ΦΣ\Phi_{\Sigma} è la CDF normale multivariata e Φ1\Phi^{-1} è la funzione quantile normale univariata.

Dipendenza nelle code: λL=λU=0\lambda_L = \lambda_U = 0 (per ρ<1\rho < 1).

La copula gaussiana ha dipendenza nelle code zero — sottostima sistematicamente la probabilità di eventi estremi congiunti. Questo è stato un fattore chiave nel mispricing dei CDO prima del 2008, ed è ugualmente pericoloso per la modellazione del rischio crypto.

Copula t di Student

La copula t introduce dipendenza simmetrica nelle code tramite un parametro gradi di libertà ν\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)

Dipendenza nelle code:

λ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)

Per ν=4\nu = 4 e ρ=0,5\rho = 0,5, questo dà λ0,18\lambda \approx 0,18 — una probabilità del 18% che entrambi gli asset si trovino contemporaneamente nel loro quantile peggiore. Un ν\nu più basso (code più pesanti) aumenta questa probabilità. I mercati crypto, con i loro rendimenti a coda pesante, richiedono tipicamente ν\nu nell'intervallo 3-8.

La copula t è un miglioramento significativo rispetto alla gaussiana, ma impone dipendenza nelle code simmetrica (λL=λU\lambda_L = \lambda_U). In pratica, gli asset crypto mostrano spesso una dipendenza nella coda inferiore più forte (crashano insieme) rispetto alla dipendenza nella coda superiore (salgono insieme).

Copula Clayton

La copula Clayton cattura la dipendenza nella coda inferiore — esattamente il tipo di comportamento di clustering dei crash asimmetrico che vediamo nelle crypto:

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

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

All'aumentare di θ\theta, la dipendenza nella coda inferiore si rafforza. Per θ=2\theta = 2, λL0,71\lambda_L \approx 0,71 — una probabilità molto alta di perdite estreme congiunte.

Copula Gumbel

La copula Gumbel è l'immagine speculare — cattura la dipendenza nella coda superiore:

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

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

Copula Frank

La copula Frank ha dipendenza nelle code zero in entrambe le code (λL=λU=0\lambda_L = \lambda_U = 0), rendendola adatta per modellare la dipendenza nel corpo della distribuzione senza effetti nelle code:

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)

Scegliere la Copula Giusta per le Crypto

Per i portafogli crypto, le evidenze empiriche indicano:

  • Clayton o Gumbel ruotata (Gumbel di sopravvivenza) per la dipendenza nella coda inferiore — catturare il contagio da crash
  • Copula t come scelta generale robusta quando la dipendenza simmetrica nelle code è accettabile
  • Copula Joe per catturare una forte dipendenza nella coda superiore nelle fasi di rally

La ricerca di Bruhn e Jeleskovic (2024) ha trovato che i modelli GARCH-copula, in particolare quelli che usano marginali t di Student con copule t, hanno costantemente sovraperformato gli approcci media-varianza e CVaR storico in condizioni di mercato di ribasso (2022), ripresa (2023) e stabilità (2024) nelle crypto.

La Maledizione della Dimensionalità: Entrano le Vine Copule

Struttura ad albero della vine copula per un portafoglio crypto

Le copule multivariate standard (gaussiana, copula t) scalano ad alte dimensioni ma impongono assunzioni restrittive. Le copule archimedee (Clayton, Gumbel, Frank) sono naturalmente bivariate — estenderle a d>2d > 2 dimensioni richiede che tutte le coppie condividano lo stesso parametro di dipendenza, il che non è realistico.

Le vine copule risolvono questo problema decomponendo una copula dd-dimensionale in una cascata di copule bivariate disposte in una struttura ad albero. Ogni coppia di variabili (condizionata alle altre) ottiene la propria famiglia di copula bivariata e parametro.

La Costruzione a Coppie di Copule

Per una densità dd-dimensionale, la fattorizzazione vine copula è:

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}

dove ci,jSc_{i,j|S} è una densità di copula bivariata per le variabili ii e jj condizionata all'insieme SS.

Una vine copula dd-dimensionale richiede (d2)=d(d1)/2\binom{d}{2} = d(d-1)/2 copule bivariate. Per un portafoglio crypto a 10 asset, sono 45 copule a coppie — ognuna potenzialmente da una famiglia diversa.

Strutture Vine: C-Vine, D-Vine, R-Vine

C-vine (Canonical vine): Ogni albero ha un singolo nodo radice collegato a tutti gli altri nodi. Ottimo quando una variabile domina — ad esempio, BTC come driver di mercato.

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

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

D-vine (Drawable vine): Una struttura a percorso sequenziale. Ottima quando le variabili hanno un ordinamento naturale (ad esempio, per capitalizzazione di mercato o settore).

R-vine (Regular vine): La struttura più generale — qualsiasi sequenza di alberi valida. Le R-vine comprendono sia le C-vine che le D-vine.

La ricerca sui portafogli di criptovalute suggerisce che le strutture D-vine producono spesso previsioni VaR superiori rispetto alle C-vine e R-vine per gli asset crypto, sebbene questo dipenda dalla composizione specifica del portafoglio.

Perché le Vine Copule Sono Importanti per le Crypto

Un portafoglio di 8 asset crypto modellato con una singola copula Clayton costringe tutte le 28 coppie a condividere lo stesso θ\theta. Ma BTC-ETH potrebbe avere θClayton=3,5\theta_{\text{Clayton}} = 3,5 (forte dipendenza da crash) mentre SOL-AVAX potrebbe avere θ=1,2\theta = 1,2 (moderata). Le vine copule permettono a ogni coppia di esprimere la propria struttura di dipendenza:

  • BTC-ETH: copula t (ν=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)

Questa flessibilità è critica per una stima accurata del rischio del portafoglio.

Modellare le Marginali: GARCH-EVT

Prima di adattare la copula, dobbiamo trasformare la serie dei rendimenti di ogni asset in variabili uniformi [0,1][0,1] (la "trasformazione integrale di probabilità"). La pipeline standard:

  1. Adattare un modello GARCH alla serie dei rendimenti di ogni asset per catturare la volatilità variabile nel tempo
  2. Estrarre i residui standardizzati zt=(rtμt)/σtz_t = (r_t - \mu_t) / \sigma_t
  3. Adattare le code usando la Teoria del Valore Estremo (EVT) — specificatamente, la Distribuzione di Pareto Generalizzata (GPD) per le code superiore e inferiore oltre una soglia (tipicamente il 5° e il 95° percentile)
  4. Usare la CDF empirica per il corpo della distribuzione
  5. Applicare la trasformazione integrale di probabilità per ottenere osservazioni pseudo-uniformi ui,t=F^i(zi,t)u_{i,t} = \hat{F}_i(z_{i,t})

Questo approccio GARCH-EVT è spesso chiamato metodo "semi-parametrico". Cattura correttamente:

  • Il clustering della volatilità (GARCH)
  • Le code pesanti (GPD dall'EVT)
  • La forma generale della distribuzione (CDF empirica per il corpo)

Per gli asset crypto, un modello EGARCH(1,1) o GJR-GARCH(1,1) con innovazioni t di Student tende a funzionare bene, poiché cattura la risposta asimmetrica della volatilità (le cattive notizie aumentano la volatilità più delle buone notizie).

VaR e CVaR del Portafoglio con le Copule

Value-at-Risk (VaR)

Il VaR del portafoglio al livello di confidenza α\alpha è:

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

dove LL è la perdita del portafoglio. Con le copule, stimiamo il VaR tramite Monte Carlo:

  1. Simulare NN campioni dalla vine copula adattata (nello spazio uniforme)
  2. Trasformare nuovamente nello spazio dei rendimenti usando le CDF marginali inverse
  3. Calcolare i rendimenti del portafoglio: rp=iwirir_p = \sum_i w_i \cdot r_i
  4. Il VaR è il quantile α\alpha della distribuzione simulata delle perdite del portafoglio

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

Il CVaR è la perdita attesa dato che la perdita supera il 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

Il CVaR è coerente (soddisfa la subaddittività), rendendolo superiore al VaR per l'ottimizzazione del portafoglio. La stima Monte Carlo è semplice — si calcola la media delle perdite che superano il VaR.

Perché il Rischio Basato sulla Copula Supera il Rischio Basato sulla Correlazione

Si considerino due portafogli con correlazioni a coppie identiche di 0,5:

  • Portafoglio A: Dipendenza con copula gaussiana (nessuna dipendenza nelle code)
  • Portafoglio B: Dipendenza con copula Clayton (θ=2\theta = 2, λL=0,71\lambda_L = 0,71)

Al livello di confidenza del 99%, il Portafoglio B avrà VaR e CVaR significativamente più alti perché la copula Clayton modella correttamente la tendenza degli asset a crashare insieme. La copula gaussiana sottostima questo rischio assumendo che i co-movimenti estremi siano estremamente rari.

Negli studi empirici sui portafogli crypto, la differenza nel CVaR al 99% tra modelli gaussiani e vine copula può superare il 30-40%, il che significa che i modelli basati sulla correlazione possono sottostimare il rischio di coda di un terzo o più.

Implementazione: Python con pyvinecopulib

Ecco una pipeline completa per adattare una vine copula ai rendimenti crypto e stimare VaR/CVaR del portafoglio.

Fase 1: Preparazione dei Dati e Adattamento delle Marginali

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()

Fase 2: Trasformazione Integrale di Probabilità

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
])

Fase 3: Adattamento della 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})")

Fase 4: VaR e CVaR con Monte Carlo

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")

Fase 5: Analisi della Dipendenza nelle Code

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))

Output tipico per un portafoglio crypto potrebbe essere simile a:

Coppia Famiglia λ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

Si noti come ogni coppia possa avere una struttura di dipendenza completamente diversa. BTC-SOL mostra una forte dipendenza nella coda inferiore (Clayton) con dipendenza nella coda superiore zero — crashano insieme ma non necessariamente salgono insieme. BTC-MATIC non mostra alcuna dipendenza nelle code (Frank), suggerendo qualche beneficio di diversificazione anche negli estremi.

Backtesting del Modello VaR con Copula

Un modello VaR è utile solo se è ben calibrato. Il backtest standard calcola le violazioni VaR — giorni in cui la perdita effettiva ha superato il VaR previsto — e verifica se il tasso di violazione corrisponde al tasso atteso.

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

Un modello VaR al 99% ben calibrato dovrebbe avere un tasso di violazione vicino all'1%. Se il tasso è significativamente più alto, il modello sottostima il rischio. Se significativamente più basso, è troppo conservativo.

Considerazioni Pratiche

Costo Computazionale

L'adattamento della vine copula è O(d2n)O(d^2 \cdot n) per livello ad albero. Per un portafoglio a 10 asset con finestre mobili di 500 giorni, un backtest completo con 50.000 simulazioni Monte Carlo per step può richiedere ore. Strategie per gestire questo:

  • Vine troncate: Impostare trunc_lvl=3 o trunc_lvl=4 — gli alberi superiori catturano dipendenze condizionali più deboli che contribuiscono meno al rischio
  • Conteggio ridotto di simulazioni: 10.000-20.000 simulazioni spesso bastano per il VaR al 99%
  • Calcolo parallelo: Gli adattamenti GARCH per ogni asset sono indipendenti e possono essere parallelizzati
  • Cache del modello: Riadattare la copula settimanalmente anziché giornalmente, aggiornando solo le previsioni GARCH

Consapevolezza dei Regimi

I mercati crypto mostrano regimi distinti (toro, orso, laterale, eventi ad alta volatilità). Una singola vine copula adattata sull'intero campione potrebbe non catturare la dipendenza dipendente dal regime. Si consideri:

  • Finestre mobili di 250-500 giorni
  • Copule a cambio di regime dove i parametri della copula dipendono da uno stato di Markov nascosto
  • Osservazioni ponderate esponenzialmente che attribuiscono maggiore peso ai dati recenti

Errori Comuni

  1. Dimenticare la PIT. Inserire i rendimenti grezzi direttamente nella copula invece delle osservazioni pseudo-uniformi produrrà risultati privi di significato. Trasformare sempre in margini uniformi prima.
  2. Overfitting con troppe famiglie. Includere ogni possibile famiglia bivariata nell'insieme di selezione può portare all'overfitting, specialmente con campioni brevi. Usare BIC per la selezione del modello e considerare di limitarsi a 4-5 famiglie.
  3. Ignorare la dipendenza seriale. Le copule modellano la dipendenza cross-sezionale in un singolo punto temporale. Se si salta il passaggio GARCH e si inseriscono rendimenti autocorrelati nella copula, la dipendenza stimata sarà contaminata dagli effetti seriali.
  4. Copule statiche in mercati dinamici. Una copula adattata sui dati del mercato toro del 2021 sarà mal calibrata per il crash del 2022. Usare sempre finestre mobili o in espansione.

Conclusione

I modelli copula — in particolare le vine copule — forniscono un framework matematicamente rigoroso per modellare il rischio congiunto dei portafogli crypto che va ben oltre ciò che la correlazione lineare può catturare. I principali vantaggi:

  • Modellazione marginale e di dipendenza separata tramite il teorema di Sklar
  • Dipendenza flessibile nelle code attraverso un'appropriata selezione della famiglia di copula (Clayton per il contagio da crash, Gumbel per il co-movimento dei rally, copula t per le code simmetriche)
  • Scalabilità ad alta dimensionalità attraverso la decomposizione vine copula, dove ogni coppia di asset ottiene la propria copula bivariata
  • Stima accurata di VaR/CVaR che tiene conto della dipendenza non lineare e asimmetrica — critica per la gestione del rischio in un mercato dove "tutto crasha insieme" è la norma, non l'eccezione

La pipeline GARCH-EVT-Copula è ora l'approccio standard presso gli hedge fund quantitativi e i desk di rischio focalizzati sulle crypto. Con librerie come pyvinecopulib, la barriera all'implementazione è abbastanza bassa da consentire a qualsiasi trader sistematico di integrare la modellazione del rischio basata su copula nel proprio workflow di gestione del portafoglio.

Il codice in questo articolo fornisce un punto di partenza funzionante. Per l'uso in produzione, si aggiungerebbero una corretta cross-validazione per la selezione dell'ordine GARCH, modelli marginali più sofisticati (ad esempio, EGARCH con effetti leva, o misure di volatilità realizzata usando dati intraday), e stress testing con parametri copula ipotetici calibrati su episodi di crisi storici.


Riferimenti

  • 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: le informazioni fornite in questo articolo hanno solo scopo didattico e informativo e non costituiscono consulenza finanziaria, di investimento o di trading. Il trading di criptovalute comporta un rischio significativo di perdita.

Autori

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

Resta un Passo Avanti al Mercato

Iscriviti alla nostra newsletter per approfondimenti esclusivi sul trading con IA, analisi di mercato e aggiornamenti sulla piattaforma.

Rispettiamo la tua privacy. Annulla l'iscrizione in qualsiasi momento.