← Voltar aos artigos
July 13, 2026
5 min read

Volatility Targeting e Trading com Previsões GARCH

#volatility
#GARCH
#volatility-targeting
#backtesting
#risk
#crypto
#algorithmic-trading

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:

wt=σtargetσ^t(depois limitado)w_t = \frac{\sigma_{\text{target}}}{\hat{\sigma}_{t}} \quad \text{(depois limitado)}

onde σ^t\hat{\sigma}_t é sua previsão de volatilidade do próximo período (feita usando apenas dados até tt), e σtarget\sigma_{\text{target}} é 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,

Vol(wtrt+1)=wtσt+1=σtargetσ^tσt+1σtarget\text{Vol}(w_t r_{t+1}) = w_t \, \sigma_{t+1} = \frac{\sigma_{\text{target}}}{\hat\sigma_t}\, \sigma_{t+1} \approx \sigma_{\text{target}}

sempre que σ^tσt+1\hat\sigma_t \approx \sigma_{t+1}. Então toda a qualidade do exercício repousa sobre uma coisa: quão próxima sua previsão σ^t\hat\sigma_t está da volatilidade real do próximo período σt+1\sigma_{t+1}. 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 R2R^2 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 1/σ21/\sigma^2 — 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 wtrt+1w_t r_{t+1} tem volatilidade fixada perto de σtarget\sigma_{\text{target}}, 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 μ\mu e variância σ2\sigma^2, a fração ótima de crescimento (Kelly integral) é

f=μσ2.f^\star = \frac{\mu}{\sigma^2}.

Se você assumir que o Sharpe ratio μ/σ\mu/\sigma é aproximadamente constante — uma suposição forte, mas a implícita em "o mercado paga um preço estável pelo risco" — então μ=Sσ\mu = S\sigma e f=S/σf^\star = S/\sigma, que é exatamente volatility targeting com σtarget=S(frac¸a˜o de Kelly)\sigma_{\text{target}} = S \cdot (\text{fração de Kelly}). 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 σ^t\hat\sigma_t 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 nn retornos diários.

σ^tRV=1n1i=1n(rti+1rˉ)2\hat\sigma_{t}^{\text{RV}} = \sqrt{\frac{1}{n-1}\sum_{i=1}^{n}\bigl(r_{t-i+1}-\bar r\bigr)^2}

