BA-CVA: the basic approach, for the banks too small for SA-CVA
Every other regulatory number this Learn section has produced needed a Monte-Carlo sweep on the adjoint tape. BA-CVA reduced needs three table lookups and a square root — real numbers: 209 nanoseconds per call, a charge that lands 20x to 25x higher than SA-CVA on the identical book, and a one-notch rating downgrade that more than doubles it with no simulation at all.
Not every bank runs an adjoint tape, a full sensitivity vector, and three correlation scenarios just to hold capital against counterparty risk. BA-CVA is the regulation's answer for the ones that don't — and, in this whole Learn section, it's the one regulatory number that costs nothing to compute.
The whole story
BaCvaParameters.riskWeight is a two-stage lookup, and reading only the
per-rating switch can mislead: BB's own multiplier is base * 1.0 —
no adjustment at all — the same as A's. But the base each one
multiplies is different: A and BBB are investment-grade, so they
share the lower sector base; BB isn't, so it starts from the
higher non-investment-grade base before any per-rating notch applies.
Real numbers for FINANCIAL: AAA comes to 3.50%, BBB to 5.75%,
BB to 12.00% — more than double BBB, entirely from which base
tier it starts in, not from a bigger multiplier.
BA-CVA reduced: no simulation, no adjoint sweep
double scva = riskWeight * exposure.effectiveMaturityYears()
* exposure.exposureAtDefault() * discount / parameters.alpha();
scvaByCounterparty.merge(exposure.counterparty().id(), scva, Double::sum);
SCVA_c = RW_c * M_c * EAD_c * DF_c / alpha
K_reduced = sqrt( (rho * sum_c SCVA_c)^2 + (1 - rho^2) * sum_c SCVA_c^2 )
Every input is a table lookup or a maturity-based discount — RW_c from
the rating/sector table above, DF_c from (1-e^{-0.05M})/(0.05M), and
EAD_c (in this demo) from alpha times the expected positive exposure
11.1's own sweep already produced. Nothing here revalues a trade twice,
shocks a risk factor, or records a tape.
Timed it directly rather than assumed it from the formula's shape: one
million calls to BaCva.charge(...).reduced() on this page's two-
counterparty book, 0.209 s total, 209 nanoseconds per call. Every
other regulatory number in Chapter 10 and the rest of Chapter 11 needed an
adjoint sweep or a Monte-Carlo replay, measured in milliseconds to tens
of seconds. This is the one calculator in the whole chapter that doesn't
— which is the entire regulatory point of calling it "basic."
Three methods, one book
PraCvaMethods runs the identical CvaCapital through all three PRA
standardised routes. Real numbers, same book as 11.1 and 11.2:
| method | charge |
|---|---|
| Alternative (BA-CVA reduced, no hedge) | $1.358m |
| Basic (BA-CVA full, hedged) | $1.093m |
| Standardised (SA-CVA, 10.3's sensitivity charge) | $0.054m |
SA-CVA lands 20×–25× lower than either BA-CVA variant on this exact
book — the payoff for the sensitivity infrastructure 10.3, 11.1 and 11.2
walked through, not a quirk of this particular book's shape.
PraCvaMethods.bindingCharge() itself reports max(all three) — its
own doc comment calls it "the method a bank on all three would report" —
so the incentive to build SA-CVA is eligibility to use it on its own,
not blending a small SA-CVA number in with two bigger BA-CVA ones.
Try it yourself
EAD_c doesn't depend on the counterparty's rating at all — only the
risk-weight lookup does. Downgrade counterparty A from BBB to BB
(one notch) and recompute, keeping every other input, including the
Monte-Carlo-derived EAD, exactly as it was. Real number: K_reduced
rises from $1.3575m to $2.8291m — more than double, entirely from the
risk-weight table's own two-stage jump (5.75% → 12.00%), with no
re-simulation of anything. The same downgrade mattering under SA-CVA
would need a fresh sensitivity vector; here it's one table lookup away.
▶️ Run it
The same three methods, live, on the same reduced single-counterparty
book 11.2's cell used — right here. BaCva and SaCva are the real,
unmodified plain-double classes; no adjoint sweep is needed for either
charge itself, only for the EPE proxy and the SA-CVA sensitivity vector
feeding them:
Or run the real thing:
mvn -o -q install
mvn -o -q -pl nablatensor-examples exec:java \
-Dexec.mainClass=com.nablatensor.examples.CvaShowcase
[6/8] and [7/8] of the same run print all three methods side by
side — no separate command for BA-CVA alone.
⚠️ What this doesn't do
This demo's EAD_c comes from the same Monte-Carlo ExposureSimulation
sweep every other page in this chapter has used — alpha × expected positive exposure. That's convenient for a demo that already has the
sweep sitting there, but it isn't the point of BA-CVA: docs-reg/cva.md
is explicit that a real small bank's EAD comes from SA-CCR, a
closed-form notional-and-add-on calculation, not a Monte-Carlo
simulation — the whole regulatory idea is that a bank eligible for BA-CVA
never needs to build an exposure simulator at all.
BaCvaParameters.standard()'s rho = 0.5, beta = 0.25, alpha = 1.4
and the risk-weight table remain explicitly indicative, same standing
caveat as every parameter table since Chapter 10.
What's next
→ Deeper: BaCvaParameters.java
has the full risk-weight table by every rating and sector this demo
supports.
→ Chapter 11 (Counterparty credit risk, deep dive) is complete. Next:
What a "generated bytecode kernel" actually looks
like, opening Chapter
12 — Under the hood.