Prerequisites

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:

Portfolio netting set value $$V_{\text{netting set}}(t) = \sum_{k=1}^{K} V_k(t)$$

The expected positive exposure without netting (gross EE profile) sums the exposures of individual trades:

Gross EE (no netting) $$\text{EE}_{\text{gross}}(t) = \sum_k \mathbb{E}^{\mathbb{Q}}\!\left[\max(V_k(t),\, 0)\right]$$

With netting, we compute the expected positive value of the sum:

Netted EE (with netting agreement) $$\text{EE}_{\text{netted}}(t) = \mathbb{E}^{\mathbb{Q}}\!\left[\max\left(\sum_k V_k(t),\, 0\right)\right]$$

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:

Netting benefit inequality $$\text{EE}_{\text{netted}}(t) \leq \text{EE}_{\text{gross}}(t)$$

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:

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:

Collateral call at the lagged date $$C(t - \text{MPR}) = \max\!\left(V_{\text{net}}(t - \text{MPR}) - H,\, 0\right) - \max\!\left(-V_{\text{net}}(t - \text{MPR}) - H,\, 0\right)$$

The bank's residual exposure at $t$ is the positive part of the net MTM after applying the lagged call:

Collateralised exposure $$V_{\text{coll}}(t) = \max\!\left(V_{\text{net}}(t) - C(t - \text{MPR}),\, 0\right)$$
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)$
Modelling caveat

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:

Portfolio CVA $$\text{CVA} = (1-R) \sum_{j=1}^{M} P(0, t_j) \cdot \text{EE}_{\text{netted or coll}}(t_j) \cdot \text{PD}(t_{j-1}, t_j)$$
$\text{EE}(t_j)$ is now computed on the netted (or collateralised) portfolio, not a single trade The hazard rate and recovery rate are for the counterparty as a whole All other components (discount factors $P(0, t_j)$ and default probabilities $\text{PD}(t_{j-1}, t_j)$) are unchanged from Chapter 03 Independence assumption: the CVA derivation in Chapter 03 requires the exposure $V^+(t)$ and the default time $\tau$ to be independent (and, under stochastic rates, the short-rate process to be independent of $\tau$). The formula above inherits the same caveat at the netting-set level.

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

Python · PyTorch
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

Python · PyTorch
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

Python · PyTorch
# 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

Exposure profiles: the impact of netting and collateral
Three EE profiles for the 3-trade netting set: gross EE (dashed grey) shows the sum of individual trade exposures. Netted EE (solid blue) reflects the offset from short and long positions. Collateralised EE (solid crimson) is further reduced by daily collateral posting.

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:

  1. IR Chapter 01: Single-Curve Bootstrapping: zero-coupon bond prices from swap and deposit rates.
  2. IR Chapter 02: Multi-Curve Bootstrapping: separating discounting from rate projection.
  3. IR Chapter 03: Swaptions and the Volatility Surface: market-implied volatilities used to calibrate the short-rate model.
  4. IR Chapter 04: Stochastic Rates: from deterministic curves to Vasicek dynamics under $\mathbb{Q}$.
  5. IR Chapter 05: Hull-White (Draft): a tractable short-rate model for path simulation.
  6. IR Chapter 06: Model Calibration (Draft): fitting Hull-White to the swaption market.
  7. IR Chapter 08: Pricing on Simulated Paths (Draft): closed-form swap repricing on every Monte Carlo state.
  8. XVA Chapter 01: Exposure: from MTM paths to $\text{EE}(t)$, EPE, and PFE on a single trade.
  9. XVA Chapter 02: Aggregation and Netting (this chapter): netting sets, collateral, and portfolio-level EE profiles.
  10. 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.

A note on practice

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

  1. Brigo, D. & Mercurio, F. (2006). Interest Rate Models: Theory and Practice (2nd ed.). Springer. The canonical reference on rates models underlying XVA computations.
  2. Gregory, J. (2020). The xVA Challenge (4th ed.). Wiley. Comprehensive treatment of CVA, DVA, FVA, and counterparty risk in practice.
  3. 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.
  4. 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.