Volatility Targeting e Trading com Previsões GARCH
As três primeiras partes desta série ensinaram você a prever volatilidade. Construímos um GARCH(1,1) univariado na Parte 1, adicionamos alavancagem e caudas gordas com GJR e inovações Student-t na Parte 2, e modelamos uma matriz de covariância inteira ao longo do tempo com DCC-GARCH na Parte 3. Ao final de cada uma, imprimimos um número: a volatilidade esperada para amanhã. E então, sejamos honestos, paramos por aí — como se produzir a previsão fosse o objetivo final.
Não é. Uma previsão de volatilidade não é um P&L. Ninguém jamais foi pago por um QLIKE score baixo. Uma previsão só se torna valiosa no exato momento em que muda uma decisão que você teria tomado de forma diferente — quanto comprar, quando cortar, quanto capital alocar. Se sua previsão não move uma posição, sua acurácia estatística é um hobby particular.
Esta parte final trata de fechar esse ciclo. Pegamos as previsões das Partes 1-3 e as colocamos para trabalhar na decisão mais limpa possível: volatility targeting — dimensionar uma posição para que a volatilidade realizada do portfólio atinja um alvo constante. Depois fazemos a coisa que este blog valoriza mais do que qualquer modelo individual: avaliamos com honestidade. Comparamos GARCH contra baselines burras porém fortes (volatilidade realizada móvel, EWMA), usamos funções de perda robustas ao fato de que nunca podemos observar a volatilidade verdadeira, rodamos um backtest walk-forward com custos e sem look-ahead, e declaramos claramente o que o vol targeting compra e o que não compra. Spoiler: melhora retornos ajustados ao risco e controla drawdowns de forma muito mais confiável do que fabrica alfa.
Por Que Volatility Targeting É o Teste Certo
Existem maneiras mais sofisticadas de usar uma previsão de volatilidade — precificação de opções, limites de VaR, hedge dinâmico — mas o volatility targeting é o que isola o valor da previsão com a menor contaminação de outras apostas. A ideia é uma única equação.
Segure um ativo de risco com uma direção implícita pelo sinal (por enquanto, apenas "comprado"). Em vez de uma posição fixa, escale a exposição de forma inversa à volatilidade prevista:
onde é sua previsão de volatilidade do próximo período (feita usando apenas dados até ), e é a volatilidade anualizada na qual você quer que a estratégia opere — digamos 15% ou 20%. Quando o modelo prevê um mercado calmo, você alavanca em direção a (ou além de) 1.0; quando prevê uma tempestade, você encolhe. A volatilidade realizada da posição escalada é, em primeira ordem,
sempre que . Então toda a qualidade do exercício repousa sobre uma coisa: quão próxima sua previsão está da volatilidade real do próximo período . Uma previsão melhor produz um perfil de volatilidade realizada mais plano e — como veremos — um Sharpe melhor. É por isso que este é o teste certo. A previsão não é decoração; é o denominador.
Por que isso eleva o Sharpe e controla drawdowns
Dois fatos empíricos fazem o trabalho aqui.
Volatilidade é muito mais previsível do que retornos. A direção do retorno de amanhã do BTC é próxima de um cara ou coroa em frequência diária; a magnitude não é. Volatilidade se agrupa — grandes movimentos seguem grandes movimentos — que é toda a razão de o GARCH existir (a Parte 1 derivou a estrutura AR(1)-na-variância que codifica isso). Um de 0.4-0.6 para variância um dia à frente é rotineiro; o mesmo número para retornos seria um sinal de nível Renaissance. Vol targeting explora a quantidade previsível e permanece agnóstico quanto à imprevisível.
Sharpe ratios não são constantes ao longo do tempo; eles caem quando a volatilidade dispara. Regimes de alta volatilidade em cripto — cascatas de deleveraging, falhas de exchanges, dias em que tudo gapeia 30% — tendem a ter retorno por unidade de risco pior, não melhor. Ao cortar exposição mecanicamente exatamente quando a vol prevista é alta, você subpondera os períodos que mais contribuem para drawdowns e menos para o retorno composto. Moreira e Muir (2017) mostraram para ações que portfólios com volatility management — exatamente esse escalonamento — elevam Sharpe ratios e produzem alfas positivos contra o fator não gerenciado. O mecanismo não é mágica; é recusar-se a manter uma posição em dólar constante em janelas previsivelmente turbulentas.
O benefício de drawdown é ainda mais direto. O drawdown máximo é dominado pela cauda da distribuição de retorno da posição. Como tem volatilidade fixada perto de , a cauda esquerda gorda que uma estratégia de notional fixo sofre durante uma explosão de vol é comprimida: você já estava pequeno na entrada. Vol targeting não prevê crashes, mas está sistematicamente subexposto quando o mercado está agitado, e agitação é quando crashes acontecem.
Relação com Kelly e dimensionamento fracionário
Volatility targeting é primo próximo do critério de Kelly. Para um único ativo com retorno excedente esperado e variância , a fração ótima de crescimento (Kelly integral) é
Se você assumir que o Sharpe ratio é aproximadamente constante — uma suposição forte, mas a implícita em "o mercado paga um preço estável pelo risco" — então e , que é exatamente volatility targeting com . Em outras palavras, volatility targeting é dimensionamento Kelly sob a suposição de que o retorno esperado escala com a volatilidade. Trabalhamos Kelly integral e fracionário, e por que ninguém em sã consciência opera com Kelly integral, em Kelly criterion and strategy sizing. A lição prática de lá se transfere: use uma fração do tamanho teórico, porque o erro de estimação no denominador (sua previsão de vol) e especialmente no numerador (retorno esperado) torna o dimensionamento integral perigosamente agressivo.
Duas conexões adicionais valem a pena ter em mente. Primeiro, vol targeting dimensiona com base na dispersão simétrica dos retornos, mas os payoffs de cripto não são simétricos — o custo de um dia de queda de 20% não é o espelho de um dia de alta de 20% uma vez que alavancagem e liquidação estão envolvidas. Tratamos essa assimetria diretamente em loss-profit asymmetry, e uma previsão GJR/EGARCH (Parte 2) já embute parte disso em ao reagir mais a choques negativos. Segundo, uma previsão de vol é uma estimativa pontual; uma visão de risco mais completa anexa um intervalo a ela. Conformal prediction for trading mostra como transformar saídas de modelo em intervalos livres de distribuição contra os quais você pode dimensionar, o que combina naturalmente com o tema de avaliação honesta deste artigo.
Os Concorrentes: O Que o GARCH Precisa Superar
Aqui está a disciplina que separa uma avaliação real de uma demonstração. Antes de coroar o GARCH, você deve dar a ele adversários que sejam baratos, óbvios e surpreendentemente difíceis de vencer. Se seu elaborado modelo GJR-t não conseguir superar um EWMA de cinco linhas, você aprendeu algo valioso e economizou muita complexidade de produção.
Comparamos quatro previsores de volatilidade do próximo período.
(a) Volatilidade realizada trailing (desvio padrão móvel)
A previsão mais ingênua: a volatilidade de amanhã é igual ao desvio padrão amostral dos últimos retornos diários.
Tem um único hiperparâmetro (a janela , tipicamente 20-60 dias) e nenhum modelo. Sua falha é que toda observação na janela recebe peso igual e depois cai abruptamente de um penhasco quando sai — o efeito "fantasma" ou "eco", onde um único dia de crash infla a previsão por exatamente dias e depois desaparece da noite para o dia, tenha o mercado realmente se acalmado ou não.
(b) EWMA / RiskMetrics ()
A média móvel exponencialmente ponderada corrige o problema do eco dando peso geometricamente decrescente a retornos ao quadrado mais antigos:
Este é o estimador RiskMetrics (J.P. Morgan, 1996). Com o canônico para dados diários, a memória efetiva é de aproximadamente dias, mas o decaimento é suave — sem penhasco. Note o que o EWMA realmente é: é um GARCH(1,1) integrado com e , ou seja, GARCH sem reversão à média e sem variância de longo prazo. Ele tem zero parâmetros livres se você aceitar , e é a baseline individual mais difícil de todo este artigo. Uma grande fração dos artigos "GARCH vence X" silenciosamente falha em vencer o EWMA fora da amostra.
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")
A sutileza que costuma pegar as pessoas: a previsão para o período (utilizável para dimensionar uma posição mantida durante ) deve ser construída a partir de retornos observados antes de . Na recursão acima, var[t] usa r2[t-1], então a série é uma genuína previsão um passo à frente. Acertar esse índice é a diferença entre um backtest e uma fantasia — mais sobre isso na seção de walk-forward.
(c) GARCH(1,1) e GJR-t (Partes 1-2)
Nossos protagonistas. GARCH(1,1) padrão:
com variância de longo prazo e a previsão um passo à frente saindo diretamente da recursão (Parte 1). A extensão GJR-GARCH adiciona um termo de alavancagem para que choques negativos elevem a variância mais do que os positivos:
e combinado com inovações Student-t para lidar com as caudas gordas de cripto, este é o GJR-t da Parte 2. A razão pela qual o GARCH pode vencer o EWMA é reversão à média: após um choque, o GARCH puxa a previsão de volta em direção a a uma taxa governada por , enquanto o EWMA (sendo integrado) nunca reverte. Quando a volatilidade dispara e depois se normaliza — o caso comum — a previsão do GARCH decai de volta mais rápido e com mais precisão. Quando a volatilidade é genuinamente persistente, os dois são quase indistinguíveis.
(d) HAR-RV sobre variância realizada (se você tiver dados intradiários)
Se você tem barras intradiárias — e em mercados cripto 24/7 quase sempre tem — pode construir uma proxy de volatilidade muito menos ruidosa do que retornos diários ao quadrado: variância realizada, a soma dos retornos intradiários ao quadrado ao longo do dia.
O modelo Heterogeneous Autoregressive de Corsi (2009) prevê a variância realizada de amanhã a partir de médias diárias, semanais e mensais do passado — uma forma tosca porém notavelmente eficaz de capturar persistência de longa memória com três regressores:
onde e são as médias móveis de 5 dias e 22 dias do diário. É uma regressão OLS simples, explora a proxy intradiária de maior qualidade, e é frequentemente o melhor previsor de vol diário dos quatro — muitas vezes vencendo o GARCH precisamente porque é um alvo mais limpo do que .
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
Uma nota sobre log-HAR: como é assimétrico à direita e estritamente positivo, muitos praticantes regridem sobre features HAR em log, o que melhora o ajuste e garante previsões positivas. Ao exponenciar de volta você deve adicionar uma correção de Jensen de meia-variância, , ou você vai sistematicamente subprever.
Com quatro previsores em mãos — RV, EWMA, GARCH/GJR-t, HAR — a pergunta se torna: como decidimos qual é o melhor, quando nunca podemos ver a coisa que todos estão tentando prever?
Avaliando uma Previsão de Volatilidade com Honestidade
Este é o ensinamento central do artigo, então vá com calma aqui.
Você quer comparar previsões contra a variância condicional verdadeira . Mas é uma quantidade latente — é um parâmetro do processo gerador de dados, nunca diretamente observado. Tudo o que você obtém é um retorno realizado por dia. Então toda avaliação de volatilidade é na verdade uma comparação da sua previsão contra uma proxy ruidosa da verdade. As duas proxies padrão:
- Retornos ao quadrado . Não enviesado para sob um modelo de média zero (), mas extremamente ruidoso: um único retorno diário é uma estimativa de uma observação de um desvio padrão. A proxy pode ser 0 (um dia plano) mesmo quando a vol verdadeira é alta, ou enorme em um dia de sorte na cauda.
- Variância realizada a partir de dados intradiários. Muito menos ruidosa — a amostragem intradiária tira a média do ruído idiossincrático de um único retorno — que é exatamente por que o HAR-RV funciona e por que você deveria usar como sua proxy se tiver quaisquer dados intradiários.
A sutileza que pega quase todo mundo: como a proxy é ruidosa, a escolha da função de perda não é inocente. Ordene duas previsões com a perda errada e a proxy ruidosa pode inverter o ranking, dizendo que a previsão pior é melhor. Patton (2011) trabalhou precisamente quais funções de perda são "robustas" no sentido de que ordenar previsões por perda esperada na proxy ruidosa dá o mesmo ranking que você obteria na variância verdadeira (inobservável). Apenas uma família específica se qualifica. Dois membros importam na prática.
MSE vs QLIKE
O erro quadrático médio na variância:
onde é a previsão e é aproximada por ou . O MSE é robusto no sentido de Patton (seu ranking é consistente com a proxy), mas é simétrico e dependente de escala: penaliza uma sobreprevisão e uma subprevisão do mesmo tamanho absoluto igualmente, e pondera erros durante períodos de alta volatilidade enormemente mais do que erros durante períodos calmos. Um modelo que acerta os 95% de dias calmos mas explode nas previsões de variância nos três dias de crise vai parecer terrível sob o MSE, mesmo que seu comportamento de crise seja o que você realmente quer.
A perda QLIKE (quasi-verossimilhança) é o cavalo de batalha:
É a perda implícita por uma verossimilhança gaussiana sobre a variância, também é robusta no sentido de Patton, e tem duas propriedades que a tornam a escolha preferida para volatilidade. Primeiro, é assimétrica na direção certa: penaliza a subprevisão de variância mais do que a sobreprevisão. Para um gestor de risco ou um praticante de vol targeting, essa é a assimetria correta — subprever vol significa que você assumiu tamanho demais logo antes de importar, que é o erro caro. Segundo, é (aproximadamente) invariante à escala: como depende da razão , um erro de previsão de 10% custa aproximadamente o mesmo, seja em um dia calmo ou em um dia de crise, então a avaliação não é sequestrada por um punhado de observações de alta variância como acontece com o MSE. Essa robustez à heterocedasticidade da proxy é exatamente o que você quer quando o ponto todo é que a volatilidade varia loucamente.
Note que , com igualdade se e somente se . Menor é melhor, assim como o 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)
Dois avisos operacionais. Mantenha a proxy e a previsão em unidades idênticas (ambas variâncias diárias, ou ambas anualizadas) ou a razão não terá sentido. E nunca deixe forecast_var chegar a zero — limite a um piso pequeno, porque vai envenenar toda a média.
Regressão de Mincer-Zarnowitz
Um único número de perda diz a você qual previsão é melhor; não diz como uma previsão está errada. A regressão de Mincer-Zarnowitz (1969) diz. Regrida a proxy sobre a previsão:
Sob uma previsão ótima e não enviesada, e : em média, a variância realizada é igual à previsão. Os desvios diagnosticam a patologia:
- com : a assinatura clássica de uma previsão volátil demais — ela reage exageradamente, prevendo extremos que não se materializam totalmente. Muito comum em modelos guiados por retornos ao quadrado brutos.
- : a previsão reage de menos, escalando pouco demais em relação à variância verdadeira.
- da regressão baixo: mesmo que pareçam bons na média, a previsão acompanha a variância mal dia a dia. Como a proxy é tão ruidosa, não se alarme que o do MZ contra seja frequentemente apenas 0.05-0.20; contra será muito mais alto. O contra é limitado bem abaixo de 1, não importa quão boa seja a previsão, puramente por causa do ruído da proxy.
Um teste conjunto de dá uma verificação formal de calibração. Na prática, use MZ como diagnóstico para entender uma previsão, e QLIKE para ordenar previsões.
Diebold-Mariano: a diferença é real?
Suponha que o QLIKE médio do GARCH seja 0.183 e o do EWMA seja 0.191. O GARCH "vence". Mas 0.008 é uma vantagem real ou ruído amostral? O teste de Diebold-Mariano (1995) responde exatamente isso. Defina o diferencial de perda por período
para duas previsões e (aqui = QLIKE). A hipótese nula é acurácia preditiva igual, . A estatística é a média do diferencial padronizada pelo seu erro padrão de longo prazo (HAC), porque é serialmente correlacionado:
onde é uma estimativa de variância de longo prazo do tipo Newey-West. Uma estatística DM além de rejeita acurácia igual a 5%. Crucialmente, DM é um teste sobre previsões, não modelos aninhados, e lida com a dependência serial na série de perdas que um teste- ingênuo sobre ignoraria.
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)
O resultado ilustrativo naquele comentário é o desfecho honesto e comum, e é toda a razão de esta seção existir: o GARCH frequentemente registra uma perda média ligeiramente menor do que o EWMA, e igualmente frequentemente essa vantagem falha em superar a barra de significância DM. Se você só relata o QLIKE médio, vai se convencer de vantagens que um teste DM teria vetado. Reporte a estatística DM. Esta é a mesma disciplina que aplicamos a retornos de estratégia em honest evaluation with no robust edge — uma estimativa pontual que supera um benchmark não é uma vantagem até que você tenha descartado que seja ruído.
O Backtest: Uma Estratégia de Vol Targeting Walk-Forward
Agora combinamos as duas metades — um previsor e uma regra de dimensionamento — em uma estratégia e a avaliamos da única forma que significa alguma coisa: walk-forward, fora da amostra, com custos.
A estratégia é deliberadamente simples, porque simplicidade é o que nos permite atribuir o resultado à previsão de vol em vez de a um sinal esperto. BTC apenas comprado, com vol targeting. A cada dia, prevemos a volatilidade do próximo dia, definimos a posição como , mantemos overnight, e repetimos. Uma variante long/flat desliga a posição quando um filtro de tendência é negativo; uma variante de pequeno portfólio dimensiona sobre a matriz de covariância DCC da Parte 3 em vez da variância de um único ativo. Descrevemos o caso apenas comprado por completo e notamos as extensões.
Mecânica walk-forward e o contrato de sem look-ahead
A propriedade mais importante deste backtest é que toda quantidade usada para dimensionar a posição no dia é computável usando apenas dados disponíveis ao fechamento do dia . Os parâmetros do GARCH são reestimados em uma janela móvel terminando em ; a previsão é o um passo à frente daquele ajuste; a posição é definida a partir dessa previsão; e o retorno auferido é , onde é o retorno do próximo dia, que o modelo nunca viu. Reajustar o GARCH na amostra completa e depois "prever" o passado é a forma mais comum de as pessoas fabricarem acidentalmente um ótimo backtest. Tratamos essa armadilha de look-ahead e a metodologia geral em walk-forward optimization.
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),
}
Algumas notas de implementação que importam mais do que parecem:
- Escalonamento para o otimizador. Os ajustes do
archsão numericamente mais felizes quando os retornos estão em percentual, daí o* 100e o/ 100**2correspondente ao desescalonar a variância. Esqueça o desescalonamento e seu alvo de vol fica errado por 10.000x. - Cadência de reajuste. Reestimar os parâmetros do GARCH todo santo dia é caro e adiciona quase nada — os parâmetros são estáveis semana a semana. Reajustar semanalmente (
refit_every=5) enquanto prevê diariamente (a recursão atualiza com novos retornos mesmo sem reajuste) é o compromisso padrão. Isso espelha o conselho de cache do pipeline de cópulas em copula models for joint risk. - O teto não é cosmético. Quando a vol prevista colapsa em um regime de calmaria total, pode explodir para 5x, 10x de alavancagem. Vol targeting sem teto vai alegremente entregar a você alavancagem catastrófica bem antes de uma mudança de regime de volatilidade — o exato momento em que a previsão está prestes a estar mais errada. Limite-a (3x aqui) e reconheça que o teto vai atuar precisamente nos períodos mais calmos e mais perigosos-em-retrospectiva.
- Custos escalam com o turnover, e vol targeting é uma máquina de turnover. Cada oscilação na previsão redimensiona a posição. Em um ativo de baixa vol com uma previsão nervosa você pode girar o book diariamente. O termo
cost_bpsnão é um detalhe de arredondamento; para um vol target de alto turnover, ele pode comer uma fração significativa da melhoria de Sharpe bruta.
Como fica a saída (ilustrativo)
Rodar isso em dados diários de BTC ao longo de uma janela de vários anos, comparando os quatro previsores como o denominador de dimensionamento, tende a produzir uma tabela com a forma a seguir. Os números abaixo são ilustrativos — escolhidos a dedo para mostrar o padrão típico, não a saída de um backtest real — mas a ordenação e as magnitudes são representativas do que os praticantes relatam.
| Previsão de dimensionamento | Sharpe | Vol realizada | Vol alvo | Drawdown máximo | Turnover anual |
|---|---|---|---|---|---|
| Notional fixo (sem targeting) | 0.71 | 0.68 | — | -0.78 | 0.1x |
| RV móvel (60d) | 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 intradiária) | 1.09 | 0.20 | 0.20 | -0.31 | 20x |
Duas variantes que vale a pena construir
O caso apenas comprado isola a previsão, mas duas extensões são comuns o suficiente para mostrar explicitamente.
Long/flat com um portão de tendência. Vol targeting dimensiona a posição mas não toma nenhuma visão direcional — está sempre comprado. Uma melhoria barata e honesta é desligar a posição quando um filtro de tendência lento vira negativo, de modo que você mantém a posição comprada com vol targeting apenas em tendências de alta e fica flat caso contrário. Isso mantém a lógica de dimensionamento idêntica e adiciona um filtro de regime tosco por cima; não pretende cronometrar entradas, apenas evitar manter posição em tendências de baixa óbvias.
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)
O portão de tendência reduz o turnover no lado negativo (você para de girar uma posição encolhendo em um mercado de baixa) mas adiciona seu próprio risco de regime — ele oscila em mercados laterais agitados e atrasa em pontos de virada. Se ele ajuda é uma questão empírica que você deve responder com o mesmo rigor walk-forward, testado por DM, que a própria regra de dimensionamento; um filtro de tendência é exatamente o tipo de complemento que parece ótimo dentro da amostra e evapora fora dela.
Vol targeting de portfólio sobre a covariância DCC. Para um book de vários ativos, a previsão escalar se torna a volatilidade de portfólio , onde é a matriz de covariância variável no tempo do DCC-GARCH da Parte 3. Você escolhe pesos base (peso igual, capitalização de mercado, ou uma inclinação média-variância), computa a vol prevista do portfólio sob , e escala todo o vetor de pesos para atingir o alvo do portfólio.
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
Esta é a ponte natural com a literatura de construção de portfólio: os pesos base podem vir de Markowitz mean-variance ou de um método baseado em risco como HRP/CVaR, e o vol targeting então se posiciona por cima como uma camada que escala o risco agregado a um valor constante. A matriz DCC importa porque correlações disparam em crashes (Parte 3) — um portfólio que parece diversificado em mercados calmos pode ter vol prevista muito mais alta do que uma covariância estática sugere exatamente quando importa, e a camada corta a exposição bruta em resposta.
Diagnósticos que você sempre deve plotar
Nunca confie apenas na tabela resumo. Para qualquer vol target, plote três coisas e avalie visualmente antes de acreditar em qualquer número de Sharpe. Primeiro, a vol móvel realizada da estratégia contra a linha alvo — ela deve abraçar o alvo; deriva sistemática acima dele significa que sua previsão está enviesada para baixo (a direção cara). Segundo, a série de posição/alavancagem — procure o teto atuando e picos de alavancagem bem antes de drawdowns, a assinatura de uma previsão que foi pega de surpresa por uma mudança de regime. Terceiro, o scatter previsão-vs-proxy (o gráfico de Mincer-Zarnowitz) — uma nuvem com inclinação distante de 1 diz que a previsão está mal-escalada de uma forma que a média do QLIKE pode esconder. Esses três gráficos capturam mais bugs e mais autoengano do que qualquer estatística isolada.
Leia essa tabela da forma que você deveria ler toda tabela de backtest: olhe o que é robusto e o que é marginal. Os fatos robustos saltam aos olhos. Toda variante com vol targeting esmaga o notional fixo em Sharpe e, de forma mais dramática, em drawdown e estabilidade de vol realizada — a estratégia de notional fixo roda a 68% de vol anualizada com drawdown de 78%, o que é simplesmente não-investível. E todo método de targeting entrega vol realizada próxima ao alvo de 20%, que é toda a promessa do mecanismo funcionando. Os fatos marginais são as diferenças entre os previsores: HAR supera GARCH que supera EWMA que supera RV móvel, mas as lacunas são pequenas — um décimo de ponto de Sharpe — e, testadas com Diebold-Mariano nas previsões ou um bootstrap nos retornos, frequentemente falhariam em superar significância. Essa lacuna pequena, frágil e dependente de regime entre o previsor sofisticado e o ingênuo é a manchete honesta de toda esta série.
Sendo Honesto Sobre o Que Isso Compra Para Você
Este blog tem uma coleção inteira sobre backtesting sem se enganar, então vamos aplicá-la ao nosso próprio resultado em vez de silenciosamente esperar que você não o faça.
Volatility targeting melhora o retorno ajustado ao risco e os drawdowns. Não fabrica alfa do nada. Olhe de novo a tabela. A melhoria de Sharpe do targeting é real e vale a pena — mas decomponha e a maior parte dela vem de não manter uma posição constante em regimes de alta vol, o que mecanicamente evita os piores drawdowns e estabiliza o caminho de composição. A estratégia ainda está comprada em BTC; não tem nenhuma visão que o mercado não lhe dê. Se o BTC tem Sharpe negativo na sua amostra, vol targeting vai lhe entregar um Sharpe negativo menos ruim, não um positivo. Ele reformata a distribuição de retorno — caudas mais finas, vol mais estável, melhor composição geométrica — mas a vantagem direcional bruta é seja lá o que for a posição comprada subjacente. Não deixe uma bela curva de patrimônio enganá-lo a acreditar que você encontrou alfa quando encontrou gestão de risco. Moreira-Muir encontraram alfa genuíno em fatores de ações a partir de volatility management, mas esse resultado trata do trade-off risco-retorno variável no tempo do fator, e não se transfere automaticamente para um único ativo cripto em uma amostra diferente.
A vantagem de qualidade de previsão do GARCH sobre o EWMA é frequentemente pequena e dependente de regime. Este é o retorno desconfortável das Partes 1-3. Você construiu modelos cada vez mais sofisticados — termos de alavancagem, caudas Student-t, correlações dinâmicas — e a contribuição marginal de cada um a um P&L de vol targeting, sobre um EWMA ingênuo, está frequentemente dentro da banda de ruído. A vantagem do GARCH (reversão à média após choques) aparece principalmente em regimes específicos: picos acentuados que depois se normalizam. Em tendências arrastadas ou regimes persistentes de alta vol ele quase não difere do EWMA. Isso não torna o GARCH inútil — a estrutura de reversão à média, os parâmetros interpretáveis, a capacidade de simular trajetórias futuras e precificar opções contra a previsão têm valor que o EWMA não tem — mas se seu único uso é dimensionamento, rode o teste DM antes de pagar o custo de complexidade, e saiba que regime detection diz a mesma coisa de um ângulo diferente: o modelo vencedor depende do regime.
Sharpe de backtest é um limite superior para o Sharpe ao vivo, e vol targeting amplia essa lacuna. Como a estratégia tem alto turnover e é escalada por alavancagem, ela é incomumente sensível às fricções que um backtest ingênuo omite: suas execuções são piores do que o fechamento no qual você dimensionou, custos de funding em posições perp alavancadas se acumulam continuamente, e o teto de alavancagem interage com mecânicas de margem e liquidação que um simples w * return ignora. Cada uma dessas coisas torna o resultado ao vivo pior do que o backtest. Tratamos essa lacuna sistematicamente em backtest-live parity; para vol targeting especificamente, orce para isso usando custos conservadores, um teto de alavancagem realista (baixo), e executando na abertura da próxima barra em vez do fechamento no qual você computou o sinal.
Um aparte sobre o prêmio de risco de volatilidade. Tudo acima prevê volatilidade realizada. Existe um objeto paralelo, negociável: volatilidade implícita, precificada em opções, que em média fica acima da volatilidade realizada subsequente — o prêmio de risco de volatilidade, compensação por carregar o risco de um pico de vol. Essa lacuna é em si uma fonte de retorno (vender variância a colhe, comprá-la protege risco de cauda), e é um jogo genuinamente diferente do vol targeting: é uma aposta no preço da volatilidade em vez de um uso de uma previsão de volatilidade. Não perseguimos isso aqui, mas o maquinário começa com o modelo de precificação em Black-Scholes options pricing, e uma boa previsão de vol realizada (Partes 1-2) é exatamente o insumo de que você precisa para julgar se a vol implícita está cara ou barata. Comparar sua previsão GARCH com a vol implícita do mercado de opções é um dos usos mais honestos de tudo que você construiu nesta série.
Considerações Práticas
Uma coleção de coisas que separam um vol target funcional de um frágil.
- Estime no horizonte certo. Se você dimensiona uma posição mantida por um dia, preveja a vol de um dia. Se rebalanceia semanalmente, preveja (e almeje) a vol semanal, ou agregue a previsão diária do GARCH sobre o horizonte — a previsão GARCH de múltiplos passos reverte à média em direção a , o que o escalonamento ingênuo "" ignora. A Parte 1 cobre previsão GARCH de múltiplos passos.
- Anualização em mercados 24/7. Cripto opera 365 dias por ano sem finais de semana ou feriados, então anualize a vol diária com , não o de ações. Errar isso mis-escala silenciosamente seu alvo em ~20%.
- O denominador pode ser uma matriz de covariância. Para um book multi-ativo, substitua o escalar pela vol de portfólio do DCC-GARCH da Parte 3, e escale todo o vetor de pesos para atingir a vol alvo do portfólio. Isso conecta vol targeting ao dimensionamento média-variância (Markowitz for crypto) e à alocação baseada em risco (HRP and CVaR pipelines) — vol targeting é o caso especial de um único ativo do escalonamento a um orçamento de risco de portfólio.
- Vol targeting é procíclico de uma forma sutil. Quando todo mundo roda a mesma regra , um pico de vol força deleveraging sincronizado, o que empurra preços para baixo, o que eleva a vol realizada, o que força mais deleveraging. Esse feedback (bem documentado no "volmageddon" de 2018 e várias cascatas de deleveraging em cripto) significa que a regra funciona pior exatamente quando muitos participantes a usam. Não é razão para abandoná-la, mas é razão para limitar a alavancagem e para não assumir que suas execuções durante um pico de vol vão se parecer com execuções de mercado calmo.
- Estabeleça piso e limite a previsão. Uma previsão de vol zero ou próxima de zero produz alavancagem infinita. Sempre estabeleça um piso sensato para e limite a posição, e registre com que frequência cada um atua — se o teto atua na maior parte do tempo, seu alvo é agressivo demais para o ativo.
Resumo
- Uma previsão de volatilidade não tem valor até que mude uma decisão. Volatility targeting — dimensionar exposição como (limitado) — é o teste mais limpo do valor de uma previsão, porque a qualidade da previsão mapeia diretamente para um perfil de vol realizada mais plano e um Sharpe mais alto.
- Vol targeting eleva o retorno ajustado ao risco e, especialmente, controla drawdowns, porque volatilidade é previsível (ela se agrupa) enquanto direção não é, e porque Sharpe ratios caem em regimes de alta vol que a regra automaticamente subpondera. É dimensionamento Kelly sob a suposição de que o retorno esperado escala com a volatilidade.
- Compare o GARCH honestamente contra baselines fortes: volatilidade realizada móvel, EWMA/RiskMetrics (, um IGARCH com zero parâmetros livres), e HAR-RV sobre uma proxy de variância realizada intradiária. EWMA e HAR são difíceis de vencer.
- Você não consegue observar a volatilidade verdadeira, então avalie contra uma proxy ( ou, muito melhor, ) usando funções de perda robustas ao ruído da proxy. Prefira QLIKE ao MSE: ele penaliza mais a subprevisão (o erro caro) e é invariante à escala, então não é sequestrado por alguns dias de alta vol. Use Mincer-Zarnowitz para diagnosticar viés e o teste de Diebold-Mariano para decidir se a vantagem de uma previsão é real ou ruído.
- Em um backtest walk-forward consciente de custos, vol targeting supera de forma confiável o notional fixo em Sharpe e drawdown, e os quatro previsores se agrupam próximos — a vantagem do GARCH sobre o EWMA é pequena, dependente de regime, e frequentemente não estatisticamente significativa. Reporte o teste DM, não apenas a perda média.
- Seja honesto: vol targeting é gestão de risco, não alfa. Ele reformata a distribuição de retorno de qualquer aposta direcional que você já tinha; não cria vantagem do nada. E tem alto turnover e é escalado por alavancagem, então resultados ao vivo ficam atrás do backtest mais do que o usual.
Referências:
- 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
Quantitative Research & Strategy