"""GIUDICE DEI CONTENDER — valuta un segnale candidato a livello PORTAFOGLIO vs TP01. Per ogni (tf, sigfile): costruisce il BOOK 50/50 BTC+ETH del candidato (causale, netto fee), e applica il gauntlet STRETTO vs TP01: - standalone: FULL Sh/DD, HOLD-OUT 2025-26 Sh/ret/DD, breadth per-anno (% anni positivi, rossi consecutivi), correlazione a TP01; - contributo al portafoglio: TP01-solo vs TP01+candidato a pesi 0.2/0.3/0.5 (Δ FULL e Δ HOLD). VERDETTO WINNER se: (A) batte TP01 standalone (book FULL Sh>1.30, hold-out Sh>~0.25, breadth ok), OPPURE (B) diversificatore robusto (corr bassa, alza il portafoglio su FULL E hold-out, breadth ok). uv run python scripts/portfolio/verify_contender.py 1d /tmp/beat_sig_0.py 12h /tmp/beat_sig_10.py ... """ from __future__ import annotations import sys import importlib.util from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) import numpy as np import pandas as pd from scripts.analysis.research_lab import load_tf, _net_series from src.portfolio.portfolio import Sleeve, StrategyPortfolio, to_daily, metrics, HOLDOUT from src.portfolio.sleeves import tp01_sleeve TP01_FULL_SH = 1.30 TP01_HOLD_SH = 0.31 def load_signal(path): spec = importlib.util.spec_from_file_location("csig_" + Path(path).stem, path) m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) return m.signal def book_perbar(signal, tf) -> pd.Series: s = {} for a in ("BTC", "ETH"): df = load_tf(a, tf) net, _, _, _ = _net_series(df, np.asarray(signal(df, a, tf), float)) s[a] = pd.Series(net, index=pd.to_datetime(df["timestamp"], unit="ms", utc=True)) J = pd.concat(s, axis=1, join="inner").fillna(0.0) return pd.Series(0.5 * J["BTC"].values + 0.5 * J["ETH"].values, index=J.index) def breadth(daily): pre = daily[daily.index < HOLDOUT] yr = [float((1 + g).prod() - 1) for _, g in pre.groupby(pre.index.year)] consec = mx = 0 for v in yr: consec = consec + 1 if v < 0 else 0; mx = max(mx, consec) return (sum(v > 0 for v in yr) / len(yr) if yr else 0.0), mx, yr def main(): args = sys.argv[1:] pairs = [(args[i], args[i + 1]) for i in range(0, len(args) - 1, 2)] tp = tp01_sleeve(1.0) tp_daily = tp.daily() base = StrategyPortfolio([tp01_sleeve(1.0)]).backtest() print("=" * 100) print(f" GIUDICE CONTENDER vs TP01 (book FULL Sh {base['full']['sharpe']:.2f} / HOLD {base['holdout']['sharpe']:.2f})") print("=" * 100) winners = [] for tf, sig in pairs: name = Path(sig).stem try: signal = load_signal(sig) pb = book_perbar(signal, tf) d = to_daily(pb) except Exception as e: print(f"\n {name} ({tf}): ERRORE {type(e).__name__}: {str(e)[:80]}"); continue f = metrics(d); h = metrics(d[d.index >= HOLDOUT]) J = pd.concat({"tp": tp_daily, "x": d}, axis=1, join="inner").dropna() corr = float(J["tp"].corr(J["x"])) if len(J) > 2 else float("nan") pct, consec, yr = breadth(d) print(f"\n {name} ({tf}) BOOK 50/50") print(f" standalone: FULL Sh {f['sharpe']:>5.2f} DD {f['maxdd']*100:>4.1f}% | HOLD Sh {h['sharpe']:>5.2f} ret {h['ret']*100:>+6.1f}% DD {h['maxdd']*100:>4.1f}%" f" | anni+ {pct*100:>3.0f}% rossi-consec {consec} | corr_TP01 {corr:+.2f} | turn n/a") # contributo al portafoglio contrib = [] for w in (0.2, 0.3, 0.5): sl = Sleeve(name, w, lambda pb=pb: pb) bt = StrategyPortfolio([tp01_sleeve(1 - w), sl]).backtest() dF = bt["full"]["sharpe"] - base["full"]["sharpe"] dH = bt["holdout"]["sharpe"] - base["holdout"]["sharpe"] contrib.append((w, bt["full"]["sharpe"], dF, bt["holdout"]["sharpe"], dH)) print(f" +TP01 w{w:.0%}: FULL {bt['full']['sharpe']:.2f} ({dF:+.2f}) | HOLD {bt['holdout']['sharpe']:.2f} ({dH:+.2f})") breadth_ok = pct >= 0.6 and consec <= 1 standalone_beats = f["sharpe"] > TP01_FULL_SH and h["sharpe"] > 0.25 and breadth_ok # diversificatore: corr<0.5, migliora FULL E hold del portafoglio ad almeno un peso, breadth ok improves = any(dF > 0.05 and dH > 0.0 for _, _, dF, _, dH in contrib) diversifier = (not np.isnan(corr) and corr < 0.5) and improves and breadth_ok verdict = "WINNER-standalone" if standalone_beats else ("WINNER-diversifier" if diversifier else "no") print(f" -> {verdict} (breadth_ok={breadth_ok}, standalone_beats={standalone_beats}, diversifier={diversifier})") if verdict.startswith("WINNER"): winners.append((name, tf, verdict)) print("\n" + "=" * 100) print(f" WINNERS: {len(winners)}") for n, tf, v in winners: print(f" {n} ({tf}): {v}") if not winners: print(" nessuno batte TP01 con criterio onesto -> serve un'altra ondata.") if __name__ == "__main__": main()