SECTION 1: LEARNING OBJECTIVES
By the end of this lesson, you will be able to:
-
Define counterparty credit risk (CCR) and distinguish it from traditional lending credit risk.
-
Understand the key components of CCR: Exposure at Default (EAD), Probability of Default (PD), and Loss Given Default (LGD) in the context of derivative and securities financing transactions.
-
Explain the concept of Potential Future Exposure (PFE) and its role in measuring CCR.
-
Understand the Standardised Approach for Counterparty Credit Risk (SA-CCR) – the Basel III framework for calculating exposure for derivatives.
-
Compute the Exposure at Default (EAD) for derivatives using the SA-CCR methodology, including the calculation of replacement cost and potential future exposure.
-
Define Credit Valuation Adjustment (CVA) and its importance in pricing and risk management.
-
Calculate CVA using a simplified formula involving Expected Exposure (EE), PD, and LGD.
-
Understand the regulatory capital requirements for CVA risk under Basel III.
-
Implement simplified CCR and CVA calculations in Python on a portfolio of interest rate swaps.
SECTION 2: WHAT IS COUNTERPARTY CREDIT RISK?
Counterparty Credit Risk (CCR) is the risk that the counterparty to a financial transaction (e.g., a derivative, securities lending, or repo) defaults before the final settlement of the transaction.
Unlike traditional lending, where exposure is the outstanding loan amount, CCR exposure is uncertain and can vary over time as the value of the underlying assets changes. This makes CCR more complex.
Key features of CCR:
-
Bilateral – both parties face each other’s default risk.
-
Uncertain exposure – depends on market movements (interest rates, FX, credit spreads).
-
Netting – agreements allow offsetting of exposures across multiple transactions.
-
Collateral – often posted to mitigate risk.
Examples of CCR:
-
An interest rate swap: if the counterparty defaults, the bank may lose the positive fair value of the swap.
-
A forward FX contract: if the counterparty defaults, the bank may lose the unrealised gain.
-
A repo: if the counterparty defaults, the bank may lose the cash lent or the securities.
SECTION 3: MEASURING CCR – EXPOSURE AT DEFAULT (EAD)
For derivatives, EAD is not simply the current market value. It must account for potential future increases in exposure before default.
Components of EAD for derivatives:
EAD=Replacement Cost (RC)+Potential Future Exposure (PFE)
-
Replacement Cost (RC): The current positive market value of the derivative (or net portfolio after netting). If the value is negative, RC is zero.
-
Potential Future Exposure (PFE): An estimate of the potential increase in exposure over the margin period of risk (typically 5-10 days for cleared trades, longer for uncleared).
SA-CCR (Standardised Approach for Counterparty Credit Risk) is the Basel III framework for calculating EAD for derivatives.
3.1 SA-CCR – Simplified
SA-CCR computes EAD using a formula that accounts for the trade’s sensitivity to market risk factors and the time horizon.
For a single trade or netting set, the EAD is:
EAD=α×(RC+PFE)
where:
-
α=1.4 (a regulatory multiplier).
-
RC = Replacement Cost (net of collateral).
-
PFE = a function of the aggregate risk factor exposures and the margin period of risk.
For simplicity, we will implement a simplified version to illustrate the concepts.
SECTION 4: CREDIT VALUATION ADJUSTMENT (CVA)
CVA is the market value of counterparty credit risk. It is the difference between the risk-free portfolio value and the true portfolio value that accounts for the possibility of counterparty default.
Definition: CVA is the expected loss due to counterparty default, discounted to the present:
CVA=(1−RR)∑t=1TEEt×PDt×DFt
where:
-
RR = Recovery Rate (1 – LGD)
-
EE_t = Expected Exposure at time t (average positive exposure)
-
PD_t = Probability of default between t-1 and t (marginal)
-
DF_t = Discount factor from t to today
Interpretation: CVA is the cost of hedging counterparty credit risk. It is added to the price of a derivative or subtracted from the asset value.
Regulatory capital for CVA:
-
Basel III requires banks to hold capital against CVA risk (the risk of changes in CVA due to market movements).
-
This is calculated using a standardised or internal model approach.
SECTION 5: IMPLEMENTATION IN PYTHON – SIMPLIFIED CCR AND CVA
# =================================================================== # MODULE 5, LESSON 6: COUNTERPARTY CREDIT RISK AND CVA # =================================================================== import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from scipy.stats import norm from scipy.optimize import minimize import warnings warnings.filterwarnings('ignore') # Set style sns.set_style("whitegrid") np.random.seed(42) print("="*70) print("COUNTERPARTY CREDIT RISK AND CREDIT VALUATION ADJUSTMENT (CVA)") print("="*70) # ---------------------------------------------------------------- # PART A: SIMULATE A PORTFOLIO OF INTEREST RATE SWAPS # ---------------------------------------------------------------- # We'll simulate 10 interest rate swaps with different maturities and notional amounts n_swaps = 10 swap_maturities = np.random.choice([1, 2, 3, 5, 7, 10], n_swaps, p=[0.1, 0.15, 0.15, 0.2, 0.2, 0.2]) swap_notionals = np.random.uniform(1e6, 10e6, n_swaps) # $1M to $10M swap_rates = np.random.normal(0.02, 0.005, n_swaps) # fixed rates around 2% # For simplicity, assume zero coupon curve flat at 2% and the swap values depend on rate movements # We'll model the value of each swap as a function of interest rate shifts # For a payer swap, value = PV(fixed leg) - PV(floating leg) ≈ notional * (fixed_rate - swap_rate) * annuity_factor # Simplified: assume each swap value = notional * (current_rate_shift) * duration # Simulate a portfolio of swaps with current mark-to-market values and sensitivities swap_durations = swap_maturities * 0.75 # approximate duration current_rate = 0.02 # 2% flat curve swap_values = np.zeros(n_swaps) # Assign values based on a random rate shift: some swaps have positive value, some negative rate_shift = np.random.normal(0, 0.005, n_swaps) # random deviation from current rate for i in range(n_swaps): # Payer swap value: positive if fixed_rate > current_rate (but fixed_rate is the market rate) # To simplify, we assume the swap is at-market at inception, so value changes with rate moves. # Value ≈ notional * duration * (rate - fixed_rate) swap_values[i] = swap_notionals[i] * swap_durations[i] * (current_rate + rate_shift[i] - swap_rates[i]) # Create DataFrame swap_df = pd.DataFrame({ 'notional': swap_notionals, 'maturity': swap_maturities, 'duration': swap_durations, 'fixed_rate': swap_rates, 'value': swap_values }) print("Swap Portfolio Summary:") print(swap_df.round(2)) print(f"Total portfolio value (MTM): ${swap_df['value'].sum():,.2f}") # Identify positive-value swaps (exposure) positive_swaps = swap_df[swap_df['value'] > 0] print(f"Number of positive-value swaps: {len(positive_swaps)}") print(f"Total positive exposure (sum of positive values): ${positive_swaps['value'].sum():,.2f}") # ---------------------------------------------------------------- # PART B: SA-CCR – SIMPLIFIED EAD CALCULATION # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART B: Simplified SA-CCR – Exposure at Default (EAD)") print("-"*60) # For each swap, compute RC = max(V, 0) (net of collateral) # Assume no collateral for simplicity rc = np.maximum(swap_values, 0) # Compute PFE: Simplified as a percentage of notional based on maturity and volatility # In SA-CCR, PFE depends on risk factors (interest rates, FX, credit) and margin period. # We'll use a simple formula: PFE = notional * 0.05 * sqrt(maturity) * volatility_factor vol_factor = 0.15 # 15% annual vol (approx for rates) margin_period = 0.02 # 2 years? Actually margin period is in years, but SA-CCR uses 10 days for cleared. # For simplicity, we'll ignore margin period and use maturity. pfe = swap_notionals * 0.02 * np.sqrt(swap_maturities) * vol_factor # simplistic # Alpha multiplier = 1.4 alpha = 1.4 ead = alpha * (rc + pfe) swap_df['RC'] = rc swap_df['PFE'] = pfe swap_df['EAD'] = ead print("\nEAD by Swap:") print(swap_df[['notional', 'value', 'RC', 'PFE', 'EAD']].round(2)) print(f"\nTotal EAD (after netting across swaps): {swap_df['EAD'].sum():,.2f}") # In practice, netting would apply to the portfolio level, not per swap. # ---------------------------------------------------------------- # PART C: EXPECTED EXPOSURE (EE) PROFILE OVER TIME # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART C: Expected Exposure Profile") print("-"*60) # We'll simulate the evolution of the swap portfolio value over time using a Monte Carlo approach # Simplified: assume each swap's value follows a normal distribution with mean drift and volatility. # We'll generate future values at discrete time points. n_steps = 10 # quarterly steps over a 2.5-year horizon (to capture maturities) time_points = np.linspace(0.1, 2.5, n_steps) # years n_sim = 10000 # number of simulations # Simulate future values for each swap # Assume each swap value decreases linearly to zero at maturity (amortizing) # And has stochastic volatility sim_values = np.zeros((n_sim, n_steps, n_swaps)) for i in range(n_swaps): maturity = swap_maturities[i] notional = swap_notionals[i] duration = swap_durations[i] # Current value current_val = swap_values[i] # Drift: value decays to zero at maturity (amortizing) # Volatility: proportional to remaining time vol = 0.2 * np.sqrt(1/12) # approx monthly vol for t_idx, t in enumerate(time_points): if t > maturity: val = 0 # matured else: # Simulate value using random walk with mean reversion to zero # We'll use a simple normal random walk with drift toward zero remaining = maturity - t # We assume the value is expected to decay linearly expected_val = current_val * (1 - t / maturity) if t < maturity else 0 # Add stochastic noise # For simplicity, we draw from normal with std proportional to sqrt(time) std = 0.05 * notional * np.sqrt(t) # scaling sim_values[:, t_idx, i] = np.random.normal(expected_val, std, n_sim) # Compute Expected Exposure (EE) at each time point: average of positive values across simulations ee_profile = [] for t_idx in range(n_steps): ee_t = np.mean(np.maximum(sim_values[:, t_idx, :].sum(axis=1), 0)) ee_profile.append(ee_t) print("Expected Exposure profile (positive expected value):") for t, ee in zip(time_points, ee_profile): print(f" t={t:.2f}: ${ee:,.2f}") # ---------------------------------------------------------------- # PART D: CVA CALCULATION # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART D: Credit Valuation Adjustment (CVA)") print("-"*60) # We need: # - EE profile (from above) # - PD term structure (annual default probabilities) # - Recovery Rate (RR) -> LGD = 1 - RR # - Discount factors (risk-free curve) # Assume a counterparty with the following PD term structure (cumulative) # PD_1y = 1%, PD_2y = 2.5%, PD_3y = 4% etc. # We'll interpolate to quarterly. pd_cum = np.array([0, 0.01, 0.025, 0.04, 0.06, 0.08, 0.105, 0.13, 0.155, 0.18, 0.205]) # cumulative for years 0..10 pd_cum_full = np.interp(time_points, np.arange(0, 11), pd_cum) # interpolate to our time points # Marginal PD (probability of default in interval) pd_marginal = np.diff(pd_cum_full, prepend=0) # Recovery Rate rr = 0.4 # 40% recovery lgd = 1 - rr # Discount factor: assume flat 2% risk-free rate discount_factors = np.exp(-0.02 * time_points) # CVA = LGD * sum( EE_t * PD_marginal_t * DF_t ) cva = lgd * np.sum(ee_profile * pd_marginal * discount_factors) print(f"Recovery Rate: {rr:.2%}") print(f"LGD: {lgd:.2%}") print(f"Risk-free discount rate: 2%") print(f"\nCVA (simplified): ${cva:,.2f}") # CVA per unit of exposure cva_rate = cva / np.mean(ee_profile) # approximate print(f"CVA as % of expected exposure: {cva_rate*100:.2f}%") # ---------------------------------------------------------------- # PART E: CVA SENSITIVITY # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART E: CVA Sensitivity Analysis") print("-"*60) # Vary PD and RR to see impact on CVA pd_scales = [0.5, 0.75, 1.0, 1.25, 1.5] rr_values = [0.2, 0.4, 0.6, 0.8] results_cva = [] for pd_scale in pd_scales: for rr_val in rr_values: lgd_tmp = 1 - rr_val pd_marg_tmp = pd_marginal * pd_scale cva_tmp = lgd_tmp * np.sum(ee_profile * pd_marg_tmp * discount_factors) results_cva.append({'PD_scale': pd_scale, 'RR': rr_val, 'CVA': cva_tmp}) cva_sens_df = pd.DataFrame(results_cva) print("\nCVA Sensitivity to PD and RR:") print(cva_sens_df.round(2)) # Visualise CVA surface fig = plt.figure(figsize=(10, 6)) ax = fig.add_subplot(111, projection='3d') X = cva_sens_df.pivot(index='PD_scale', columns='RR', values='CVA').values Y = cva_sens_df.pivot(index='PD_scale', columns='RR', values='CVA').columns Z = cva_sens_df.pivot(index='PD_scale', columns='RR', values='CVA').index X, Y = np.meshgrid(Y, Z) surf = ax.plot_surface(X, Y, cva_sens_df.pivot(index='PD_scale', columns='RR', values='CVA').values, cmap='coolwarm', alpha=0.8) ax.set_xlabel('RR') ax.set_ylabel('PD Scale') ax.set_zlabel('CVA ($)') ax.set_title('CVA Sensitivity Surface') plt.tight_layout() plt.savefig('cva_sensitivity.png', dpi=300) plt.show() # ---------------------------------------------------------------- # PART F: REGULATORY CAPITAL FOR CVA RISK # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART F: Regulatory Capital for CVA Risk (Basel III)") print("-"*60) # Under Basel III, banks must hold capital against CVA risk. # The standardised approach uses risk weights based on counterparty credit rating. # Simplified: assume a risk weight of 10% for investment-grade counterparties. risk_weight = 0.10 cva_capital = risk_weight * cva print(f"CVA Risk Capital (simplified): ${cva_capital:,.2f}") # In practice, the calculation is more complex, involving sensitivities of CVA to market factors. print(""" Note: Actual CVA risk capital under Basel III uses the SA-CVA approach, which involves calculating sensitivities of CVA to market risk factors (interest rates, credit spreads, FX). The simplified approach above is for illustration. """) # ---------------------------------------------------------------- # PART G: BUSINESS IMPLICATIONS # ---------------------------------------------------------------- print("\n" + "="*70) print("PART G: Business Implications and Risk Management") print("="*70) print(""" Key Takeaways: - Counterparty Credit Risk (CCR) is a major risk for derivative portfolios. - Exposure is uncertain and can increase significantly due to market movements. - SA-CCR provides a standardised method to compute EAD for derivatives. - Credit Valuation Adjustment (CVA) is the cost of CCR and impacts derivative pricing. - CVA is sensitive to PD, LGD, and Expected Exposure profiles. - Regulatory capital for CVA risk is a key component of Basel III. - Mitigation techniques: collateral posting, netting, and central clearing reduce CCR. - In practice, banks use sophisticated models for both EAD and CVA, often employing Monte Carlo simulation. """)
SECTION 6: REGULATORY CONTEXT – BASEL III SA-CCR AND CVA
-
SA-CCR (Standardised Approach for Counterparty Credit Risk) replaced the old CEM (Current Exposure Method) and IMM (Internal Model Method) for most banks.
-
It uses trade-level sensitivities to compute PFE, which is more risk-sensitive than the old methods.
-
CVA capital is calculated using either the standardised approach (SA-CVA) or an internal model approach.
-
Central clearing reduces CCR significantly; uncleared derivatives attract higher capital charges.
SECTION 7: SUMMARY FOR THE DATA PRACTITIONER
-
Counterparty Credit Risk arises from derivatives, repos, and securities lending.
-
EAD = RC + PFE under SA-CCR; PFE depends on trade characteristics and market risk.
-
CVA is the expected loss due to counterparty default, calculated from EE, PD, LGD, and discount factors.
-
CVA is a key component of derivative pricing and risk management.
-
Regulatory capital for CCR and CVA risk is a significant part of Basel III.
-
Mitigation tools include collateral, netting, and central clearing.
SECTION 8: RECOMMENDED NEXT STEPS
-
Apply SA-CCR to a real derivative portfolio (interest rate swaps, FX forwards).
-
Implement a more realistic CVA model with stochastic interest rates and default probabilities.
-
Explore the CVA/DVA (Debt Valuation Adjustment) and its impact on pricing.
-
Study the xVA framework (CVA, DVA, FVA, MVA, KVA).
-
Learn about central clearing and Initial Margin for uncleared derivatives.
-
Prepare for the next module on Advanced Topics: Natural Language Processing for Finance.
[END OF LESSON 6 – MODULE 5]