Uma fórmula em vez de um histórico
O VaR histórico lê a perda nos retornos passados. O VaR paramétrico supõe que os retornos seguem uma distribuição normal e precisa de apenas dois números, a média e o desvio-padrão. A perda excedida com probabilidade 1 - confidence é então z * sigma - mu, em que z é o quantil da normal padrão (1,645 a 95%, 2,326 a 99%). É rápido, suave e se estende facilmente a uma carteira inteira, por isso foi o padrão do setor por décadas.
from statistics import NormalDist
def normal_var(mu, sigma, confidence):
z = NormalDist().inv_cdf(confidence)
return z * sigma - mu
print(round(NormalDist().inv_cdf(0.95), 4), round(NormalDist().inv_cdf(0.99), 4))
print(round(normal_var(0.0005, 0.012, 0.95), 5)) # a 95% one-day loss limit
print(round(normal_var(0.0005, 0.012, 0.99), 5)) # a higher confidence, a larger limit# The multiplier z for common confidence levels
from statistics import NormalDist
for confidence in (0.90, 0.95, 0.975, 0.99, 0.999):
print(confidence, round(NormalDist().inv_cdf(confidence), 3)) # 1.282, 1.645, 1.96, 2.326, 3.09De um ativo a uma carteira, e a dez dias
Para uma carteira, a média é weights @ mu e o desvio-padrão sqrt(weights @ cov @ weights), as quantidades do tópico de variância de carteira. A diversificação aparece diretamente: correlações baixas reduzem o desvio-padrão e o VaR. Para um horizonte maior, a média cresce com o número de dias e o desvio-padrão com sua raiz, então um VaR de 10 dias é z * sigma * sqrt(10) - mu * 10.
import numpy as np
from statistics import NormalDist
def normal_var(mu, sigma, confidence):
return NormalDist().inv_cdf(confidence) * sigma - mu
weights = np.array([0.6, 0.4])
mu = np.array([0.0005, 0.0003])
cov = np.array([[0.00016, 0.00004], [0.00004, 0.00009]]) # volatilities 1.26% and 0.95%, correlation 0.33
mean, std = float(weights @ mu), float(np.sqrt(weights @ cov @ weights))
print(round(mean, 5), round(std, 5))
print(round(normal_var(mean, std, 0.99), 5))
print(round(normal_var(mean * 10, std * np.sqrt(10), 0.99), 5)) # the 10-day figureOnde a suposição normal falha
Os retornos reais têm caudas pesadas: os dias extremos são muito mais frequentes do que uma normal permite. O VaR paramétrico parece então pequeno demais, sobretudo com confiança alta. Compará-lo com o VaR histórico nos mesmos dados mostra a diferença: positiva significa que o modelo normal subestima o risco. A 95% os dois costumam ser próximos, a 99% podem diferir muito.
# Fat-tailed returns: the normal model understates the 99% loss
import numpy as np
from statistics import NormalDist
rng = np.random.default_rng(2)
returns = rng.standard_t(3, size=5000) * 0.008 # a fat-tailed distribution (3 degrees of freedom)
for confidence in (0.95, 0.99):
historical = -np.quantile(returns, 1 - confidence)
parametric = NormalDist().inv_cdf(confidence) * returns.std(ddof=1) - returns.mean()
print(confidence, round(float(historical), 5), round(float(parametric), 5), round(float(historical - parametric), 5))- VaR paramétrico =
z * sigma - mucomz = NormalDist().inv_cdf(confidence): bastam a média e o desvio-padrão. - Uma carteira usa
weights @ muesqrt(weights @ cov @ weights); um horizonte multiplica a média pordayse a volatilidade porsqrt(days). - Retornos reais têm caudas pesadas: compare com o VaR histórico; uma diferença positiva significa que o modelo normal subestima o risco.
A seguir: o expected shortfall, a perda média nos dias em que o VaR é excedido.
Lab: o que é avaliado
- Transformar dois números em um limite com
normal_var25 pts - Cobrir uma carteira inteira com
portfolio_normal_var25 pts - Olhar mais adiante com
horizon_var25 pts - Medir as caudas pesadas com
normality_gap25 pts