BrianIsaac's picture
feat: implement P1 features and production infrastructure
76897aa
"""Predefined stress test scenarios for portfolio analysis.
This module contains historical crisis scenarios and custom scenario definitions
for stress testing portfolios against extreme market conditions.
"""
from typing import Dict, List, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass
class StressScenario:
"""Definition of a stress test scenario.
Attributes:
name: Human-readable scenario name
description: Detailed description of the scenario
asset_class_shocks: Dictionary mapping asset classes to percentage shocks
duration_days: Estimated duration of the stress event
recovery_days: Estimated time to recover to pre-crisis levels
correlation_multiplier: Factor to multiply correlations (>1 = increased correlation)
start_date: Historical start date (if applicable)
end_date: Historical end date (if applicable)
"""
name: str
description: str
asset_class_shocks: Dict[str, float]
duration_days: int
recovery_days: Optional[int] = None
correlation_multiplier: float = 1.0
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
def get_shock_for_asset(self, asset_type: str) -> float:
"""Get shock percentage for a specific asset type.
Args:
asset_type: Asset type identifier
Returns:
Shock percentage (negative for losses)
"""
# Default to equity shock if asset type not specified
return self.asset_class_shocks.get(asset_type, self.asset_class_shocks.get('equity', -20.0))
# Historical Crisis Scenarios
FINANCIAL_CRISIS_2008 = StressScenario(
name="2008 Financial Crisis",
description="Global financial crisis triggered by subprime mortgage collapse. "
"Equity markets declined 54% from peak to trough, whilst bonds provided positive returns.",
asset_class_shocks={
'equity': -54.0, # S&P 500 peak to trough
'bond': 6.0, # Treasury bonds rallied
'reit': -68.0, # REITs severely impacted
'commodity': -45.0, # Commodities declined
'cash': 2.0, # Cash preserved value
},
duration_days=517, # October 2007 to March 2009
recovery_days=1825, # ~5 years to new highs
correlation_multiplier=1.8, # Correlations spike during crisis
start_date=datetime(2007, 10, 9),
end_date=datetime(2009, 3, 9)
)
COVID_PANDEMIC_2020 = StressScenario(
name="COVID-19 Pandemic (2020)",
description="Rapid market decline due to COVID-19 pandemic and global lockdowns. "
"S&P 500 fell 34% in just 23 trading days, followed by unprecedented recovery.",
asset_class_shocks={
'equity': -34.0, # S&P 500 peak to trough
'bond': 8.0, # Flight to safety
'reit': -40.0, # Real estate impacted
'commodity': -50.0, # Oil crash
'cash': 0.0,
},
duration_days=33, # February 19 to March 23, 2020
recovery_days=150, # ~5 months to new highs (V-shaped recovery)
correlation_multiplier=2.0, # Extreme correlation spike
start_date=datetime(2020, 2, 19),
end_date=datetime(2020, 3, 23)
)
DOTCOM_BUBBLE_2000 = StressScenario(
name="Dot-com Bubble (2000-2002)",
description="Technology stock bubble burst. NASDAQ declined 78% from peak to trough, "
"whilst broader market fell more moderately.",
asset_class_shocks={
'equity': -49.0, # S&P 500 peak to trough
'tech': -78.0, # NASDAQ peak to trough
'bond': 15.0, # Bonds rallied
'reit': -15.0,
'commodity': -30.0,
'cash': 3.0,
},
duration_days=929, # March 2000 to October 2002
recovery_days=1825, # ~5 years to break even
correlation_multiplier=1.3,
start_date=datetime(2000, 3, 24),
end_date=datetime(2002, 10, 9)
)
EUROPEAN_DEBT_CRISIS_2011 = StressScenario(
name="European Debt Crisis (2011)",
description="Eurozone sovereign debt crisis causing market volatility and flight to quality.",
asset_class_shocks={
'equity': -19.4, # S&P 500 decline
'bond': 9.0, # US Treasuries rallied
'european_equity': -35.0, # European stocks hit harder
'commodity': -20.0,
'cash': 0.0,
},
duration_days=157, # May to October 2011
recovery_days=365,
correlation_multiplier=1.5,
start_date=datetime(2011, 5, 2),
end_date=datetime(2011, 10, 3)
)
CHINA_DEVALUATION_2015 = StressScenario(
name="China Devaluation (2015)",
description="China's surprise currency devaluation triggered global market selloff.",
asset_class_shocks={
'equity': -12.4, # S&P 500 correction
'emerging_markets': -25.0, # EM hit harder
'commodity': -30.0, # Commodities declined sharply
'bond': 2.0,
'cash': 0.0,
},
duration_days=98, # August to November 2015
recovery_days=180,
correlation_multiplier=1.6,
start_date=datetime(2015, 8, 10),
end_date=datetime(2015, 11, 3)
)
INFLATION_SHOCK_2022 = StressScenario(
name="Inflation Shock (2022)",
description="Aggressive interest rate hiking to combat inflation. Both stocks and bonds declined together.",
asset_class_shocks={
'equity': -25.4, # S&P 500 bear market
'bond': -13.0, # Worst bond year since 1788
'tech': -33.0, # Growth stocks hit harder
'reit': -28.0,
'commodity': 15.0, # Commodities benefited
'cash': 3.0,
},
duration_days=282, # January to October 2022
recovery_days=425,
correlation_multiplier=0.8, # Stock-bond correlation broke down
start_date=datetime(2022, 1, 3),
end_date=datetime(2022, 10, 12)
)
# Hypothetical Severe Scenarios
SEVERE_RECESSION = StressScenario(
name="Severe Recession (Hypothetical)",
description="Hypothetical severe economic recession worse than historical averages.",
asset_class_shocks={
'equity': -40.0,
'bond': 10.0,
'reit': -50.0,
'commodity': -35.0,
'cash': 1.0,
},
duration_days=365,
recovery_days=1095, # 3 years
correlation_multiplier=1.7
)
STAGFLATION = StressScenario(
name="Stagflation (Hypothetical)",
description="Hypothetical stagflation scenario with high inflation and low growth. "
"Similar to 1970s environment.",
asset_class_shocks={
'equity': -25.0,
'bond': -15.0, # Bonds suffer in stagflation
'reit': -30.0,
'commodity': 40.0, # Commodities thrive
'cash': -5.0, # Real returns negative
},
duration_days=730, # 2 years
recovery_days=1825, # 5 years
correlation_multiplier=0.9
)
FLASH_CRASH = StressScenario(
name="Flash Crash (Hypothetical)",
description="Sudden, sharp market decline due to liquidity crisis or technical failure.",
asset_class_shocks={
'equity': -20.0,
'bond': 5.0,
'reit': -25.0,
'commodity': -15.0,
'cash': 0.0,
},
duration_days=1, # Single day event
recovery_days=30, # Quick recovery
correlation_multiplier=2.5 # Extreme correlation
)
# Aggregate all predefined scenarios
STRESS_SCENARIOS: Dict[str, StressScenario] = {
'2008_financial_crisis': FINANCIAL_CRISIS_2008,
'covid_2020': COVID_PANDEMIC_2020,
'dotcom_bubble': DOTCOM_BUBBLE_2000,
'european_debt_2011': EUROPEAN_DEBT_CRISIS_2011,
'china_2015': CHINA_DEVALUATION_2015,
'inflation_2022': INFLATION_SHOCK_2022,
'severe_recession': SEVERE_RECESSION,
'stagflation': STAGFLATION,
'flash_crash': FLASH_CRASH,
}
def create_custom_scenario(
name: str,
description: str,
equity_shock: float,
bond_shock: float = 0.0,
duration_days: int = 90,
correlation_multiplier: float = 1.0
) -> StressScenario:
"""Create a custom stress scenario.
Args:
name: Scenario name
description: Scenario description
equity_shock: Equity return shock (percentage)
bond_shock: Bond return shock (percentage)
duration_days: Duration of stress event
correlation_multiplier: Factor to multiply correlations
Returns:
Custom StressScenario instance
"""
return StressScenario(
name=name,
description=description,
asset_class_shocks={
'equity': equity_shock,
'bond': bond_shock,
'reit': equity_shock * 1.2, # REITs typically more volatile
'commodity': equity_shock * 0.8,
'cash': 0.0,
},
duration_days=duration_days,
correlation_multiplier=correlation_multiplier
)
def get_scenario_names() -> List[str]:
"""Get list of all available scenario names.
Returns:
List of scenario identifiers
"""
return list(STRESS_SCENARIOS.keys())
def get_scenario(scenario_id: str) -> Optional[StressScenario]:
"""Get scenario by identifier.
Args:
scenario_id: Scenario identifier
Returns:
StressScenario or None if not found
"""
return STRESS_SCENARIOS.get(scenario_id)