Tutorial 3: Scenario Analysis

Perform stress testing and scenario analysis.


Step 1: Load Trained Models

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import torch
from privatecredit.models import MacroVAE, TransitionTransformer, LoanTrajectoryModel

# Load saved models
checkpoint = torch.load('models/trained_models.pt')

macro_vae = MacroVAE(checkpoint['configs']['macro_vae'])
macro_vae.load_state_dict(checkpoint['macro_vae'])

transition_model = TransitionTransformer(checkpoint['configs']['transition'])
transition_model.load_state_dict(checkpoint['transition_model'])

trajectory_model = LoanTrajectoryModel(checkpoint['configs']['trajectory'])
trajectory_model.load_state_dict(checkpoint['trajectory_model'])

Step 2: Define Scenarios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Standard regulatory scenarios
SCENARIOS = {
    0: 'Baseline',
    1: 'Adverse',
    2: 'Severely Adverse',
    3: 'Stagflation',
}

# Custom scenario: rapid rate hike
custom_scenario = {
    'gdp_growth_yoy': -0.02,        # Mild recession
    'unemployment_rate': 0.06,       # Moderate unemployment
    'inflation_rate': 0.08,          # High inflation
    'policy_rate': 0.07,             # Aggressive rate hikes
    'credit_spread_hy': 600,         # Wide spreads
}

Step 3: Generate Macro Paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Generate paths for each scenario
macro_paths = {}

for scenario_id, name in SCENARIOS.items():
    paths = macro_vae.generate(
        scenario=scenario_id,
        seq_length=60,
        n_samples=1000
    )
    macro_paths[name] = paths
    print(f"{name}: Generated {len(paths)} paths")

# Visualize scenarios
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 2, figsize=(12, 8))
variables = ['gdp_growth_yoy', 'unemployment_rate',
             'credit_spread_hy', 'policy_rate']

for ax, var in zip(axes.flat, variables):
    for name, paths in macro_paths.items():
        mean_path = paths[var].mean(axis=0)
        ax.plot(mean_path, label=name)
    ax.set_title(var)
    ax.legend()

plt.tight_layout()
plt.savefig('scenario_comparison.pdf')

Step 4: Conditional Generation

1
2
3
4
5
6
7
8
9
10
11
# Condition on specific macro outcomes
conditioned_paths = macro_vae.generate_conditional(
    conditions={
        'gdp_growth_yoy': {'month': 12, 'value': -0.04},  # -4% GDP at month 12
        'unemployment_rate': {'month': 24, 'value': 0.10}, # 10% unemployment at month 24
    },
    seq_length=60,
    n_samples=500
)

print(f"Generated {len(conditioned_paths)} conditioned paths")

Step 5: Run Stress Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from privatecredit.data import LoanTapeGenerator

# Generate test portfolio
generator = LoanTapeGenerator(n_loans=5000, random_seed=123)
loans_df, _ = generator.generate_static_features()

# Run stress test for each scenario
stress_results = {}

for name, paths in macro_paths.items():
    # Get cohort transitions
    cohort_transitions = transition_model.predict(
        cohort_features=loans_df,
        macro_paths=paths
    )

    # Generate loan trajectories
    trajectories = trajectory_model.generate(
        loan_features=loans_df,
        cohort_transitions=cohort_transitions,
        macro_paths=paths,
        n_samples=1000
    )

    # Calculate losses
    losses = trajectories['loss'].sum(dim=-1) / loans_df['original_balance'].sum()

    stress_results[name] = {
        'mean_loss': losses.mean().item(),
        'var_99': losses.quantile(0.99).item(),
        'max_loss': losses.max().item(),
    }

    print(f"{name}: Mean Loss = {stress_results[name]['mean_loss']:.2%}")

Step 6: Sensitivity Analysis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Analyze sensitivity to individual macro shocks
sensitivities = {}

base_scenario = macro_paths['Baseline'].mean(axis=0)
shock_size = 0.01  # 1% shock

for var in ['gdp_growth_yoy', 'unemployment_rate', 'credit_spread_hy']:
    # Apply shock
    shocked_scenario = base_scenario.copy()
    shocked_scenario[var] += shock_size

    # Recompute losses
    shocked_transitions = transition_model.predict(
        cohort_features=loans_df,
        macro_paths=shocked_scenario.unsqueeze(0)
    )

    shocked_trajectories = trajectory_model.generate(
        loan_features=loans_df,
        cohort_transitions=shocked_transitions,
        macro_paths=shocked_scenario.unsqueeze(0),
        n_samples=500
    )

    shocked_loss = shocked_trajectories['loss'].sum() / loans_df['original_balance'].sum()
    base_loss = stress_results['Baseline']['mean_loss']

    sensitivities[var] = (shocked_loss - base_loss) / shock_size
    print(f"dLoss/d{var} = {sensitivities[var]:.4f}")

Step 7: Reverse Stress Testing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from privatecredit.evaluation import reverse_stress_test

# Find scenarios that produce specific loss levels
target_losses = [0.05, 0.10, 0.15]  # 5%, 10%, 15%

for target in target_losses:
    scenario = reverse_stress_test(
        macro_vae=macro_vae,
        transition_model=transition_model,
        trajectory_model=trajectory_model,
        loans_df=loans_df,
        target_loss=target,
        n_iterations=100
    )

    print(f"\nScenario for {target:.0%} loss:")
    print(f"  GDP: {scenario['gdp_growth_yoy']:.1%}")
    print(f"  Unemployment: {scenario['unemployment_rate']:.1%}")
    print(f"  HY Spread: {scenario['credit_spread_hy']:.0f} bps")

Step 8: Generate Report

1
2
3
4
5
6
7
8
9
10
11
from privatecredit.evaluation import StressTestReport

report = StressTestReport(
    portfolio=loans_df,
    scenarios=SCENARIOS,
    results=stress_results,
    sensitivities=sensitivities
)

report.generate_pdf('stress_test_report.pdf')
print("Report generated: stress_test_report.pdf")

Summary Table

Scenario Expected Loss VaR 99% Max Loss
Baseline 2.0% 3.5% 5.0%
Adverse 4.5% 7.0% 10.0%
Severely Adverse 8.0% 12.0% 18.0%
Stagflation 6.0% 9.5% 14.0%

Next Steps