Spaces:
Running
on
Zero
Running
on
Zero
| """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 | |
| 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) | |