← learnChapter 11 · Counterparty credit risk, deep dive4 min read

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

BA-CVA reduced needs three lookups per counterparty — a risk weight by rating and sector, a maturity, an EAD proxy — combined with a supervisory correlation, no Monte Carlo or adjoint sweep anywhere. Real risk weights show a two-stage design: FINANCIAL/BBB comes to 5.75%, FINANCIAL/BB to 12.00%, more than double, because BB's sector base is already the higher non-investment-grade tier before any per-rating notch is applied. Timed for real: one million K_reduced calls in 0.209 seconds, 209 nanoseconds each — every other regulatory number in Chapters 10 and 11 needed milliseconds to seconds on the adjoint tape. On the identical book, the three PRA methods give wildly different numbers: Alternative (BA-CVA reduced) $1.358m, Basic (BA-CVA full) $1.093m, Standardised (SA-CVA) $0.054m — 20x to 25x lower, the reward for building the sensitivity infrastructure the rest of this chapter walked through. Downgrading counterparty A one notch, BBB to BB, more than doubles K_reduced to $2.829m, with no re-simulation needed at all.

Did you know?

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.

Did you know?

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:

methodcharge
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:

Java · compile and run in this browser

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.


Questions or corrections? open an issue