"""GATE PORT06 — fade MR01/MR02/MR07 a 15m (origine: probe ACCEL50 2026-06-12). Domanda onesta: i 6 sleeve fade girati a 15m (parametri live 1h NON ri-tunati, trasferimento pre-registrato anti-overfit) MIGLIORANO il PORT06, o sono solo una variante piu' veloce e correlata degli STESSI fade 1h? Metodo (engine CANONICO build_trades/fade_daily_equity, NON le classi Strategy): [1] PARITA': il builder locale a tf='1h' == sleeve canonico di build_everything. [2] STANDALONE daily 1h vs 15m per twin (Sharpe/DD FULL e OOS su IDX comune) + stress fee 2x (0.20% RT) sul 15m (4x trade -> fee di prim'ordine). [3] CORRELAZIONE daily 15m vs twin 1h: se ~1 e' ridondante (il pairs 15m passo' a 0.37). [4] GATE PORT06: baseline vs ADD (19+6 sleeve) vs SWAP (15m al posto del 1h) vs BLEND (sleeve fade = 0.5*1h + 0.5*15m). Promosso se vs baseline l'OOS Sharpe non peggiora E il DD scende (criterio standard dei gate). uv run python scripts/analysis/fade15m_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 src.data.downloader import load_data from src.portfolio import weighting as W from scripts.analysis.combine_portfolio import ( IDX, SPLIT, OOS_DATE, _norm, metrics, port_returns, ) from scripts.analysis.risk_management import strats_for, build_trades, INIT, POS from scripts.analysis.report_families import build_everything from scripts.portfolios._defs import PORTFOLIOS FADE_IDS = [f"{nm}_{a}" for a in ("BTC", "ETH") for nm in ("MR01", "MR02", "MR07")] def fade_daily_tf(asset: str, fn, params, tf: str, fee_rt: float = 0.001) -> pd.Series: """fade_daily_equity canonico, parametrizzato sul timeframe (stesso engine).""" df = load_data(asset, tf) ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True) trades = build_trades(fn(df, **params), df, fee_rt=fee_rt, trend_max=3.0) n = len(df) eq = np.full(n, INIT, dtype=float) cap = INIT for i, j, ret in sorted(trades, key=lambda t: t[1]): cap = max(cap + cap * POS * ret, 10.0) eq[j:] = cap s = pd.Series(eq, index=ts).resample("1D").last().reindex(IDX).ffill().bfill() return _norm(s) def build_fade15(fee_rt: float = 0.001) -> dict[str, pd.Series]: out = {} for asset in ("BTC", "ETH"): for nm, (fn, params) in strats_for(asset).items(): out[f"{nm}_{asset}_15M"] = fade_daily_tf(asset, fn, params, "15m", fee_rt) return out def std(label: str, eq: pd.Series) -> str: r = eq.pct_change().fillna(0.0) f, o = metrics(r), metrics(r, lo=SPLIT) return (f" {label:<16s} FULL ret{f['ret']:>+8.0f}% DD{f['dd']:>6.1f}% Sh{f['sharpe']:>6.2f}" f" | OOS ret{o['ret']:>+7.0f}% DD{o['dd']:>5.1f}% Sh{o['sharpe']:>6.2f}") def port_metrics(members: dict[str, pd.Series], p) -> tuple[dict, dict]: ids = list(members) dr = pd.DataFrame({i: members[i].pct_change().fillna(0.0) for i in ids}) clusters = {i: i for i in ids} w = W.weight_vector(p.weighting, ids, dr, weights=p.weights, caps=p.caps, clusters=clusters, lookback=p.vol_lookback) drp = port_returns(members, w) return metrics(drp), metrics(drp, lo=SPLIT) def prow(label: str, fo: tuple[dict, dict]) -> str: f, o = fo return (f" {label:<22s} FULL CAGR{f['cagr']:>5.0f}% DD{f['dd']:>6.2f}% Sh{f['sharpe']:>6.2f}" f" | OOS CAGR{o['cagr']:>5.0f}% DD{o['dd']:>6.2f}% Sh{o['sharpe']:>6.2f}") def main() -> None: print("=" * 100) print(" GATE PORT06 — fade 15m (MR01/02/07 x BTC/ETH) vs 1h deployato") print(f" Finestra comune {IDX[0].date()} -> {IDX[-1].date()}, OOS da {OOS_DATE}") print("=" * 100) print("\nCostruzione sleeve canonici (2-3 min)...") S, pairs, tsm, shape = build_everything() canon = {**S, **pairs, **tsm, **shape} # [1] PARITA' del builder locale a 1h print("\n[1] PARITA' builder locale 1h == canonico") for sid in FADE_IDS: nm, asset = sid.split("_") fn, params = strats_for(asset)[nm] mine = fade_daily_tf(asset, fn, params, "1h") diff = float((mine - canon[sid]).abs().max()) print(f" {sid:<10s} max|diff| = {diff:.2e} {'OK' if diff < 1e-9 else 'VIOLAZIONE!'}") # [2] STANDALONE 1h vs 15m + stress fee 2x sul 15m print("\n[2] STANDALONE daily (engine canonico, pos 0.15 lev 3, fee 0.10% RT)") fade15 = build_fade15() fade15_fee2 = build_fade15(fee_rt=0.002) for sid in FADE_IDS: print(std(sid + " 1h", canon[sid])) print(std(sid + " 15m", fade15[sid + "_15M"])) print(std(sid + " 15m f2x", fade15_fee2[sid + "_15M"])) # [3] CORRELAZIONE twin 15m vs 1h print("\n[3] CORRELAZIONE daily 15m vs twin 1h (pairs 15m promosso a 0.37)") cors = [] for sid in FADE_IDS: c = canon[sid].pct_change().corr(fade15[sid + "_15M"].pct_change()) cors.append(c) print(f" {sid:<10s} corr = {c:.2f}") print(f" media = {np.mean(cors):.2f}") # [4] GATE PORT06 print("\n[4] GATE PORT06 (weighting cap PAIRS 0.33 / SHAPE 0.0588)") p = PORTFOLIOS["PORT06"] base = {sid: canon[sid] for sid in p.sleeve_ids} add = {**base, **fade15} swap = dict(base) blend = dict(base) for sid in FADE_IDS: e15 = fade15[sid + "_15M"] swap[sid] = e15 rb = 0.5 * base[sid].pct_change().fillna(0.0) + 0.5 * e15.pct_change().fillna(0.0) eq = (1 + rb).cumprod() blend[sid] = eq / eq.iloc[0] rows = {"BASELINE (1h)": port_metrics(base, p), "ADD (+6 sleeve 15m)": port_metrics(add, p), "SWAP (15m al posto 1h)": port_metrics(swap, p), "BLEND 50/50": port_metrics(blend, p)} for label, fo in rows.items(): print(prow(label, fo)) fb, ob = rows["BASELINE (1h)"] print("\nVERDETTO (criterio: OOS Sharpe non peggiora E DD scende vs baseline):") for label in ("ADD (+6 sleeve 15m)", "SWAP (15m al posto 1h)", "BLEND 50/50"): f, o = rows[label] ok = o["sharpe"] >= ob["sharpe"] - 1e-9 and (o["dd"] < ob["dd"] or f["dd"] < fb["dd"]) print(f" {label:<22s} -> {'PROMOSSO' if ok else 'bocciato'}") if __name__ == "__main__": main()