Una fórmula en vez de un historial
El VaR histórico lee la pérdida en los rendimientos pasados. El VaR paramétrico supone que los rendimientos siguen una distribución normal y solo necesita dos números, la media y la desviación típica. La pérdida superada con probabilidad 1 - confidence es entonces z * sigma - mu, donde z es el cuantil de la normal estándar (1,645 al 95 %, 2,326 al 99 %). Es rápido, suave y se extiende fácilmente a toda una cartera, por lo que fue el estándar del sector durante 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 un activo a una cartera, y a diez días
Para una cartera, la media es weights @ mu y la desviación típica sqrt(weights @ cov @ weights), las cantidades del tema de varianza de cartera. La diversificación aparece directamente: correlaciones bajas reducen la desviación y el VaR. Para un horizonte mayor, la media crece con el número de días y la desviación con su raíz cuadrada, así que un VaR a 10 días es 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 figureDonde falla el supuesto normal
Los rendimientos reales tienen colas gruesas: los días extremos son mucho más frecuentes de lo que permite una normal. El VaR paramétrico parece entonces demasiado pequeño, sobre todo con confianza alta. Compararlo con el VaR histórico sobre los mismos datos muestra la diferencia: positiva significa que el modelo normal subestima el riesgo. Al 95 % ambos suelen ser parecidos, al 99 % pueden diferir mucho.
# 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 - muconz = NormalDist().inv_cdf(confidence): solo hacen falta la media y la desviación típica. - Una cartera usa
weights @ muysqrt(weights @ cov @ weights); un horizonte multiplica la media pordaysy la volatilidad porsqrt(days). - Los rendimientos reales tienen colas gruesas: compare con el VaR histórico; una diferencia positiva significa que el modelo normal subestima el riesgo.
A continuación: el expected shortfall, la pérdida media en los días en que se supera el VaR.
Lab: qué se califica
- Convertir dos números en un límite con
normal_var25 pts - Cubrir toda una cartera con
portfolio_normal_var25 pts - Mirar más lejos con
horizon_var25 pts - Medir las colas gruesas con
normality_gap25 pts