Core mechanism
Counterparty exposures are weighted and aggregated according to the prescribed formula, with a defined treatment of eligible hedges. It deliberately does not depend on a bank's full CVA sensitivity model.
The basic approach charge, with and without CDS hedge recognition, alongside SA-CVA on the same two netting sets.
The full charge applies the regulation’s hedge recognition to CPTY-A; the reduced charge does not recognise the CDS hedge.
This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.
import com.nablatensor.cva.BaCva;
import com.nablatensor.cva.BaCvaParameters;
import com.nablatensor.cva.BaCvaResult;
import com.nablatensor.cva.CollateralAgreement;
import com.nablatensor.cva.CreditName;
import com.nablatensor.cva.CvaHedge;
import com.nablatensor.cva.CvaMarket;
import com.nablatensor.cva.CvaResult;
import com.nablatensor.cva.CvaRiskFactors;
import com.nablatensor.cva.ExposureSimulation;
import com.nablatensor.cva.HazardCurve;
import com.nablatensor.cva.InterestRateSwap;
import com.nablatensor.cva.NettingSet;
import com.nablatensor.cva.SaCva;
import com.nablatensor.cva.SaCvaParameters;
import com.nablatensor.cva.SaCvaSensitivities;
import com.nablatensor.risk.Sensitivities;
import java.util.List;
public final class BaCvaRiskStudio {
public static void main(String[] args) {
double r0 = 0.0200000000000, hazardA = 150 / 10000.0, hazardB = 110 / 10000.0, recovery = 40 / 100.0,
hedgeNotional = 1500000.00000;
long paths = 3000L, seed = 20260902L;
int steps = 20;
CreditName a = CreditName.of()
.id("CPTY-A")
.curve(HazardCurve.fromFlatSpread(hazardA * 10000.0, recovery, 10.0))
.recovery(recovery)
.rating(CreditName.RatingEnum.BBB)
.sector(CreditName.SectorEnum.FINANCIAL)
.build();
NettingSet nsA = NettingSet.of()
.id("NS-CPTY-A")
.counterparty(a)
.trades(List.of(InterestRateSwap.of()
.id("A-SWAP-PAY")
.side(InterestRateSwap.SideEnum.PAY_FIXED)
.notional(100000000.0)
.fixedRate(0.02)
.startYears(0.0)
.maturityYears(7.0)
.accrualYears(0.5)
.build(), InterestRateSwap.of()
.id("A-SWAP-REC")
.side(InterestRateSwap.SideEnum.RECEIVE_FIXED)
.notional(40000000.0)
.fixedRate(0.036)
.startYears(0.0)
.maturityYears(5.0)
.accrualYears(0.5)
.build()))
.collateral(CollateralAgreement.uncollateralised())
.build();
CreditName b = CreditName.of()
.id("CPTY-B")
.curve(HazardCurve.fromFlatSpread(hazardB * 10000.0, recovery, 10.0))
.recovery(recovery)
.rating(CreditName.RatingEnum.A)
.sector(CreditName.SectorEnum.CORPORATE)
.build();
NettingSet nsB = NettingSet.of()
.id("NS-CPTY-B")
.counterparty(b)
.trades(List.of(InterestRateSwap.of()
.id("B-SWAP-PAY")
.side(InterestRateSwap.SideEnum.PAY_FIXED)
.notional(75000000.0)
.fixedRate(0.022)
.startYears(0.0)
.maturityYears(7.0)
.accrualYears(0.5)
.build()))
.collateral(CollateralAgreement.dailyMargined(2000000.0))
.build();
CvaMarket market = CvaMarket.of()
.r0(r0)
.hwLevel(r0)
.hwMeanReversion(0.03)
.hwSigma(0.01)
.hazardShort(hazardA)
.hazardMid(hazardA)
.hazardLong(hazardA)
.recovery(recovery)
.fxSpot(1.1)
.fxVol(0.12)
.fxForeignRate(0.024)
.build();
ExposureSimulation simA = ExposureSimulation.of(nsA, steps)
.on("cpu")
.fp64(true);
CvaResult resultA = simA.run(market, paths, seed);
CvaMarket marketB = CvaMarket.of()
.r0(r0)
.hwLevel(r0)
.hwMeanReversion(0.03)
.hwSigma(0.01)
.hazardShort(hazardB)
.hazardMid(hazardB)
.hazardLong(hazardB)
.recovery(recovery)
.fxSpot(1.1)
.fxVol(0.12)
.fxForeignRate(0.024)
.build();
ExposureSimulation simB = ExposureSimulation.of(nsB, steps)
.on("cpu")
.fp64(true);
CvaResult resultB = simB.run(marketB, paths, seed + 1);
BaCvaParameters bp = BaCvaParameters.standard();
double alpha = bp.alpha();
BaCva calc = new BaCva(bp);
BaCva.Exposure ea = new BaCva.Exposure(a, nsA.effectiveMaturityYears(), alpha * resultA.expectedPositiveExposure());
BaCva.Exposure eb = new BaCva.Exposure(b, nsB.effectiveMaturityYears(), alpha * resultB.expectedPositiveExposure());
BaCvaResult reduced = calc.charge(List.of(ea, eb), List.of());
CvaHedge hedge = CvaHedge.of()
.kind(CvaHedge.KindEnum.SINGLE_NAME_CDS)
.referenceId("CPTY-A")
.notional(hedgeNotional)
.maturityYears(7.0)
.riskWeight(0.05)
.correlation(1.0)
.build();
BaCvaResult full = calc.charge(List.of(ea, eb), hedge.notional() > 0 ? List.of(hedge) : List.of());
// Reuse the same exposures and charge implementation; do not rerun Monte Carlo for the sweep.
if (hedge.kind() == CvaHedge.KindEnum.SINGLE_NAME_CDS && hedge.referenceId()
.equals(a.id())) {
double scvaA = reduced.scvaByCounterparty()
.get(a.id()), scvaB = reduced.scvaByCounterparty()
.get(b.id());
double unit = hedge.riskWeight() * hedge.maturityYears() * hedge.supervisoryDiscount();
double maxNotional = Math.max(1000000.0, Math.max(2.0 * hedge.notional(), unit > 0 ? 2.0 * (Math.abs(scvaA)
+ Math.abs(scvaB)) / unit : 0.0));
for (int i = 0; i <= 80; i++) {
double notional = maxNotional * i / 80.0;
CvaHedge trial = CvaHedge.of()
.kind(hedge.kind())
.referenceId(hedge.referenceId())
.notional(notional)
.maturityYears(hedge.maturityYears())
.riskWeight(hedge.riskWeight())
.correlation(hedge.correlation())
.build();
BaCvaResult point = calc.charge(List.of(ea, eb), List.of(trial));
System.out.println("BA_SWEEP|" + notional + "|" + point.reduced() + "|" + point.full());
}
System.out.println("BA_SELECTED|" + hedge.notional() + "|" + full.reduced() + "|" + full.full());
System.out.println("BA_COUNTERPARTY|CPTY-A|" + scvaA + "|" + (scvaA - hedge.correlation() * hedge.discountedWeightedNotional()));
System.out.println("BA_COUNTERPARTY|CPTY-B|" + scvaB + "|" + scvaB);
}
CvaRiskFactors ka = new CvaRiskFactors("USD", a, "EURUSD"), kb = new CvaRiskFactors("USD", b,
"EURUSD");
Sensitivities sens = SaCvaSensitivities.adjoint(resultA, ka)
.plus(SaCvaSensitivities.adjoint(resultB, kb));
double sa = new SaCva(SaCvaParameters.demo())
.charge(sens)
.total();
System.out.println("RESULT|" + resultA.value() + "|" + resultB.value() + "|" + reduced.reduced()
+ "|" + full.full() + "|" + sa + "|" + resultA.sweepSeconds() + "|" + resultB.sweepSeconds()
+ "|" + reduced.scvaByCounterparty()
.get("CPTY-A") + "|" + reduced.scvaByCounterparty()
.get("CPTY-B"));
}
}
Results from a precomputed Java run.
| Counterparty | SCVA_c | CVA |
|---|---|---|
| CPTY-A | 617,117 | 109,479 |
| CPTY-B | 5,786 | 1,431 |
Precomputed Java adjoint exposure sweeps: 0.259s + 0.120s.
Vary the CDS on CPTY-A while holding both exposure inputs, parameters and other hedge settings fixed. The full charge retains an unhedged component; excessive protection can increase it. Hover over a curve point for its amounts.
Scroll horizontally to see all chart labels on narrow screens.
Chosen CDS: 1.500 $m · Full K: 0.286 $m · Reduced K: 0.619 $m. Indicative parameters; K values precede the Basel 0.65 scalar.
Compare unhedged SCVA with the residual after recognising the selected CDS. CPTY-B is unchanged. Negative residuals indicate overhedging. These inputs undergo correlation aggregation and do not add directly to capital.
Scroll horizontally to see all chart labels on narrow screens.
The Basic Approach uses supervisory parameters and exposure measures to provide a simpler CVA-capital result than SA-CVA.
Counterparty exposures are weighted and aggregated according to the prescribed formula, with a defined treatment of eligible hedges. It deliberately does not depend on a bank's full CVA sensitivity model.
Identify the applicable regulatory approach, map counterparties and maturities correctly, apply the current supervisory parameters, and reconcile scope with the SA-CVA population.
BA-CVA calculates capital for changes in a portfolio's credit valuation adjustment (CVA). The reduced version ignores hedges; the full version recognises eligible credit hedges. Portfolio CVA is the valuation adjustment itself, so it is a different measure from either capital charge.
This example has two counterparties: an uncollateralised BBB financial firm (CPTY-A) and a daily-margined A-rated corporate (CPTY-B). Simulated expected positive exposure supplies a simplified exposure-at-default input, EAD = 1.4 × expected positive exposure. This is a teaching proxy rather than a complete regulatory exposure calculation.
For each counterparty, the calculator combines EAD, effective maturity, a rating-and-sector risk weight and a supervisory discount factor into stand-alone capital (SCVA). The reduced charge aggregates these amounts with correlation ρ = 0.5. The full charge combines 25% of the reduced charge with 75% of the charge after hedge recognition.
Change the CDS notional to hedge CPTY-A. The reduced charge stays constant because it ignores hedges; the full charge initially falls, then can rise if the hedge becomes too large. The chart reuses the same exposure inputs at every notional, so the hedge sweep does not rerun the simulation.
The expensive step in this demo is generating the exposure paths. Once exposures are available, BA-CVA is a short aggregation calculation. The SA-CVA comparison uses adjoint CVA sensitivities from the same simulated portfolio; its result depends on a different method and parameter set. The example parameters are indicative, and the displayed charges illustrate the calculation rather than provide a regulatory reporting result.
A simple formula is still regulatory reporting: eligibility, legal scope, parameter version and audit trail are essential.