Files
PythagorasGoal/scripts/analysis/fade15m_port06_gate.py
T
Adriano Dal Pastro 82a1c60e08 research(gate): fade 15m PROMOSSI al gate PORT06 (ADD: OOS Sh 10.07->10.48, DD giu' su entrambe le finestre)
Parita' builder esatta; corr 15m-1h media 0.26 (vera diversificazione);
flat-entry-skip OK su ETH (edge reale, non stale-print). Raccomandata ADD.
Deploy plumbing elencato nel diario (non eseguito).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 20:37:53 +00:00

152 lines
6.2 KiB
Python

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