Targeting della Volatilita e Trading con Previsioni GARCH
Le prime tre parti di questa serie ti hanno insegnato a prevedere la volatilita. Abbiamo costruito un GARCH(1,1) univariato nella Parte 1, aggiunto leva finanziaria e code pesanti con GJR e innovazioni Student-t nella Parte 2, e modellato un'intera matrice di covarianza nel tempo con DCC-GARCH nella Parte 3. Alla fine di ognuna, abbiamo stampato un numero: la volatilita attesa di domani. E poi, se siamo onesti, ci siamo fermati — come se produrre la previsione fosse il punto.
Non lo e. Una previsione di volatilita non e un P&L. Nessuno e mai stato pagato per un punteggio QLIKE basso. Una previsione diventa preziosa solo nell'esatto momento in cui cambia una decisione che avresti altrimenti preso diversamente — quanto comprare, quando tagliare, quanto capitale allocare. Se la tua previsione non muove una posizione, la sua accuratezza statistica e un hobby privato.
Questa parte finale riguarda la chiusura di quel cerchio. Prendiamo le previsioni delle Parti 1-3 e le mettiamo al lavoro nella decisione piu pulita possibile: il volatility targeting — dimensionare una posizione in modo che la volatilita realizzata del portafoglio raggiunga un target costante. Poi facciamo la cosa a cui questo blog tiene piu di qualsiasi singolo modello: valutiamo onestamente. Confrontiamo GARCH con benchmark stupidi ma forti (volatilita realizzata rolling, EWMA), usiamo funzioni di perdita robuste al fatto che non possiamo mai osservare la vera volatilita, eseguiamo un backtest walk-forward con costi e senza look-ahead, e dichiariamo chiaramente cosa il vol targeting offre e cosa non offre. Spoiler: migliora i rendimenti aggiustati per il rischio e doma i drawdown molto piu affidabilmente di quanto fabbrichi alpha.
Perche il Volatility Targeting e il Test Giusto
Ci sono modi piu sofisticati di usare una previsione di volatilita — pricing delle opzioni, limiti VaR, hedging dinamico — ma il volatility targeting e quello che isola il valore della previsione con la minima contaminazione da altre scommesse. L'idea e un'unica equazione.
Detieni un asset rischioso con una direzione implicita da un segnale (per ora, solo "long"). Invece di una posizione fissa, scala l'esposizione inversamente alla volatilita prevista:
dove e la tua previsione della volatilita del periodo successivo (costruita usando solo dati disponibili fino a ), e e la volatilita annualizzata a cui vuoi che la strategia operi — diciamo 15% o 20%. Quando il modello prevede un mercato calmo, aumenti la leva verso (o oltre) 1.0; quando prevede una tempesta, riduci. La volatilita realizzata della posizione scalata e, al primo ordine,
ogni volta che . Quindi l'intera qualita dell'esercizio poggia su una cosa: quanto la tua previsione e vicina alla volatilita effettiva del periodo successivo . Una previsione migliore produce un profilo di volatilita realizzata piu piatto e — come vedremo — un miglior Sharpe ratio. Ecco perche questo e il test giusto. La previsione non e decorazione; e il denominatore.
Perche questo aumenta lo Sharpe e controlla i drawdown
Due fatti empirici fanno il lavoro qui.
La volatilita e molto piu prevedibile dei rendimenti. La direzione del rendimento BTC di domani e vicina a un lancio di moneta a frequenza giornaliera; la magnitudine no. La volatilita si raggruppa — grandi movimenti seguono grandi movimenti — che e l'intera ragione per cui esiste GARCH (la Parte 1 ha derivato la struttura AR(1)-in-varianza che codifica questo). Un di 0.4-0.6 per la varianza a un giorno e di routine; lo stesso numero per i rendimenti sarebbe un segnale da Renaissance. Il vol targeting sfrutta la quantita prevedibile e resta agnostico su quella imprevedibile.
Gli Sharpe ratio non sono costanti nel tempo; scendono quando la volatilita esplode. I regimi ad alta volatilita nel crypto — cascate di deleveraging, fallimenti di exchange, i giorni in cui tutto salta del 30% — tendono ad avere un rendimento per unita di rischio peggiore, non migliore. Tagliando meccanicamente l'esposizione esattamente quando la volatilita prevista e alta, sottopesi i periodi che contribuiscono di piu ai drawdown e meno al rendimento composto. Moreira e Muir (2017) hanno mostrato per le azioni che i portafogli vol-managed — esattamente questo scaling — aumentano gli Sharpe ratio e producono alpha positivi rispetto al fattore non gestito. Il meccanismo non e magia; e rifiutare di detenere una posizione in dollari costante in finestre prevedibilmente turbolente.
Il beneficio sul drawdown e ancora piu diretto. Il drawdown massimo e dominato dalla coda della distribuzione dei rendimenti della posizione. Poiche ha volatilita ancorata vicino a , la coda sinistra grassa che una strategia a nozionale fisso subisce durante un'esplosione di volatilita e compressa: eri gia piccolo all'entrata. Il vol targeting non predice i crash, ma e sistematicamente sottoesposto quando il mercato e agitato, e l'agitazione e quando accadono i crash.
Relazione con Kelly e il dimensionamento frazionario
Il volatility targeting e un cugino di primo grado del criterio di Kelly. Per un singolo asset con rendimento in eccesso atteso e varianza , la frazione growth-optimal (full-Kelly) e
Se assumi che lo Sharpe ratio sia approssimativamente costante — un'ipotesi forte, ma quella implicita in "il mercato paga un prezzo stabile per il rischio" — allora e , che e esattamente il volatility targeting con . In altre parole, il volatility targeting e il dimensionamento Kelly sotto l'assunzione che il rendimento atteso scali con la volatilita. Approfondiamo Kelly pieno e frazionario, e perche nessuno sano di mente fa trading con Kelly pieno, in criterio di Kelly e dimensionamento della strategia. La lezione pratica di li si applica anche qui: usa una frazione della dimensione teorica, perche l'errore di stima nel denominatore (la tua previsione di volatilita) e specialmente nel numeratore (il rendimento atteso) rende il dimensionamento pieno pericolosamente aggressivo.
Altre due connessioni da tenere a mente. Primo, il vol targeting dimensiona sulla dispersione simmetrica dei rendimenti, ma i payoff crypto non sono simmetrici — il costo di un giorno di ribasso del 20% non e lo specchio di un giorno di rialzo del 20% una volta che leva e liquidazione sono coinvolte. Trattiamo direttamente questa asimmetria in asimmetria perdita-profitto, e una previsione GJR/EGARCH (Parte 2) gia incorpora un po' di questo in reagendo di piu agli shock negativi. Secondo, una previsione di volatilita e una stima puntuale; una visione del rischio piu completa vi allega un intervallo. Conformal prediction per il trading mostra come trasformare gli output del modello in intervalli distribution-free su cui dimensionare, che si accoppia naturalmente con il tema della valutazione onesta di questo articolo.
I Concorrenti: Cosa Deve Battere GARCH
Ecco la disciplina che separa una valutazione reale da una demo. Prima di incoronare GARCH, devi dargli avversari economici, ovvi e sorprendentemente difficili da battere. Se il tuo elaborato modello GJR-t non riesce a superare un EWMA di cinque righe, hai imparato qualcosa di prezioso e ti sei risparmiato molta complessita produttiva.
Confrontiamo quattro previsori della volatilita del periodo successivo.
(a) Volatilita realizzata trailing (deviazione standard rolling)
La previsione piu naive: la volatilita di domani e uguale alla deviazione standard campionaria degli ultimi rendimenti giornalieri.
Ha un iperparametro (la finestra , tipicamente 20-60 giorni) e nessun modello. Il suo difetto e che ogni osservazione nella finestra riceve peso uguale e poi cade bruscamente da un dirupo quando esce — l'effetto "ghosting" o "eco", dove un singolo giorno di crash gonfia la previsione per esattamente giorni e poi svanisce da un giorno all'altro, che il mercato si sia effettivamente calmato o no.
(b) EWMA / RiskMetrics ()
La media mobile ponderata esponenzialmente risolve il problema dell'eco dando peso decrescente geometricamente ai rendimenti quadrati piu vecchi:
Questo e lo stimatore RiskMetrics (J.P. Morgan, 1996). Con il canonico per dati giornalieri, la memoria effettiva e circa giorni, ma il decadimento e liscio — nessun dirupo. Nota cosa e effettivamente EWMA: e un GARCH(1,1) integrato con e , cioe GARCH senza mean reversion e senza varianza di lungo periodo. Ha zero parametri liberi se accetti , ed e il singolo benchmark piu duro in questo intero articolo. Una larga frazione dei paper "GARCH batte X" fallisce silenziosamente nel battere EWMA out of sample.
import numpy as np
import pandas as pd
def ewma_vol(returns: pd.Series, lam: float = 0.94, sigma0: float | None = None) -> pd.Series:
"""RiskMetrics EWMA conditional volatility (returns in decimal, e.g. 0.03).
sigma2_t = lam * sigma2_{t-1} + (1 - lam) * r_t^2
Returns a series aligned to `returns` where value at t uses info up to t.
"""
r2 = np.square(returns.values)
var = np.empty_like(r2)
var[0] = sigma0**2 if sigma0 is not None else r2[0]
for t in range(1, len(r2)):
var[t] = lam * var[t - 1] + (1.0 - lam) * r2[t - 1] # note: r_{t-1}, no look-ahead
return pd.Series(np.sqrt(var), index=returns.index, name="ewma_vol")
La sottigliezza che inganna quasi tutti: la previsione per il periodo (usabile per dimensionare una posizione detenuta durante ) deve essere costruita da rendimenti osservati prima di . Nella ricorsione sopra, var[t] usa r2[t-1], quindi la serie e una genuina previsione a un passo in avanti. Ottenere questo indice giusto e la differenza tra un backtest e una fantasia — piu su questo nella sezione walk-forward.
(c) GARCH(1,1) e GJR-t (Parti 1-2)
I nostri protagonisti. GARCH(1,1) standard:
con varianza di lungo periodo e la previsione a un passo che deriva direttamente dalla ricorsione (Parte 1). L'estensione GJR-GARCH aggiunge un termine di leva cosicche gli shock negativi aumentano la varianza piu di quelli positivi:
e abbinato a innovazioni Student-t per gestire le code grasse del crypto, questo e il GJR-t della Parte 2. La ragione per cui GARCH puo battere EWMA e la mean reversion: dopo uno shock, GARCH tira indietro la previsione verso a un tasso governato da , mentre EWMA (essendo integrato) non torna mai indietro. Quando la volatilita esplode e poi si normalizza — il caso comune — la previsione di GARCH decade indietro piu velocemente e piu accuratamente. Quando la volatilita e genuinamente persistente, i due sono quasi indistinguibili.
(d) HAR-RV sulla varianza realizzata (se hai dati intraday)
Se hai barre intraday — e nei mercati crypto 24/7 quasi sempre le hai — puoi costruire un proxy di volatilita molto meno rumoroso dei rendimenti giornalieri al quadrato: la varianza realizzata, la somma dei rendimenti intraday al quadrato durante il giorno.
Il modello Heterogeneous Autoregressive di Corsi (2009) prevede la varianza realizzata di domani dalle medie giornaliere, settimanali e mensili di passati — un modo grezzo ma notevolmente efficace di catturare la persistenza a memoria lunga con tre regressori:
dove e sono le medie trailing a 5 giorni e 22 giorni del giornaliero. E una semplice regressione OLS, sfrutta il proxy intraday di qualita superiore, ed e spesso il migliore previsore di volatilita giornaliero dei quattro — spesso battendo GARCH proprio perche e un target piu pulito di .
import numpy as np
import pandas as pd
def realized_variance(intraday_returns: pd.Series, day_index) -> pd.Series:
"""Daily realized variance = sum of squared intraday (log) returns per day.
`intraday_returns` indexed by timestamp; `day_index` maps to calendar day.
"""
return intraday_returns.pow(2).groupby(day_index).sum()
def har_features(rv: pd.Series) -> pd.DataFrame:
"""Build HAR regressors from a daily realized-variance series."""
df = pd.DataFrame({"rv": rv})
df["rv_d"] = df["rv"].shift(1) # yesterday
df["rv_w"] = df["rv"].shift(1).rolling(5).mean() # trailing week
df["rv_m"] = df["rv"].shift(1).rolling(22).mean() # trailing month
df["target"] = df["rv"] # predict today's RV
return df.dropna()
def fit_har(rv: pd.Series):
"""Fit HAR-RV by OLS. Returns (coef_dict, predict_fn)."""
df = har_features(rv)
X = np.column_stack([np.ones(len(df)), df["rv_d"], df["rv_w"], df["rv_m"]])
y = df["target"].values
beta, *_ = np.linalg.lstsq(X, y, rcond=None)
names = ["const", "rv_d", "rv_w", "rv_m"]
def predict(rv_d, rv_w, rv_m):
return float(beta @ np.array([1.0, rv_d, rv_w, rv_m]))
return dict(zip(names, beta)), predict
Una nota sul log-HAR: poiche e asimmetrico a destra e strettamente positivo, molti pratici regrediscono sulle feature HAR logaritmiche, il che migliora il fit e garantisce previsioni positive. Quando esponenzi indietro dovresti aggiungere una correzione di Jensen a mezza varianza, , altrimenti sottostimerai sistematicamente.
Con quattro previsori in mano — RV, EWMA, GARCH/GJR-t, HAR — la domanda diventa: come decidiamo quale sia il migliore, quando non possiamo mai vedere la cosa che tutti stanno cercando di prevedere?
Valutare Onestamente una Previsione di Volatilita
Questo e l'insegnamento centrale dell'articolo, quindi rallenta qui.
Vuoi confrontare le previsioni contro la vera varianza condizionale . Ma e una quantita latente — e un parametro del processo generatore di dati, mai osservato direttamente. Tutto cio che ottieni e un rendimento realizzato al giorno. Quindi ogni valutazione di volatilita e in realta un confronto della tua previsione contro un proxy rumoroso della verita. I due proxy standard:
- Rendimenti al quadrato . Non distorto per sotto un modello a media zero (), ma estremamente rumoroso: un singolo rendimento giornaliero e una stima a una osservazione di una deviazione standard. Il proxy puo essere 0 (un giorno piatto) anche quando la vera vol e alta, o enorme in un giorno di coda fortunato.
- Varianza realizzata da dati intraday. Molto meno rumoroso — il campionamento intraday media via il rumore idiosincratico del singolo rendimento — che e esattamente perche HAR-RV funziona e perche dovresti usare come tuo proxy se hai dati intraday.
La sottigliezza che inganna quasi tutti: poiche il proxy e rumoroso, la scelta della funzione di perdita non e innocente. Classifica due previsioni con la perdita sbagliata e il proxy rumoroso puo ribaltare la classifica, dicendoti che la previsione peggiore e migliore. Patton (2011) ha elaborato precisamente quali funzioni di perdita sono "robuste" nel senso che classificare le previsioni per perdita attesa sul proxy rumoroso da la stessa classifica che otterresti sulla vera varianza (non osservabile). Solo una famiglia specifica si qualifica. Due membri contano nella pratica.
MSE vs QLIKE
L'errore quadratico medio sulla varianza:
dove e la previsione e e proxato da o . MSE e robusto nel senso di Patton (la sua classifica e proxy-consistente), ma e simmetrico e dipendente dalla scala: penalizza una sovra-previsione e una sotto-previsione della stessa dimensione assoluta ugualmente, e pesa gli errori durante periodi ad alta volatilita enormemente di piu degli errori durante periodi calmi. Un modello che azzecca il calmo 95% dei giorni ma fallisce le previsioni di varianza nei tre giorni di crisi apparira terribile sotto MSE, anche se il suo comportamento in crisi e cio che effettivamente vuoi.
La perdita QLIKE (quasi-likelihood) e il cavallo di battaglia.
E la perdita implicata da una likelihood gaussiana sulla varianza, e anch'essa robusta nel senso di Patton, e ha due proprieta che la rendono la scelta preferita per la volatilita. Primo, e asimmetrica nella direzione giusta: penalizza la sotto-previsione della varianza piu della sovra-previsione. Per un risk manager o un vol-targeter, questa e l'asimmetria corretta: sottostimare la vol significa che hai preso troppa dimensione proprio prima che contasse, che e l'errore costoso. Secondo, e (approssimativamente) invariante alla scala: poiche dipende dal rapporto , un errore di previsione del 10% costa circa lo stesso sia che accada in un giorno calmo o in un giorno di crisi, quindi la valutazione non e dirottata da una manciata di osservazioni ad alta varianza come accade con MSE. Quella robustezza all'eteroschedasticita del proxy e esattamente cio che vuoi quando l'intero punto e che la volatilita varia selvaggiamente.
Nota , con uguaglianza se e solo se . Piu basso e meglio, come per MSE.
import numpy as np
def qlike(proxy: np.ndarray, forecast_var: np.ndarray) -> np.ndarray:
"""Per-observation QLIKE loss. `proxy` is r^2 or RV (a variance proxy),
`forecast_var` is h = sigma_hat^2. Both strictly positive, same units.
"""
ratio = proxy / forecast_var
return ratio - np.log(ratio) - 1.0
def mse_var(proxy: np.ndarray, forecast_var: np.ndarray) -> np.ndarray:
"""Per-observation MSE on the variance scale."""
return np.square(proxy - forecast_var)
Due avvertimenti operativi. Mantieni proxy e previsione in unita identiche (entrambe varianze giornaliere, o entrambe annualizzate) altrimenti il rapporto non ha senso. E non lasciare mai che forecast_var tocchi zero — limitalo a un pavimento piccolo, perche avvelenera l'intera media.
Regressione Mincer-Zarnowitz
Un singolo numero di perdita ti dice quale previsione e migliore; non ti dice come una previsione e sbagliata. La regressione Mincer-Zarnowitz (1969) lo fa. Regredisci il proxy sulla previsione:
Sotto una previsione ottimale e non distorta, e : in media la varianza realizzata eguaglia la previsione. Le deviazioni diagnosticano la patologia:
- con : la firma classica di una previsione troppo volatile — reagisce eccessivamente, prevedendo estremi che non si materializzano pienamente. Molto comune per modelli guidati da rendimenti quadrati grezzi.
- : la previsione sotto-reagisce, scalando troppo poco con la vera varianza.
- basso della regressione: anche se sembrano a posto in media, la previsione traccia la varianza male giorno per giorno. Poiche il proxy e cosi rumoroso, non allarmarti se l' MZ contro e spesso solo 0.05-0.20; contro sara molto piu alto. L' contro e limitato ben al di sotto di 1 indipendentemente da quanto sia buona la previsione, puramente a causa del rumore del proxy.
Un F-test congiunto di fornisce un controllo di calibrazione formale. In pratica, usa MZ come diagnostica per capire una previsione, e QLIKE per classificare le previsioni.
Diebold-Mariano: la differenza e reale?
Supponi che il QLIKE medio di GARCH sia 0.183 e quello di EWMA 0.191. GARCH "vince." Ma 0.008 e un vantaggio reale o rumore di campionamento? Il test Diebold-Mariano (1995) risponde esattamente a questo. Definisci il differenziale di perdita per periodo
per due previsioni e (qui = QLIKE). L'ipotesi nulla e accuratezza predittiva uguale, . La statistica e il differenziale medio standardizzato dal suo errore standard a lungo termine (HAC), perche e serialmente correlato:
dove e una stima di varianza a lungo termine tipo Newey-West. Una statistica DM oltre rifiuta l'accuratezza uguale al 5%. Cruciale, DM e un test riguardo previsioni, non modelli annidati, e gestisce la dipendenza seriale nella serie di perdite che un t-test naive su ignorerebbe.
import numpy as np
from scipy import stats
def diebold_mariano(loss_a: np.ndarray, loss_b: np.ndarray, h: int = 1):
"""Diebold-Mariano test of equal predictive accuracy.
loss_a, loss_b: per-period losses (e.g. QLIKE) for forecasts A and B.
h: forecast horizon; Newey-West lag = h - 1 (>=0), plus a small-sample buffer.
Returns (DM_stat, p_value). Negative DM => A has lower loss (A better).
"""
d = np.asarray(loss_a) - np.asarray(loss_b)
T = len(d)
d_bar = d.mean()
lag = max(h - 1, 0)
gamma0 = np.mean((d - d_bar) ** 2)
lrv = gamma0
for k in range(1, lag + 1):
w = 1.0 - k / (lag + 1)
cov = np.mean((d[k:] - d_bar) * (d[:-k] - d_bar))
lrv += 2.0 * w * cov
dm = d_bar / np.sqrt(lrv / T)
adj = np.sqrt((T + 1 - 2 * h + h * (h - 1) / T) / T)
dm *= adj
p = 2.0 * (1.0 - stats.t.cdf(abs(dm), df=T - 1))
return float(dm), float(p)
Il risultato illustrativo in quel commento e l'esito onesto e comune, ed e l'intera ragione per cui questa sezione esiste: GARCH spesso registra una perdita media leggermente piu bassa di EWMA, e altrettanto spesso quel vantaggio non riesce a superare la soglia di significativita DM. Se riporti solo la QLIKE media, ti convincerai di vantaggi che un test DM avrebbe vietato. Riporta la statistica DM. Questa e la stessa disciplina che applichiamo ai rendimenti della strategia in valutazione onesta senza vantaggio robusto — una stima puntuale che batte un benchmark non e un vantaggio finche non hai escluso che sia rumore.
Il Backtest: Una Strategia Vol-Targeted Walk-Forward
Ora combiniamo le due meta — un previsore e una regola di dimensionamento — in una strategia e la valutiamo nell'unico modo che significhi qualcosa: walk-forward, out of sample, con costi.
La strategia e deliberatamente semplice, perche la semplicita e cio che ci permette di attribuire il risultato alla previsione di volatilita piuttosto che a un segnale astuto. BTC long-only, vol-targeted. Ogni giorno, prevedi la volatilita del giorno successivo, imposta la posizione a , mantieni durante la notte, e ripeti. Una variante long/flat disattiva la posizione quando un filtro di trend e negativo; una variante a portafoglio piccolo dimensiona sulla matrice di covarianza DCC della Parte 3 invece della varianza di un singolo asset. Descriviamo il caso long-only per intero e notiamo le estensioni.
Meccanica walk-forward e il contratto no-look-ahead
La proprieta piu importante di questo backtest e che ogni quantita usata per dimensionare la posizione al giorno e calcolabile usando solo dati disponibili alla chiusura del giorno . I parametri GARCH sono ristimati su una finestra rolling che termina a ; la previsione e la a un passo in avanti da quel fit; la posizione e impostata da quella previsione; e il rendimento guadagnato e , dove e il rendimento del giorno successivo, che il modello non ha mai visto. Ristimare GARCH sull'intero campione e poi "prevedere" il passato e il modo piu comune in cui le persone fabbricano accidentalmente un ottimo backtest. Trattiamo questa trappola del look-ahead e la metodologia generale in ottimizzazione walk-forward.
import numpy as np
import pandas as pd
from arch import arch_model
def walk_forward_voltarget(
returns: pd.Series, # daily log returns in decimal (e.g. 0.021)
proxy_var: pd.Series, # variance proxy aligned to returns (RV or r^2)
target_vol_annual: float = 0.20,
window: int = 750, # rolling estimation window (days)
refit_every: int = 5, # refit GARCH weekly, forecast daily (cost saver)
w_max: float = 3.0, # leverage cap
cost_bps: float = 5.0, # per-unit-turnover cost in basis points
ann: int = 365, # crypto trades 365 days/yr
):
"""Long-only vol-targeted BTC, GARCH(1,1)-t forecasts, strictly walk-forward.
Returns a DataFrame with position, forecast vol, net returns, and turnover.
"""
idx = returns.index
target_daily = target_vol_annual / np.sqrt(ann)
fcast_vol = pd.Series(index=idx, dtype=float, name="fcast_vol")
last_res = None
for i in range(window, len(returns) - 1):
if (i - window) % refit_every == 0 or last_res is None:
train = returns.iloc[i - window:i + 1] * 100.0 # scale for the optimizer
am = arch_model(train, mean="Constant", vol="GARCH",
p=1, o=1, q=1, dist="t") # GJR-t, Part 2
last_res = am.fit(disp="off")
f = last_res.forecast(horizon=1, reindex=False)
var_next = f.variance.values[-1, 0] / (100.0 ** 2) # unscale
fcast_vol.iloc[i + 1] = np.sqrt(var_next)
raw_w = (target_daily / fcast_vol).clip(upper=w_max)
position = raw_w.shift(0) # w_{i+1} known at close i
gross_ret = position * returns # earned on day i+1
turnover = position.diff().abs().fillna(0.0)
cost = turnover * (cost_bps / 1e4)
net_ret = (gross_ret - cost).dropna()
out = pd.DataFrame({
"position": position,
"fcast_vol": fcast_vol,
"gross_ret": gross_ret,
"turnover": turnover,
"net_ret": net_ret,
}).dropna()
return out
def performance_stats(net_ret: pd.Series, ann: int = 365) -> dict:
"""Sharpe, realized vol, max drawdown, CAGR, annual turnover."""
mu = net_ret.mean() * ann
sigma = net_ret.std() * np.sqrt(ann)
sharpe = mu / sigma if sigma > 0 else np.nan
equity = (1.0 + net_ret).cumprod()
peak = equity.cummax()
max_dd = (equity / peak - 1.0).min()
cagr = equity.iloc[-1] ** (ann / len(net_ret)) - 1.0
return {
"sharpe": round(sharpe, 2),
"realized_vol": round(sigma, 3),
"max_drawdown": round(max_dd, 3),
"cagr": round(cagr, 3),
}
Alcune note di implementazione che contano piu di quanto sembrino:
- Scaling per l'ottimizzatore. I fit di
archsono numericamente piu felici quando i rendimenti sono in percentuale, da qui il* 100e il corrispondente/ 100**2quando si de-scala la varianza. Dimentica il de-scaling e il tuo target di volatilita sara sbagliato di 10.000 volte. - Cadenza di refit. Ristimare i parametri GARCH ogni singolo giorno e costoso e non aggiunge quasi nulla — i parametri sono stabili settimana per settimana. Ristimare settimanalmente (
refit_every=5) mentre si prevede giornalmente (la ricorsione aggiorna da nuovi rendimenti anche senza ristima) e il compromesso standard. Questo rispecchia il consiglio di caching della pipeline copula in modelli copula per il rischio congiunto. - Il cap non e cosmetico. Quando la vol prevista collassa in un regime morto-calmo, puo esplodere a leva 5x, 10x. Il vol targeting non limitato ti consegnera felicemente una leva catastrofica proprio prima di un cambio di regime di volatilita — il momento esatto in cui la previsione sta per essere piu sbagliata. Limitalo (3x qui) e riconosci che il cap si attivera esattamente nei periodi piu calmi e piu pericolosi col senno di poi.
- I costi scalano con il turnover, e il vol targeting e una macchina di turnover. Ogni oscillazione nella previsione ridimensiona la posizione. Su un asset a bassa volatilita con una previsione nervosa puoi ruotare il book giornalmente. Il termine
cost_bpsnon e un dettaglio di arrotondamento; per un vol-target ad alto turnover puo mangiare una frazione significativa del miglioramento dello Sharpe lordo.
Come appare l'output (illustrativo)
Eseguire questo su dati giornalieri BTC su una finestra pluriennale, confrontando i quattro previsori come denominatore di dimensionamento, tende a produrre una tabella con la seguente forma. I numeri sotto sono illustrativi — scelti a mano per mostrare il pattern tipico, non l'output di un backtest reale — ma l'ordinamento e le magnitudini sono rappresentativi di cio che riportano i pratici.
| Previsione dimensionamento | Sharpe | Vol realizzata | Vol target | Max drawdown | Turnover ann. |
|---|---|---|---|---|---|
| Nozionale fisso (senza targeting) | 0.71 | 0.68 | — | -0.78 | 0.1x |
| RV rolling (60g) | 0.94 | 0.24 | 0.20 | -0.41 | 12x |
| EWMA () | 1.02 | 0.21 | 0.20 | -0.35 | 19x |
| GARCH(1,1)-t | 1.05 | 0.21 | 0.20 | -0.33 | 22x |
| HAR-RV (proxy intraday) | 1.09 | 0.20 | 0.20 | -0.31 | 20x |
Due varianti che vale la pena costruire
Il caso long-only isola la previsione, ma due estensioni sono abbastanza comuni da mostrare esplicitamente.
Long/flat con un gate di trend. Il vol targeting dimensiona la posizione ma non prende alcuna vista direzionale — e sempre long. Un miglioramento economico e onesto e disattivare la posizione quando un filtro di trend lento diventa negativo, cosicche mantieni il long vol-targeted solo nei trend rialzisti e resti flat altrimenti. Questo mantiene la logica di dimensionamento identica e stratifica un filtro di regime grezzo sopra; non pretende di temporizzare gli ingressi, solo di evitare di detenere posizioni durante trend ribassisti evidenti.
def apply_trend_gate(position: pd.Series, price: pd.Series, fast: int = 20, slow: int = 100):
"""Zero out the (already vol-targeted) position when the fast SMA is below
the slow SMA. Both SMAs use only past prices, so no look-ahead is introduced.
"""
fast_ma = price.rolling(fast).mean().shift(1) # shift: known at prior close
slow_ma = price.rolling(slow).mean().shift(1)
gate = (fast_ma > slow_ma).astype(float) # 1.0 in uptrend, 0.0 otherwise
return position * gate.reindex(position.index).fillna(0.0)
Il gate di trend riduce il turnover sul lato negativo (smetti di ruotare una posizione che si restringe in un mercato ribassista) ma aggiunge il proprio rischio di regime — oscilla in mercati laterali agitati e ritarda alle inversioni. Se aiuti e una domanda empirica a cui devi rispondere con lo stesso rigore walk-forward, testato con DM, della regola di dimensionamento stessa; un filtro di trend e esattamente il tipo di aggiunta che sembra ottima in-sample ed evapora out of sample.
Portfolio vol targeting sulla covarianza DCC. Per un book di piu asset, la previsione scalare diventa volatilita del portafoglio , dove e la matrice di covarianza variabile nel tempo dal DCC-GARCH della Parte 3. Scegli pesi base (peso uguale, capitalizzazione di mercato, o un tilt mean-variance), calcoli la vol prevista del portafoglio sotto , e scali l'intero vettore di pesi per raggiungere il target del portafoglio.
def portfolio_voltarget_weights(base_w, cov_forecast, target_vol_daily, w_gross_max=3.0):
"""Scale a base weight vector so forecast portfolio vol hits the target.
base_w: (n,) base allocation (need not sum to 1).
cov_forecast: (n, n) one-step-ahead covariance from DCC-GARCH (daily units).
"""
base_w = np.asarray(base_w, dtype=float)
port_var = float(base_w @ cov_forecast @ base_w)
port_vol = np.sqrt(max(port_var, 1e-12))
scale = min(target_vol_daily / port_vol, w_gross_max / np.abs(base_w).sum())
return scale * base_w
Questo e il ponte naturale verso la letteratura di costruzione del portafoglio: i pesi base possono venire da Markowitz mean-variance o da un metodo risk-based come HRP/CVaR, e il vol targeting poi si posiziona sopra come un overlay che scala il rischio aggregato a una costante. La matrice DCC conta perche le correlazioni salgono nei crash (Parte 3) — un portafoglio che sembra diversificato nei mercati calmi puo avere una vol prevista molto piu alta di quanto implichi una covarianza statica esattamente quando conta, e l'overlay taglia l'esposizione lorda in risposta.
Diagnostiche da tracciare sempre
Non fidarti mai della sola tabella riassuntiva. Per ogni vol-target, traccia tre cose e valutale a occhio prima di credere a qualsiasi numero di Sharpe. Primo, vol rolling realizzata della strategia contro la linea target — dovrebbe abbracciare il target; una deriva sistematica sopra significa che la tua previsione e distorta verso il basso (la direzione costosa). Secondo, la serie posizione/leva — cerca il cap che si attiva e picchi di leva proprio prima dei drawdown, la firma di una previsione colta di sorpresa da un cambio di regime. Terzo, lo scatter previsione-vs-proxy (l'immagine Mincer-Zarnowitz) — una nuvola con pendenza lontana da 1 ti dice che la previsione e mal scalata in un modo che la media QLIKE puo nascondere. Questi tre grafici catturano piu bug e piu auto-inganno di qualsiasi statistica singola.
Leggi questa tabella nel modo in cui dovresti leggere ogni tabella di backtest: guarda cosa e robusto e cosa e marginale. I fatti robusti saltano fuori. Ogni variante vol-targeted schiaccia il nozionale fisso sullo Sharpe e, ancora piu drammaticamente, sul drawdown e sulla stabilita della vol realizzata — la strategia a nozionale fisso gira a una vol annualizzata del 68% con un drawdown del 78%, che e semplicemente non investibile. E ogni metodo di targeting consegna una vol realizzata vicina al target del 20%, che e l'intera promessa del meccanismo funzionante. I fatti marginali sono le differenze tra i previsori: HAR batte GARCH batte EWMA batte RV rolling, ma i divari sono piccoli — un decimo di punto Sharpe — e, testati con Diebold-Mariano sulle previsioni o un bootstrap sui rendimenti, fallirebbero frequentemente a superare la significativita. Quel divario piccolo, fragile e regime-dipendente tra il previsore sofisticato e quello naive e il titolo onesto di questa intera serie.
Essere Onesti su Cosa Questo Ti Offre
Questo blog ha un'intera collezione sul backtesting senza ingannare se stessi, quindi applichiamola al nostro risultato invece di sperare silenziosamente che tu non lo faccia.
Il volatility targeting migliora il rendimento aggiustato per il rischio e i drawdown. Non fabbrica alpha dal nulla. Guarda di nuovo la tabella. Il miglioramento dello Sharpe dal targeting e reale e vale la pena averlo — ma scomponilo e la maggior parte proviene dal non detenere una posizione costante in regimi ad alta volatilita, il che meccanicamente evita i peggiori drawdown e stabilizza il percorso di compounding. La strategia e ancora long BTC; non ha alcuna vista che il mercato non le fornisca. Se BTC ha uno Sharpe negativo sul tuo campione, il vol targeting ti dara uno Sharpe negativo meno negativo, non positivo. Rimodella la distribuzione dei rendimenti — code piu sottili, vol piu stabile, compounding geometrico migliore — ma il vantaggio direzionale grezzo e qualunque cosa abbia il long sottostante. Non lasciare che una bella curva di equity ti inganni facendoti credere di aver trovato alpha quando hai trovato risk management. Moreira-Muir hanno trovato alpha genuino nei fattori azionari dalla gestione della vol, ma quel risultato riguarda il trade-off rischio-rendimento variabile nel tempo del fattore, e non si trasferisce automaticamente a un singolo asset crypto su un campione diverso.
Il vantaggio di qualita della previsione di GARCH su EWMA e spesso piccolo e regime-dipendente. Questo e il compenso scomodo delle Parti 1-3. Hai costruito modelli sempre piu sofisticati — termini di leva, code Student-t, correlazioni dinamiche — e il contributo marginale di ciascuno a un P&L di vol-targeting, rispetto a un EWMA naive, e spesso entro la banda di rumore. Il vantaggio di GARCH (mean reversion dopo gli shock) si manifesta principalmente in regimi specifici: picchi acuti che poi si normalizzano. In trend sfibranti o regimi ad alta vol persistenti, differisce a malapena da EWMA. Questo non rende GARCH inutile — la struttura di mean reversion, i parametri interpretabili, la capacita di simulare percorsi futuri e prezzare opzioni contro la previsione hanno tutti valore che EWMA manca — ma se il tuo unico uso e il dimensionamento, esegui il test DM prima di pagare il costo della complessita, e sappi che il rilevamento di regime ti sta dicendo la stessa cosa da un'angolazione diversa: il modello vincente dipende dal regime.
Lo Sharpe del backtest e un limite superiore allo Sharpe live, e il vol targeting allarga il divario. Poiche la strategia e ad alto turnover e scalata dalla leva, e insolitamente sensibile alle frizioni che un backtest naive omette: le tue esecuzioni sono peggiori del close su cui hai dimensionato, i costi di funding sulle posizioni perp con leva accumulano continuamente, e il cap di leva interagisce con meccanismi di margine e liquidazione che un semplice w * return ignora. Ognuno di questi rende il live peggiore del backtest. Trattiamo questo divario sistematicamente in parita backtest-live; per il vol targeting specificamente, preventiva per esso usando costi conservativi, un cap di leva realistico (basso), ed eseguendo sulla prossima barra di apertura piuttosto che sul close su cui hai calcolato il segnale.
Una nota a margine sul premio per il rischio di volatilita. Tutto quanto sopra prevede la volatilita realizzata. C'e un oggetto parallelo, negoziabile: la volatilita implicita, prezzata nelle opzioni, che in media si trova sopra la vol realizzata successiva — il premio per il rischio di volatilita, compensazione per il rischio di sopportare un picco di vol. Quel divario e esso stesso una fonte di rendimento (vendere varianza lo raccoglie, comprarla copre il rischio di coda), ed e un gioco genuinamente diverso dal vol targeting: e una scommessa sul prezzo della volatilita piuttosto che un uso di una previsione della volatilita. Non lo perseguiamo qui, ma il meccanismo inizia con il modello di pricing in pricing delle opzioni Black-Scholes, e una buona previsione di vol realizzata (Parti 1-2) e esattamente l'input di cui hai bisogno per giudicare se la vol implicita e cara o economica. Confrontare la tua previsione GARCH con la vol implicita del mercato delle opzioni e uno degli usi piu onesti di tutto cio che hai costruito in questa serie.
Considerazioni Pratiche
Un misto di cose che separano un vol-target funzionante da uno fragile.
- Stima sull'orizzonte giusto. Se dimensioni una posizione detenuta per un giorno, prevedi la vol a un giorno. Se ribilanci settimanalmente, prevedi (e mira) la vol settimanale, o aggrega la previsione GARCH giornaliera sull'orizzonte — la previsione GARCH multi-step torna verso , cosa che lo scaling naive "" ignora. La Parte 1 copre la previsione GARCH multi-step.
- Annualizzazione in mercati 24/7. Il crypto opera 365 giorni all'anno senza weekend o festivita, quindi annualizza la vol giornaliera con , non delle azioni. Sbagliare questo mis-scala silenziosamente il tuo target di circa il 20%.
- Il denominatore puo essere una matrice di covarianza. Per un book multi-asset, sostituisci lo scalare con la vol del portafoglio dal DCC-GARCH della Parte 3, e scala l'intero vettore di pesi per raggiungere la vol target del portafoglio. Questo collega il vol targeting al dimensionamento mean-variance (Markowitz per il crypto) e all'allocazione risk-based (pipeline HRP e CVaR) — il vol targeting e il caso speciale a singolo asset dello scaling a un budget di rischio di portafoglio.
- Il vol targeting e prociclico in modo sottile. Quando tutti eseguono la stessa regola , un picco di vol forza un deleveraging sincronizzato, che spinge i prezzi verso il basso, che aumenta la vol realizzata, che forza piu deleveraging. Questo feedback (ben documentato nel "volmageddon" del 2018 e in varie cascate di deleveraging crypto) significa che la regola funziona peggio esattamente quando molti operatori la usano. Non e un motivo per abbandonarla, ma e un motivo per limitare la leva e per non assumere che le tue esecuzioni durante un picco di vol assomiglieranno alle esecuzioni in mercato calmo.
- Poni un floor e limita la previsione. Una previsione di vol zero o vicina a zero produce leva infinita. Poni sempre un floor a a un minimo sensato e limita la posizione, e registra quanto spesso ciascuno si attiva — se il cap si attiva la maggior parte del tempo, il tuo target e troppo aggressivo per l'asset.
Riepilogo
- Una previsione di volatilita non ha valore finche non cambia una decisione. Il volatility targeting — dimensionare l'esposizione come (limitato) — e il test piu pulito del valore di una previsione, perche la qualita della previsione si traduce direttamente in un profilo di vol realizzata piu piatto e uno Sharpe piu alto.
- Il vol targeting aumenta il rendimento aggiustato per il rischio e, soprattutto, controlla i drawdown, perche la volatilita e prevedibile (si raggruppa) mentre la direzione no, e perche gli Sharpe ratio scendono nei regimi ad alta vol che la regola automaticamente sottopesa. E dimensionamento Kelly sotto l'assunzione che il rendimento atteso scali con la volatilita.
- Confronta GARCH onestamente con benchmark forti: volatilita realizzata rolling, EWMA/RiskMetrics (, un IGARCH con zero parametri liberi), e HAR-RV su un proxy di varianza realizzata intraday. EWMA e HAR sono difficili da battere.
- Non puoi osservare la vera volatilita, quindi valuta contro un proxy ( o, molto meglio, ) usando funzioni di perdita robuste al rumore del proxy. Preferisci QLIKE a MSE: penalizza la sotto-previsione di piu (l'errore costoso) ed e invariante alla scala, quindi non e dirottato da alcuni giorni ad alta vol. Usa Mincer-Zarnowitz per diagnosticare il bias e il test Diebold-Mariano per decidere se il vantaggio di una previsione e reale o rumore.
- In un backtest walk-forward, cost-aware, il vol targeting batte affidabilmente il nozionale fisso su Sharpe e drawdown, e i quattro previsori si raggruppano vicini tra loro — il vantaggio di GARCH su EWMA e piccolo, regime-dipendente, e spesso non statisticamente significativo. Riporta il test DM, non solo la perdita media.
- Sii onesto: il vol targeting e risk management, non alpha. Rimodella la distribuzione dei rendimenti di qualunque scommessa direzionale tu gia avessi; non crea vantaggio dal nulla. Ed e ad alto turnover e scalato dalla leva, quindi i risultati live restano indietro rispetto al backtest piu del solito.
Riferimenti:
- Patton, A. J. (2011). Volatility forecast comparison using imperfect volatility proxies. Journal of Econometrics, 160(1), 246-256. DOI
- Corsi, F. (2009). A simple approximate long-memory model of realized volatility. Journal of Financial Econometrics, 7(2), 174-196. DOI
- Diebold, F. X., & Mariano, R. S. (1995). Comparing predictive accuracy. Journal of Business & Economic Statistics, 13(3), 253-263. DOI
- Mincer, J., & Zarnowitz, V. (1969). The evaluation of economic forecasts. In Economic Forecasts and Expectations, NBER.
- Moreira, A., & Muir, T. (2017). Volatility-managed portfolios. Journal of Finance, 72(4), 1611-1644. DOI
- J.P. Morgan / Reuters (1996). RiskMetrics Technical Document, 4th ed.
- Bollerslev, T. (1986). Generalized autoregressive conditional heteroskedasticity. Journal of Econometrics, 31(3), 307-327. 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
- Harvey, D., Leybourne, S., & Newbold, P. (1997). Testing the equality of prediction mean squared errors. International Journal of Forecasting, 13(2), 281-291. DOI
MarketMaker.cc Team
Ricerca Quantitativa e Strategia