Tamanhos de posição fixos ignoram as condições do mercado. O alvo de volatilidade ajusta quanto você aloca com base em quão volátil o mercado está no momento.
A mesma estratégia fica tão entediante nos meses calmos que dá vontade de desligá-la; e então, na semana em que o mercado começa a se mexer com violência, ela cai muito mais fundo do que a média do backtest fazia esperar. O sinal não mudou, o mercado mudou — e o tamanho de cada aposta que você faz continuou exatamente o mesmo.
O alvo de volatilidade é fazer o tamanho da posição andar de forma inversa à volatilidade realizada do mercado: quando a volatilidade está acima do seu alvo você aloca menos, quando está abaixo você aloca mais — até um limite.
Uma estratégia básica do Tipo A gera um sinal de 1.0 (comprado) ou 0.0 (fora do mercado). O executor aloca todo o capital destinado quando o sinal é 1.0. Isso funciona, mas tem um problema oculto: a estratégia faz uma aposta do mesmo tamanho, independentemente de o mercado estar calmo ou caótico.
Em um período de baixa volatilidade, uma variação diária de 1% é grande. Em um período de alta volatilidade, uma variação de 1% é ruído. Se sua estratégia entra com o mesmo tamanho de posição nos dois ambientes, seu risco real em dólares varia drasticamente — e seus drawdowns em períodos voláteis serão muito maiores do que a média do seu backtest sugere.
O Blave Agent implementa o alvo de volatilidade em duas etapas:
log_return = log(Close / Close.shift(1)) realized_vol = log_return.rolling(lookback).std() * sqrt(periods_per_year)
Isso calcula o desvio padrão móvel dos retornos logarítmicos e depois anualiza multiplicando por sqrt(periods_per_year). O resultado é a volatilidade realizada anualizada — a mesma unidade da volatilidade usada no Sharpe ratio.
Padrões: lookback=720 barras, periods_per_year=8760. Esses valores são calibrados para uma estratégia de intervalo de 1h: 720 barras = 30 dias × 24 horas; 8760 = 24 × 365.
scale = (target_vol / realized_vol).clip(upper=vol_cap) scaled_signal = signal * scale
O sinal (1.0 = totalmente comprado) é multiplicado pela razão de volatilidade, limitado a vol_cap. O resultado é um tamanho de posição fracionário.
Padrões: target_vol=0.30 (30% anualizado), vol_cap=2.0 (alavancagem máxima de 2×).
| Vol. realizada (anual.) | Volatilidade-alvo | Escala bruta | Após limite | Tamanho da posição |
|---|---|---|---|---|
| 10% | 30% | 3.0× | 2.0× (limitado) | 200% da alocação |
| 20% | 30% | 1.5× | 1.5× | 150% da alocação |
| 30% | 30% | 1.0× | 1.0× | 100% — base |
| 60% | 30% | 0.5× | 0.5× | 50% da alocação |
| 90% | 30% | 0.33× | 0.33× | 33% da alocação |
Com 30% de volatilidade realizada, a estratégia opera em tamanho total. Durante uma crise, quando a volatilidade realizada dispara para 90%, a estratégia reduz automaticamente para um terço da posição. Em um período calmo, com 10% de volatilidade, ela escala até o limite de 2×.
Os padrões (lookback=720, periods_per_year=8760) são para uma estratégia de 1h. Para outros intervalos, os dois valores precisam ser recalculados:
| Intervalo | periods_per_year | lookback (janela de 30 dias) |
|---|---|---|
5min | 105,120 (12 × 24 × 365) | 8,640 barras |
1h (padrão) | 8,760 | 720 barras |
4h | 2,190 | 180 barras |
8h | 1,095 | 90 barras |
1d | 365 | 30 barras |
VOL_TARGETING = True
TARGET_VOL = 0.30 # alvo de 30% anualizado
VOL_CAP = 2.0 # alavancagem máxima de 2×
VOL_LOOKBACK = 720 # barras na janela de lookback
PERIODS_PER_YEAR = 8760 # para intervalo de 1h
def fetch_data(hdrs):
from lib.data import fetch_kline
from lib.strategy import add_realized_vol
df = fetch_kline(SYMBOL, INTERVAL, START, END, hdrs)
if VOL_TARGETING:
add_realized_vol(df, VOL_LOOKBACK, PERIODS_PER_YEAR)
return df
def compute_signals(df):
from lib.strategy import apply_vol_scaling
signal = pd.Series(np.nan, index=df.index)
# ... sua lógica de sinal aqui ...
if VOL_TARGETING:
return apply_vol_scaling(signal, df, TARGET_VOL, VOL_CAP)
return signal
| Parâmetro | Padrão | O que significa | Quando mudar |
|---|---|---|---|
TARGET_VOL | 0.30 | Volatilidade-alvo anualizada de 30% | Conforme o MDD que você aceita ÷ 2 |
VOL_CAP | 2.0 | No máximo 2× de alavancagem | Se não quiser alavancagem alguma, use 1.0 |
VOL_LOOKBACK | 720 | Comprimento da janela de estimativa de volatilidade (em barras) | Recalcular ao mudar de intervalo, veja a tabela acima |
PERIODS_PER_YEAR | 8760 | Períodos por ano usados para anualizar (intervalo de 1h) | Recalcular ao mudar de intervalo, veja a tabela acima |
Observe que add_realized_vol precisa ser chamado em fetch_data (antes de os sinais serem calculados), porque apply_vol_scaling lê df['realized_vol'], que é definido in-place pela primeira função.
O limite existe porque o alvo de volatilidade pode gerar alavancagem extrema em ambientes de volatilidade persistentemente baixa. Se a volatilidade realizada cair para 5%, a escala sem limite seria de 6× — três vezes a alocação. Em um movimento adverso de 1 dia, isso pode causar um drawdown catastrófico mesmo com um sinal fundamentalmente sólido.
O limite padrão de 2× restringe a estratégia a alocar, no máximo, o dobro do capital destinado. Para estratégias em que você quer evitar qualquer alavancagem, defina vol_cap=1.0. Isso transforma o alvo de volatilidade em um dimensionamento de posição puramente ajustado à volatilidade, sem alavancagem — o tamanho da posição só diminui em alta volatilidade, nunca ultrapassa o tamanho total.
Uma regra prática útil: target_vol ≈ MDD aceitável ÷ 2. Isso vem da relação empírica entre volatilidade anualizada e drawdown máximo em estratégias de trend-following.
| target_vol | MDD típico implícito | Caso de uso |
|---|---|---|
| 10% | ~−20% | Conservador, prioridade de preservação de capital |
| 20% | ~−40% | Moderado |
| 30% (padrão) | ~−60% | Agressivo — orçamento de risco total |
periods_per_year bate com o seu intervalo? O padrão 8760 é para 1h. É 105,120 para 5min, 2,190 para 4h, 1,095 para 8h e 365 para 1d. Errando aqui, o passo de anualização sai errado, e com ele todas as células seguintes.
lookback ainda é 30 dias? 720 barras = 1h × 30 dias. Mude junto com o intervalo: 8,640 barras para 5min, 180 para 4h, 90 para 8h, 30 para 1d. No diário, só 30 barras é pouco — vale considerar 252 (um ano) para uma estimativa mais estável.
target_vol bate com o MDD que você aceita? A regra prática é target_vol ≈ MDD aceitável ÷ 2: 10% para cerca de −20%, 20% para cerca de −40%, e o padrão de 30% para cerca de −60%. (Como ler o MDD, veja Como ler um backtest.)
vol_cap te dá a alavancagem que você quer? O padrão 2.0 significa no máximo o dobro do capital alocado. Colocando 1.0, ele só vai encolher a posição quando a volatilidade estiver alta, e nunca vai passar da alocação cheia quando ela estiver baixa.
O TARGET_VOL da estratégia individual está consistente com o target_vol_pct do Manager de portfólio? O Manager decide a alavancagem geral com a mesma lógica (alavancagem = target_vol ÷ volatilidade realizada do portfólio). Se os dois divergem, combinar várias estratégias conta risco em dobro.
lookback barras: quanto mais longa a janela, mais estável a estimativa e mais lenta a reação; quanto mais curta, mais ela exagera diante de um pico passageiro. Nenhuma das duas avisa com antecedência que a volatilidade vai saltar.