Une formule au lieu d'un historique
La VaR historique lit la perte dans les rendements passés. La VaR paramétrique suppose que les rendements suivent une loi normale et n'a besoin que de deux nombres, la moyenne et l'écart-type. La perte dépassée avec une probabilité 1 - confidence vaut alors z * sigma - mu, où z est le quantile de la loi normale centrée réduite (1,645 à 95 %, 2,326 à 99 %). Elle est rapide, lisse et s'étend facilement à tout un portefeuille, d'où son statut de standard du secteur pendant des décennies.
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.09D'un actif à un portefeuille, et à dix jours
Pour un portefeuille, la moyenne est weights @ mu et l'écart-type sqrt(weights @ cov @ weights), les quantités du sujet sur la variance de portefeuille. La diversification apparaît directement : de faibles corrélations réduisent l'écart-type et la VaR. Pour un horizon plus long, la moyenne croît avec le nombre de jours et l'écart-type avec sa racine carrée, donc une VaR à 10 jours vaut 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 figureLà où l'hypothèse normale échoue
Les rendements réels ont des queues épaisses : les jours extrêmes sont bien plus fréquents que ne le permet une loi normale. La VaR paramétrique paraît alors trop faible, surtout à confiance élevée. La comparer à la VaR historique sur les mêmes données montre l'écart : positif, il signifie que le modèle normal sous-estime le risque. À 95 %, les deux sont souvent proches, à 99 % elles peuvent différer beaucoup.
# 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étrique =
z * sigma - muavecz = NormalDist().inv_cdf(confidence): seuls la moyenne et l'écart-type sont nécessaires. - Un portefeuille utilise
weights @ muetsqrt(weights @ cov @ weights); un horizon multiplie la moyenne pardayset la volatilité parsqrt(days). - Les rendements réels ont des queues épaisses : comparez à la VaR historique ; un écart positif signifie que le modèle normal sous-estime le risque.
À suivre : l'expected shortfall, la perte moyenne les jours où la VaR est dépassée.
Lab : ce qui est noté
- Transformer deux nombres en limite avec
normal_var25 pts - Couvrir tout un portefeuille avec
portfolio_normal_var25 pts - Regarder plus loin avec
horizon_var25 pts - Mesurer les queues épaisses avec
normality_gap25 pts