← Torna agli articoli
July 11, 2026
5 min di lettura

GARCH asimmetrico e a code pesanti: EGARCH, GJR e Student-t

#volatility
#GARCH
#EGARCH
#risk
#VaR
#crypto
#algorithmic-trading

Nella Parte 1 di questa serie abbiamo costruito il GARCH(1,1) da zero: l'intuizione del clustering della volatilità, la ricorsione della varianza condizionata, la stima di massima verosimiglianza, la previsione e le diagnostiche standard sui residui con la libreria arch. Se non l'hai letta, comincia da lì: questo articolo presuppone che tu sappia già stimare e interpretare un GARCH(1,1) semplice e non ne rideriverà le basi.

Il GARCH(1,1) semplice è una buona base di partenza e una cattiva risposta finale. Ha due difetti strutturali che è economico ignorare in un backtest e costoso ignorare con capitale reale. Primo, è simmetrico: il modello reagisce a una giornata di +5%+5\% esattamente come reagisce a una giornata di 5%-5\%, perché lo shock entra nella ricorsione della varianza solo attraverso il suo quadrato, εt12\varepsilon_{t-1}^2. Elevare al quadrato butta via il segno. Secondo, assume innovazioni gaussiane: anche dopo che il GARCH ha assorbito il clustering della volatilità, i residui standardizzati di BTC ed ETH sono visibilmente a code pesanti, e una verosimiglianza gaussiana sottostima sistematicamente il prezzo della coda. Un VaR al 99% GARCH(1,1)-Normale verrà violato molto più dell'1% delle volte.

Questo articolo corregge entrambi i difetti. Aggiungiamo l'asimmetria con GJR-GARCH ed EGARCH, e le code pesanti con le innovazioni Student-tt e skew-tt di Hansen. Poi facciamo la cosa che effettivamente paga l'affitto: trasformiamo la distribuzione condizionata stimata in una previsione a un passo di Value-at-Risk ed Expected Shortfall, e testiamo quella previsione in modo onesto con i test di Kupiec e Christoffersen. Un modello di volatilità che non sottoponi mai a un risk-test è una decorazione.

L'effetto leva, e perché le criptovalute sono più caotiche

Nei mercati azionari l'asimmetria ha un nome e una storia. L'effetto leva (Black, 1976): quando l'azione di un'impresa scende, il suo rapporto debito/patrimonio aumenta, l'equity diventa meccanicamente più rischioso e la volatilità cresce. Le cattive notizie aumentano la volatilità futura più delle buone notizie di pari entità. Empiricamente questo è uno dei fatti stilizzati più robusti nella letteratura sulla volatilità azionaria.

Le criptovalute non hanno equity né leva di bilancio nel senso societario, eppure un'asimmetria simile all'effetto leva si manifesta comunque la maggior parte delle volte, guidata dal deleveraging forzato piuttosto che dalla contabilità. Quando BTC crolla bruscamente, i prestiti sovracollateralizzati vengono liquidati, le posizioni long sui future perpetui vengono chiuse forzatamente, il funding si inverte, e la cascata alimenta la volatilità. Quindi il meccanismo differisce ma il segno spesso concorda con quello azionario: i movimenti al ribasso fanno impennare la volatilità di più.

L'avvertenza importante: le criptovalute sono più caotiche, e dovresti trattare l'asimmetria come una questione empirica piuttosto che come una legge. Anche movimenti al rialzo violenti (short squeeze, un melt-up alimentato dalla leva, un gap da approvazione di un ETF) possono far impennare la volatilità realizzata. A seconda dell'asset e della finestra campionaria, l'asimmetria stimata può essere forte, debole o occasionalmente del segno "sbagliato". La disciplina su cui questo articolo insiste: stima il modello asimmetrico, verifica se il parametro di asimmetria è statisticamente significativo e nella direzione attesa, e conserva il parametro aggiuntivo solo se si guadagna il suo posto. Non dare per scontato che la storia azionaria si trasferisca; testala.

Testare l'asimmetria prima di modellarla

Il brief qui sopra dice "tratta l'asimmetria come empirica", quindi prima di stimare un modello asimmetrico, esegui un test formale ed economico per verificare se l'asimmetria è persino presente. I test di sign-bias di Engle-Ng (1993) fanno esattamente questo. Stima prima un GARCH(1,1) simmetrico, prendi i suoi residui standardizzati al quadrato zt2z_t^2 e regredili sugli indicatori del segno e della dimensione dello shock precedente:

zt2=a0+a1St1+a2St1εt1+a3St1+εt1+utz_t^2 = a_0 + a_1 S_{t-1}^- + a_2 S_{t-1}^- \varepsilon_{t-1} + a_3 S_{t-1}^+ \varepsilon_{t-1} + u_t

dove St1=1{εt1<0}S_{t-1}^- = \mathbf{1}\{\varepsilon_{t-1} < 0\} e St1+=1St1S_{t-1}^+ = 1 - S_{t-1}^-. La logica: se il modello simmetrico ha già catturato tutto, il segno e la dimensione dello shock di ieri non dovrebbero prevedere il residuo al quadrato di oggi, quindi a1=a2=a3=0a_1 = a_2 = a_3 = 0. I singoli test tt sono i test di sign-bias (a1a_1), negative-size-bias (a2a_2) e positive-size-bias (a3a_3); un test FF congiunto su tutti e tre è quello omnibus. Un a1a_1 o a2a_2 significativo indica che gli shock negativi sono sistematicamente prezzati male dal modello simmetrico: il tuo segnale che GJR o EGARCH aiuteranno.

import statsmodels.api as sm

def sign_bias_test(symmetric_res):
    """Engle-Ng sign-bias tests on a fitted symmetric GARCH result."""
    z = symmetric_res.std_resid.dropna()
    z2 = (z ** 2).values[1:]
    eps_lag = z.values[:-1]                      # standardized shock proxy
    neg = (eps_lag < 0).astype(float)
    X = np.column_stack([
        np.ones_like(eps_lag),                   # intercept
        neg,                                     # sign bias
        neg * eps_lag,                           # negative size bias
        (1 - neg) * eps_lag,                     # positive size bias
    ])
    ols = sm.OLS(z2, X).fit()
    names = ["const", "sign_bias", "neg_size_bias", "pos_size_bias"]
    for nm, coef, t, p in zip(names, ols.params, ols.tvalues, ols.pvalues):
        print(f"{nm:16s} coef={coef:+.4f}  t={t:+.2f}  p={p:.3f}")
    print(f"Joint F p-value: {ols.f_pvalue:.4f}")
    return ols

sign_bias_test(models["GARCH-N"])