Tem um único hiperparâmetro (a janela nn, 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 nn dias e depois desaparece da noite para o dia, tenha o mercado realmente se acalmado ou não.

(b) EWMA / RiskMetrics (λ=0.94\lambda = 0.94)

A média móvel exponencialmente ponderada corrige o problema do eco dando peso geometricamente decrescente a retornos ao quadrado mais antigos:

σ^t2=λσ^t12+(1λ)rt2\hat\sigma_{t}^{2} = \lambda\,\hat\sigma_{t-1}^{2} + (1-\lambda)\,r_{t}^{2}

Este é o estimador RiskMetrics (J.P. Morgan, 1996). Com o canônico λ=0.94\lambda = 0.94 para dados diários, a memória efetiva é de aproximadamente 1/(1λ)171/(1-\lambda) \approx 17 dias, mas o decaimento é suave — sem penhasco. Note o que o EWMA realmente é: é um GARCH(1,1) integrado com ω=0\omega = 0 e α+β=1\alpha + \beta = 1, ou seja, GARCH sem reversão à média e sem variância de longo prazo. Ele tem zero parâmetros livres se você aceitar λ=0.94\lambda = 0.94, 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 tt (utilizável para dimensionar uma posição mantida durante tt) deve ser construída a partir de retornos observados antes de tt. 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:

σt2=ω+αϵt12+βσt12,ω>0, α,β0, α+β<1\sigma_t^2 = \omega + \alpha\, \epsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2, \qquad \omega>0,\ \alpha,\beta\ge0,\ \alpha+\beta<1

com variância de longo prazo σˉ2=ω/(1αβ)\bar\sigma^2 = \omega/(1-\alpha-\beta) 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:

σt2=ω+(α+γ1[ϵt1<0])ϵt12+βσt12\sigma_t^2 = \omega + (\alpha + \gamma\, \mathbb{1}[\epsilon_{t-1}<0])\,\epsilon_{t-1}^2 + \beta\,\sigma_{t-1}^2

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 σˉ2\bar\sigma^2 a uma taxa governada por α+β\alpha+\beta, 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.

RVt=i=1Mrt,i2(ex. M=288 barras de cinco minutos)RV_t = \sum_{i=1}^{M} r_{t,i}^2 \qquad (\text{ex. } M = 288 \text{ barras de cinco minutos})

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 RVRV passado — uma forma tosca porém notavelmente eficaz de capturar persistência de longa memória com três regressores:

RVt+1=β0+βdRVt+βwRVt(5)+βmRVt(22)+ut+1RV_{t+1} = \beta_0 + \beta_d\, RV_t + \beta_w\, \overline{RV}_t^{(5)} + \beta_m\, \overline{RV}_t^{(22)} + u_{t+1}

onde RVt(5)\overline{RV}_t^{(5)} e RVt(22)\overline{RV}_t^{(22)} são as médias móveis de 5 dias e 22 dias do RVRV 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 RVRV é um alvo mais limpo do que r2r^2.

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 RVRV é assimétrico à direita e estritamente positivo, muitos praticantes regridem logRVt+1\log RV_{t+1} 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, RV^=exp(y^+12σ^u2)\widehat{RV} = \exp(\hat{y} + \tfrac{1}{2}\hat\sigma_u^2), 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 σ^t2\hat\sigma_t^2 contra a variância condicional verdadeira σt2\sigma_t^2. Mas σt2\sigma_t^2 é 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 rt2r_t^2. Não enviesado para σt2\sigma_t^2 sob um modelo de média zero (E[rt2Ft1]=σt2\mathbb{E}[r_t^2 \mid \mathcal{F}_{t-1}] = \sigma_t^2), mas extremamente ruidoso: um único retorno diário é uma estimativa de uma observação de um desvio padrão. A proxy rt2r_t^2 pode ser 0 (um dia plano) mesmo quando a vol verdadeira é alta, ou enorme em um dia de sorte na cauda.
  • Variância realizada RVtRV_t 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 RVRV 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:

LMSE(σ2,h)=(σ2h)2L_{\text{MSE}}(\sigma^2, h) = (\sigma^2 - h)^2

onde h=σ^2h = \hat\sigma^2 é a previsão e σ2\sigma^2 é aproximada por r2r^2 ou RVRV. 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:

LQLIKE(σ2,h)=σ2hlogσ2h1L_{\text{QLIKE}}(\sigma^2, h) = \frac{\sigma^2}{h} - \log\frac{\sigma^2}{h} - 1

É 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 σ2/h\sigma^2/h, 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 LQLIKE0L_{\text{QLIKE}} \ge 0, com igualdade se e somente se h=σ2h = \sigma^2. 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 log0\log 0 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:

rt2  =  a+bσ^t2+ut.r_t^2 \;=\; a + b\,\hat\sigma_t^2 + u_t.

Sob uma previsão ótima e não enviesada, a=0a = 0 e b=1b = 1: em média, a variância realizada é igual à previsão. Os desvios diagnosticam a patologia:

  • b<1b < 1 com a>0a > 0: 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.
  • b>1b > 1: a previsão reage de menos, escalando pouco demais em relação à variância verdadeira.
  • R2R^2 da regressão baixo: mesmo que a,ba,b 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 R2R^2 do MZ contra r2r^2 seja frequentemente apenas 0.05-0.20; contra RVRV será muito mais alto. O R2R^2 contra r2r^2 é 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 FF conjunto de H0:(a,b)=(0,1)H_0: (a,b) = (0,1) 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

dt=L(proxyt,htA)L(proxyt,htB)d_t = L(\text{proxy}_t, h^A_t) - L(\text{proxy}_t, h^B_t)

para duas previsões AA e BB (aqui LL = QLIKE). A hipótese nula é acurácia preditiva igual, H0:E[dt]=0H_0: \mathbb{E}[d_t] = 0. A estatística é a média do diferencial padronizada pelo seu erro padrão de longo prazo (HAC), porque dtd_t é serialmente correlacionado:

DM=dˉLRV^(dt)/T  d  N(0,1)\text{DM} = \frac{\bar d}{\sqrt{\widehat{\mathrm{LRV}}(d_t)/T}} \;\xrightarrow{d}\; \mathcal{N}(0,1)

onde LRV^\widehat{\mathrm{LRV}} é uma estimativa de variância de longo prazo do tipo Newey-West. Uma estatística DM além de ±1.96\pm 1.96 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-tt ingênuo sobre dtd_t 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 wt=min(σtarget/σ^t, wmax)w_t = \min(\sigma_{\text{target}}/\hat\sigma_t,\ w_{\max}), 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 t+1t+1 é computável usando apenas dados disponíveis ao fechamento do dia tt. Os parâmetros do GARCH são reestimados em uma janela móvel terminando em tt; a previsão é o σ^t+1\hat\sigma_{t+1} um passo à frente daquele ajuste; a posição é definida a partir dessa previsão; e o retorno auferido é wt+1rt+1w_{t+1} r_{t+1}, onde rt+1r_{t+1} é 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 arch são numericamente mais felizes quando os retornos estão em percentual, daí o * 100 e o / 100**2 correspondente 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 σt2\sigma_t^2 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 wmaxw_{\max} não é cosmético. Quando a vol prevista colapsa em um regime de calmaria total, σtarget/σ^t\sigma_{\text{target}}/\hat\sigma_t 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_bps nã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 (λ=0.94\lambda=0.94) 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 σ^t\hat\sigma_t se torna a volatilidade de portfólio wΣtw\sqrt{w^\top \Sigma_t w}, onde Σt\Sigma_t é a matriz de covariância variável no tempo do DCC-GARCH da Parte 3. Você escolhe pesos base w0w_0 (peso igual, capitalização de mercado, ou uma inclinação média-variância), computa a vol prevista do portfólio sob Σt\Sigma_t, 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 w0w_0 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 σˉ2\bar\sigma^2, o que o escalonamento ingênuo "hdiaˊrio\sqrt{h}\cdot\text{diário}" 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 365\sqrt{365}, não o 252\sqrt{252} 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 σ^t\hat\sigma_t escalar pela vol de portfólio wΣtw\sqrt{w^\top \Sigma_t w} 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 1/σ1/\sigma, 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 σ^t\hat\sigma_t 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 wt=σtarget/σ^tw_t = \sigma_{\text{target}}/\hat\sigma_t (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 (λ=0.94\lambda=0.94, 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 (r2r^2 ou, muito melhor, RVRV) 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
blog.disclaimer

MarketMaker.cc Team

Quantitative Research & Strategy

Discuss in Telegram
Newsletter

Fique à frente do mercado

Assine nossa newsletter para insights exclusivos sobre trading com IA, análises de mercado e atualizações da plataforma.

Respeitamos sua privacidade. Cancele a inscrição a qualquer momento.