Files
vibe-invest/analyze_regime.py
zastian-dev 79816b9e2e Experiment with hourly timeframe-specific stops
- Added HOURLY_ATR_STOP_MULTIPLIER (1.8x) vs daily (3.5x)
- Added hourly-specific trail multipliers
- Strategy now uses timeframe field to select appropriate stops
- Tested multiple configurations on hourly:
  * 3.5x stops: -0.5% return, 45% max DD
  * 1.8x stops: -45% return, 53% max DD (worse)
  * Conservative regime (0.25x): -65% return, 67% max DD (terrible)
- Conclusion: Hourly doesn't work with this strategy
- Daily with relaxed regime remains best: +17.4% over 5yr, 24% max DD

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-13 19:20:01 +00:00

120 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Analyze SPY regime detection during backtest periods."""
import pandas as pd
import yfinance as yf
from datetime import datetime, timedelta
def calculate_ema(series, period):
"""Calculate EMA using pandas."""
return series.ewm(span=period, adjust=False).mean()
def determine_regime(price, ema50, ema200):
"""Replicate the Rust regime detection logic."""
if pd.isna(price) or pd.isna(ema50) or pd.isna(ema200):
return "Caution"
# Bear: price below 200 EMA AND 50 EMA below 200 EMA
if price < ema200 and ema50 < ema200:
return "Bear"
# Caution: price below 50 EMA
if price < ema50:
return "Caution"
# Bull: price above both, 50 above 200
if ema50 > ema200:
return "Bull"
# Edge case: price above both but 50 still below 200
return "Caution"
def analyze_period(start_date, end_date, period_name):
"""Analyze SPY regime for a given period."""
print(f"\n{'='*70}")
print(f"{period_name}: {start_date} to {end_date}")
print('='*70)
# Fetch SPY data with extra warmup for EMA-200
warmup_start = (datetime.strptime(start_date, '%Y-%m-%d') - timedelta(days=400)).strftime('%Y-%m-%d')
spy = yf.download('SPY', start=warmup_start, end=end_date, progress=False)
if spy.empty:
print(f"ERROR: No SPY data available for {period_name}")
return
# Calculate EMAs
spy['EMA50'] = calculate_ema(spy['Close'], 50)
spy['EMA200'] = calculate_ema(spy['Close'], 200)
# Determine regime for each day
spy['Regime'] = spy.apply(
lambda row: determine_regime(row['Close'], row['EMA50'], row['EMA200']),
axis=1
)
# Filter to actual trading period
trading_period = spy[start_date:end_date].copy()
if trading_period.empty:
print(f"ERROR: No trading data for {period_name}")
return
# Calculate SPY return
spy_start = trading_period['Close'].iloc[0]
spy_end = trading_period['Close'].iloc[-1]
spy_return = (spy_end - spy_start) / spy_start * 100
print(f"\nSPY Performance:")
print(f" Start: ${spy_start:.2f}")
print(f" End: ${spy_end:.2f}")
print(f" Return: {spy_return:+.2f}%")
# Count regime days
regime_counts = trading_period['Regime'].value_counts()
total_days = len(trading_period)
print(f"\nRegime Distribution ({total_days} trading days):")
for regime in ['Bull', 'Caution', 'Bear']:
count = regime_counts.get(regime, 0)
pct = count / total_days * 100
print(f" {regime:8s}: {count:4d} days ({pct:5.1f}%)")
# Show regime transitions
regime_changes = trading_period[trading_period['Regime'] != trading_period['Regime'].shift(1)]
if len(regime_changes) > 0:
print(f"\nRegime Transitions ({len(regime_changes)} total):")
for date, row in regime_changes.head(20).iterrows():
print(f" {date.strftime('%Y-%m-%d')}: {row['Regime']:8s} (SPY: ${row['Close']:.2f}, "
f"EMA50: ${row['EMA50']:.2f}, EMA200: ${row['EMA200']:.2f})")
if len(regime_changes) > 20:
print(f" ... and {len(regime_changes) - 20} more transitions")
# Identify problematic Bear periods during bull markets
bear_days = trading_period[trading_period['Regime'] == 'Bear']
if len(bear_days) > 0:
print(f"\n⚠️ WARNING: {len(bear_days)} days classified as BEAR:")
for date, row in bear_days.head(10).iterrows():
print(f" {date.strftime('%Y-%m-%d')}: SPY=${row['Close']:.2f}, "
f"EMA50=${row['EMA50']:.2f}, EMA200=${row['EMA200']:.2f}")
if len(bear_days) > 10:
print(f" ... and {len(bear_days) - 10} more Bear days")
# Show first and last months in detail
print(f"\nFirst Month Detail:")
first_month = trading_period.head(22)[['Close', 'EMA50', 'EMA200', 'Regime']]
for date, row in first_month.iterrows():
print(f" {date.strftime('%Y-%m-%d')}: {row['Regime']:8s} | "
f"SPY: ${row['Close']:7.2f} | EMA50: ${row['EMA50']:7.2f} | EMA200: ${row['EMA200']:7.2f}")
if __name__ == '__main__':
# Analyze 2023
analyze_period('2023-01-01', '2023-12-31', '2023 Backtest')
# Analyze 2024
analyze_period('2024-01-01', '2024-12-31', '2024 Backtest')
print(f"\n{'='*70}")
print("Analysis complete.")
print('='*70)