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
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.
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:
| measure | LOW | MEDIUM | HIGH | selected |
|---|---|---|---|---|
| delta | 10.9871 | 10.7829 | 10.5747 | LOW → 10.9871 |
| vega | 2.8189 | 3.0770 | 3.3150 | HIGH → 3.3150 |
| curvature | 11.6361 | 11.6677 | 11.7084 | HIGH → 11.7084 |
| subtotal | 26.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:
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.