"""GATE PORT06 — ETH/BTC pairs a 15m (origine: gioco "Blind Traders", vincitore #43). Domanda onesta sollevata dal gioco: la coppia ETH/BTC (gia' deployata in PR01 a 1h, config UNIV n=50 z_in=2.0 z_exit=0.75 max_bars=72) MIGLIORA se girata a 15m con la config trovata dal gioco (n=66 z_in=1.67 z_exit=1.0 max_bars=35), oppure e' solo una variante piu' veloce, correlata, dello STESSO spread? Metodo (engine di PRODUZIONE pairs_sim, NON il motore-giocattolo del gioco): [1] PARITA': pairs_sim ETH/BTC 1h UNIV (pos0.15 lev3) == sleeve canonico PR_ETHBTC. [2] CORRELAZIONE 1h vs 15m (rendimenti giornalieri): se ~1 e' ridondante. [3] STANDALONE 1h vs 15m (+ griglia robustezza n x z_in su 15m, + stress fee 2x). [4] GATE PORT06: baseline(1h) vs SWAP(15m) vs BLEND(0.5*1h+0.5*15m) per la sleeve ETH/BTC; promosso se vs baseline l'OOS Sharpe non peggiora E il DD scende (PORT06 e famiglia), come gli altri gate del progetto. uv run python scripts/analysis/pairs15m_port06_gate.py """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd PROJECT_ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(PROJECT_ROOT)) from scripts.analysis.combine_portfolio import port_returns, metrics, SPLIT, OOS_DATE, IDX from scripts.analysis.pairs_research import pairs_sim, OOS_FRAC from scripts.analysis.report_families import daily_from from scripts.portfolios._defs import PORTFOLIOS from src.portfolio import weighting as W POS, LEV = 0.15, 3.0 # config CANONICA (== build_everything) UNIV_1H = dict(tf="1h", n=50, z_in=2.0, z_exit=0.75, max_bars=72) GAME_15M = dict(tf="15m", n=66, z_in=1.674, z_exit=1.0, max_bars=35) # vincitore gioco def eth_btc_daily(cfg): r = pairs_sim("ETH", "BTC", **{**cfg, "pos": POS, "lev": LEV}) return daily_from(r["eq_ts"], r["eq_v"]), r def std_metrics(cfg, fee_rt=0.001): f = pairs_sim("ETH", "BTC", **{**cfg, "pos": POS, "lev": LEV, "fee_rt": fee_rt}) o = pairs_sim("ETH", "BTC", **{**cfg, "pos": POS, "lev": LEV, "fee_rt": fee_rt, "split_frac": 1 - OOS_FRAC}) yrs = f["yearly"]; pos_y = sum(1 for v in yrs.values() if v > 0) return f, o, pos_y, len(yrs) def port_metrics(members, p): ids = p.sleeve_ids dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids}) w = W.weight_vector(p.weighting, ids, dr, weights=p.weights, caps=p.caps, clusters=p.clusters, lookback=p.vol_lookback) drp = port_returns({i: members[i] for i in ids}, w) return metrics(drp), metrics(drp, lo=SPLIT) def fam_metrics(eqs): dr = port_returns(eqs) return metrics(dr), metrics(dr, lo=SPLIT) def blend(e1, e2, w1=0.5): """Sleeve combinata: media pesata dei rendimenti giornalieri (ribilancio 1D).""" r1 = e1.reindex(IDX).ffill().bfill().pct_change().fillna(0.0) r2 = e2.reindex(IDX).ffill().bfill().pct_change().fillna(0.0) rb = w1 * r1 + (1 - w1) * r2 eq = (1 + rb).cumprod() return eq / eq.iloc[0] def main(): p = PORTFOLIOS["PORT06"] pair_ids = [s.sid for s in p.sleeves if s.sid.startswith("PR_")] print("=" * 100) print(" GATE PORT06 — ETH/BTC pairs 15m (vincitore gioco) vs 1h deployato") print(f" pos={POS} lev={LEV} (canonico) | OOS da {OOS_DATE} | coppie PORT06: {pair_ids}") print("=" * 100) from src.portfolio.sleeves import all_sleeve_equities eq_base = dict(all_sleeve_equities()) # [1] PARITA' print("\n[1] PARITA' pairs_sim ETH/BTC 1h UNIV (pos0.15 lev3) == sleeve canonico PR_ETHBTC:") e1h, r1h = eth_btc_daily(UNIV_1H) base = eq_base["PR_ETHBTC"] corr = base.pct_change().fillna(0).corr(e1h.pct_change().fillna(0)) rb = (base.iloc[-1] / base.iloc[0] - 1) * 100 rr = (e1h.iloc[-1] / e1h.iloc[0] - 1) * 100 par_ok = corr > 0.999 and abs(rr - rb) <= max(1.0, abs(rb) * 0.01) print(f" corr={corr:.5f} ret canon {rb:+.0f}% vs replay {rr:+.0f}% " f"{'OK' if par_ok else '<-- MISMATCH (STOP)'}") if not par_ok: return # [2] CORRELAZIONE 1h vs 15m e15, r15 = eth_btc_daily(GAME_15M) c = e1h.pct_change().fillna(0).corr(e15.pct_change().fillna(0)) print(f"\n[2] CORRELAZIONE rendimenti giornalieri ETH/BTC 1h vs 15m: {c:.3f}") print(f" {'(quasi-duplicato se >0.8; diversificatore se <0.5)':<60s}") # [3] STANDALONE 1h vs 15m print("\n[3] STANDALONE ETH/BTC (netto fee 0.20% RT/coppia, leva 3x):") print(f" {'cfg':<10s}{'trd':>6s}{'win%':>6s}{'FULL%':>9s}{'OOS%':>9s}{'CAGR%':>7s}" f"{'DD%':>6s}{'oDD%':>7s}{'Shrp':>6s}{'anni+':>7s}{'fee2x FULL%':>12s}") for tag, cfg in [("1h UNIV", UNIV_1H), ("15m gioco", GAME_15M)]: f, o, py, ny = std_metrics(cfg) f2, _, _, _ = std_metrics(cfg, fee_rt=0.002) print(f" {tag:<10s}{f['trades']:>6d}{f['win']:>6.1f}{f['ret']:>+9.0f}{o['ret']:>+9.0f}" f"{f['cagr']:>7.0f}{f['dd']:>6.0f}{o['dd']:>7.0f}{f['sharpe']:>6.2f}" f"{f'{py}/{ny}':>7s}{f2['ret']:>+12.0f}") # robustezza: plateau n x z_in su 15m (Sharpe>1?) print("\n Robustezza 15m (Sharpe full, griglia n x z_in, z_exit=1.0 max_bars=35):") ns = [40, 50, 66, 80]; zs = [1.5, 1.7, 2.0, 2.5] cells = 0; tot = 0 hdr = " n\\z_in " + "".join(f"{z:>7.1f}" for z in zs) print(hdr) for n in ns: row = f" {n:>6d} " for z in zs: s = pairs_sim("ETH", "BTC", tf="15m", n=n, z_in=z, z_exit=1.0, max_bars=35, pos=POS, lev=LEV)["sharpe"] tot += 1; cells += s > 1 row += f"{s:>7.2f}" print(row) print(f" -> {cells}/{tot} celle Sharpe>1 (plateau se ~tutte; picco se poche)") # [4] GATE PORT06 print("\n[4] GATE PORT06 — sleeve ETH/BTC: baseline(1h) vs SWAP(15m) vs BLEND(50/50):") variants = { "baseline 1h": e1h, "SWAP 15m": e15, "BLEND 1h+15m": blend(e1h, e15, 0.5), } print(f" {'variante':<14s} | {'FULL Sh':>8s}{'FULL DD%':>9s}{'CAGR':>6s}" f" | {'OOS Sh':>7s}{'OOS DD%':>8s} | {'famSh':>6s}{'famDD%':>7s}") print(" " + "-" * 78) res = {} for tag, eth in variants.items(): members = dict(eq_base) members["PR_ETHBTC"] = eth f, o = port_metrics(members, p) fam_eqs = {sid: (eth if sid == "PR_ETHBTC" else eq_base[sid]) for sid in pair_ids} ff, _ = fam_metrics(fam_eqs) res[tag] = (f, o, ff) print(f" {tag:<14s} | {f['sharpe']:>8.2f}{f['dd']:>9.2f}{f['cagr']:>5.0f}%" f" | {o['sharpe']:>7.2f}{o['dd']:>8.2f} | {ff['sharpe']:>6.2f}{ff['dd']:>7.1f}") # VERDETTO fb, ob, _ = res["baseline 1h"] print("\n" + "=" * 100) print(" VERDETTO vs baseline 1h: promosso se OOS Sharpe non peggiora E DD scende (PORT06 e famiglia)") print("=" * 100) for tag in ("SWAP 15m", "BLEND 1h+15m"): f, o, ff = res[tag] ok = (o["sharpe"] >= ob["sharpe"] - 0.02 and o["dd"] <= ob["dd"] + 1e-9 and f["sharpe"] >= fb["sharpe"] - 0.02) print(f" {tag:<14s}: OOS Sh {ob['sharpe']:.2f}->{o['sharpe']:.2f} " f"DD {ob['dd']:.2f}->{o['dd']:.2f} | FULL Sh {fb['sharpe']:.2f}->{f['sharpe']:.2f} " f"DD {fb['dd']:.2f}->{f['dd']:.2f} => {'PROMOSSO' if ok else 'bocciato'}") if __name__ == "__main__": main()