"""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)