Premchan369 commited on
Commit
7d67e7a
·
verified ·
1 Parent(s): 55ccb64

Upload stress_test.py

Browse files
Files changed (1) hide show
  1. stress_test.py +160 -0
stress_test.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Stress Testing Engine - Simulate extreme market scenarios."""
2
+ import numpy as np
3
+ import pandas as pd
4
+ from typing import Dict, List, Optional
5
+ import warnings
6
+ warnings.filterwarnings('ignore')
7
+
8
+
9
+ class StressTestEngine:
10
+ """Simulate portfolio performance under extreme scenarios."""
11
+
12
+ def __init__(self):
13
+ self.scenarios = {
14
+ 'crisis_2008': {
15
+ 'name': '2008 Financial Crisis',
16
+ 'returns_shock': -0.05,
17
+ 'vol_shock': 3.0,
18
+ 'correlation_shock': 2.0,
19
+ 'duration': 60,
20
+ 'description': 'Lehman collapse + credit freeze'
21
+ },
22
+ 'covid_crash': {
23
+ 'name': 'COVID-19 Crash',
24
+ 'returns_shock': -0.04,
25
+ 'vol_shock': 4.0,
26
+ 'correlation_shock': 1.5,
27
+ 'duration': 30,
28
+ 'description': 'Pandemic-driven market panic'
29
+ },
30
+ 'rate_hike': {
31
+ 'name': 'Aggressive Rate Hike',
32
+ 'returns_shock': -0.02,
33
+ 'vol_shock': 1.5,
34
+ 'correlation_shock': 1.2,
35
+ 'duration': 90,
36
+ 'description': 'Central bank tightening cycle'
37
+ },
38
+ 'vol_spike': {
39
+ 'name': 'Volatility Spike',
40
+ 'returns_shock': -0.01,
41
+ 'vol_shock': 6.0,
42
+ 'correlation_shock': 1.8,
43
+ 'duration': 15,
44
+ 'description': 'VIX explosion event'
45
+ },
46
+ 'flash_crash': {
47
+ 'name': 'Flash Crash',
48
+ 'returns_shock': -0.03,
49
+ 'vol_shock': 5.0,
50
+ 'correlation_shock': 1.0,
51
+ 'duration': 5,
52
+ 'description': 'Algorithmic trading cascade'
53
+ }
54
+ }
55
+ self.results = []
56
+
57
+ def run_scenario(self,
58
+ portfolio: Dict[str, float],
59
+ base_returns: pd.DataFrame,
60
+ scenario_name: str,
61
+ initial_value: float = 1_000_000) -> Dict:
62
+ """Run a stress test scenario."""
63
+ scenario = self.scenarios.get(scenario_name)
64
+ if scenario is None:
65
+ raise ValueError(f"Unknown scenario: {scenario_name}")
66
+
67
+ # Extract base parameters
68
+ mu = base_returns.mean().values
69
+ sigma = base_returns.std().values
70
+ corr = base_returns.corr().values
71
+
72
+ # Apply shocks
73
+ mu_shocked = mu + scenario['returns_shock']
74
+ sigma_shocked = sigma * scenario['vol_shock']
75
+
76
+ # Shock correlations
77
+ n = len(mu)
78
+ corr_shocked = corr * scenario['correlation_shock']
79
+ np.fill_diagonal(corr_shocked, 1.0)
80
+ corr_shocked = np.clip(corr_shocked, -1, 1)
81
+
82
+ # Build shocked covariance
83
+ D = np.diag(sigma_shocked)
84
+ cov_shocked = D @ corr_shocked @ D
85
+
86
+ # Simulate returns
87
+ np.random.seed(42)
88
+ n_days = scenario['duration']
89
+ shocked_returns = np.random.multivariate_normal(mu_shocked / 252, cov_shocked / 252, n_days)
90
+
91
+ # Portfolio simulation
92
+ weights = np.array([portfolio.get(c, 0) for c in base_returns.columns])
93
+ weights /= weights.sum()
94
+
95
+ port_returns = shocked_returns @ weights
96
+ equity = initial_value * np.cumprod(1 + port_returns)
97
+
98
+ # Metrics
99
+ total_return = (equity[-1] / initial_value) - 1
100
+ max_idx = np.argmax(np.maximum.accumulate(equity) - equity)
101
+ max_drawdown = (np.min(equity) / np.maximum.accumulate(equity)[np.argmin(equity)]) - 1
102
+
103
+ peak_equity = np.max(equity)
104
+ recovery_days = (np.argmax(equity >= peak_equity * 0.95) if np.any(equity >= peak_equity * 0.95) else n_days)
105
+
106
+ result = {
107
+ 'scenario': scenario['name'],
108
+ 'total_return': total_return,
109
+ 'max_drawdown': max_drawdown,
110
+ 'min_equity': np.min(equity),
111
+ 'recovery_days': recovery_days,
112
+ 'sharpe': np.mean(port_returns) / np.std(port_returns) * np.sqrt(252) if np.std(port_returns) > 0 else 0,
113
+ 'var_99': -np.percentile(port_returns, 1),
114
+ 'cvar_99': -port_returns[port_returns <= np.percentile(port_returns, 1)].mean(),
115
+ 'daily_vol': np.std(port_returns) * np.sqrt(252)
116
+ }
117
+
118
+ self.results.append(result)
119
+ return result
120
+
121
+ def run_all_scenarios(self, portfolio: Dict[str, float], base_returns: pd.DataFrame) -> pd.DataFrame:
122
+ """Run all stress test scenarios."""
123
+ results = []
124
+ for scenario_name in self.scenarios:
125
+ result = self.run_scenario(portfolio, base_returns, scenario_name)
126
+ results.append(result)
127
+ return pd.DataFrame(results)
128
+
129
+ def survival_analysis(self, portfolio: Dict[str, float], base_returns: pd.DataFrame, n_simulations: int = 1000) -> Dict:
130
+ """Monte Carlo survival analysis."""
131
+ mu = base_returns.mean().values
132
+ cov = base_returns.cov().values * 252
133
+ weights = np.array([portfolio.get(c, 0) for c in base_returns.columns])
134
+ weights /= weights.sum()
135
+
136
+ port_mu = weights @ mu
137
+ port_var = weights @ cov @ weights
138
+ port_sigma = np.sqrt(port_var)
139
+
140
+ np.random.seed(42)
141
+ years = 5
142
+ daily_returns = np.random.normal(port_mu / 252, port_sigma / np.sqrt(252), (n_simulations, years * 252))
143
+
144
+ equity_curves = np.cumprod(1 + daily_returns, axis=1)
145
+
146
+ final_values = equity_curves[:, -1]
147
+ var_95_final = -np.percentile(final_values, 5)
148
+ prob_loss = np.mean(final_values < 1.0)
149
+ prob_ruin = np.mean(np.min(equity_curves, axis=1) < 0.5)
150
+
151
+ return {
152
+ 'expected_final_value': np.mean(final_values),
153
+ 'median_final_value': np.median(final_values),
154
+ 'worst_case_5th': np.percentile(final_values, 5),
155
+ 'best_case_95th': np.percentile(final_values, 95),
156
+ 'probability_of_loss': prob_loss,
157
+ 'probability_of_ruin': prob_ruin,
158
+ 'n_simulations': n_simulations,
159
+ 'horizon_years': years
160
+ }