Skip to content

Regime Detection Demo

IMPORTANT: The current experiment uses volatility/VIX quantile regimes, NOT Hidden Markov Models. HMM-based detection is available as an optional extension but is not used in the reported results.

Overview

We'll explore: 1. Current implementation: Volatility/VIX quantile regimes 2. Regime feature engineering 3. Optional: HMM model training (not used in reported results) 4. Regime prediction and analysis 5. Regime visualization and interpretation

# Import required libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import date, timedelta
import warnings
warnings.filterwarnings('ignore')

# Import Cross-Asset Alpha Engine components
from cross_asset_alpha_engine.data import load_daily_bars, AssetUniverse
from cross_asset_alpha_engine.regimes import RegimeHMM, RegimeFeatureEngine
from cross_asset_alpha_engine.utils import setup_logger, plot_regime_overlay

# Setup
logger = setup_logger("regime_demo", console_output=True)
print("✅ All imports successful!")

1. Load Multi-Asset Data for Regime Detection

# Load regime detection symbols
universe = AssetUniverse()
regime_symbols = universe.get_market_regime_symbols()
print(f"Regime detection symbols: {regime_symbols}")

# Load data
end_date = date.today()
start_date = end_date - timedelta(days=365)  # 1 year of data

print(f"\nLoading data from {start_date} to {end_date}")

try:
    regime_data = load_daily_bars(regime_symbols, start_date, end_date, use_cache=True)
    if regime_data.empty:
        raise ValueError("No data returned")
    print(f"✅ Loaded {len(regime_data)} bars from API")
except Exception as e:
    print(f"⚠️ API error: {e}")
    print("📊 Creating sample regime data...")

    # Create sample multi-asset data
    dates = pd.date_range(start=start_date, end=end_date, freq='D')
    sample_data = []

    # Define different regime periods with distinct characteristics
    regime_periods = [
        (0, len(dates)//3, 'low_vol'),      # Low volatility regime
        (len(dates)//3, 2*len(dates)//3, 'high_vol'),  # High volatility regime  
        (2*len(dates)//3, len(dates), 'crisis')        # Crisis regime
    ]

    for symbol in regime_symbols:
        base_price = {'SPY': 400, 'QQQ': 300, 'IWM': 200, 'VIX': 20, 'TLT': 120, 'GLD': 180}.get(symbol, 100)
        prices = [base_price]

        for i in range(1, len(dates)):
            # Determine current regime
            current_regime = None
            for start_idx, end_idx, regime_type in regime_periods:
                if start_idx <= i < end_idx:
                    current_regime = regime_type
                    break

            # Set volatility based on regime
            if current_regime == 'low_vol':
                vol = 0.008 if symbol != 'VIX' else 0.15
            elif current_regime == 'high_vol':
                vol = 0.020 if symbol != 'VIX' else 0.25
            else:  # crisis
                vol = 0.035 if symbol != 'VIX' else 0.40

            # VIX tends to be negatively correlated with equities
            if symbol == 'VIX' and current_regime == 'crisis':
                daily_return = abs(np.random.randn()) * vol  # VIX spikes in crisis
            elif symbol == 'VIX':
                daily_return = np.random.randn() * vol
            else:
                daily_return = np.random.randn() * vol
                if current_regime == 'crisis':
                    daily_return -= 0.001  # Slight negative drift in crisis

            new_price = prices[-1] * (1 + daily_return)
            prices.append(max(new_price, 0.01))  # Ensure positive prices

        for i, (date_val, price) in enumerate(zip(dates, prices)):
            daily_return = np.random.randn() * 0.005
            open_price = price * (1 + np.random.randn() * 0.002)
            close_price = price
            high_price = max(open_price, close_price) * (1 + abs(np.random.randn()) * 0.005)
            low_price = min(open_price, close_price) * (1 - abs(np.random.randn()) * 0.005)

            sample_data.append({
                'symbol': symbol,
                'timestamp': date_val,
                'open': open_price,
                'high': high_price,
                'low': low_price,
                'close': close_price,
                'volume': np.random.randint(1000000, 50000000),
                'vwap': (open_price + high_price + low_price + close_price) / 4
            })

    regime_data = pd.DataFrame(sample_data)
    print(f"✅ Created sample regime dataset with {len(regime_data)} bars")

print(f"\nData shape: {regime_data.shape}")
print(f"Symbols: {regime_data['symbol'].unique()}")
print(f"Date range: {regime_data['timestamp'].min()} to {regime_data['timestamp'].max()}")