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>
This commit is contained in:
Adriano Dal Pastro
2026-06-12 20:37:53 +00:00
parent 515193a203
commit 82a1c60e08
2 changed files with 204 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
# 2026-06-12 — GATE PORT06: fade 15m PROMOSSI (tutte e tre le varianti)
Seguito del probe ACCEL50: i 6 sleeve fade (MR01/02/07 × BTC/ETH) a 15m,
parametri live 1h NON ri-tunati, engine canonico `build_trades`/`fade_daily_equity`
parametrizzato sul timeframe. Script: `scripts/analysis/fade15m_port06_gate.py`.
## Risultati
**[1] Parità.** Builder locale a 1h == sleeve canonico, diff 0.00 esatto su tutti e 6.
**[2] Standalone (daily, pos 0.15 lev 3, fee 0.10% RT).** Il 15m batte il twin 1h
quasi ovunque; OOS Sharpe: MR01_ETH 4.94 (vs 1.10), MR02_ETH 6.40 (vs 4.72),
MR07_ETH 4.44 (vs 1.97), MR07_BTC 2.37 (vs 1.59). Fee 2x: regge ovunque tranne
MR02_BTC (OOS 0.60 — il piu' fee-sensitive, ma a fee reali e' il migliore BTC).
**[3] Correlazione 15m↔1h: media 0.26** (range 0.16-0.41) — più decorrelato del
pairs 15m promosso a 0.37. NON è la stessa scommessa più veloce: è un edge a
orizzonte diverso.
**[4] Gate PORT06** (cap PAIRS 0.33 / SHAPE 0.0588):
| variante | FULL CAGR | FULL DD | FULL Sh | OOS CAGR | OOS DD | OOS Sh |
|----------|-----------|---------|---------|----------|--------|--------|
| BASELINE (1h) | 74% | 3.46% | 7.34 | 111% | 1.48% | 10.07 |
| **ADD (+6 sleeve 15m)** | 85% | **2.73%** | 8.02 | 125% | **1.38%** | **10.48** |
| SWAP (15m al posto 1h) | **101%** | 2.47% | 8.13 | **163%** | 2.09% | 10.86 |
| BLEND 50/50 | 87% | 2.31% | 8.06 | 136% | 1.77% | 10.82 |
Tutte e tre PROMOSSE dal criterio standard (OOS Sharpe non peggiora E DD scende).
**[5] Anti-illusione flat (il check che ha ucciso pairs-alt e XEX/DOGE).**
ETH 15m ha 14-30%/anno di candele flat. Con flat-entry-skip l'edge SOPRAVVIVE:
MR01_ETH Sh 3.11→2.85 (OOS 4.94→4.60), MR02_ETH 4.72→4.63, MR07_ETH 3.76→3.55;
BTC invariato (≤0.01). Coerente col finding registrato 2026-05-28. La quota di
ritorno persa (~30-40% del FULL ETH) è il caveat slippage onesto: il ledger
reale shadow sarà il giudice, come per il pairs 15m.
## Raccomandazione
**ADD** è la promozione più pulita: migliora TUTTE le metriche senza buttare il
track record live dei 6 sleeve 1h (SWAP rinuncia alla config validata dal vivo;
il suo OOS DD peggiora 1.48→2.09). In subordine BLEND (miglior FULL DD 2.31%).
## Per il deploy (non fatto qui)
1. `_defs.py`: 6 SleeveSpec nuovi `MR0x_{asset}_15M` con `tf: 15m` nei params.
2. Runner: estendere il fetch sub-orario (già esistente per PR_ETHBTC_15M) ai
fade 15m; verificare lookback (EMA200 a 15m = 50h → bastano ~2200 barre).
3. Validazione worker a 15m (replay == backtest, come validate_worker_pairs).
4. Sizing: a peso pieno i 6 sleeve 15m raddoppiano la famiglia FADE; valutare
position_size ridotto come il blend pairs (il gate ADD qui è già a peso pieno
e migliora comunque, ma il caveat slippage 15m suggerisce prudenza).
5. MR02_BTC 15m è il più fee-sensitive (fee2x OOS 0.60): monitorare le fee reali.
+151
View File
@@ -0,0 +1,151 @@
"""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()