Portfolio Stress Testing System Development
Stress testing is a verification of portfolio performance under extreme scenarios that go beyond normal statistical risk. VaR shows "normal" risk. Stress testing shows what happens during real catastrophes.
Types of Stress Tests
Historical stress scenarios: apply historically occurred crises to the current portfolio.
Hypothetical scenarios: model possible but unrealized events.
Sensitivity analysis: change one parameter (price, volatility, correlation) and observe P&L.
Reverse stress testing: find scenarios where the portfolio loses a critical amount.
Historical Scenarios for Crypto Market
CRYPTO_STRESS_SCENARIOS = {
'covid_march_2020': {
'BTC': -0.50, # -50% in a week
'ETH': -0.60,
'DEFAULT': -0.55,
'USDT': 0.0,
'duration_days': 7
},
'china_ban_may_2021': {
'BTC': -0.53,
'ETH': -0.60,
'DEFAULT': -0.65,
'duration_days': 30
},
'luna_terra_collapse_2022': {
'LUNA': -0.99,
'UST': -0.95,
'BTC': -0.30,
'ETH': -0.35,
'DEFAULT': -0.45,
'USDC': 0.0,
'duration_days': 7
},
'ftx_collapse_2022': {
'FTT': -0.97,
'SOL': -0.60,
'BTC': -0.25,
'ETH': -0.28,
'DEFAULT': -0.35,
'duration_days': 14
},
'full_crypto_winter': {
'BTC': -0.80, # 2022
'ETH': -0.82,
'DEFAULT': -0.90,
'duration_days': 365
}
}
Stress Testing Implementation
class StressTester:
def __init__(self, scenarios):
self.scenarios = scenarios
def run_scenario(self, positions, current_prices, scenario_name):
scenario = self.scenarios[scenario_name]
total_pnl = 0
position_results = {}
for symbol, qty in positions.items():
current_price = current_prices.get(symbol, 0)
shock = scenario.get(symbol, scenario.get('DEFAULT', 0))
stressed_price = current_price * (1 + shock)
pnl = qty * (stressed_price - current_price)
position_results[symbol] = {
'shock': shock,
'pnl': pnl,
'current_value': qty * current_price,
'stressed_value': qty * stressed_price
}
total_pnl += pnl
return {
'scenario': scenario_name,
'total_pnl': total_pnl,
'position_results': position_results
}
def run_all_scenarios(self, positions, current_prices):
results = {}
for scenario_name in self.scenarios:
results[scenario_name] = self.run_scenario(
positions, current_prices, scenario_name
)
# Sort by worst case
return sorted(results.values(), key=lambda x: x['total_pnl'])
Sensitivity Analysis
Sensitivity analysis: how does P&L change when one factor changes?
def sensitivity_analysis(positions, current_prices, factor='price',
range_pct=(-0.50, 0.50), steps=20):
"""
Change all asset prices by X% and observe P&L
"""
results = []
for shock in np.linspace(range_pct[0], range_pct[1], steps):
total_pnl = 0
for symbol, qty in positions.items():
price = current_prices[symbol]
total_pnl += qty * price * shock
results.append({'shock_pct': shock * 100, 'pnl': total_pnl})
return results
Correlation stress: during crisis all assets correlate more strongly. What if correlation of all assets = 0.95?
def correlation_stress_test(portfolio_returns_history, stressed_correlation=0.95):
cov_matrix = portfolio_returns_history.cov()
# Replace all off-diagonal elements with stressed_correlation
std_devs = np.sqrt(np.diag(cov_matrix))
stressed_cov = np.outer(std_devs, std_devs) * stressed_correlation
np.fill_diagonal(stressed_cov, np.diag(cov_matrix)) # keep variances
return stressed_cov
Reverse Stress Testing
Inverse task: find scenario where losses reach critical level (e.g., 20% of capital).
from scipy.optimize import minimize
def reverse_stress_test(positions, current_prices, target_loss,
max_shock_per_asset=0.99):
"""
Minimum total shock where losses = target_loss
"""
symbols = list(positions.keys())
current_values = [positions[s] * current_prices[s] for s in symbols]
def portfolio_loss(shocks):
return sum(v * s for v, s in zip(current_values, shocks))
constraints = [
{'type': 'eq', 'fun': lambda x: portfolio_loss(x) - (-target_loss)}
]
bounds = [(-max_shock_per_asset, 0) for _ in symbols]
# Minimize L2 norm of shocks (find smallest shock)
result = minimize(
lambda x: np.sum(x**2),
x0=np.full(len(symbols), -0.1),
constraints=constraints,
bounds=bounds
)
return dict(zip(symbols, result.x))
Correlations During Crisis
One key observation: during crisis correlations between assets rise sharply. Diversification "collapses" precisely when it's needed most.
Stress test "correlation crisis": replace covariance matrix with "stressed" version with high correlations and recalculate VaR.
Reporting
Stress Test Report — monthly or upon significant portfolio changes:
- Results table for all scenarios
- Worst case P&L in absolute values and % of capital
- Breakdown: which positions caused largest losses
- Sensitivity curve (P&L vs % shock graph)
- Reverse stress: at which shock critical loss is reached
Visualization: tornado chart (each position's contribution to stress loss), waterfall chart (loss accumulation by position), sensitivity curve chart.
Develop stress testing system with library of historical scenarios, sensitivity analysis, reverse stress testing and automatic report generation.







