← learnChapter 10 · Regulatory capital6 min read

FRTB standardised approach, the rest of it

10.1 answered one question about one option in one bucket. A real return needs seven risk classes, three measures each under three correlation scenarios, a default risk charge, and a residual risk add-on — summed with zero diversification between any of them. Real numbers from a run: SBM 70.0327, DRC 0.0533, RRAO 0.0320, total 70.1180.

10.1 answered one question about one option in one bucket. A real FRTB return needs an answer for every bucket in seven risk classes, three measures for each of them, three correlation scenarios per measure, and two more charges on top that have nothing to do with sensitivities at all. None of that is harder math than 10.1 — it's the same nested aggregation and the same netting, run many more times and never allowed to offset across the boundaries the regulation draws.

The whole story

Seven risk classes (GIRR, CSR non-sec, CSR sec, CSR sec CTP, equity, commodity, FX), 89 demo buckets, 268 net risk factors — but only one of them, equity bucket 5's Asian-call curvature, is a real Monte-Carlo repricing; the rest are a demo formula. Each class runs delta, vega and curvature under LOW/MEDIUM/HIGH correlation and keeps its own worst scenario; SBM sums the seven selected subtotals to 70.0327 with no cross-class diversification. A default risk charge nets jump-to-default per obligor into a hedge-benefit ratio, 0.0533 in the base case — and rises to 0.0820, not down, once the one short obligor is fully hedged and can no longer offset the longs. A residual risk add-on adds a flat 0.0320. Total: 70.1180, no diversification between any of the three.

Did you know?

CRR3 / MAR21.6 states the top-level shape in one line: SBM, DRC and RRAO are "computed independently and summed" — no diversification benefit between them, ever. That's not a simplification the demo takes; it's visible directly in FrtbFullShowcase.main, which computes capital.total() + drc.total() + rrao.total() with a plain +, and inside SBM itself, Capital.total() is byClass.values().stream() .mapToDouble(ClassCapital::total).sum() — another plain sum, this time over seven risk classes that each already picked their own worst correlation scenario. A long GIRR position never gets to offset a short FX position, structurally, by design.

Load the market, the trades, and the demo parameter tables

Three trades across two netting sets, and a market snapshot:

private static final String TRADES_CSV = """
    id,nettingSet,notional,side
    ASIAN-001,NS-OPTIONS,12000000,-1
    ASIAN-HEDGE,NS-OPTIONS,4500000,1
    MACRO-HEDGE,NS-MACRO,8000000,1
    """;

ParameterSet.demoMar21() builds one risk-weight/correlation table per risk class — 89 buckets total, across all seven RiskClass values the enum already declares. The showcase prints its own warning about them: "DEMO tables are illustrative; a real run requires rulebook validation" — these are shaped like MAR21's tables, not transcribed from them.

The one real repricing

Bucket 5 of the equity class carries this page's only Monte-Carlo number — the exact calculation 10.1 already walked through, on the same short Asian call, rerun here at this showcase's own default scale:

double base = position * pv.get("base").price();
double delta = position * risk.greek(EquityMarket::spot);
double cvr = -Math.min(upResidual, downResidual);

Real numbers, 250,000 scenarios, 252 fixings, cpu-jit, seed 42: base -5.305091, up -31.055417, down -0.006057, delta -0.561906, CVR 11.558145 — matching 10.1's shape almost exactly (down shock worse, CVR around 11.5) at a quarter of that page's scenario count, because an Asian option's average price is already stable there.

Map trades to risk factors, then net

buildPortfolio tags every bucket in every class with a RiskFactor, scaled by each trade's notional and side, then Portfolio.aggregate() — 9.4's own plain-addition machinery, unchanged — nets them across trades:

Sensitivities netted = portfolio.aggregate();

Real numbers: 802 trade-level factor observations collapse to 268 distinct net risk factors, 2 netting sets, 3 trades.

Did you know?

Only one of those 268 net risk factors — equity bucket 5's curvature — is the real Monte-Carlo number above. buildPortfolio fills every one of the other 267 (all of GIRR, CSR, commodity, FX, and equity's own delta and vega) from a demo formula: trade notional/side × a market level × an alternating +1/-0.55 sign, source-quoted directly from FrtbFullShowcase.buildPortfolio. This showcase demonstrates the aggregation — nested correlations, LOW/MEDIUM/HIGH, netting, DRC, RRAO — not six more asset-class pricing engines. Reading the [4/8] console output without knowing this, it's easy to assume every one of those 268 numbers came from a kernel.

Each class: delta, vega, curvature, three scenarios

For every risk class and every measure, aggregateMeasure builds a fresh NestedAggregation under each CorrelationScenario and keeps the worst:

