"""Rank all top strategies head-to-head on the same 10-year PIT-safe data.""" from __future__ import annotations import sys sys.path.insert(0, ".") import numpy as np import pandas as pd import data_manager import metrics import universe_history as uh from main import backtest from trader import STRATEGY_REGISTRY, ETF_STRATEGY_UNIVERSES, MIXED_STRATEGY_EXTRA_TICKERS, filter_tradable_tickers from universe import UNIVERSES YEARS = 10 CAPITAL = 100_000 TX_COST = 0.001 FIXED_FEE = 2.0 # Only the most promising strategies — skip redundant freq variants CANDIDATES = [ # ETF tactical allocation "trend_rider_v7", "trend_rider_v7_vt24", "trend_rider_v7_vt32", "trend_rider_v3_vt28", "trend_rider_v3_vt32", "trend_rider_v5_us", "trend_rider_v5_panic", "trend_rider_v3_us", # V6 hybrids (stock + regime) "trend_rider_v6", "trend_rider_v6_top10", # Stock pickers "recovery_mom_top10", "recovery_mom_top20", "trend_following", "fc_rec_mfilt_deep_upvol_monthly", "fc_rec_mfilt_deep_upvol_daily", # Ensembles "ensemble_alpha_top10", "sharpe_boosted_ensemble_top8", "risk_managed_ensemble_top10", "enhanced_factor_combo_top10", ] def main(): print("=" * 95) print(" COMPREHENSIVE STRATEGY RANKING (10y PIT-safe)") print("=" * 95) # Load S&P 500 + PIT print("\n[1] Loading data...") universe = UNIVERSES["us"] tickers = universe["fetch"]() pit_intervals = uh.load_sp500_history() hist_tickers = uh.all_tickers_ever(pit_intervals) # Collect all ETF tickers needed all_etf = set() for name in CANDIDATES: base = name.removeprefix("sim_") if base in ETF_STRATEGY_UNIVERSES: all_etf.update(ETF_STRATEGY_UNIVERSES[base]) if base in MIXED_STRATEGY_EXTRA_TICKERS: all_etf.update(MIXED_STRATEGY_EXTRA_TICKERS[base]) all_etf.update(["SPY", "GLD", "DBC", "SHY", "TQQQ", "UPRO", "TLT", "IEF"]) all_tickers = sorted(set(tickers + hist_tickers + list(all_etf))) print(f" {len(all_tickers)} tickers to download...") stock_data = data_manager.update("us", all_tickers, with_open=False) if isinstance(stock_data, tuple): stock_data = stock_data[0] cutoff = stock_data.index[-1] - pd.DateOffset(years=YEARS) stock_data = stock_data[stock_data.index >= cutoff] stock_data = uh.mask_prices(stock_data, pit_intervals) stock_tickers = [t for t in stock_data.columns if t not in all_etf and stock_data[t].notna().any()] # Also load pure ETF panel (for pure-ETF strategies that use separate data) etf_list = sorted(all_etf) etf_data = data_manager.update("etfs", etf_list, with_open=False) if isinstance(etf_data, tuple): etf_data = etf_data[0] etf_cutoff = etf_data.index[-1] - pd.DateOffset(years=YEARS) etf_data = etf_data[etf_data.index >= etf_cutoff] print(f" Stocks: {len(stock_tickers)}, ETFs: {len(etf_list)}") print(f" Period: {stock_data.index[0].date()} → {stock_data.index[-1].date()}") # Run strategies print("\n[2] Running strategies...") results: list[tuple[str, dict]] = [] for name in CANDIDATES: if name not in STRATEGY_REGISTRY: print(f" SKIP {name} (not in registry)") continue base = name.removeprefix("sim_") print(f" {name}...", end=" ", flush=True) try: if base in ETF_STRATEGY_UNIVERSES: # Pure ETF strategy etf_tickers = ETF_STRATEGY_UNIVERSES[base] tradable = [t for t in etf_tickers if t in etf_data.columns] strategy = STRATEGY_REGISTRY[name]() eq = backtest(strategy, etf_data[tradable], initial_capital=CAPITAL, transaction_cost=TX_COST, fixed_fee=FIXED_FEE) elif base in MIXED_STRATEGY_EXTRA_TICKERS: # Mixed: stocks + ETFs in one panel extra = MIXED_STRATEGY_EXTRA_TICKERS[base] panel_cols = stock_tickers + [t for t in extra if t in stock_data.columns] panel = stock_data[[c for c in panel_cols if c in stock_data.columns]] strategy = STRATEGY_REGISTRY[name]() eq = backtest(strategy, panel, initial_capital=CAPITAL, transaction_cost=TX_COST, fixed_fee=FIXED_FEE) else: # Pure stock strategy strategy = STRATEGY_REGISTRY[name](top_n=10) eq = backtest(strategy, stock_data[stock_tickers], initial_capital=CAPITAL, transaction_cost=TX_COST, fixed_fee=FIXED_FEE) m = metrics.raw_summary(eq) results.append((name, m)) print(f"Ann={m['annualizedReturn']*100:.1f}%") except Exception as e: print(f"FAILED: {e}") # SPY benchmark spy = stock_data["SPY"].dropna() spy_eq = (spy / spy.iloc[0]) * CAPITAL spy_m = metrics.raw_summary(spy_eq) results.append(("SPY (benchmark)", spy_m)) # Sort by annualized return results.sort(key=lambda x: x[1]["annualizedReturn"], reverse=True) print(f"\n[3] Ranking ({YEARS}y, ${CAPITAL:,.0f}, tx={TX_COST*100:.1f}bps + ${FIXED_FEE:.0f}/trade)") print("=" * 110) print(f"{'#':<4} {'Strategy':<40} {'Ann%':>8} {'Vol%':>8} {'Sharpe':>8} {'Sortino':>8} {'MaxDD%':>8} {'Calmar':>8}") print("-" * 110) for i, (name, m) in enumerate(results, 1): print(f"{i:<4} {name:<40} " f"{m['annualizedReturn']*100:>7.1f}% " f"{m['annualizedVolatility']*100:>7.1f}% " f"{m['sharpeRatio']:>8.2f} " f"{m['sortinoRatio']:>8.2f} " f"{m['maxDrawdown']*100:>7.1f}% " f"{m['calmarRatio']:>8.2f}") print("=" * 110) if __name__ == "__main__": main()