a5a61ac7e3
Espansione universo (su input utente "storico da cerbero"): il Cerbero MCP col token MAINNET serve Hyperliquid (230 perp REALI, storia nativa dal 2024). fetch_hyperliquid.py certifica 19 alt liquidi a 1d (flat 0%, cross-venue 4-9 bps vs Binance) -> data/raw/hl_*_1d.parquet. Abilita le strategie CROSS-SECTIONAL (impossibili a 2 asset). XS01 = cross-sectional momentum market-neutral (long 5 forti / short 5 deboli su ret 30g, ogni 10g, vol-target 20%). Validato onesto: plateau (config/k/subset), fee-robusto (0.3% RT), scorrelato a TP01 (-0.06), positivo OGNI anno 2024-26, meccanismo complementare (lavora nella dispersione quando TP01 e' in cash). Diverso dal regime-luck RV bocciato (19 asset, plateau, ogni anno+). Contributo al portafoglio (outer-join + pesi rinormalizzati per sleeve a date diverse): TP01-solo FULL 1.30 / HOLD 0.31 -> TP01 70% + XS01 30%: FULL 1.41 / HOLD 1.15, DD giu', ~ogni anno+. -> XS01 BATTE il portafoglio esistente: inserito in active_sleeves. Caveat (documentati): storia XS ~2.5 anni; STAT-MODE (book 19 gambe non eseguibile a 2k -> ~20k), sleeve diagnostico/forward-monitor. portfolio.combine ora outer-join+renorm. 12 test passano. Diario 2026-06-19-hyperliquid-xsec.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
110 lines
4.9 KiB
Python
110 lines
4.9 KiB
Python
"""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()
|