This chapter requires XVA-01 (EE$(t)$ for a single trade). You should understand the definition of Expected Exposure and how it is computed from $V(t, \omega)$.
This chapter introduces netting sets and CSA (Credit Support Annex), the legal structures that reduce exposure. No prior knowledge of ISDA agreements or collateral mechanics is assumed.
By the end, you can collapse a portfolio into a single netted exposure profile and apply a CSA to reduce it further.
The pipeline so far: IR-08 (draft) produced $V(t, \omega)$ for each trade. XVA-01 turned that into EE$(t)$ per trade. This chapter answers: what is the dealer's net exposure when trades are governed by a single netting agreement? The input is a set of EE$(t)$ profiles; the output is a single netted EE$(t)$ profile per netting set, ready for CVA computation in XVA-03.
From single trades to portfolios
In the Interest Rate Modeling series, we built the foundations for pricing derivatives under stochastic rates: yield curve bootstrapping, Hull-White modeling (Draft), and model calibration (Draft). CVA (Credit Value Adjustment) is the expected loss from counterparty default, computed as an integral of the exposure profile against the counterparty's default probability. The full derivation appears in XVA Chapter 03; here we focus on how netting and collateral reshape the exposure profile that feeds into that integral. The single-trade picture is clean and intellectually satisfying.
But it is not the way real banks do business. In practice, a bank does not compute CVA trade by trade. Instead, it manages netting sets: collections of trades governed by a single ISDA Master Agreement with a given counterparty. If the counterparty defaults, all trades in the netting set are netted together. Instead of paying out a thousand individual claims to the bank, the bankruptcy trustee nets them all and pays out only the single net amount.
This creates a fundamental change to the exposure calculation. Instead of summing the expected positive exposures of individual trades, we must compute the expected positive exposure of the net portfolio value. This aggregation introduces a netting benefit: trades that move in opposite directions partially offset each other, reducing the overall exposure the bank runs. Furthermore, if the counterparty posts collateral under a Credit Support Annex (CSA), the bank's net exposure is further reduced by the value of collateral posted.
This chapter brings together everything we have learned and shows how to handle aggregation, netting, and collateral to arrive at the true portfolio-level CVA.
Netting: why it matters
Consider a simple example: you have a payer swap and a receiver swap with the same counterparty, both at-the-money initially. Over time, rates fall. The payer swap becomes valuable to you (you pay a fixed rate that is now above market). The receiver swap becomes a liability (you receive a fixed rate that is now below market). In the absence of netting, you would compute CVA as the expected positive exposure of the payer plus the expected positive exposure of the receiver, which could be substantial.
Under netting, the two MTMs offset. The net exposure is the sum of the two values, which may be close to zero or even negative (in which case the counterparty owes you net). The netting benefit is the reduction in aggregate exposure.
Formally, let $V_k(t)$ denote the mark-to-market of trade $k$ at time $t$. The netting set value is:
The expected positive exposure without netting (gross EE profile) sums the exposures of individual trades:
With netting, we compute the expected positive value of the sum:
By subadditivity of the positive-part operator ($\max(\sum_k x_k,\, 0) \leq \sum_k \max(x_k,\, 0)$ for all real $x_k$), taking expectations on both sides gives:
Per the convention from XVA Chapter 01, $\text{EE}(t)$ is the time-varying expected positive exposure and EPE is its scalar time-average $\text{EPE} = \tfrac{1}{T}\int_0^T \text{EE}(t)\,dt$; the inequality above carries through to the time-averages, so $\text{EPE}_{\text{netted}} \leq \text{EPE}_{\text{gross}}$.
The more negatively correlated the trades, the larger the benefit. If you always have matching positions (equal and opposite), the netting benefit is the greatest. If all trades move together, there is no netting benefit.
Collateral and the Credit Support Annex
Most derivative trades between investment-grade counterparties are governed by a Credit Support Annex (CSA), an agreement to post collateral daily or at regular intervals. The CSA specifies a threshold $H$ (typically zero or a small amount) and a margin period of risk (MPR), usually around 10 business days. Here is the mechanism:
- Each day, the bank and counterparty compute the net MTM of the netting set.
- If the net MTM exceeds the threshold, the losing party posts cash collateral equal to the excess.
- The party holding the collateral earns a floating rate (the OIS rate or similar).
- If the counterparty defaults, there is an MPR delay (e.g., 10 business days) before the bank can liquidate the posted collateral and use it to offset losses.
Collateral dramatically reduces exposure. Under a two-way CSA with bilateral threshold $H$, the collateral call observed at the lagged date $t - \text{MPR}$ is the part of the net MTM (signed) that exceeds the threshold on either side:
The bank's residual exposure at $t$ is the positive part of the net MTM after applying the lagged call:
V_net(t): net MTM of the netting set at time $t$
H: bilateral CSA threshold (typically zero or a small amount); a deductible on both sides
MPR: margin period of risk, the delay before collateral can be liquidated after default (typically 10 business days)
If no CSA, then $C(t) \equiv 0$ for all $t$ and $V_{\text{coll}}(t) = \max(V_{\text{net}}(t),\, 0)$
Both the formula above and the library implementation use a one-snapshot proxy: the call at $t - \text{MPR}$ is computed from the lagged net MTM rather than from a running collateral balance (which would track every margin call, partial release, and unposting between $t - \text{MPR}$ and $t$). For monitoring grids that are coarse relative to the MPR this is adequate; for daily or intraday margining, a path-by-path balance simulation is closer to the true CSA mechanics. See Gregory (2020) Ch. 11 for the bilateral CSA convention with thresholds, minimum transfer amounts, and independent amount.
The collateralised exposure profile is much flatter and lower than the netted profile. This is why CVA tends to be small for collateralised counterparties and substantial for uncollateralised ones.
Portfolio-level CVA
The CVA formula from Chapter 03 applies directly to a netting set. We simply replace the single-trade EE profile with the netted (or collateralised) portfolio EE profile:
The structure is identical to the single-trade case; only the EE input changes. This is the power of the framework we have built: once you can compute EE for a portfolio, the CVA formula is straightforward.
Computing portfolio exposure in code
Below is a complete PyTorch implementation in three parts: the netting set class, the CVA function, and an example.
Part 1: Netting set and exposure profiles
import torch
class NettingSet:
"""Collection of trades under a single ISDA Master Agreement.
Tensor convention (paths-first): each trade MTM tensor has shape
(num_paths, num_times), matching xvafoundations.xva.netting and
xvafoundations.xva.exposure.
"""
def __init__(self, mtm_paths_per_trade: list[torch.Tensor]) -> None:
if not mtm_paths_per_trade:
raise ValueError("NettingSet requires at least one trade.")
# Stack along a trade dimension: (num_trades, num_paths, num_times)
self._paths = torch.stack(mtm_paths_per_trade, dim=0)
@property
def num_paths(self) -> int:
return self._paths.shape[1]
@property
def num_times(self) -> int:
return self._paths.shape[2]
def gross_epe(self) -> torch.Tensor:
"""Gross EE profile: sum of per-trade positive exposures, shape (num_times,).
Note: this returns the *function of time* EE_gross(t_j); the
scalar EPE is its time-average. The library keeps the legacy
name `gross_epe` for the function call.
"""
per_trade_ee = self._paths.clamp(min=0.0).mean(dim=1) # (num_trades, num_times)
return per_trade_ee.sum(dim=0)
def netted_epe(self) -> torch.Tensor:
"""Netted EE profile of the net portfolio value, shape (num_times,)."""
net_mtm = self._paths.sum(dim=0) # (num_paths, num_times)
return net_mtm.clamp(min=0.0).mean(dim=0)
def collateralised_epe(
self,
threshold: float = 0.0,
mpr: int = 1,
) -> torch.Tensor:
"""Collateralised EE profile with threshold H and MPR delay.
Single-snapshot proxy: the call at step j is computed from the
net MTM at step j - mpr, not from a running collateral balance.
"""
net_mtm = self._paths.sum(dim=0) # (num_paths, num_times)
T = self.num_times
collateral = torch.zeros_like(net_mtm)
for j in range(T):
ref = j - mpr
if ref >= 0:
collateral[:, j] = (net_mtm[:, ref] - threshold).clamp(min=0.0)
residual = (net_mtm - collateral).clamp(min=0.0)
return residual.mean(dim=0)
Part 2: CVA computation from an EE profile
def portfolio_cva(ee: torch.Tensor, disc: torch.Tensor,
hazard_rate: float, recovery: float,
mon_years: torch.Tensor) -> torch.Tensor:
"""Compute CVA from an EE profile.
Args:
ee: (n_mon,) EE(t_j) at each monitoring date
disc: (n_mon,) discount factors P(0, t_j)
hazard_rate: annual CDS-implied hazard rate
recovery: recovery rate (e.g., 0.40 for 40%)
mon_years: (n_mon,) time grid in years
Returns:
scalar CVA in the same currency as EE
"""
# Survival probabilities: Q(tau > t) = exp(-lambda * t)
surv = torch.exp(-hazard_rate * mon_years)
surv_prev = torch.exp(-hazard_rate *
torch.cat([torch.tensor([0.0]), mon_years[:-1]]))
# Marginal default probability in each interval
pd = surv_prev - surv
return (1.0 - recovery) * (disc * ee * pd).sum()
Part 3: Example with a 3-trade netting set
# Example: 3-trade netting set
torch.manual_seed(42)
n_paths, n_mon = 5000, 20
mon_years = torch.arange(0.5, 10.5, 0.5)
# Simulate three correlated swap MTMs (paths-first layout)
# Share common random factor to create correlation
z = torch.randn(n_paths, n_mon)
e1 = torch.randn(n_paths, n_mon)
e2 = torch.randn(n_paths, n_mon)
e3 = torch.randn(n_paths, n_mon)
# Payer 10-year swap: 0.3*z + noise
mtm_payer_10y = 1e6 * (0.3 * z + 0.1 * e1)
# Receiver 5-year swap: -0.6*z + noise (opposite direction, higher loading)
mtm_receiver_5y = -0.6e6 * (0.3 * z + 0.1 * e2)
# Payer 3-year swap: 0.4*z + noise
mtm_payer_3y = 0.4e6 * (0.2 * z + 0.15 * e3)
# Zero out trades after maturity (mask along the time axis)
mtm_payer_3y[:, mon_years > 3.0] = 0
mtm_receiver_5y[:, mon_years > 5.0] = 0
# Construct netting set with CSA (paths-first tensors per trade)
netting_set = NettingSet([mtm_payer_10y, mtm_receiver_5y, mtm_payer_3y])
# Compute EE profiles (the per-time function, not the scalar EPE)
gross = netting_set.gross_epe()
netted = netting_set.netted_epe()
coll = netting_set.collateralised_epe(threshold=0.0, mpr=1) # ~6m MPR
# Discount factors and credit data
disc = torch.exp(-0.035 * mon_years)
cds_spread = 0.0120 # 120 basis points
recovery = 0.40
lam = cds_spread / (1.0 - recovery)
# CVA under each scenario
cva_gross = portfolio_cva(gross, disc, lam, recovery, mon_years)
cva_netted = portfolio_cva(netted, disc, lam, recovery, mon_years)
cva_coll = portfolio_cva(coll, disc, lam, recovery, mon_years)
print(f"CVA (gross): EUR {cva_gross:>12,.0f}")
print(f"CVA (netted): EUR {cva_netted:>12,.0f}")
print(f"CVA (collateralised): EUR {cva_coll:>12,.0f}")
print()
print(f"Netting benefit: {(1 - cva_netted/cva_gross)*100:>6.1f}%")
print(f"Collateral benefit: {(1 - cva_coll/cva_netted)*100:>6.1f}%")
Gross vs. netted vs. collateralised EE profiles
The complete XVA pipeline
This chapter sits at the intersection of the two series. The Interest Rate Modeling series provides the simulation and pricing engine; the XVA series provides the credit and aggregation layer. Together, the pipeline runs from market data to portfolio-level CVA:
- IR Chapter 01: Single-Curve Bootstrapping: zero-coupon bond prices from swap and deposit rates.
- IR Chapter 02: Multi-Curve Bootstrapping: separating discounting from rate projection.
- IR Chapter 03: Swaptions and the Volatility Surface: market-implied volatilities used to calibrate the short-rate model.
- IR Chapter 04: Stochastic Rates: from deterministic curves to Vasicek dynamics under $\mathbb{Q}$.
- IR Chapter 05: Hull-White (Draft): a tractable short-rate model for path simulation.
- IR Chapter 06: Model Calibration (Draft): fitting Hull-White to the swaption market.
- IR Chapter 08: Pricing on Simulated Paths (Draft): closed-form swap repricing on every Monte Carlo state.
- XVA Chapter 01: Exposure: from MTM paths to $\text{EE}(t)$, EPE, and PFE on a single trade.
- XVA Chapter 02: Aggregation and Netting (this chapter): netting sets, collateral, and portfolio-level EE profiles.
- XVA Chapter 03: Credit Value Adjustment: hazard rates and the CVA integral over the netted EE profile.
From yield curves to simulated paths to repriced derivatives to netted portfolios: this is the full XVA pipeline that powers modern counterparty risk management.
Real XVA systems are far more complex. Banks must handle CVA aggregation across multiple legal entities, incorporate bilateral credit adjustments (DVA), compute sensitivities to market moves, manage incremental CVA for new trades, and perform extensive backtesting and stress testing. But the core principles (simulation, repricing, aggregation, and discounting) remain unchanged. Everything in these chapters is foundational to that practice.
References
- Brigo, D. & Mercurio, F. (2006). Interest Rate Models: Theory and Practice (2nd ed.). Springer. The canonical reference on rates models underlying XVA computations.
- Gregory, J. (2020). The xVA Challenge (4th ed.). Wiley. Comprehensive treatment of CVA, DVA, FVA, and counterparty risk in practice.
- Crépey, S. (2015). Counterparty Risk and Funding: A Tale of Two Puzzles. Chapman & Hall/CRC. Rigorous mathematical treatment of counterparty risk and funding costs.
- Pykhtin, M. & Zhu, S. (2007). "A Guide to Modelling Counterparty Credit Risk." GARP Risk Review. Excellent overview of EPE, netting, and collateral modelling.
We now have a collateralised, netted exposure profile for each netting set. In XVA-03, we use this, together with default probabilities from CDS spreads, to compute CVA: the price the dealer should charge (or reserve) for the credit risk embedded in the portfolio.