Skip to content

ShrishDhuria/market-risk-engine

Repository files navigation

Market Risk Engine

tests

A multi-asset market-risk system for a sell-side trading book, implemented end to end across nine phases: from raw market data through sensitivities, VaR/ES, FRTB backtesting, stress testing, risk attribution, and finally a single regulatory capital number under both Basel III and FRTB. It is built around one organising idea — a closed loop in which every measure feeds the next, so the capital figure at the end is consistent with the risk factors at the start.

What it demonstrates

On a real 14-position multi-asset book the engine produces one consistent capital number end to end — and, the part that matters for a risk seat, the backtesting catches what an average-rate test misses: Christoffersen's independence test flags exception clustering (the failure mode that shows up inside a single stress window) even when Kupiec's unconditional rate looks acceptable. The dashboard below shows 1-day VaR/ES across four methods, the P&L-attribution check (Spearman 0.9985 between Taylor and full-reval P&L), 500-day backtesting with the breach count and traffic-light, the Basel-vs-FRTB capital comparison, stress-scenario P&L, and the component-VaR attribution.

Market-risk dashboard — VaR/ES, backtesting, capital, stress, attribution

End-to-end risk dashboard on the US book, regenerated by the pipeline from cached market data. FRTB IMA capital $22M vs Basel 2.5 $63M; 9 breaches over 500 days (amber); component-VaR concentration HHI 0.20.

The reference book is a 14-position US trading book: five long single-name equities (AAPL, MSFT, NVDA, GOOGL, and a JPM short), two long equity options, the 2y/5y/10y UST curve, a short US IG credit position, and three FX pairs. An EU book of identical structure is also provided — geography lives only in the data fetchers and the book definition, not in the engine.

Status

Phase Module Status
1 Data layer & portfolio definition Complete
2 Greeks & risk-theoretical P&L Complete
3 VaR engine (parametric, historical, Monte Carlo, filtered HS) Complete
4 FRTB Expected Shortfall with liquidity horizons Complete
5 P&L Attribution test + SbM standardised charge Complete
6 Backtesting suite (Kupiec, Christoffersen, traffic light) Complete
7 Stress testing (historical & hypothetical scenarios) Complete
8 Component VaR, marginal VaR, concentration Complete
9 Regulatory capital (Basel III vs FRTB) + dashboard Complete

The closed loop

   positions + market data            (Phase 1)
            │
            ▼
   sensitivities  Δ Γ V ρ θ            (Phase 2)
            │
            ├──────────────► RTPL  (Taylor / risk-theoretical P&L)
            │                          │
   full revaluation ──► HPL            │   (Phase 2 / 5)
            │                          │
            ▼                          ▼
   VaR (4 methods) + FRTB ES   ◄───────┘   (Phase 3 / 4)
            │
            ▼
   backtesting → traffic-light multiplier   (Phase 6)
            │
            ▼
   stressed-period calibration (worst 250d)  (Phase 7)
            │
            ▼
   component / marginal VaR + concentration  (Phase 8)
            │
            ▼
   regulatory capital: Basel 2.5 vs FRTB IMA vs SA   (Phase 9)

Two P&L streams run through the whole system. The RTPL (risk-theoretical P&L) is the Taylor/sensitivities reconstruction in greeks/pnl_attribution.py; the HPL (hypothetical P&L) is the full front-office revaluation in frtb/actual_pnl.py. The FRTB PLA test compares them; the VaR/ES engines run on the RTPL distribution; the capital layer consumes the backtesting multiplier, the stressed ES, and the SbM charge that the earlier phases produce.

Architecture

