← learnModule 9 · Risk aggregation5 min read

VaR and Expected Shortfall: three routes to one number

A bank's daily VaR figure looks like one calculation. Built from a linear delta-normal route, a quadratic delta-gamma route, and a full-revaluation historical route on the same book, it turns out to be three different numbers that agree only as well as their assumptions hold.

A risk report's VaR number looks like a single fact about a portfolio. It isn't — it's the output of a chosen method, and this module builds the same number three genuinely different ways on the same book: treat the P&L as linear in the risk factors, treat it as quadratic, or just resample it directly and read off the empirical tail. All three are "correct." They don't agree, and the gap between them is itself information about the book.

The whole story

Delta-normal VaR is z_alpha times the standard deviation of a linear P&L, built straight from an adjoint sensitivity vector. Delta-gamma Cornish-Fisher diagonalises the quadratic P&L's covariance-weighted Gamma matrix with the same Pca class 7.6's GARCH module used for curve factors (a different eigensolver algorithm than 8.1's Gauss-Hermite quadrature), turning it into closed-form cumulants and a fourth-order tail expansion. The real run on a three-factor, short-gamma book: delta-normal 67,294, delta-gamma Cornish-Fisher 70,175 (verified against a 2-million-path Monte-Carlo to 0.25%), historical 67,945, expected shortfall 76,744 — all at 99% one-day confidence, with 10-day delta-normal VaR scaling exactly by the square root of 10.

Did you know?

VarEsShowcase's own doc comment is explicit about what this module is and isn't about: "the sensitivity vector and the gamma matrix here stand in for what one adjoint reverse sweep of a portfolio tape returns; the point is the tail mathematics on top, not the pricing underneath." Every other module in this Learn section has built the sensitivity vector from a recorded tape; this one takes it as given and asks what a risk desk does with it once they have it. ValueAtRisk.deltaNormal doesn't know or care whether its sensitivities argument came from .greeks() on a Nabla.Pricer or a spreadsheet — the tail math is the same either way.

Linear: the adjoint's sensitivity vector, squared

Delta-normal VaR treats the portfolio's P&L as linear in the risk-factor moves — s · x for sensitivities s and a Gaussian move x ~ N(0, Σ) — which makes the loss itself Gaussian, with a closed-form standard deviation:

double variance = 0.0;
for (int i = 0; i < n; i++) {
  for (int j = 0; j < n; j++) {
    variance += sensitivities[i] * covariance[i][j] * sensitivities[j];
  }
}
double sd = Math.sqrt(Math.max(variance, 0.0)) * Math.sqrt(horizonDays);
return Normal.inverseCdf(alpha) * sd;

s' Σ s is a quadratic form in the sensitivity vector, and multi-day figures scale the standard deviation by √horizonDays — not the variance — because variance itself is what scales linearly with time under i.i.d. daily moves.

Quadratic: the same eigensolver, a different job

A real book isn't linear — options carry gamma — so deltaGammaCornishFisher models the P&L as delta'x + 0.5x'Γx instead. Diagonalizing Σ^½ Γ Σ^½ turns that quadratic form into a sum of independent a_i y_i + 0.5 b_i y_i² terms, whose first four cumulants have closed forms:

Pca eig = Pca.of(m);              // m = Sigma^{1/2} Gamma Sigma^{1/2}
double[] b = eig.eigenvalues();
double[][] u = eig.loadings();

That's Pca — the same class 7.6's GARCH showcase used for a term- structure level/slope/curvature decomposition — repurposed here for a completely different job: diagonalizing a P&L quadratic form and, in matrixSqrt, computing Σ^½ itself. It's not the same eigensolver 8.1 used for Gauss-Hermite quadrature, though — that one was tqli, a QL algorithm with implicit shifts; Pca uses cyclic Jacobi rotations instead. Two independently-written symmetric eigensolvers in this codebase, each reused for more than the one thing it was originally built for.

Did you know?

Wrote and ran a standalone probe (javac+java against the built classes, same approach as every prior module) to check the deeper doc's own claim — delta-gamma Cornish-Fisher VaR "within ~10%" of a two-million- path Monte-Carlo — rather than trust it. On this page's own three-factor book: Cornish-Fisher gives 70,175; the 2,000,000-path empirical quantile of the exact quadratic P&L gives 70,351 — a relative error of 0.25%, far tighter than the claimed bound. A fourth-order tail expansion earning that kind of accuracy on a real short-gamma book is a genuinely strong result for a closed-form approximation, not just a passing one.

The real run

VarEsShowcase builds a three-factor book — equity spot, a 5y rate, and EURUSD — with sensitivities {2.4M, −18M, 1.1M} and a gamma matrix that's short (negative) convexity in all three factors, at 99% one-day confidence:

route1-day VaR
delta-normal67,294
delta-gamma (Cornish-Fisher)70,175
historical (4,000-day empirical)67,945
expected shortfall (historical)76,744

Delta-normal and historical land close together, as they should for a book that's only mildly non-linear. Delta-gamma sits meaningfully above both: short gamma in every factor produces negative skew (−0.108, computed alongside the VaR) — a fatter loss tail than a pure Gaussian approximation sees — and Cornish-Fisher's expansion pushes the quantile out to match. Expected shortfall, the mean loss beyond the 99% quantile, sits above every VaR figure, which it always must: it's an average over outcomes already worse than VaR, never a quantile itself. Ten-day delta-normal VaR comes out to 212,804 — exactly the one-day figure times √10, the scaling confirmed directly in source.

Try it yourself

Flip the sign of every entry in GAMMA (long gamma instead of short) and predict whether Cornish-Fisher VaR lands above or below delta-normal's 67,294 this time. (It flips to below: 64,427, with skewness flipping sign too, from −0.108 to +0.108 — a positively-convex book has a thinner loss tail than the Gaussian approximation sees, so the tail expansion pulls the quantile in instead of pushing it out.)

▶️ Run it

mvn -o -q -pl nablatensor-examples exec:java \
  -Dexec.mainClass=com.nablatensor.examples.VarEsShowcase -Dwindow=4000

Nothing here touches the tape or an engine string — every route in this module is host-side linear algebra and closed-form tail mathematics, once the sensitivity vector already exists.

⚠️ What this doesn't do

Delta-gamma Cornish-Fisher assumes the risk factors themselves move jointly Gaussian — real factor returns have their own fat tails the Cornish-Fisher expansion doesn't correct for, only the portfolio's own convexity does. The historical route here draws its 4,000-day sample from the same quadratic model being tested, not real historical data, so its agreement with delta-normal is a consistency check on the mathematics, not a claim about a real book's empirical tail. And this page stops at the number itself — whether 67,294 or 70,175 is the right one-day 99% VaR to report, and whether either one clears regulatory backtesting, is Module 9.2's question, not this one's.

What's next

→ Deeper: Value at Risk and Expected Shortfall has the full method table and the pinned test tolerances this page's numbers are checked against. → Next: Backtesting a VaR model — Module 9.2 asks whether any of these three numbers actually held up against what happened next.


Questions or corrections? open an issue