SA-CCR step by step: implementing the regulatory formula

SA-CCR (Standardised Approach for Counterparty Credit Risk) replaced the CEM method in Basel III for a specific reason: CEM treated a three-month interest rate swap and a twenty-nine-year swap with the same coarse factor grid. SA-CCR makes the calculation sensitive to actual maturity and the direction of risk, at the cost of a noticeably denser formula. This article implements that formula step by step, for a simplified case: a single trade, a single asset class. The full supervisory factor tables and multi-class aggregation follow the same logic, but would weigh down the example without adding to the understanding.
The headline formula#
EAD = alpha × (RC + PFE)
alpha is set by the regulator at 1.4, a flat multiplier that has nothing to do with the trade itself. All the actual work sits in the other two terms.
RC: the replacement cost#
RC answers a simple question: if the counterparty defaults today, how much does it cost to put the same trade back on in the market? For an unmargined trade:
function replacementCost(marketValue: number): number {
return Math.max(marketValue, 0);
}That Math.max(..., 0) isn't cosmetic: a negative exposure doesn't reduce counterparty risk below zero, it simply means you owe the counterparty money, not the other way around. For a margined trade, RC is reduced by the net collateral held, with a floor that depends on contractual thresholds (threshold, minimum transfer amount) defined in the collateral agreement: that level of detail follows the same logic, but is contract-specific, so it's deliberately omitted here.
PFE: the potential future exposure#
PFE measures how much the exposure could grow before the next margin call date. It's built in three steps.
1. The per-trade AddOn#
type Trade = {
notional: number;
maturityYears: number;
supervisoryFactor: number; // e.g. 0.005 for an IRS (0.5%)
delta: number; // +1 long, -1 short, for non-linear positions
};
function maturityFactor(maturityYears: number): number {
return Math.sqrt(Math.min(maturityYears, 1));
}
function addOn(trade: Trade): number {
const mf = maturityFactor(trade.maturityYears);
return trade.notional * trade.supervisoryFactor * mf * trade.delta;
}The maturity factor caps the effect of time at one year: a ten-year trade and a three-year trade get the same MF, because beyond one year the uncertainty on future exposure no longer grows proportionally with remaining time under the SA-CCR model. A six-month trade, on the other hand, gets a reduced MF (sqrt(0.5) ≈ 0.71), reflecting a shorter risk window.
2. Aggregating into an asset-class AddOn#
In the real case, several trades in the same asset class (rates, FX, credit) aggregate using supervisory correlation parameters, not a simple sum. For a single trade, the aggregate AddOn collapses to the trade's own AddOn.
3. The multiplier#
function pfeMultiplier(marketValue: number, collateral: number, addOnAggregate: number): number {
const floor = 0.05;
const excess = marketValue - collateral;
if (excess >= 0) return 1;
return Math.max(
floor,
floor + (1 - floor) * Math.exp(excess / (2 * (1 - floor) * addOnAggregate)),
);
}
function potentialFutureExposure(addOnAggregate: number, multiplier: number): number {
return multiplier * addOnAggregate;
}The multiplier recognizes that excess collateral reduces future risk, but never below a 5% floor of the AddOn: even over-collateralized, a netting set retains a non-zero potential future exposure, because today's collateral is no guarantee of tomorrow's.
Putting the three pieces together#
function calculateEAD(trade: Trade, marketValue: number, collateral: number): number {
const alpha = 1.4;
const rc = replacementCost(marketValue - collateral);
const trAddOn = addOn(trade);
const multiplier = pfeMultiplier(marketValue, collateral, trAddOn);
const pfe = potentialFutureExposure(trAddOn, multiplier);
return alpha * (rc + pfe);
}What this implementation doesn't cover#
This code handles a single trade in one asset class. A production implementation needs to handle aggregation across multiple hedging sets with per-asset-class supervisory correlations, rules specific to non-linear trades (delta computed by an options model, not fixed at ±1), and the contract-specific collateralization thresholds of each CSA. That isn't a detail to add later, it's the majority of the actual complexity of a production SA-CCR engine. The goal here isn't to provide a complete engine, it's to make readable the structure that those more complex rules go on to populate: two numbers, RC and PFE, a regulatory multiplier, and a formula that fits on one line once you know what each term actually represents.