market-risk-engine/
├── data/                      # Risk factors, instruments, portfolios, market data
│   ├── risk_factors.py        # RiskFactor / RiskFactorSet, native-unit conventions
│   ├── portfolio.py           # Instrument hierarchy, Position, Portfolio
│   ├── market_data.py         # FRED / yfinance fetchers (+ on-disk cache)
│   ├── book_us.py             # US trading book (14 positions)
│   └── book_eu.py             # EU trading book (parallel structure)
├── greeks/                    # Sensitivities + risk-theoretical P&L
│   ├── sensitivities.py       # Δ, Γ, V, ρ, θ per (position, risk factor)
│   └── pnl_attribution.py     # Taylor-expansion P&L = RTPL  (+ by-greek/-position/-RF)
├── var/                       # VaR engines, common 250d window
│   ├── types.py               # Shared VaRResult schema
│   ├── parametric.py          # Delta-normal
│   ├── historical.py          # Historical simulation
│   ├── monte_carlo.py         # Multivariate-normal / multivariate-t (shared χ² mixing)
│   ├── filtered_hs.py         # GARCH(1,1) filtered HS
│   └── var_engine.py          # Unified facade (run_all_methods)
├── frtb/                      # FRTB measures
│   ├── liquidity_horizons.py  # LH band partitioning
│   ├── expected_shortfall.py  # 97.5% ES, LH-scaled to the 10-day base horizon
│   ├── actual_pnl.py          # HPL via full revaluation (compute_hpl)
│   ├── pla_test.py            # P&L Attribution test (Spearman + KS, MAR32)
│   ├── stressed_calibration.py# Rolling-window stressed-period search
│   └── sbm_capital.py         # Standardised approach (SbM), 3 correlation scenarios
├── backtest/                  # Backtesting & multiplier
│   ├── exceptions.py          # Rolling VaR exception stream
│   ├── coverage_tests.py      # Kupiec POF + Christoffersen independence/CC
│   ├── traffic_light.py       # Basel & FRTB multipliers from exception count
│   └── backtest_engine.py     # One VaR, tested vs RTPL and HPL
├── stress/                    # Scenario analysis
│   ├── scenarios.py           # GFC / COVID / 2022 / supervisory shocks
│   └── scenario_engine.py     # Full-reval headline + greek decomposition + residual
├── attribution/               # Risk attribution
│   ├── component_var.py       # Euler component VaR + marginal + incremental
│   ├── concentration.py       # Herfindahl, effective-N, diversification ratio
│   └── attribution_engine.py  # Parametric vs historical, side by side
├── capital/                   # Capital aggregation & reporting
│   ├── regulatory_capital.py  # Basel 2.5 vs FRTB IMA vs SA
│   └── dashboard.py           # Six-panel matplotlib desk view
├── phase4_test.py … phase9_test.py   # Per-phase integration tests
├── verify_fred.py             # FRED API-key check
├── risk_dashboard.png         # Sample dashboard output
├── requirements.txt
├── sources.md                 # Data provenance and methodology references
├── data_cache/                # Cached RF history for offline runs (use_cache=True)
├── LICENSE
└── README.md

Quick start

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

# Reproduce every phase end to end (uses the bundled data_cache, no keys needed):
python phase4_test.py    # FRTB ES + stressed calibration
python phase5_test.py    # PLA test + SbM standardised charge
python phase6_test.py    # backtesting + traffic-light multiplier
python phase7_test.py    # stress scenarios
python phase8_test.py    # component/marginal VaR + concentration
python phase9_test.py    # Basel vs FRTB capital + dashboard

