Files
quant/research/sota_ranking.py
Gahow Wang 1f50253d13 research: extensive V7 optimization and V8 (TMF) evaluation
Research scripts exploring paths beyond V7+VT36:
- regime_stock_picker_eval: V3 regime + S&P 500 stock picking
- v7_parameter_sweep: VT range (20-48%) + adaptive PT variants
- v7_synthetic_leverage_eval: synthetic 2x/3x leveraged individual stocks
- v7_breakthrough_eval/fixed: ensemble, cross-market, alt regime engines
- v7_three_ideas_eval: TMF risk-off, PT entry reset, fast exit
- v7_trade_audit: full 10y trade log and alpha attribution
- sota_ranking: comprehensive cross-strategy ranking

Key findings:
- VT36 is optimal risk-return tradeoff (+7% vs VT28, Sharpe ~flat)
- PT30 is structural optimum for 3x ETFs (all adaptive variants worse)
- V8 (TMF risk-off) debunked: +5% was 1-day lookahead bias artifact
- V3 regime engine irreplaceable (all simplified alternatives fail)
- PT mechanism is dominant alpha source (+15.6pp ann, +0.58 Sharpe)

V8 strategy file kept for reference (not registered).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-21 20:57:34 +08:00

167 lines
5.9 KiB
Python

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