Â
SECTION 1: LEARNING OBJECTIVES
By the end of this lesson, you will be able to:
-
Define market risk and its key components – interest rate, FX, equity, and commodity risk.
-
Apply Value at Risk (VaR)Â and Expected Shortfall (ES) methodologies.
-
Implement stress testing for market risk.
-
Understand the regulatory framework – Basel III, FRTB.
-
Measure market risk using key metrics.
-
Develop a market risk strategy for a digital bank.
SECTION 2: WHAT IS MARKET RISK?
2.1 Definition
Market risk is the risk of losses arising from movements in market prices – interest rates, foreign exchange rates, equity prices, commodity prices, and credit spreads.
2.2 Key Market Risk Components
| Component | Description | Example |
|---|---|---|
| Interest Rate Risk | Risk from interest rate movements. | Bond price changes. |
| FX Risk | Risk from exchange rate movements. | Currency fluctuations. |
| Equity Risk | Risk from stock price movements. | Stock market declines. |
| Commodity Risk | Risk from commodity price movements. | Oil price changes. |
| Credit Spread Risk | Risk from credit spread changes. | Corporate bond spread widening. |
2.3 Market Risk in Digital Banking
| Activity | Market Risk Exposure | Mitigation |
|---|---|---|
| Trading | Price movements. | Hedging, position limits. |
| Lending | Interest rate changes. | ALM, hedging. |
| Investments | Asset price movements. | Diversification. |
| FX Transactions | Currency movements. | Hedging, netting. |
SECTION 3: VALUE AT RISK (VAR) AND EXPECTED SHORTFALL (ES)
3.1 Value at Risk (VaR)
VaRÂ is a statistical measure that quantifies the maximum potential loss of a portfolio over a given time horizon at a given confidence level.
P(L≤VaRα)=1−α
Common VaR Methods:
| Method | Description | Pros | Cons |
|---|---|---|---|
| Historical Simulation | Use historical returns. | Non-parametric, simple. | History may not repeat. |
| Parametric (Normal) | Assume normal distribution. | Fast, easy. | Underestimates tail risk. |
| Monte Carlo | Simulate scenarios. | Flexible, complex. | Computationally intensive. |
3.2 Expected Shortfall (ES)
Expected Shortfall is the average loss given that the loss exceeds the VaR threshold.
ESα=E[L∣L>VaRα]
Why ES is Preferred:
-
Coherent risk measure (sub-additive).
-
Captures tail risk better.
-
Required by FRTB.
SECTION 4: REGULATORY FRAMEWORK
4.1 Key Regulations
| Regulation | Region | Focus |
|---|---|---|
| Basel III | Global | Capital for market risk. |
| FRTB (Fundamental Review of the Trading Book) | Global | Enhanced market risk framework. |
| MiFID II | EU | Trading and investor protection. |
| EMIR | EU | Derivatives regulation. |
4.2 FRTB Key Changes
| Change | Description |
|---|---|
| ES over VaR | Replace VaR with Expected Shortfall. |
| Standardised Approach | Enhanced sensitivity-based method. |
| Internal Models | Desk-level approval required. |
| Non-Modellable Risk Factors | Additional capital for non-modellable risks. |
SECTION 5: IMPLEMENTATION IN PYTHON – MARKET RISK
# =================================================================== # MODULE 8, LESSON 3: MARKET RISK MANAGEMENT # =================================================================== import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy.stats import norm import warnings warnings.filterwarnings('ignore') print("="*70) print("MARKET RISK MANAGEMENT IN DIGITAL BANKING") print("="*70) # ---------------------------------------------------------------- # PART A: GENERATE MARKET DATA # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART A: Generating Market Data") print("-"*60) np.random.seed(42) n_days = 500 # Generate correlated asset returns assets = ['Stock A', 'Stock B', 'Bond Fund', 'FX Pair'] n_assets = len(assets) # Asset characteristics returns = [0.0005, 0.0008, 0.0002, 0.0003] # Daily returns volatilities = [0.02, 0.025, 0.008, 0.012] # Daily volatilities # Correlation matrix correlations = np.array([ [1.00, 0.70, 0.10, 0.20], [0.70, 1.00, 0.15, 0.25], [0.10, 0.15, 1.00, 0.05], [0.20, 0.25, 0.05, 1.00] ]) # Generate returns cov_matrix = np.diag(volatilities) @ correlations @ np.diag(volatilities) daily_returns = np.random.multivariate_normal(returns, cov_matrix, n_days) asset_returns = pd.DataFrame(daily_returns, columns=assets) print("Asset Returns Generated:") print(asset_returns.head()) # Portfolio weights (equal-weighted) weights = np.ones(n_assets) / n_assets portfolio_returns = asset_returns @ weights # Calculate portfolio statistics port_mean = np.mean(portfolio_returns) port_std = np.std(portfolio_returns) print(f"Portfolio Mean Return: {port_mean*100:.4f}%") print(f"Portfolio Volatility: {port_std*100:.4f}%") # ---------------------------------------------------------------- # PART B: HISTORICAL VAR # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART B: Historical VaR and ES") print("-"*60) # Sort returns sorted_returns = np.sort(portfolio_returns) # Confidence levels confidence_levels = [0.95, 0.99] def historical_var_es(returns, confidence): """Calculate historical VaR and ES.""" alpha = 1 - confidence var_idx = int(alpha * len(returns)) var = -sorted_returns[var_idx] losses_beyond = -sorted_returns[:var_idx] es = np.mean(losses_beyond) if len(losses_beyond) > 0 else var return var, es # Calculate VaR and ES var_hist = {} es_hist = {} for conf in confidence_levels: var_hist[conf], es_hist[conf] = historical_var_es(portfolio_returns, conf) print(f"Confidence {conf*100:.0f}%: VaR = {var_hist[conf]*100:.4f}%, ES = {es_hist[conf]*100:.4f}%") # Dollar VaR (assuming $1M portfolio) portfolio_value = 1_000_000 for conf in confidence_levels: var_dollar = var_hist[conf] * portfolio_value es_dollar = es_hist[conf] * portfolio_value print(f"${portfolio_value:,.0f} portfolio, {conf*100:.0f}%: VaR = ${var_dollar:,.2f}, ES = ${es_dollar:,.2f}") # ---------------------------------------------------------------- # PART C: PARAMETRIC (NORMAL) VAR # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART C: Parametric (Normal) VaR and ES") print("-"*60) def normal_var_es(mu, sigma, confidence): """Calculate normal VaR and ES.""" alpha = 1 - confidence z = norm.ppf(alpha) var = -(mu + z * sigma) es = -mu + sigma * norm.pdf(z) / alpha return var, es # Calculate var_norm = {} es_norm = {} for conf in confidence_levels: var_norm[conf], es_norm[conf] = normal_var_es(port_mean, port_std, conf) print(f"Confidence {conf*100:.0f}%: VaR = {var_norm[conf]*100:.4f}%, ES = {es_norm[conf]*100:.4f}%") # ---------------------------------------------------------------- # PART D: MONTE CARLO VAR # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART D: Monte Carlo VaR") print("-"*60) def monte_carlo_var(mu, sigma, n_simulations=100000, horizon=1, confidence=0.95): """Calculate VaR using Monte Carlo simulation.""" # Simulate returns simulated_returns = np.random.normal(mu * horizon, sigma * np.sqrt(horizon), n_simulations) # Calculate VaR alpha = 1 - confidence var = -np.percentile(simulated_returns, alpha * 100) # Calculate ES losses_beyond = -simulated_returns[simulated_returns < -var] es = np.mean(losses_beyond) if len(losses_beyond) > 0 else var return var, es # Calculate for conf in confidence_levels: var_mc, es_mc = monte_carlo_var(port_mean, port_std, n_simulations=100000, confidence=conf) print(f"Monte Carlo {conf*100:.0f}%: VaR = {var_mc*100:.4f}%, ES = {es_mc*100:.4f}%") # ---------------------------------------------------------------- # PART E: STRESS TESTING # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART E: Stress Testing") print("-"*60) # Define stress scenarios stress_scenarios = { 'Market Crash': {'Stock A': -0.15, 'Stock B': -0.12, 'Bond Fund': -0.02, 'FX Pair': 0.02}, 'Rate Hike': {'Stock A': -0.05, 'Stock B': -0.03, 'Bond Fund': -0.08, 'FX Pair': 0.04}, 'FX Shock': {'Stock A': -0.02, 'Stock B': -0.01, 'Bond Fund': 0.01, 'FX Pair': -0.10}, 'Volatility Spike': {'Stock A': -0.08, 'Stock B': -0.06, 'Bond Fund': -0.01, 'FX Pair': -0.02} } def calculate_portfolio_stress(weights, scenario_returns): """Calculate portfolio return under stress scenario.""" returns_vector = np.array([scenario_returns.get(asset, 0) for asset in assets]) return weights @ returns_vector stress_results = [] for name, scenario in stress_scenarios.items(): port_loss = -calculate_portfolio_stress(weights, scenario) * 100 stress_results.append({ 'Scenario': name, 'Portfolio Loss (%)': port_loss }) stress_df = pd.DataFrame(stress_results) print("Stress Test Results:") print(stress_df.to_string(index=False)) # Visualise fig, axes = plt.subplots(2, 2, figsize=(14, 10)) # VaR Comparison ax = axes[0, 0] conf_labels = [f'{int(conf*100)}%' for conf in confidence_levels] var_hist_values = [var_hist[conf]*100 for conf in confidence_levels] var_norm_values = [var_norm[conf]*100 for conf in confidence_levels] x = np.arange(len(conf_labels)) width = 0.35 ax.bar(x - width/2, var_hist_values, width, label='Historical', color='blue', alpha=0.7) ax.bar(x + width/2, var_norm_values, width, label='Parametric', color='green', alpha=0.7) ax.set_xlabel('Confidence Level') ax.set_ylabel('VaR (%)') ax.set_title('VaR Comparison: Historical vs Parametric') ax.set_xticks(x) ax.set_xticklabels(conf_labels) ax.legend() ax.grid(True, alpha=0.3) # Stress Test Results ax = axes[0, 1] ax.barh(stress_df['Scenario'], stress_df['Portfolio Loss (%)'], color='red', alpha=0.7) ax.set_xlabel('Portfolio Loss (%)') ax.set_title('Stress Test Results') ax.grid(True, alpha=0.3) # Portfolio Returns Distribution ax = axes[1, 0] ax.hist(portfolio_returns * 100, bins=50, edgecolor='black', alpha=0.7, color='teal') ax.axvline(-var_hist[0.95]*100, color='red', linestyle='--', label=f'VaR 95%: {var_hist[0.95]*100:.2f}%') ax.axvline(-var_hist[0.99]*100, color='orange', linestyle='--', label=f'VaR 99%: {var_hist[0.99]*100:.2f}%') ax.set_xlabel('Daily Return (%)') ax.set_ylabel('Frequency') ax.set_title('Portfolio Return Distribution with VaR') ax.legend() ax.grid(True, alpha=0.3) # Rolling VaR ax = axes[1, 1] window = 60 rolling_var = [] for i in range(window, len(portfolio_returns)): window_returns = portfolio_returns[i-window:i] var, _ = historical_var_es(window_returns, 0.95) rolling_var.append(var) ax.plot(range(window, len(portfolio_returns)), rolling_var, 'b-', linewidth=1.5) ax.set_xlabel('Time') ax.set_ylabel('VaR 95% (%)') ax.set_title('Rolling VaR (60-day window)') ax.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('market_risk_analysis.png', dpi=300, bbox_inches='tight') plt.show() print("Market risk analysis visualisation saved as 'market_risk_analysis.png'") # ---------------------------------------------------------------- # PART F: MARKET RISK METRICS # ---------------------------------------------------------------- print("\n" + "-"*60) print("PART F: Market Risk Metrics Dashboard") print("-"*60) market_metrics = pd.DataFrame({ 'Metric': [ 'VaR (95%, 1-day)', 'VaR (99%, 1-day)', 'Expected Shortfall (95%)', 'Expected Shortfall (99%)', 'Stress Test Loss', 'Portfolio Volatility', 'Beta to Market', 'Tracking Error' ], 'Current Value': [ f'{var_hist[0.95]*100:.2f}%', f'{var_hist[0.99]*100:.2f}%', f'{es_hist[0.95]*100:.2f}%', f'{es_hist[0.99]*100:.2f}%', f'{stress_df["Portfolio Loss (%)"].max():.2f}%', f'{port_std*100:.2f}%', '0.85', '2.5%' ], 'Target Value': [ '< 3%', '< 5%', '< 4%', '< 7%', '< 15%', '< 3%', '< 1.0', '< 3%' ], 'Status': ['🟢', '🟢', '🟢', '🟢', '🟢', '🟢', '🟢', '🟢'] }) print("Market Risk Metrics Dashboard:") print(market_metrics.to_string(index=False)) # ---------------------------------------------------------------- # PART G: SUMMARY AND RECOMMENDATIONS # ---------------------------------------------------------------- print("\n" + "="*70) print("PART G: Summary and Recommendations") print("="*70) print(""" Market Risk Management – Key Takeaways: 1. Market risk includes interest rate, FX, equity, commodity, and credit spread risk. 2. VaR and Expected Shortfall are key risk measures. 3. VaR methods: historical, parametric, Monte Carlo. 4. Expected Shortfall captures tail risk better than VaR. 5. Stress testing evaluates portfolio resilience under extreme scenarios. 6. Regulatory framework: Basel III, FRTB (ES over VaR). 7. Key metrics: VaR, ES, stress test loss, volatility, beta. Recommendations: - Implement VaR and ES for market risk measurement. - Use multiple VaR methods for validation. - Conduct regular stress testing. - Hedge material market risks. - Ensure compliance with FRTB requirements. - Monitor market risk metrics continuously. """) print("="*70) print("END OF LESSON 3 – MODULE 8") print("="*70)
SECTION 6: SUMMARY FOR THE DATA PRACTITIONER
-
Market risk includes interest rate, FX, equity, commodity, and credit spread risk.
-
VaRÂ quantifies maximum potential loss at a given confidence level.
-
Expected Shortfall (ES)Â captures average loss beyond VaR and is required by FRTB.
-
VaR methods include historical simulation, parametric (normal), and Monte Carlo.
-
Stress testing evaluates portfolio resilience under extreme scenarios.
-
Regulatory framework includes Basel III and FRTB (which replaces VaR with ES).
-
Key metrics include VaR, ES, stress test loss, portfolio volatility, and beta.
SECTION 7: RECOMMENDED NEXT STEPS
-
Implement VaR and ES for market risk measurement.
-
Use multiple VaR methods for validation.
-
Conduct regular stress testing.
-
Hedge material market risks.
-
Ensure compliance with FRTB requirements.
-
Monitor market risk metrics continuously.
-
Prepare for Lesson 4:Â Operational Risk Management.
[END OF LESSON 3 – MODULE 8]