Se il test FF congiunto è non significativo, hai licenza empirica di rimanere simmetrico e risparmiare due parametri. Se è significativo (l'esito comune per BTC/ETH) procedi verso GJR/EGARCH con la coscienza pulita, sapendo di modellare una caratteristica reale e non di rincorrere il rumore. Questa è la disciplina empirica che l'incipit richiedeva: non dare per scontata la storia della leva azionaria, testala.

GJR-GARCH: asimmetria tramite un termine a soglia

Il modello Glosten-Jagannathan-Runkle (1993), talvolta chiamato TGARCH o threshold GARCH, è la più piccola modifica possibile al GARCH(1,1) che consente alle cattive e alle buone notizie di avere effetti differenti. Ricordiamo la ricorsione simmetrica della varianza condizionata dalla Parte 1:

σt2=ω+αεt12+βσt12\sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2

GJR aggiunge un termine a soglia: una dose extra di varianza che si attiva solo dopo uno shock negativo.

σt2=ω+αεt12+γIt1εt12+βσt12\sigma_t^2 = \omega + \alpha\,\varepsilon_{t-1}^2 + \gamma\,I_{t-1}\,\varepsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2

dove It1I_{t-1} e l'indicatore

It1={1if εt1<00if εt10I_{t-1} = \begin{cases} 1 & \text{if } \varepsilon_{t-1} < 0 \\ 0 & \text{if } \varepsilon_{t-1} \geq 0 \end{cases}

Leggi la ricorsione per casi. Dopo uno shock positivo (εt10\varepsilon_{t-1} \geq 0), l'indicatore è zero e l'impatto dello shock al quadrato sulla varianza del periodo successivo è semplicemente α\alpha. Dopo uno shock negativo, l'indicatore è uno e l'impatto è α+γ\alpha + \gamma. Il parametro γ\gamma è l'intera storia dell'asimmetria in un solo numero:

  • γ>0\gamma > 0: gli shock negativi aumentano la volatilità più degli shock positivi della stessa entità. Questo è l'effetto leva, ed è ciò che ci si aspetta di trovare in BTC/ETH la maggior parte delle volte.
  • γ=0\gamma = 0: il modello ricollassa nel GARCH(1,1) simmetrico. Un test del rapporto di verosimiglianza o un test tt su γ\gamma è quindi un test diretto del se l'asimmetria esista affatto.
  • γ<0\gamma < 0: gli shock positivi aumentano la volatilità di più: l'occasionale regime di melt-up cripto. Raro, ma non escluderlo a priori.

Positività e stazionarietà

Poiché σt2\sigma_t^2 è ancora costruita in modo additivo, abbiamo bisogno che ciascun termine rimanga non negativo. Le condizioni sufficienti di positività sono

ω>0,α0,α+γ0,β0.\omega > 0, \quad \alpha \geq 0, \quad \alpha + \gamma \geq 0, \quad \beta \geq 0.

Nota che a γ\gamma stesso è consentito essere negativo purché α+γ0\alpha + \gamma \geq 0, cosicché l'impatto post-cattiva-notizia non diventi mai negativo.

Per la stazionarietà in covarianza, assumi che le innovazioni zt=εt/σtz_t = \varepsilon_t/\sigma_t siano standardizzate con una distribuzione simmetrica attorno allo zero, cosicché P(εt1<0)=1/2P(\varepsilon_{t-1} < 0) = 1/2 è l'indicatore contribuisca in media γ/2\gamma/2. La condizione di stazionarietà diventa

α+β+12γ<1.\alpha + \beta + \tfrac{1}{2}\gamma < 1.

La varianza incondizionata (di lungo periodo) e allora

σˉ2=ω1αβ12γ.\bar{\sigma}^2 = \frac{\omega}{1 - \alpha - \beta - \tfrac{1}{2}\gamma}.

Questo e l'analogo GJR del risultato della Parte 1 σˉ2=ω/(1αβ)\bar{\sigma}^2 = \omega/(1-\alpha-\beta), con il termine extra 12γ\tfrac{1}{2}\gamma che tiene conto del contributo medio dell'emivita della leva. Se la tua distribuzione delle innovazioni è asimmetrica (la skew-tt di Hansen, più sotto), l'1/21/2 è sostituito dalla probabilità effettiva che zt<0z_t < 0, ma 1/21/2 è il riferimento standard usato per la persistenza riportata.

EGARCH: modellare la log-varianza, senza vincoli di positività

GJR ti tiene dentro una camicia di forza di positività della varianza: ogni combinazione di parametri deve essere controllata rispetto a vincoli di disuguaglianza, il che è fastidioso durante l'ottimizzazione e peggiore durante la ri-stima su finestra mobile, quando una finestra occasionalmente vaga in una regione non ammissibile. L'Exponential GARCH di Nelson (1991) aggira interamente il problema modellando il logaritmo della varianza condizionata. Poiché logσt2\log \sigma_t^2 può essere qualunque numero reale, σt2=exp(logσt2)\sigma_t^2 = \exp(\log \sigma_t^2) è automaticamente positivo qualunque siano i parametri. Nessun vincolo da imporre.

Scrivi la ricorsione in termini dell'innovazione standardizzata zt1=εt1/σt1z_{t-1} = \varepsilon_{t-1}/\sigma_{t-1}:

logσt2=ω+βlogσt12+α(zt1Ezt1)+γzt1\log \sigma_t^2 = \omega + \beta \log \sigma_{t-1}^2 + \alpha\bigl(|z_{t-1}| - \mathbb{E}|z_{t-1}|\bigr) + \gamma\, z_{t-1}

Due termini trasportano lo shock, e separarli e l'idea centrale:

  • Il termine di magnitudine α(zt1Ezt1)\alpha(|z_{t-1}| - \mathbb{E}|z_{t-1}|) risponde alla dimensione dello shock, con il segno rimosso. Sottrarre Ezt1\mathbb{E}|z_{t-1}| lo centra, cosicché uno shock di magnitudine media non contribuisca nulla. Per una normale standard, Ez=2/π0.7979\mathbb{E}|z| = \sqrt{2/\pi} \approx 0.7979; per una Student-tt standardizzata il valore assoluto atteso è più piccolo e dipende da ν\nu, ma arch gestisce questo internamente.
  • Il termine di segno γzt1\gamma\, z_{t-1} è l'asimmetria. È lineare nell'innovazione con segno, cosicché un zt1z_{t-1} negativo spinge logσt2\log\sigma_t^2 nella direzione opposta rispetto a uno positivo.

La convenzione sul segno conta e trae in inganno. In questa parametrizzazione l'effetto leva (le cattive notizie aumentano la volatilità) corrisponde a γ<0\gamma < 0: uno shock negativo zt1<0z_{t-1} < 0 rende allora γzt1>0\gamma z_{t-1} > 0, aumentando la log-varianza. Questo è il segno opposto rispetto al γ>0\gamma > 0 di GJR. Leggi sempre la documentazione del modello stesso per la convenzione invece di assumerla; arch riporta EGARCH con la propria convenzione di segno, e la verifichiamo rispetto a una news impact curve più sotto invece di fidarci della memoria.

Poiché tutto è additivo nei logaritmi, la persistenza di un EGARCH(1,1) è governata dal singolo coefficiente autoregressivo β\beta su logσt12\log\sigma_{t-1}^2; la stazionarietà richiede solo β<1|\beta| < 1. È una condizione molto più pulita della disuguaglianza GJR, ed è un vantaggio pratico concreto quando ri-stimi su finestre mobili.

Una sottigliezza che vale la pena esplicitare: la risposta di EGARCH agli shock è esponenziale nell'innovazione (esponenzi alla fine), mentre GJR è quadratica. EGARCH reagisce dunque più violentemente ai grandi shock, una caratteristica utile nelle criptovalute, dove gli eventi di coda sono quelli che contano, ma anche una ragione per cui EGARCH può occasionalmente produrre previsioni di varianza implausibilmente grandi dopo una giornata di outlier. Nessuno dei due è universalmente migliore; scegli in base all'adattamento fuori campione e ai risk backtest, che è il punto di tutta questa serie.

La news impact curve

Il modo più pulito per vedere la differenza tra GARCH simmetrico, GJR ed EGARCH è la news impact curve (Engle e Ng, 1993): tieni σt1\sigma_{t-1} fisso al suo livello di lungo periodo e traccia la varianza condizionata del periodo successivo σt2\sigma_t^2 in funzione dell'ultimo shock εt1\varepsilon_{t-1}. Risponde alla domanda "dato uno shock di questa dimensione e segno, di quanto il modello alza la volatilità di domani?"

  • GARCH simmetrico produce una parabola simmetrica centrata sullo zero. Uno shock di 5%-5\% e uno di +5%+5\% atterrano alla stessa altezza. Questo è precisamente il difetto che stiamo correggendo.
  • GJR produce una parabola con un punto angoloso in zero, più ripida a sinistra (shock negativi) che a destra quando γ>0\gamma > 0. Le due metà hanno curvatura α+γ\alpha+\gamma e α\alpha rispettivamente.
  • EGARCH produce una forma a V asimmetrica ed esponenziale: i due bracci hanno pendenze differenti a causa del termine γz\gamma z, e l'intera curva sale più velocemente di una parabola per via dell'esponenziazione finale.

Tracciamo tutte e tre a partire dai parametri stimati più avanti, nella sezione di implementazione: è la singola diagnostica più utile per comunicare cosa ti compra l'asimmetria.

Code pesanti: innovazioni Student-t e skew-t

L'asimmetria corregge la risposta del modello al segno degli shock. Non fa nulla riguardo alla distribuzione degli shock stessi. Il GARCH semplice assume ztN(0,1)z_t \sim \mathcal{N}(0,1), e quell'assunzione è quasi sempre sbagliata per le criptovalute. Anche dopo che il GARCH ha rimosso il clustering della volatilità, i residui standardizzati zt=εt/σtz_t = \varepsilon_t / \sigma_t conservano un eccesso di curtosi: sono a code pesanti. Una verosimiglianza gaussiana, adattandosi alle spalle della distribuzione, sottopesa quanto spesso si verifichi effettivamente una giornata standardizzata a 44, 55 o 66 sigma.

La conseguenza per il rischio è diretta. Un VaR gaussiano al 99% usa il quantile Φ1(0.01)2.326\Phi^{-1}(0.01) \approx -2.326, quindi prevede VaR0.992.326σt\text{VaR}_{0.99} \approx 2.326\,\sigma_t. Se la vera distribuzione standardizzata è Student-tt con, diciamo, ν=5\nu = 5 gradi di libertà, il vero quantile all'1% è vicino a 3.36-3.36: il VaR gaussiano è ottimistico di circa il 44%44\% a quel livello di confidenza. Lo violerai molto più dell'1% delle volte e sarai sistematicamente sorpreso da giornate "impossibili". Questo non è una stranezza cripto; Bollerslev (1987) introdusse il tt-GARCH proprio perché i residui azionari e valutari mostravano le stesse code pesanti. Le criptovalute sono solo una versione più estrema dello stesso problema.

Student-t standardizzata

La densità Student-tt ha un parametro di gradi di libertà ν>2\nu > 2 che controlla lo spessore delle code: un ν\nu piccolo significa code pesanti, e per ν\nu \to \infty la tt converge alla gaussiana. Il tranello è che la distribuzione tνt_\nu grezza ha varianza ν/(ν2)1\nu/(\nu-2) \neq 1, quindi dobbiamo standardizzarla a varianza unitaria prima di usarla come innovazione, altrimenti il "σt\sigma_t" nella ricorsione GARCH non sarebbe effettivamente la deviazione standard condizionata.

L'innovazione Student-tt standardizzata con varianza unitaria ha densità

f(z;ν)=Γ ⁣(ν+12)π(ν2)  Γ ⁣(ν2)(1+z2ν2)ν+12,ν>2.f(z;\nu) = \frac{\Gamma\!\left(\frac{\nu+1}{2}\right)}{\sqrt{\pi(\nu-2)}\;\Gamma\!\left(\frac{\nu}{2}\right)}\left(1 + \frac{z^2}{\nu-2}\right)^{-\frac{\nu+1}{2}}, \qquad \nu > 2.

Nota il (ν2)(\nu-2) all'interno: quella è la standardizzazione, che riscala cosicché Var(z)=1\mathrm{Var}(z)=1. Il contributo alla log-verosimiglianza di una osservazione, data la varianza condizionata GARCH σt2\sigma_t^2 e zt=εt/σtz_t = \varepsilon_t/\sigma_t, è

t=logΓ ⁣(ν+12)logΓ ⁣(ν2)12log(π(ν2))12logσt2ν+12log ⁣(1+zt2ν2).\ell_t = \log\Gamma\!\left(\tfrac{\nu+1}{2}\right) - \log\Gamma\!\left(\tfrac{\nu}{2}\right) - \tfrac{1}{2}\log\bigl(\pi(\nu-2)\bigr) - \tfrac{1}{2}\log\sigma_t^2 - \frac{\nu+1}{2}\log\!\left(1 + \frac{z_t^2}{\nu-2}\right).

Il termine 12logσt2-\tfrac{1}{2}\log\sigma_t^2 è lo Jacobiano della trasformazione da εt\varepsilon_t a ztz_t, lo stesso termine che hai visto nella verosimiglianza del GARCH gaussiano nella Parte 1. Cambia solo la forma. Massimizzare tt\sum_t \ell_t congiuntamente sui parametri GARCH e su ν\nu è esattamente ciò che fa arch quando passi dist='t'.

Il ν\nu stimato è di per sé informativo. Per i rendimenti giornalieri di BTC/ETH ti trovi tipicamente nell'intervallo ν36\nu \approx 3\text{–}6: code pesanti, ma con varianza finita (che richiede ν>2\nu > 2) e di solito curtosi finita (che richiede ν>4\nu > 4). Se il tuo ν\nu stimato scende sotto 4, tieni presente che la curtosi campionaria è tecnicamente infinita nel modello e alcuni stimatori diventano instabili; è un segnale per guardare con attenzione agli outlier e alla qualità dei dati.

La skew-t di Hansen

La Student-tt è a code pesanti ma comunque simmetrica: le code sinistra e destra sono ugualmente pesanti. I residui dei rendimenti cripto sono spesso anche asimmetrici: la coda sinistra (i crolli) è più pesante di quella destra. La skew-tt di Hansen (1994) generalizza la tt standardizzata con un parametro di asimmetria λ(1,1)\lambda \in (-1, 1) accanto a ν\nu:

f(z;ν,λ)={bc(1+1ν2(bz+a1λ)2)ν+12z<a/bbc(1+1ν2(bz+a1+λ)2)ν+12za/bf(z;\nu,\lambda) = \begin{cases} b\,c\left(1 + \dfrac{1}{\nu-2}\left(\dfrac{bz+a}{1-\lambda}\right)^2\right)^{-\frac{\nu+1}{2}} & z < -a/b \\[2mm] b\,c\left(1 + \dfrac{1}{\nu-2}\left(\dfrac{bz+a}{1+\lambda}\right)^2\right)^{-\frac{\nu+1}{2}} & z \geq -a/b \end{cases}

dove le costanti a=4λcν2ν1a = 4\lambda c\,\frac{\nu-2}{\nu-1}, b2=1+3λ2a2b^2 = 1 + 3\lambda^2 - a^2 e c=Γ(ν+12)π(ν2)Γ(ν2)c = \frac{\Gamma(\frac{\nu+1}{2})}{\sqrt{\pi(\nu-2)}\,\Gamma(\frac{\nu}{2})} sono scelte in modo che zz abbia media nulla e varianza unitaria per ogni (ν,λ)(\nu,\lambda) valido. La distribuzione si divide in z=a/bz = -a/b, usando un diverso fattore di scala in ciascun tratto per piegare più massa in una coda.

Interpretazione: λ<0\lambda < 0 dà una distribuzione asimmetrica a sinistra (ribasso più pesante), che è il risultato usuale per le criptovalute e ciò che ti aspetteresti di abbinare a un effetto leva. λ=0\lambda = 0 recupera la Student-tt simmetrica, quindi un test su λ=0\lambda = 0 ti dice se il termine di asimmetria stia comprando qualcosa. In arch questo è dist='skewt', che stima sia ν\nu che λ\lambda. La ricompensa è un VaR il cui quantile di coda sinistra è onestamente più pesante di quello di coda destra: esattamente ciò che vuoi quando le perdite a cui stai cercando di sopravvivere sono asimmetriche. Questo si collega direttamente all'asimmetria tra perdita e profitto negli esiti delle posizioni: un drawdown del x%x\% richiede più del x%x\% per recuperare, quindi modellare male la coda sinistra è più costoso che modellare male quella destra.

Implementazione in Python

Ora stimiamo tutto questo con la libreria arch. La configurazione rispecchia la Parte 1: scarica i rendimenti giornalieri, scala per 100 per il condizionamento numerico (gli ottimizzatori GARCH si comportano male quando i rendimenti sono O(0.01)O(0.01)) e stima con una media costante. Se vuoi dati intraday o un diverso modello per la media, il meccanismo e identico.

Configurazione e dati

import numpy as np
import pandas as pd
from arch import arch_model
from arch.univariate import GARCH, EGARCH, ConstantMean, StudentsT, SkewStudent
from scipy import stats

def fetch_returns(symbol="BTC-USD", start="2019-01-01", end="2025-12-31"):
    """Daily log returns, in percent (scaled x100 for the optimizer)."""
    import yfinance as yf
    px = yf.download(symbol, start=start, end=end)["Close"].dropna()
    ret = 100.0 * np.log(px / px.shift(1)).dropna()
    ret.name = symbol
    return ret

r = fetch_returns("BTC-USD")
print(f"{len(r)} daily observations, "
      f"annualized vol ~ {r.std() * np.sqrt(365):.0f}%")

Le criptovalute vengono scambiate 24/7, quindi annualizziamo con 365, non 252: una fonte piccola ma ricorrente di confusione quando confronti uno Sharpe o una vol cripto con i numeri di un desk azionario.

Stima di quattro modelli

Lo schema in arch: vol='Garch' con p=1, q=1 è il GARCH simmetrico; aggiungere o=1 attiva il termine a soglia GJR; vol='EGARCH' passa al modello a log-varianza. La distribuzione delle innovazioni si imposta con dist: 'normal', 't', 'skewt'.

def fit(returns, vol="Garch", p=1, o=0, q=1, dist="normal"):
    am = arch_model(returns, mean="Constant",
                    vol=vol, p=p, o=o, q=q, dist=dist)
    res = am.fit(disp="off")
    return res

models = {
    "GARCH-N":    fit(r, vol="Garch",  o=0, dist="normal"),  # Part 1 baseline
    "GJR-t":      fit(r, vol="Garch",  o=1, dist="t"),        # asymmetry + fat tails
    "EGARCH-t":   fit(r, vol="EGARCH", o=1, dist="t"),        # log-variance asymmetry
    "GJR-skewt":  fit(r, vol="Garch",  o=1, dist="skewt"),    # + skew
}

for name, res in models.items():
    print(f"\n===== {name} =====")
    print(res.summary().tables[1])   # parameter table

Per vol='EGARCH', l'argomento o controlla il termine asimmetrico (γz\gamma z) e p/q controllano i termini di magnitudine e di ritardo; o=1, p=1, q=1 è l'EGARCH(1,1) standard. Un tranello: i nomi dei parametri di EGARCH in arch sono le stesse lettere, ma la convenzione di segno sul termine di asimmetria è quella di Nelson, quindi una stima negativa è l'effetto leva. Lo verifichiamo dalla news impact curve piuttosto che dalla memoria.

Leggere la stima GJR

Una tabella di parametri GJR-tt ha più o meno questo aspetto (valori illustrativi, non un esperimento riportato: ri-stima sui tuoi dati):

                  coef    std err       t      P>|t|
omega           0.0480     0.017     2.82     0.005
alpha[1]        0.0620     0.018     3.44     0.001
gamma[1]        0.0910     0.028     3.25     0.001
beta[1]         0.8850     0.021    42.1      0.000
nu              4.30       0.55      7.82     0.000

Come leggerla:

  • gamma[1] = 0.091 con una statistica tt superiore a 3 è un effetto leva statisticamente significativo. Dopo uno shock negativo l'impatto dello shock al quadrato è α+γ=0.062+0.091=0.153\alpha + \gamma = 0.062 + 0.091 = 0.153; dopo uno shock positivo è solo α=0.062\alpha = 0.062. Le cattive notizie muovono la volatilità di questo modello circa 2.5×2.5\times più delle buone notizie della stessa entità.
  • nu = 4.3 conferma le code pesanti, ben lontano dalla gaussiana (ν\nu \to \infty), è abbastanza basso da rendere il quarto momento appena finito. Un VaR gaussiano su questa serie sarebbe gravemente ottimistico.
  • La persistenza è α+β+12γ=0.062+0.885+0.04550.993\alpha + \beta + \tfrac{1}{2}\gamma = 0.062 + 0.885 + 0.0455 \approx 0.993: molto alta, come al solito per le cripto giornaliere: gli shock decadono lentamente e la volatilità è fortemente raggruppata.

La singola riga più importante da controllare è quella di γ\gamma. Se il suo pp-value è grande, il termine asimmetrico non si sta guadagnando il posto su questo asset e questa finestra, e dovresti preferire il modello simmetrico più semplice. Questa è disciplina di selezione del modello, non decorazione: più su questo sotto.

Confrontare i modelli tramite criteri di informazione

La log-verosimiglianza migliora sempre quando aggiungi parametri, quindi non puoi selezionare basandoti sulla sola log-verosimiglianza. Usa AIC/BIC, che penalizzano il numero di parametri (BIC più aggressivamente):

def compare(models: dict) -> pd.DataFrame:
    rows = []
    for name, res in models.items():
        rows.append({
            "model": name,
            "n_params": len(res.params),
            "loglik": res.loglikelihood,
            "AIC": res.aic,
            "BIC": res.bic,
        })
    df = pd.DataFrame(rows).set_index("model")
    return df.sort_values("BIC")

print(compare(models))

Regole empiriche di interpretazione: un miglioramento del BIC di più di ~6 rispetto alla base è forte evidenza che la struttura extra sia reale; una differenza di 1-2 è rumore. Se GJR-t batte GARCH-N di oltre 30 punti BIC ma GJR-skewt batte GJR-t di appena 1, tieni la tt e scarta l'asimmetria: il parametro di skew non si sta ripagando su questi dati. Non leggere AIC/BIC come un sostituto della validazione fuori campione; premiano l'adattamento in campione aggiustato per la complessità, che è necessario ma non sufficiente. Il vero test è il backtest del VaR e, in ultima analisi, la valutazione walk-forward.

Tracciare la news impact curve

Questo e il grafico che ripaga: rende visibile l'asimmetria e verifica la convenzione di segno di EGARCH.

import matplotlib.pyplot as plt

def news_impact_curve(res, shock_grid):
    """
    Next-period conditional variance as a function of the last shock,
    holding sigma_{t-1} at the model's unconditional level.
    Works for symmetric GARCH, GJR (o=1), and EGARCH.
    """
    p = res.params
    vol_name = res.model.volatility.__class__.__name__
    sigma2_bar = np.mean(res.conditional_volatility ** 2)

    omega = p["omega"]
    alpha = p.get("alpha[1]", 0.0)
    gamma = p.get("gamma[1]", 0.0)
    beta  = p.get("beta[1]", 0.0)

    if vol_name == "EGARCH":
        sig_prev = np.sqrt(sigma2_bar)
        z = shock_grid / sig_prev
        E_abs = np.sqrt(2 / np.pi)   # approx; arch uses the fitted dist's E|z|
        log_s2 = (omega + beta * np.log(sigma2_bar)
                  + alpha * (np.abs(z) - E_abs) + gamma * z)
        return np.exp(log_s2)
    else:
        ind = (shock_grid < 0).astype(float)
        return omega + (alpha + gamma * ind) * shock_grid**2 + beta * sigma2_bar

shocks = np.linspace(-8, 8, 401)   # daily % shocks
plt.figure(figsize=(8, 5))
for name in ["GARCH-N", "GJR-t", "EGARCH-t"]:
    nic = news_impact_curve(models[name], shocks)
    plt.plot(shocks, nic, label=name, lw=2)
plt.axvline(0, color="gray", lw=0.8, ls="--")
plt.xlabel("Last shock  $\\varepsilon_{t-1}$  (daily %)")
plt.ylabel("Next-period conditional variance  $\\sigma_t^2$")
plt.title("News impact curve: symmetric vs GJR vs EGARCH (BTC)")
plt.legend()
plt.tight_layout()

Quando lo esegui, la curva simmetrica GARCH-N è una parabola pulita centrata sullo zero: uno shock di 6%-6\% e uno di +6%+6\% danno varianza identica. GJR-t è una parabola con un punto angoloso nell'origine, più alta sul braccio sinistro. EGARCH-t è la V esponenziale, e se il suo braccio sinistro sta sopra quello destro hai confermato l'effetto leva e la convenzione di segno in un colpo d'occhio. Se il braccio sinistro di EGARCH sta sotto quello destro, o γ\gamma è stimato positivo (un regime di up-vol) oppure hai il segno invertito: il grafico ti dice quale senza alcuna congettura.

Un confronto affiancato dei quattro modelli

Prima di passare al rischio, aiuta tenere i quattro modelli uno accanto all'altro. Ogni riga è una decisione di progettazione, e le colonne mostrano cosa quella decisione costa e cosa compra.

Proprietà GARCH-N GJR-t EGARCH-t GJR-skewt
Asimmetria (segno dello shock) nessuna soglia γIε2\gamma I\varepsilon^2 con segno γz\gamma z soglia γIε2\gamma I\varepsilon^2
Forma della coda dell'innovazione gaussiana Student-tt Student-tt skew-tt
Asimmetria dell'innovazione no no no sì (λ\lambda)
Vincoli di positività sì (α+γ0\alpha+\gamma\ge0) nessuno (forma log)
Condizione di stazionarietà α+β<1\alpha+\beta<1 α+β+12γ<1\alpha+\beta+\tfrac12\gamma<1 β<1\lvert\beta\rvert<1 α+β+P(z<0)γ<1\alpha+\beta+P(z<0)\gamma<1
Parametri extra vs base 0 γ,ν\gamma,\nu γ,ν\gamma,\nu γ,ν,λ\gamma,\nu,\lambda
Verdetto tipico cripto fallisce il backtest VaR forte, robusto forte, robusto marginale su GJR-t

Lo schema da interiorizzare: il salto dalla colonna 1 alla colonna 2 (aggiungere insieme asimmetria e code pesanti) è dove risiede quasi tutto il miglioramento nella calibrazione del rischio. I raffinamenti successivi (la forma funzionale di EGARCH, il termine di skew) sono reali ma di secondo ordine, e su molte serie cripto sono dentro il rumore. Spendi il tuo budget di modellazione sul primo salto e sii scettico sul resto.

Applicazione al rischio: VaR ed Expected Shortfall

Stimare un modello di volatilità più sofisticato vale la pena solo se migliora una decisione. La decisione più pulita da migliorare è la previsione a un passo del rischio di coda: quanto può andar male domani? Produciamo un Value-at-Risk e un Expected Shortfall a un giorno (noto anche come Conditional VaR, che la pipeline di portafoglio HRP/CVaR usa come funzione obiettivo) direttamente dalla previsione GARCH-tt/skew-tt stimata.

Dalla distribuzione condizionata al VaR

Il meccanismo GARCH fornisce una previsione a un passo della media condizionata μt+1\mu_{t+1} e della deviazione standard condizionata σt+1\sigma_{t+1}. Il rendimento è modellato come rt+1=μt+1+σt+1zt+1r_{t+1} = \mu_{t+1} + \sigma_{t+1} z_{t+1} con zt+1z_{t+1} estratto dalla distribuzione standardizzata stimata (gaussiana, tt o skew-tt). Quindi il quantile α\alpha del rendimento è solo una trasformazione affine del quantile α\alpha della distribuzione standardizzata:

VaRα(t+1)=(μt+1+σt+1Fz1(1α))\text{VaR}_{\alpha}(t+1) = -\Bigl(\mu_{t+1} + \sigma_{t+1}\, F_z^{-1}(1-\alpha)\Bigr)

dove Fz1F_z^{-1} è il quantile (CDF inversa) dell'innovazione standardizzata e il segno meno iniziale segue la convenzione che il VaR sia un numero di perdita positivo. Per un VaR al 99%, α=0.99\alpha = 0.99 e inserisci Fz1(0.01)F_z^{-1}(0.01). L'intero beneficio della tt/skew-tt si manifesta qui: Fz1(0.01)F_z^{-1}(0.01) è più negativo del 2.326-2.326 gaussiano, quindi il VaR è onestamente più grande.

Expected Shortfall

Il VaR ti dice la soglia; non dice nulla su quanto sia grave la violazione quando accade. L'Expected Shortfall (la perdita media condizionata al superamento del VaR) lo fa, ed è coerente (subadditivo), motivo per cui è la misura di rischio dietro l'ottimizzazione CVaR e per cui Basilea vi si è spostata. Per un modello location-scale,

ESα(t+1)=(μt+1+σt+1E ⁣[zzFz1(1α)]).\text{ES}_{\alpha}(t+1) = -\Bigl(\mu_{t+1} + \sigma_{t+1}\, \mathbb{E}\!\left[z \mid z \leq F_z^{-1}(1-\alpha)\right]\Bigr).

Il termine di aspettativa condizionata di coda E[zzq]\mathbb{E}[z \mid z \le q] ha forme chiuse per le distribuzioni standard. Per la gaussiana, con q=Φ1(1α)q = \Phi^{-1}(1-\alpha),

E[zzq]=ϕ(q)1α,\mathbb{E}[z \mid z \le q] = -\frac{\phi(q)}{1-\alpha},

dove ϕ\phi e la pdf normale standard. Per la Student-tt standardizzata con ν\nu gradi di libertà e q=tν1(1α)q = t_\nu^{-1}(1-\alpha) (sulla scala standardizzata), l'aspettativa di coda e

E[zzq]=ν+q2ν1gν(q)1α,\mathbb{E}[z \mid z \le q] = -\frac{\nu + q^2}{\nu - 1}\cdot\frac{g_\nu(q)}{1-\alpha},

dove gνg_\nu e la pdf tt standardizzata. L'Expected Shortfall della tt supera quello gaussiano più di quanto faccia il VaR, perché la coda della tt non è solo più lontana: è più pesante, quindi la perdita media oltre la soglia è sproporzionatamente grande. Quel divario extra è il numero che un modello gaussiano ti nasconde.

Calcolare VaR ed ES da un modello arch stimato

Le distribuzioni di arch espongono un metodo ppf (quantile), quindi possiamo ottenere il quantile standardizzato direttamente ed evitare di riderivare qualsiasi cosa. Per l'ES integriamo numericamente, il che è robusto e funziona in modo uniforme su normal/t/skewt.

from scipy import integrate

def var_es_forecast(res, alpha=0.99):
    """
    One-step-ahead VaR and ES at level alpha, on the same (x100) scale
    as the returns fed to the model. Divide by 100 for fractional units.
    """
    fc = res.forecast(horizon=1, reindex=False)
    mu = fc.mean.iloc[-1, 0]
    sigma = np.sqrt(fc.variance.iloc[-1, 0])

    dist = res.model.distribution          # StudentsT / SkewStudent / Normal
    dp = [res.params[k] for k in res.params.index
          if k in ("nu", "eta", "lambda", "lam")]

    z_q = dist.ppf(1 - alpha, dp) if dp else dist.ppf(1 - alpha)
    z_q = float(np.atleast_1d(z_q)[0])

    def pdf(z):
        arr = np.atleast_1d(z).astype(float)
        lp = dist.loglikelihood(dp, arr, np.ones_like(arr), individual=True) \
             if dp else dist.loglikelihood([], arr, np.ones_like(arr),
                                           individual=True)
        return np.exp(lp)

    num, _ = integrate.quad(lambda z: z * pdf(z), -30, z_q, limit=200)
    es_z = num / (1 - alpha)               # E[z | z <= z_q]

    var = -(mu + sigma * z_q)
    es  = -(mu + sigma * es_z)
    return {"mu": mu, "sigma": sigma, "z_q": z_q,
            "VaR": var, "ES": es}

for name in ["GARCH-N", "GJR-t", "GJR-skewt"]:
    out = var_es_forecast(models[name], alpha=0.99)
    print(f"{name:11s}  sigma={out['sigma']:.2f}%  "
          f"z_q={out['z_q']:+.2f}  "
          f"VaR99={out['VaR']:.2f}%  ES99={out['ES']:.2f}%")

La colonna z_q e l'intera storia in un solo numero. Il modello gaussiano usa zq2.33z_q \approx -2.33; la tt con ν4.3\nu \approx 4.3 usa qualcosa vicino a 3.3-3.3; la skew-tt spinge il quantile di sinistra ancora più in là mentre tira dentro quello di destra. Stesso σt+1\sigma_{t+1}, VaR materialmente più grande. Se hai eseguito il VaR gaussiano sulle criptovalute, questo è il divario che hai assorbito silenziosamente.

Un passo contro multi-passo: un'avvertenza

Tutto quanto sopra è una previsione a un giorno, ed è lì che il VaR GARCH è più pulito. Due cose complicano gli orizzonti più lunghi e dovresti conoscerle prima di estrapolare.

Primo, le previsioni di varianza tornano verso la media. La varianza condizionata a hh passi da un GARCH stazionario converge verso il livello incondizionato σˉ2\bar\sigma^2 al crescere di hh, e la varianza cumulata a hh giorni e la somma delle previsioni per singolo passo: non è h×σt+12h\times\sigma_{t+1}^2 a meno che la volatilità non sia alla sua media di lungo periodo. L'ingenuo scaling "radice del tempo" VaR(h)=hVaR(1)\text{VaR}(h) = \sqrt{h}\,\text{VaR}(1) ignora questo ritorno alla media ed è sbagliato proprio dopo uno shock, quando più hai bisogno del numero. Usa il percorso di varianza multi-passo del modello stesso.

Secondo, la distribuzione di un rendimento su più giorni non ha la stessa forma dell'innovazione a un giorno. Sommare diversi shock giornalieri distribuiti come tt (attraverso la ricorsione GARCH non lineare) non dà una distribuzione tt all'orizzonte di hh giorni; non c'è una forma chiusa pulita. Per il VaR su più giorni la via onesta è la simulazione: estrai percorsi di innovazioni dalla distribuzione standardizzata stimata, falli passare attraverso la ricorsione GARCH per ottenere percorsi di rendimento simulati, aggrega a rendimenti su hh giorni e leggi il quantile empirico. Questo gestisce anche naturalmente il caso skew-tt, dove non esiste affatto un quantile analitico multi-orizzonte. Le formule analitiche a un passo di questo articolo sono esatte; tratta qualunque scorciatoia multi-passo come un'approssimazione da validare.

Backtest del VaR: Kupiec e Christoffersen

Una previsione di VaR è un'affermazione probabilistica: "la perdita supererà questa soglia solo in una frazione (1α)(1-\alpha) dei giorni". La testi contando le violazioni (giorni in cui la perdita realizzata ha superato il VaR previsto) su una valutazione walk-forward e controllando due cose. Primo, il tasso di violazioni è corretto? Secondo, le violazioni sono indipendenti, o si raggruppano (il che significa che il modello fallisce esattamente quando conta, durante le impennate di volatilità)?

Sia It=1{perditat>VaRt}I_t = \mathbf{1}\{\text{perdita}_t > \text{VaR}_t\} la sequenza delle violazioni, N=ItN = \sum I_t il numero di violazioni su TT giorni, e π^=N/T\hat{\pi} = N/T il tasso osservato. Tasso obiettivo p=1αp = 1-\alpha.

Il test di copertura incondizionata di Kupiec (1995) controlla π^p\hat\pi \approx p tramite un rapporto di verosimiglianza:

LRuc=2log ⁣[pN(1p)TNπ^N(1π^)TN]    χ12.LR_{uc} = -2\log\!\left[\frac{p^{N}(1-p)^{T-N}}{\hat\pi^{N}(1-\hat\pi)^{T-N}}\right] \;\sim\; \chi^2_1.

Il test di indipendenza di Christoffersen (1998) controlla che una violazione di oggi non sia prevista da una violazione di ieri. Sia nijn_{ij} il conteggio delle transizioni dallo stato ii allo stato jj nella sequenza delle violazioni, π01=n01/(n00+n01)\pi_{01} = n_{01}/(n_{00}+n_{01}), π11=n11/(n10+n11)\pi_{11} = n_{11}/(n_{10}+n_{11}) e π=(n01+n11)/T\pi = (n_{01}+n_{11})/T. Allora

LRind=2log ⁣[(1π)n00+n10πn01+n11(1π01)n00π01n01(1π11)n10π11n11]    χ12.LR_{ind} = -2\log\!\left[\frac{(1-\pi)^{n_{00}+n_{10}}\,\pi^{\,n_{01}+n_{11}}}{(1-\pi_{01})^{n_{00}}\pi_{01}^{n_{01}}(1-\pi_{11})^{n_{10}}\pi_{11}^{n_{11}}}\right] \;\sim\; \chi^2_1.

I due si combinano nel test di copertura condizionata LRcc=LRuc+LRindχ22LR_{cc} = LR_{uc} + LR_{ind} \sim \chi^2_2, che controlla simultaneamente il tasso corretto e l'indipendenza. Un modello può superare Kupiec (numero corretto di violazioni) e tuttavia fallire Christoffersen (tutte ammassate in una settimana di crollo): quella è la modalità di fallimento che vuoi più di tutto individuare, perché le violazioni raggruppate sono quelle che fanno saltare un conto.

from scipy.stats import chi2

def var_backtest(losses, var, p):
    """
    losses, var: 1D arrays, same length; loss>0 is a loss, var>0 threshold.
    p = 1 - alpha (target violation rate, e.g. 0.01 for 99% VaR).
    Returns Kupiec, Christoffersen-independence, and conditional-coverage LR.
    """
    losses = np.asarray(losses)
    var = np.asarray(var)
    I = (losses > var).astype(int)          # violation indicators
    T = len(I)
    N = int(I.sum())
    pi_hat = N / T

    eps = 1e-12
    ll_null = N * np.log(p + eps) + (T - N) * np.log(1 - p + eps)
    ll_alt  = N * np.log(pi_hat + eps) + (T - N) * np.log(1 - pi_hat + eps)
    LR_uc = -2 * (ll_null - ll_alt)
    p_uc = 1 - chi2.cdf(LR_uc, df=1)

    n00 = np.sum((I[:-1] == 0) & (I[1:] == 0))
    n01 = np.sum((I[:-1] == 0) & (I[1:] == 1))
    n10 = np.sum((I[:-1] == 1) & (I[1:] == 0))
    n11 = np.sum((I[:-1] == 1) & (I[1:] == 1))
    pi01 = n01 / (n00 + n01) if (n00 + n01) else 0.0
    pi11 = n11 / (n10 + n11) if (n10 + n11) else 0.0
    pi   = (n01 + n11) / (n00 + n01 + n10 + n11)

    def _ll(pi01, pi11, pi_pooled, use_pooled):
        if use_pooled:
            a = (n00 + n10) * np.log(1 - pi_pooled + eps)
            b = (n01 + n11) * np.log(pi_pooled + eps)
            return a + b
        return (n00 * np.log(1 - pi01 + eps) + n01 * np.log(pi01 + eps)
                + n10 * np.log(1 - pi11 + eps) + n11 * np.log(pi11 + eps))

    LR_ind = -2 * (_ll(pi01, pi11, pi, True) - _ll(pi01, pi11, pi, False))
    p_ind = 1 - chi2.cdf(LR_ind, df=1)

    LR_cc = LR_uc + LR_ind
    p_cc = 1 - chi2.cdf(LR_cc, df=2)

    return {
        "T": T, "violations": N,
        "obs_rate": pi_hat, "target_rate": p,
        "LR_uc": LR_uc, "p_uc": p_uc,
        "LR_ind": LR_ind, "p_ind": p_ind,
        "LR_cc": LR_cc, "p_cc": p_cc,
    }

Per generare gli input losses/var in modo onesto, ri-stimi (o almeno ri-prevedi) su una finestra espandente o mobile e registri il VaR a un passo per ogni giorno fuori campione, poi lo confronti con la perdita realizzata di quel giorno. Non fare mai il backtest del VaR in campione: un modello stimato sullo stesso crollo che gli si chiede di prevedere sembrerà molto migliore di quanto sia. Questa è la stessa disciplina della parità backtest-live: la valutazione deve usare solo informazioni disponibili al momento della decisione.

def walk_forward_var(returns, alpha=0.99, start=750, refit_every=25,
                     vol="Garch", o=1, dist="t"):
    """
    Expanding-window one-step VaR. Refit every `refit_every` days
    (refitting daily is correct but slow; every ~25 days is a common
    compromise -- validate the shortcut on your data).
    """
    losses, vars_ = [], []
    res = None
    for t in range(start, len(returns)):
        if res is None or (t - start) % refit_every == 0:
            am = arch_model(returns.iloc[:t], mean="Constant",
                            vol=vol, p=1, o=o, q=1, dist=dist)
            res = am.fit(disp="off")
        fc = res.forecast(horizon=1, reindex=False)
        mu = fc.mean.iloc[-1, 0]
        sig = np.sqrt(fc.variance.iloc[-1, 0])
        dp = [res.params[k] for k in res.params.index
              if k in ("nu", "eta", "lambda", "lam")]
        z_q = float(np.atleast_1d(
            res.model.distribution.ppf(1 - alpha, dp) if dp
            else res.model.distribution.ppf(1 - alpha))[0])
        var_t = -(mu + sig * z_q)
        vars_.append(var_t)
        losses.append(-returns.iloc[t])     # realized loss for that day
    return np.array(losses), np.array(vars_)

losses, vars_ = walk_forward_var(r, alpha=0.99, dist="t")
print(var_backtest(losses, vars_, p=0.01))

La lettura: un VaR al 99% ben calibrato mostra un tasso osservato vicino all'1%, un Kupiec non significativo (p_uc grande) e un Christoffersen non significativo (p_ind grande): nessun raggruppamento. In pratica l'esito onesto sulle criptovalute è che il GARCH-Normale fallisce Kupiec (troppe violazioni, p_uc minuscolo) mentre il GJR-tt o l'EGARCH-tt lo supera o ci va vicino. Quel contrasto è l'intero argomento di questo articolo reso come test d'ipotesi. Se anche il modello tt mostra violazioni raggruppate (p_ind piccolo), le tue dinamiche di volatilità sono ancora mal specificate, spesso un segno che ti serve una memoria più lunga (component/FIGARCH) o uno strato a regimi, il che si collega al rilevamento dei regimi con gli HMM.

Ordinare i modelli per perdita di coda, non solo passa/non passa

Kupiec e Christoffersen ti danno un verdetto binario: il modello è o non è rigettato. È necessario ma grossolano: due modelli possono entrambi "passare" mentre uno è significativamente più affilato. Per ordinare previsioni di VaR concorrenti, valutale con una funzione di perdita strettamente consistente per il quantile, la pinball loss (quantile loss):

Lτ(r,q)=(τ1{r<q})(rq),τ=1α,L_\tau(r, q) = \bigl(\tau - \mathbf{1}\{r < q\}\bigr)\,(r - q), \qquad \tau = 1-\alpha,

dove qq e il quantile del VaR (con segno) e rr il rendimento realizzato. Mediata sui giorni fuori campione, una pinball loss media più bassa significa un quantile meglio calibrato e più affilato; poiché la perdita e consistente per il τ\tau-quantile, minimizzarla non premia un modello per essere pigramente largo. Per confrontare due modelli formalmente, dai le differenze di perdita giornaliere in pasto a un test di Diebold-Mariano.

def pinball_loss(returns, var, alpha=0.99):
    tau = 1 - alpha
    q = -np.asarray(var)               # VaR is a positive loss; quantile is negative
    r = np.asarray(returns)
    hit = (r < q).astype(float)
    return np.mean((tau - hit) * (r - q))

Per l'Expected Shortfall in particolare, nota che l'ES non è elicitabile da solo (non esiste una funzione di perdita il cui minimizzatore sia l'ES da solo), il che è una genuina complicazione teorica: valuti l'ES congiuntamente con il VaR usando le regole di scoring di Fissler-Ziggel, oppure ripieghi sulla pratica più semplice di controllare che la magnitudine media della violazione corrisponda all'ES previsto dal modello. Un controllo dell'ES grezzo ma utile: tra i giorni di violazione del VaR, confronta la perdita realizzata media con l'ES previsto medio in quei giorni: dovrebbero essere vicini.

L'inquadramento regolamentare è l'approccio del semaforo di Basilea: su 250 giorni di negoziazione, 0-4 violazioni di un VaR al 99% è "verde" (accettabile), 5-9 è "giallo" (sotto esame), 10+ è "rosso" (il modello è rigettato e i moltiplicatori di capitale salgono). È un cugino più grossolano di Kupiec, ma è il linguaggio che i comitati di rischio parlano davvero, e vale la pena riportarlo accanto alle statistiche LR.

Considerazioni pratiche

Quando i parametri extra non ripagano

Il default onesto è lo scetticismo verso la complessità. Ogni parametro che aggiungi è una manopola che l'ottimizzatore può sovradattare, e il GARCH asimmetrico a code pesanti ne ha diversi. Indicazioni concrete:

  • Campioni illiquidi o corti. Con qualche centinaio di osservazioni giornaliere, l'errore standard su γ\gamma e λ\lambda sarà grande, e "rileverai" asimmetrie che sono rumore campionario. Su un'altcoin nuova o poco liquida, un GARCH-tt simmetrico è spesso il modello più complesso che i dati possono sostenere. Stimare uno skew-tt EGARCH su 200 giorni e prendersi in giro.
  • Il termine di skew spesso non copre il suo costo. In pratica, passare da Normale a tt è un miglioramento grande e affidabile (le code pesanti sono reali e forti). Passare da tt a skew-tt è spesso marginale, un guadagno di BIC di 1 o 2, a volte negativo. Aggiungi lo skew solo quando i dati lo chiedono chiaramente.
  • EGARCH contro GJR è di solito equivalente sui dati giornalieri. Codificano la stessa storia qualitativa con forme funzionali differenti. Scegli in base al backtest VaR fuori campione, non in base a quale abbia la log-verosimiglianza migliore in campione.
  • La frequenza più alta cambia la risposta. Su barre orarie o al minuto, la stagionalità intraday e la microstruttura dominano, e un GARCH in stile giornaliero è mal specificato a prescindere dall'asimmetria. Problema diverso, strumentazione diversa.

Questa è la stessa lezione della valutazione onesta senza edge robusto: un modello più complesso che non sopravvive al test fuori campione è peggiore di quello semplice che ha rimpiazzato, perché porta con sé l'illusione della precisione. Riporta il risultato negativo (" lo skew non ha aiutato su ETH") come una scoperta reale, e usa la walk-forward optimization come arbitro, non l'AIC in campione.

Queste sono le marginali su cui tutti gli altri costruiscono

I modelli qui non sono un punto d'arrivo; sono il mattone univariato per il meccanismo del rischio congiunto. L'articolo sui modelli copula per il rischio cripto congiunto usa esattamente EGARCH/GJR-tt come marginali GARCH-EVT prima di stimare una vine copula: stimi un GARCH asimmetrico a code pesanti per asset, estrai i residui standardizzati e solo allora modelli la dipendenza tra asset. Se la tua marginale è un GARCH gaussiano simmetrico, la copula eredita i suoi errori di coda per quanto buono sia il modello di dipendenza. Marginali spazzatura, VaR congiunto spazzatura.

Per il problema della volatilità multivariata (le correlazioni variabili nel tempo piuttosto che le varianze per singolo asset) vedi la Parte 3, DCC-GARCH, che sovrappone un modello a correlazione dinamica a queste stime univariate. E per trasformare una previsione di volatilità in dimensionamento della posizione e in un backtest di trading, la Parte 4 sul volatility targeting usa le previsioni di σt+1\sigma_{t+1} da questi stessi modelli per scalare l'esposizione inversamente al rischio previsto.

Un'alternativa distribution-free

Tutto nella sezione sul rischio poggia su un'assunzione parametrica: che i residui standardizzati seguano una tt o una skew-tt. Quell'assunzione è testabile e di solito ragionevole, ma può fallire. Se preferisci non impegnarti affatto su una forma di coda, la conformal prediction fornisce intervalli di previsione distribution-free con garanzie di copertura a campione finito, una filosofia genuinamente diversa che non fa alcuna affermazione sulla distribuzione delle innovazioni. I due approcci sono complementari: il GARCH-tt parametrico ti dà una densità condizionata completa (e dunque l'ES, che gli intervalli conformi non forniscono direttamente), mentre il conformal ti dà una copertura che vale anche quando la tua densità è sbagliata. In produzione, usare entrambi come controllo incrociato e un'assicurazione economica.

Igiene numerica e di workflow

  • Scala i rendimenti per 100. Gli ottimizzatori GARCH convergono in modo molto più affidabile su rendimenti in percentuale che su rendimenti frazionari grezzi. Ricorda di riscalare VaR/ES se riporti in unità frazionarie.
  • Osserva la persistenza. Se α+β+12γ\alpha + \beta + \tfrac12\gamma è stimata sopra ~0.999, il modello è quasi-integrato (in stile IGARCH); le previsioni tornano alla media estremamente lentamente e le previsioni di varianza a lungo orizzonte diventano inaffidabili. Non necessariamente sbagliato, ma segnalalo.
  • Fallimenti di convergenza su finestre mobili. La forma log di EGARCH evita i vincoli di positività ma può comunque non convergere su una finestra patologica. Avvolgi fit() in un try/except e ripiega sui parametri della finestra precedente invece di far crashare un backtest live.
  • Modello per la media. Abbiamo usato una media costante ovunque. Per la maggior parte delle cripto giornaliere la media condizionata è vicina allo zero e sommersa dalla volatilità; non spendere complessità del modello cercando di prevederla a meno che tu non abbia una ragione reale.

Riepilogo

  • Il GARCH(1,1) semplice ha due difetti strutturali: è simmetrico (reagisce a +x%+x\% e x%-x\% in modo identico perché gli shock entrano come ε2\varepsilon^2) e assume innovazioni gaussiane (sottostimando il prezzo delle code pesanti delle cripto). Entrambi costano denaro reale attraverso un VaR ottimistico.
  • GJR-GARCH aggiunge un termine a soglia γI(εt1<0)εt12\gamma\,I(\varepsilon_{t-1}<0)\,\varepsilon_{t-1}^2. Un γ>0\gamma > 0 significativo è l'effetto leva: le cattive notizie aumentano la volatilità di più. La positività richiede α+γ0\alpha+\gamma\ge0; la persistenza è α+β+12γ\alpha+\beta+\tfrac12\gamma.
  • EGARCH modella logσt2\log\sigma_t^2, quindi niente vincoli di positività e la stazionarietà è solo β<1|\beta|<1. L'asimmetria entra attraverso un termine con segno γzt1\gamma z_{t-1} (la leva è γ<0\gamma<0 in questa convenzione) separato da un termine di magnitudine zt1|z_{t-1}|.
  • La news impact curve (varianza del periodo successivo contro l'ultimo shock) rende visibile l'asimmetria e verifica la convenzione di segno di EGARCH in un colpo d'occhio.
  • Le innovazioni Student-tt (dist='t') correggono le code tramite un grado di libertà ν\nu (tipicamente 3-6 per le cripto); la skew-tt di Hansen (dist='skewt') aggiunge un'asimmetria λ\lambda per una coda sinistra più pesante. Passare da Normale a tt è un guadagno grande e affidabile; da tt a skew-tt è spesso marginale.
  • VaR ed ES seguono dalla distribuzione condizionata stimata: VaRα=(μt+1+σt+1Fz1(1α))\text{VaR}_\alpha = -(\mu_{t+1} + \sigma_{t+1}F_z^{-1}(1-\alpha)), con il quantile a code pesanti che rende il rischio onestamente più grande di quello gaussiano. L'ES (coerente, \approx CVaR) cattura la perdita media oltre il VaR.
  • Fai il backtest con Kupiec e Christoffersen. Kupiec controlla il tasso di violazioni; Christoffersen controlla che le violazioni non siano raggruppate. Un modello può superare l'uno e fallire l'altro: le violazioni raggruppate sono la modalità di fallimento pericolosa. Fai il backtest rigorosamente fuori campione.
  • Disciplina sulla complessità. Aggiungi asimmetria/skew solo quando sopravvive al BIC e a un backtest VaR fuori campione. Su serie corte o poco liquide, il modello più semplice di solito vince.

References:

  • Nelson, D. B. (1991). Conditional heteroskedasticity in asset returns: A new approach. Econometrica, 59(2), 347-370. DOI
  • Glosten, L. R., Jagannathan, R., & Runkle, D. E. (1993). On the relation between the expected value and the volatility of the nominal excess return on stocks. Journal of Finance, 48(5), 1779-1801. DOI
  • Bollerslev, T. (1987). A conditionally heteroskedastic time series model for speculative prices and rates of return. Review of Economics and Statistics, 69(3), 542-547. DOI
  • Hansen, B. E. (1994). Autoregressive conditional density estimation. International Economic Review, 35(3), 705-730. DOI
  • Engle, R. F., & Ng, V. K. (1993). Measuring and testing the impact of news on volatility. Journal of Finance, 48(5), 1749-1778. DOI
  • Christoffersen, P. F. (1998). Evaluating interval forecasts. International Economic Review, 39(4), 841-862. DOI
  • Kupiec, P. H. (1995). Techniques for verifying the accuracy of risk measurement models. Journal of Derivatives, 3(2), 73-84. DOI
  • Black, F. (1976). Studies of stock price volatility changes. Proceedings of the American Statistical Association, Business and Economic Statistics Section, 177-181.
  • McNeil, A. J., Frey, R., & Embrechts, P. (2015). Quantitative Risk Management: Concepts, Techniques and Tools (2nd ed.). Princeton University Press.
  • Sheppard, K. (2023). arch: Autoregressive conditional heteroskedasticity models in Python. GitHub.
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.

MarketMaker.cc Team

Ricerca Quantitativa e Strategia

Discuti su Telegram
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.