Skip to content
Riadh Mnasri
← Back to blog
4 min read

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:

typescript
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#

typescript
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#

typescript
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#

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