Preguntar «¿y si?» en vez de «¿qué probabilidad?»
El VaR y el expected shortfall se estiman con rendimientos pasados, así que no ven lo que nunca ha ocurrido. Una prueba de estrés abandona las probabilidades y aplica un escenario elegido: una caída del 20 % de las acciones, una subida de 2 puntos de los tipos, la crisis de 2008 repetida, una devaluación. La pérdida de la cartera en ese escenario es un número que un comité de riesgos puede discutir y revela concentraciones que un promedio estadístico oculta.
import numpy as np
values = np.array([500000.0, 300000.0, -200000.0]) # two longs and a short
shocks = np.array([-0.20, -0.10, 0.15]) # one scenario: stocks -20%, bonds -10%, the shorted stock +15%
pnl = values * shocks # per position
print(pnl) # [-100000. -30000. -30000.] the short loses when its stock rises
print(float(pnl.sum())) # -160000.0Una tabla de escenarios
Los equipos de riesgos mantienen una lista permanente de escenarios, históricos (1987, 2008, marzo de 2020) e hipotéticos. Colócalos en una matriz con una fila por escenario y una columna por posición: la pérdida total en cada escenario es entonces un único producto matriz-vector, scenarios @ values. Tomar el mínimo de ese vector nombra el peor caso, y el escenario que más daña es en sí información, pues dice dónde está expuesta la cartera.
import numpy as np
values = np.array([500000.0, 300000.0, -200000.0])
scenarios = np.array([[-0.20, -0.10, 0.15], # equity crash
[-0.05, 0.02, 0.00], # mild dip
[ 0.10, 0.05, -0.20]]) # the shorted stock falls 20%: the short gains
names = ["equity crash", "mild dip", "rally and short gain"]
pnl = scenarios @ values
for name, p in zip(names, pnl):
print(name, round(float(p)))
worst = int(np.argmin(pnl))
print("worst:", names[worst], round(float(pnl[worst])))# A shock does not have to be a price move: a rate jump hits a bond through its duration
duration = 7.5 # years, from the duration topic
bond_value = 1_000_000.0
rate_jump = 0.02 # +2 percentage points
print(round(bond_value * (-duration * rate_jump))) # -150000 the shock expressed as a price return of -15%La prueba de estrés inversa
Invirtamos la pregunta: ¿cuán grave tendría que ser un shock para costarnos una cantidad dada, como el colchón de capital? Tomemos una dirección de shocks (por ejemplo, todo cae a la vez) y escalémosla por un factor k. La pérdida es -k * (values @ direction), lineal en k, así que el factor que pierde exactamente loss_limit es loss_limit / -(values @ direction). Si la dirección no pierde dinero, ningún factor alcanza el límite.
import numpy as np
def reverse_stress(values, direction, loss_limit):
change = float(values @ direction)
if change >= 0:
return float("inf")
return loss_limit / -change
values = np.array([500000.0, 300000.0, -200000.0])
direction = np.array([-1.0, -1.0, 0.0]) # the two longs fall by the same percentage
k = reverse_stress(values, direction, 100000.0)
print(round(k, 4)) # 0.125 a 12.5% fall in both longs loses 100,000
print(round(float(-(values @ (k * direction))), 2)) # 100000.0- Una prueba de estrés aplica shocks elegidos: el pnl de una posición es
values * shocks, negativo en pérdidas y positivo para el corto que gana. - Varios escenarios a la vez son un producto,
scenarios @ values; el mínimo nombra el peor caso y muestra dónde está expuesta la cartera. - Estrés inverso: el múltiplo de un shock que pierde exactamente el límite es
loss_limit / -(values @ direction), o infinito si la dirección nunca pierde.
A continuación: los modelos ARIMA, pronosticar una serie a partir de su propio pasado.
Lab: qué se califica
- Golpear cada posición con
shocked_pnl25 pts - Ejecutar varios escenarios a la vez con
scenario_pnl25 pts - Nombrar el peor caso con
worst_scenario25 pts - Partir de una pérdida con
reverse_stress25 pts