Lo que el VaR no dice
Un VaR al 99 % del 3 % dice que 99 días de cada 100 pierdes menos del 3 %. No dice nada del día 100: puede ser 3,1 % o 30 %. El expected shortfall (ES, CVaR) cierra el hueco. Es la pérdida media en los días en que se supera el VaR, así que describe la forma de la cola. Al promediar, también cambia con suavidad y, a diferencia del VaR, premia siempre la diversificación, por lo que Basilea pasó del VaR al ES para el riesgo de mercado.
import numpy as np
rng = np.random.default_rng(5)
returns = rng.standard_t(4, size=1000) * 0.01
cutoff = np.quantile(returns, 0.05) # the 95% VaR is minus this
tail = np.sort(returns[returns <= cutoff]) # the worst 5% of the days, lowest first
print(len(tail), round(float(-cutoff), 5)) # 50 days; the VaR
print(np.round(tail[:4], 4)) # the four worst daysPromediar la cola
Históricamente el ES es directo: se seleccionan los rendimientos iguales o inferiores al umbral del VaR, se promedian y se cambia el signo. El resultado nunca es menor que el VaR, porque cada rendimiento de la cola es al menos tan malo como el umbral. El cociente ES / VaR indica cuán pesada es la cola. Para una normal es de alrededor de 1,2 al 95 %, y los datos de colas gruesas suelen mostrar un cociente mayor.
import numpy as np
def tail_returns(returns, confidence):
cutoff = np.quantile(returns, 1 - confidence)
return np.sort(returns[returns <= cutoff])
def historical_es(returns, confidence):
return float(-tail_returns(returns, confidence).mean())
rng = np.random.default_rng(5)
returns = rng.standard_t(4, size=1000) * 0.01
for confidence in (0.95, 0.99):
var = -np.quantile(returns, 1 - confidence)
es = historical_es(returns, confidence)
print(confidence, round(float(var), 5), round(es, 5), round(es / float(var), 3))Una fórmula para el caso normal
Para un rendimiento normal el promedio de la cola tiene forma cerrada: ES = sigma * pdf(z) / (1 - confidence) - mu, donde z es el cuantil usado para el VaR y pdf la densidad normal estándar en z. Reduce el cálculo a una función de dos líneas y solo es exacto si los datos son normales, así que compararlo con el ES histórico es una prueba rápida de cuánto se apartan los datos de la normalidad.
from statistics import NormalDist
def normal_es(mu, sigma, confidence):
z = NormalDist().inv_cdf(confidence)
return sigma * NormalDist().pdf(z) / (1 - confidence) - mu
for confidence in (0.95, 0.975, 0.99):
var = NormalDist().inv_cdf(confidence) # in standard deviations, mu = 0
es = normal_es(0.0, 1.0, confidence)
print(confidence, round(var, 3), round(es, 3), round(es / var, 3)) # ES/VaR: 1.254, 1.193, 1.146# The formula against a simulated tail
import numpy as np
from statistics import NormalDist
rng = np.random.default_rng(0)
sample = rng.normal(0.0, 0.01, size=1_000_000)
tail = sample[sample <= np.quantile(sample, 0.01)]
simulated = -tail.mean()
z = NormalDist().inv_cdf(0.99)
formula = 0.01 * NormalDist().pdf(z) / 0.01
print(round(float(simulated), 5), round(formula, 5)) # both close to 0.02665- El ES es la pérdida media en la cola más allá del VaR: menos la media de los rendimientos iguales o inferiores al cuantil
(1 - confidence). - Nunca es menor que el VaR;
ES / VaRmide el peso de la cola, y la fórmula normal essigma * pdf(z) / (1 - confidence) - mu. - Informe el VaR como umbral y el ES como gravedad; el ES es más ruidoso porque se apoya en pocas observaciones de cola.
A continuación: las pruebas de estrés, preguntar cuánto perdería una cartera en un escenario concreto y extremo.
Lab: qué se califica
- Aislar los peores días con
tail_returns25 pts - Promediar la cola con
historical_es25 pts - Obtenerlo con una fórmula con
normal_es25 pts - Comparar cola y umbral con
es_var_ratio25 pts