for (CorrelationScenario scenario : CorrelationScenario.values()) {
  NestedAggregation aggregation = measure == RiskMeasure.CURVATURE
    ? NestedAggregation.curvature(...)
    : NestedAggregation.delta(...);
  charges.put(scenario, aggregation.aggregate(sensitivities).total());
}

Equity — the class carrying the real number — worked in full:

measureLOWMEDIUMHIGHselected
delta10.987110.782910.5747LOW → 10.9871
vega2.81893.07703.3150HIGH → 3.3150
curvature11.636111.667711.7084HIGH → 11.7084
subtotal26.0105

The other six classes pick their own scenario independently: GIRR 1.9165, CSR non-sec 8.5062, CSR sec 11.5610, CSR sec CTP 7.6154, commodity 10.8767, FX 3.5464. Summed with no cross-class correlation at all: SBM = 70.0327.

Default risk charge

Jump-to-default, netted per obligor, then a hedge-benefit ratio caps how much a short can offset a long:

double hbr = longs + shorts == 0.0 ? 0.0 : longs / (longs + shorts);
return new DrcResult(Math.max(weightedLongs - hbr * weightedShorts, 0.0), ...);

Three obligors, real numbers: ACME nets +2.4000 (two positions, same risk weight 0.03), SMALLCO -0.2000 (RW 0.15), SOVEREIGN +2.0000 (RW 0.005). HBR = 4.4000 / 4.6000 = 0.9565, DRC = max(0.0820 − 0.9565 × 0.0300, 0) = 0.0533.

Try it yourself

SMALLCO's -0.2000 is the only short obligor in the book — the only thing giving HBR anything to discount. Add a fourth position, new DefaultPosition("SMALLCO", 200_000, 0.15), exactly offsetting the existing one, and rerun defaultRiskCharge. Net JTD nets to obligor level first, so SMALLCO's net drops to 0 and disappears from both the long and short side entirely — longs stays 4.4000, shorts drops to 0.0000, HBR rises to 1.0000. DRC rises too, from 0.0533 to 0.0820 — the fully-hedged, zero-net-risk obligor doesn't lower this charge, it removes the only short that was letting HBR discount the book's longs at all.

Residual risk add-on

A flat notional surcharge, no aggregation formula at all:

double exotic = ... * 0.01;   // 1.0% of exotic-underlying gross notional
double other = ... * 0.001;   // 0.1% of everything else

The demo's two residual positions: ASIAN-001's own $12.0m notional (not exotic, 0.1% → 0.0120) and a WEATHER-OPTION at $2.0m (exotic underlying, 1.0% → 0.0200). RRAO = 0.0320.

▶️ Run it

The same shape, live, at a smaller scale — one demo factor per class instead of 89 buckets, plus 10.1's own real Asian-call curvature for equity — right here. NestedAggregation, CorrelationScenario and the DRC hedge-benefit-ratio quirk are the real, unmodified mechanics — only the parameter tables are smaller:

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.FrtbFullShowcase

Defaults to cpu-jit, 250,000 scenarios, no GPU. A second example, FrtbFullOnFastestBackend, runs the identical calculation on whichever backend the machine can drive fastest — it wasn't run for this page, since picking "fastest" means probing every backend including the GPU ones, which this session's standing safety rule keeps off by default.

⚠️ What this doesn't do

Two honest gaps, one about scope and one about architecture.

Scope: docs-reg/frtb-sa.md's own regulation section (Part I) describes DRC over three buckets — corporates, sovereigns, and local governments — with hedge offsetting inside each; this demo computes one HBR/DRC across every obligor combined, no bucket split. Full securitisation risk weights, the CRR3 relieved/unrelieved dual run, and COREP output are all out of scope here too, same as the regulation doc's own deferred list.

Architecture: that same doc's Part II plans a dedicated com.nablatensor.reg module — GirrSbm, CsrNonSecSbm, DrcNonSec, Rrao as first-class types behind a RiskClassProfile interface. None of that exists in the current source. What actually runs this page is one showcase class composing nablatensor-risk's already-generic NestedAggregation and Portfolio — the exact classes Chapter 9 already taught — over an illustrative parameter table, with DRC and RRAO as two plain local methods. Simpler than the plan, and it's what's real.

What's next

→ Deeper: FRTB standardised approach — regulation & implementation plan is the regulation text this page draws its structure from (Part I) — read Part II's implementation plan as a roadmap, not as a description of what ships today. → Next: SA-CVA: turning a regulation into code — the same NestedAggregation engine again, but every sensitivity now costs a full counterparty-exposure simulation to get, not a repricing.


Questions or corrections? open an issue