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

python
# ===================================================================
# 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

  1. Implement VaR and ES for market risk measurement.

  2. Use multiple VaR methods for validation.

  3. Conduct regular stress testing.

  4. Hedge material market risks.

  5. Ensure compliance with FRTB requirements.

  6. Monitor market risk metrics continuously.

  7. Prepare for Lesson 4: Operational Risk Management.


[END OF LESSON 3 – MODULE 8]


Â