Chapter 13 Laboratory — Module 13: The Risk Office¶

Mathematical Foundations of Modern Finance · Part IV · Week 13

Four panels: the coherence-axioms panel, the VaR/ES measure panel, the robustness panel, and the backtesting-audit panel. Reproduces the two-bond (−4, 98) VaR subadditivity violation that convicts VaR, and confirms that expected shortfall is coherent.

Seeds: 20261301–20261304.

In [1]:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams.update({"figure.figsize": (7, 4.2), "axes.grid": True,
                     "grid.alpha": 0.3, "font.size": 11})
BRASS, INK = "#B4884A", "#0F1E3D"
import mfmf_engine_ch13 as eng

E1 — The two-bond example that convicts VaR (supports LOS 13.1–13.3)¶

Two defaultable bonds, each defaulting with 4% probability. At the 95% level each standalone VaR is negative (default sits in the 5% tail), but the diversified portfolio's VaR is positive: VaR is not subadditive. Expected shortfall, a tail mean, is.

In [2]:
r1 = eng.E1_var_subadditivity()
print("Value-at-Risk (95%):")
print(f"   bond 1: {r1['VaR_bond1']:+.1f}   bond 2: {r1['VaR_bond2']:+.1f}")
print(f"   sum of standalones: {r1['VaR_sum']:+.1f}")
print(f"   portfolio         : {r1['VaR_portfolio']:+.1f}")
print(f"   VaR violates subadditivity: {r1['VaR_violates_subadditivity']}   (book: -4 vs 98)")
print()
print("Expected Shortfall:")
print(f"   sum of standalones: {r1['ES96_sum']:.1f}")
print(f"   portfolio         : {r1['ES96_portfolio']:.1f}")
print(f"   ES is subadditive : {r1['ES_is_subadditive']}")

fig, ax = plt.subplots()
ax.bar(["VaR sum", "VaR portfolio"], [r1['VaR_sum'], r1['VaR_portfolio']],
       color=[INK, BRASS])
ax.axhline(0, color="grey", lw=1)
ax.set_ylabel("95% VaR"); ax.set_title("E1: portfolio VaR exceeds the sum — subadditivity fails")
plt.tight_layout(); plt.show()
Value-at-Risk (95%):
   bond 1: -2.0   bond 2: -2.0
   sum of standalones: -4.0
   portfolio         : +98.0
   VaR violates subadditivity: True   (book: -4 vs 98)

Expected Shortfall:
   sum of standalones: 102.1
   portfolio         : 100.1
   ES is subadditive : True
No description has been provided for this image

E2 — VaR and ES across confidence levels (supports LOS 13.2–13.3)¶

Meridian's loss distribution, measured at 95% and 99%. The Rockafellar–Uryasev objective's minimum lands at the ES, and its minimizer is the VaR.

In [3]:
r2 = eng.E2_measure()
for k, v in r2['figures'].items():
    print(f"{k:8s}: {v:.1f}")
print(f"Panel reference figures ($M): {r2['panel_targets']}")

losses = np.random.default_rng(1).normal(0, 250, 200000)
ru = eng.rockafellar_uryasev(losses, 0.99)
print(f"\nRockafellar-Uryasev minimizer (VaR): {ru['argmin_z']:.1f}")
print(f"RU minimum value (ES)              : {ru['min_value']:.1f}")
print(f"Empirical VaR                      : {ru['empirical_VaR']:.1f}")
VaR_95  : 411.2
ES_95   : 515.7
VaR_99  : 581.6
ES_99   : 666.3
Panel reference figures ($M): [426, 546, 625, 737]
Rockafellar-Uryasev minimizer (VaR): 578.8
RU minimum value (ES)              : 667.0
Empirical VaR                      : 579.9

E3 — Robustness: ES over an ambiguity box (supports LOS 13.4–13.5)¶

When the loss distribution's parameters are themselves uncertain, sweep ES over a $(\mu, \sigma)$ box. The worst-case ES is the robust risk number — larger than the nominal by a quantifiable premium.

In [4]:
r3 = eng.E3_robustness()
print(f"Nominal 99% ES     : {r3['nominal_ES99']:.1f}")
print(f"Worst-case 99% ES  : {r3['worst_case_ES99']:.1f}")
print(f"Robustness premium : {r3['robustness_premium']:.1f}")
Nominal 99% ES     : 666.3
Worst-case 99% ES  : 829.6
Robustness premium : 163.3

E4 — Backtesting and detection power (supports LOS 13.5–13.6)¶

If the model understates true volatility by 15%, how many years does the exception test need to reject it at 95% power? The answer has direct governance implications.

In [5]:
r4 = eng.E4_backtest()
print(f"Model understates volatility by : {r4['understate_pct']:.0f}%")
print(f"Model 99% VaR                   : {r4['model_VaR']:.3f}")
print(f"Years to reject at 95% power    : {r4['years_to_95pct_power']}")
Model understates volatility by : 15%
Model 99% VaR                   : 1.977
Years to reject at 95% power    : 1000

Validation checks¶

Every laboratory report must reproduce these; a report whose checks do not pass is returned ungraded.

In [6]:
checks = eng.validation_checks()
for k, v in checks.items():
    if k.startswith("_"): continue
    print(f"[{'PASS' if v else 'FAIL'}] {k}")
assert checks["ALL_PASS"], "Validation failed — do not submit."
print("\nAll Module validation checks pass.")
[PASS] V1_VaR_violates_subadditivity
[PASS] V2_ES_subadditive
[PASS] V3_RU_min_is_ES
[PASS] V4_robust_ES_exceeds_nominal
[PASS] ALL_PASS

All Module validation checks pass.