82a1c60e08
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>
152 lines
6.2 KiB
Python
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()
|