For a live data pull instead of the cache, sign up for a free FRED key (https://fredaccount.stlouisfed.org/apikeys), export FRED_API_KEY=..., run python verify_fred.py, and call the builders with use_cache=False.

from data.market_data import build_us_risk_factor_set
from data.book_us import build_book
from greeks.sensitivities import build_sensitivities
from capital.regulatory_capital import compute_regulatory_capital

rfs  = build_us_risk_factor_set(start="2010-01-01", end="2024-12-31", use_cache=True)
pf   = build_book(rfs)
sens = build_sensitivities(pf)
print(compute_regulatory_capital(pf, sens).summary())

Headline results (reference US book)

Measure Value
VaR, 1-day 99% (historical sim) ~$1.8M
VaR, 10-day 99% $5.8M
Stressed VaR, 10-day (COVID window) $10.6M
FRTB ES, 10-day 97.5% $6.0M
Stressed ES / IMCC, 10-day $11.4M
Backtest (trailing 250d, 99%) Amber, 9 exceptions, IMA retained
Basel multiplier / FRTB multiplier 3.85 / 1.92
Basel 2.5 capital $63M
FRTB IMA capital $22M (35% of Basel)
FRTB SA (SbM, high-correlation scenario) $15M

Three findings are worth calling out, because they are what the engine exists to surface:

  • The regime shift cuts capital here. FRTB IMA is 35% of Basel 2.5 for this book, because Basel double-counts (VaR plus stressed VaR, each ×3.85) while FRTB charges a single stressed ES at a multiplier that roughly halved (3.0 → 1.5). The well-documented industry-level capital increases under FRTB come mostly from desks pushed onto the punitive Standardised Approach, not from desks that retain model approval.

  • Stress finds a risk that VaR cannot. The worst named scenario is the 2022 rate shock (−$13.6M), ahead of GFC (−$11.3M) and COVID (−$9.4M), even though GFC has the larger raw equity loss. The reason is a sign flip on the UST book: bonds rally in GFC/COVID (flight to quality, a hedge) but sell off in 2022 (a headwind) — an ~$8M swing on the same positions.

  • Diversification depends on which tail you look at. NVDA alone carries ~32% of VaR and the top three names 68% (Herfindahl 0.198, an effective 5 of 14 positions). And the parametric and historical component-VaR decompositions disagree on the sign of the bonds, the JPM short, and the IG short: small risk adders under the Gaussian/average-correlation view, but hedges in the empirical loss tail (where flight-to-quality and risk-off make them pay off). The long option positions flip the same way through convexity.

Known simplifications

These are deliberate scope choices, documented inline at each site, not oversights:

  • SbM is one bucket per risk class. It applies the prescribed MAR21 within-bucket correlations (GIRR tenor matrix, 25% large-cap equity, 60% FX) and the three correlation scenarios, but omits the full bucket taxonomy, the curvature charge, and most vega. The SA is therefore understated, so the SA-below-IMA ordering for this book should not be over-read; in production SA is usually a conservative floor above IMA.
  • VaR_avg60 / IMCC_avg60 are proxied by the current value rather than a trailing-60-day average of the measure; since the multipliers exceed 1, the multiplier term binds and K ≈ m × (current measure).
  • VIX is used as a single-name equity-vol proxy for the option vega.
  • Component/marginal VaR is delta-normal — options enter through delta only (the historical decomposition does carry their convexity).
  • No default risk charge (DRC), NMRF add-on, or residual risk (RRAO) — the reference book has no non-modellable factors and no defaultable single names beyond the IG index.

Methodology references

See sources.md for the full list. Key sources: FRTB (BCBS d457 / MAR), Basel III VaR-based capital (BCBS d128), Kupiec (1995) and Christoffersen (1998) backtests, and RiskMetrics (1996) for EWMA volatility.

Limitations

What the numbers can and cannot tell you:

  • Window dependence. VaR/ES are estimated on a trailing 250-day window; historical simulation can never exceed the worst day in that window, so genuinely unprecedented tail risk is understated — mitigated, not solved, by the stressed-calibration and ES phases.
  • Taylor P&L. Sensitivities are a delta-gamma-vega-theta-rho approximation; the P&L-attribution panel (Spearman 0.9985) shows it tracks full revaluation closely on this book, but large moves and exotic payoffs would diverge.
  • Backtest power. Exception counts are sample-dependent and the coverage tests have limited power in short windows — a clean traffic light is necessary, not sufficient.
  • Liquidity horizons are simplified. RF-to-LH assignment follows the MAR33 buckets but does not model name-level liquidity or market impact beyond the horizon scaling.
  • Single daily-close vendor. No intraday data, no bid-ask, and no liquidity/valuation adjustment beyond the LH treatment.

Testing

The tests/ directory holds a pytest suite asserting the regulatory and mathematical identities at the core of the engine. It is driven by fixed-seed synthetic books, so it runs offline with no market-data calls.

  • VaR / ES — ES ≥ VaR at matched confidence, exact √-time horizon scaling, and an ES/VaR ratio matching the Gaussian closed form on a normal P&L.
  • FRTB ES — for a single-liquidity-horizon book the aggregated ES equals the 1-day band ES lifted to the 10-day base horizon by √10 (MAR33).
  • Attribution — the delta-normal Euler decomposition is exact (component VaR sums to portfolio VaR); concentration (HHI / effective-N) is well-formed and orders correctly between a balanced and a concentrated book.
  • Backtesting — Kupiec POF is zero when the exception rate matches the VaR level and rejects an excess; Christoffersen conditional coverage decomposes exactly as LR_cc = LR_uc + LR_ind; the Basel/FRTB traffic-light zones and multipliers and the FRTB IMA hard gate map correctly.
pip install -r requirements-dev.txt
pytest tests/ -q          # 12 tests

Tests run automatically on every push via GitHub Actions (.github/workflows/tests.yml).

examples/ vs tests/ — the phase-by-phase walkthrough scripts now live in examples/ and are run from the project root, e.g. python -m examples.phase5_demo. They are illustrative demos, not assertions; the automated suite is tests/.

About

End-to-end sell-side market-risk engine: VaR/ES across four methods, FRTB Expected Shortfall with liquidity horizons, Basel III vs FRTB capital, Kupiec/Christoffersen backtesting, stress testing and component-VaR attribution.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages