The brief
A risk desk reports a one-day 99% value at risk (VaR) on a $10,000,000 holding in one stock. Its system fits a normal model to the last 500 trading days. The head of risk has seen losses beat that figure more often than 1 day in 100, and wants a second opinion.
The stock's returns have fat tails, so a normal model and the sample disagree about the worst days. The desk needs both figures and a test of the normal one. It also needs to know how far the historical figure could move with another sample.
Your task
Each function takes returns, a list or array of daily log returns, a confidence level and the position value in dollars. VaR is a positive loss: , where is the value and the return quantile at level.
- Write
normal_var(returns, level=0.99, value=VALUE). Use , where is the mean return, isnorm.ppf(1 - level)and is the sample standard deviation dividing by . - Write
historical_var(returns, level=0.99, value=VALUE), with =np.quantile(returns, 1 - level). - Write
var_interval(returns, level=0.99, value=VALUE, n_boot=2000, seed=1): a 95% bootstrap percentile interval(low, high)for the historical VaR. Resample the days with replacementn_boottimes, withnp.random.default_rng(seed). - Write
backtest(returns, var, level=0.99, value=VALUE), which returns(exceedances, p_value).exceedancescounts the days whose loss was larger thanvar.p_valueis the chance of at least that many if the VaR is right.
Every function raises ValueError if level is not strictly between 0 and 1, or value is not positive. It raises it too for a missing return (NaN), and for fewer than returns, which leave no day beyond the level. backtest raises it when var is not positive.
The data
RETURNS holds the 500 daily log returns, oldest first, and VALUE the position. The returns are synthetic, drawn with a fixed seed from a fat-tailed distribution and rounded to 0.001%. The worst day lost 6.30%.
Hint: Where to start
Case study 5.3.5 reads both VaRs from a return series: one from the sample's 1% quantile, one from the normal quantile . Lesson 5.4.3 gives from norm.ppf. Use np.expm1(q) for .
Hint: How sure is the historical figure?
The 1% tail of 500 days rests on five or six of them. Lesson 5.8.2's bootstrap measures what that costs. Draw 500 days with replacement and recompute the VaR, many times; then read the 2.5% and 97.5% quantiles of the results.
Hint: Testing the normal model
If the VaR is right, each day beats it with probability level, so the count is binomial (lesson 5.2.2). Lesson 5.8.3 turns a count into a p-value: is binom.sf(k - 1, n, p).