Um limite de perda com um nível de confiança
O Value at Risk responde: quanto posso perder num dia ruim, mas não no pior? Um VaR de 1 dia a 95% de 2% diz que em 95 dias de 100 a perda não deve passar de 2%. O método histórico não precisa de modelo. Pegam-se os retornos passados, ordenam-se e lê-se a perda no percentil 5: o VaR é menos esse quantil. Reguladores e limites de risco o usam em todo lugar porque é um número único que um gestor pode comparar com um limite.
import numpy as np
rng = np.random.default_rng(3)
returns = rng.normal(0.0004, 0.012, size=500) # two years of daily returns
var_95 = -np.quantile(returns, 0.05) # minus the 5th percentile
var_99 = -np.quantile(returns, 0.01)
print(round(float(var_95), 5), round(float(var_99), 5)) # 0.01928 0.027 the 99% loss limit is largerTestar um VaR: as violações
Um VaR é uma previsão, então pode ser checado. Uma violação é um dia em que a perda excedeu o VaR, return < -VaR. Com um VaR a 95% esperam-se cerca de 5% dos dias como violações: muito mais significa que o modelo subestima o risco, muito menos que é cauteloso demais. Reguladores contam violações em 250 dias exatamente por isso. Note a desigualdade estrita: uma perda igual ao VaR não é uma violação.
import numpy as np
rng = np.random.default_rng(3)
returns = rng.normal(0.0004, 0.012, size=500)
var_95 = -np.quantile(returns, 0.05)
exceptions = int(np.sum(returns < -var_95))
print(exceptions, "exceptions in", len(returns), "days; expected about", int(0.05 * len(returns))) # 25 25Horizontes mais longos, e como aconteceu
Um VaR de 10 dias costuma ser exigido. Com retornos diários independentes, a volatilidade cresce com a raiz do tempo, então a regra da raiz do tempo escala: VaR_T = VaR_1 * sqrt(T). É uma aproximação, mais fraca com caudas pesadas e retornos autocorrelacionados. Backtests honestos também respeitam o tempo: o VaR usado no dia t só pode usar os retornos antes do dia t, a mesma disciplina de evitar antecipação. Um VaR móvel faz isso com uma janela dos últimos n dias.
# The square-root-of-time rule for several horizons
import math
one_day = 0.0186
for days in (1, 5, 10, 20):
print(days, round(one_day * math.sqrt(days), 4)) # 1: 0.0186, 5: 0.0416, 10: 0.0588, 20: 0.0832import numpy as np
def historical_var(returns, confidence):
return float(-np.quantile(returns, 1 - confidence))
def rolling_var(returns, window, confidence):
out = np.full(len(returns), np.nan)
for t in range(window, len(returns)):
out[t] = historical_var(returns[t - window:t], confidence) # the days BEFORE day t
return out
rng = np.random.default_rng(3)
returns = rng.normal(0.0004, 0.012, size=500)
rolling = rolling_var(returns, 250, 0.99)
print(np.isnan(rolling[:250]).all(), round(float(rolling[250]), 5), round(float(rolling[-1]), 5))
exceptions = int(np.sum(returns[250:] < -rolling[250:]))
print(exceptions, "exceptions in", len(returns) - 250, "days (about 2 expected at 99%)")
print(round(historical_var(returns, 0.99) * np.sqrt(10), 5)) # a 10-day figure by the square-root rule- O VaR histórico é menos o quantil
(1 - confidence)dos retornos passados (np.quantile): uma fração de perda positiva sem modelo. - Teste contando violações (
return < -VaR): cerca de1 - confidencedos dias; mais significa um VaR baixo demais. - Escale para um horizonte com
var * sqrt(days); uma janela móvel usa só dias anteriores; o VaR dá um limiar, não o tamanho das perdas além.
A seguir: o VaR paramétrico, calcular o mesmo limiar a partir de uma distribuição normal em vez do histórico bruto.
Lab: o que é avaliado
- Ler a perda no histórico com
historical_var25 pts - Contar as violações com
var_exceptions25 pts - Esticar o horizonte com
scale_var25 pts - Estimar como aconteceu com
rolling_var25 pts