chore(reset): v2.0.0 — storico certificato Deribit mainnet, ripartenza pulita
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
"""GATE PORT06: trend_max=3.0/ema_long=200 sulle 6 fade LIVE (improvement-sweep punto 7).
|
||||
|
||||
Contesto. Il backtest canonico (build_everything) applica il filtro trend alle fade,
|
||||
ma in produzione le SleeveSpec di _defs.py NON lo passano -> le fade live fadano anche
|
||||
i trend/crolli estesi (es. MR01/MR02_ETH long ripetuti nel crash ETH del 2026-06-05).
|
||||
Anche il test PORT06 del loss-guard Hurst (fade_lossguard_port_test) fu misurato su
|
||||
entries GIA' trend-filtrate -> la config live attuale (hurst SENZA trend) non e' mai
|
||||
stata gateata. hurst e trend si sovrappongono (entrambi tagliano il regime trending):
|
||||
il delta marginale va misurato sul path live, non assunto dai numeri canonici.
|
||||
|
||||
Confronto, a livello PORT06 (stessa matematica pesi cap di Portfolio.backtest):
|
||||
LIVE = fade con hurst_max=0.55 + EXIT-16 (sl_confirm 0.5 ATR), trend OFF [prod oggi]
|
||||
CANDIDATO = LIVE + trend_max=3.0 / ema_long=200 [proposta]
|
||||
TREND-ONLY= EXIT-16 + trend, senza hurst [diagnostica overlap]
|
||||
|
||||
Parita' preliminare: il replay (mode=orig, trend ON, no hurst, no EXIT-16) deve
|
||||
riprodurre le equity fade canoniche (come exit16_port06_impact).
|
||||
|
||||
La maschera Hurst e' quella ESATTA del live: fade_base.hurst_skip_mask (close-only,
|
||||
window=100, step=6) — non la cache di regime_lab.
|
||||
|
||||
GATE (sweep): PROMOSSO se OOS Sharpe non peggiora (>= base-0.02) E il DD scende,
|
||||
e in FULL non degrada.
|
||||
|
||||
uv run python scripts/analysis/trendmax_port06_impact.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from src.data.downloader import load_data
|
||||
from src.strategies.fade_base import hurst_skip_mask
|
||||
from scripts.analysis.risk_management import strats_for
|
||||
from scripts.analysis.combine_portfolio import OOS_DATE
|
||||
from scripts.analysis._port06_gate_common import (
|
||||
build_trades_variant, equity_from_trades, port_metrics, dd as _dd,
|
||||
)
|
||||
from scripts.portfolios._defs import PORTFOLIOS
|
||||
|
||||
HURST_MAX = 0.55 # loss-guard live
|
||||
TREND_MAX = 3.0
|
||||
|
||||
|
||||
def main():
|
||||
p = PORTFOLIOS["PORT06"]
|
||||
fade_ids = [s.sid for s in p.sleeves if s.sid.startswith("MR")]
|
||||
print("=" * 100)
|
||||
print(" GATE PORT06 — trend_max=3.0/ema200 sulle fade LIVE (hurst 0.55 + EXIT-16 gia' attivi)")
|
||||
print(f" fade sleeve: {fade_ids} | caps={p.caps}")
|
||||
print("=" * 100)
|
||||
|
||||
print("\n[1] build_everything() canonico (cache)...")
|
||||
from src.portfolio.sleeves import all_sleeve_equities
|
||||
eq_base = dict(all_sleeve_equities())
|
||||
print(f" sleeve totali: {len(eq_base)}")
|
||||
|
||||
# dati + entries + maschera hurst (identica al live: close-only, w=100, step=6)
|
||||
dfs, masks, entries = {}, {}, {}
|
||||
for asset in ("BTC", "ETH"):
|
||||
dfs[asset] = load_data(asset, "1h")
|
||||
masks[asset] = hurst_skip_mask(dfs[asset], HURST_MAX)
|
||||
print(f" {asset}: {len(dfs[asset])} barre, hurst-skip {masks[asset].mean()*100:.1f}% delle barre")
|
||||
for nm, (fn, params) in strats_for(asset).items():
|
||||
sid = f"{nm}_{asset}"
|
||||
if sid in fade_ids:
|
||||
entries[sid] = (asset, fn(dfs[asset], **params))
|
||||
|
||||
# --- [2] PARITA': mode=orig, trend ON, no hurst, no exit16 == canonico ---
|
||||
print("\n[2] PARITA' replay (orig, trend ON, no hurst) vs canonico:")
|
||||
print(f" {'sleeve':<10s}{'corr':>10s}{'ret_canon%':>14s}{'ret_replay%':>14s}{'diff%':>9s}")
|
||||
parity_ok = True
|
||||
for sid in fade_ids:
|
||||
asset, ents = entries[sid]
|
||||
rep = equity_from_trades(dfs[asset], build_trades_variant(
|
||||
ents, dfs[asset], mode="orig", trend_max=TREND_MAX))
|
||||
base = eq_base[sid]
|
||||
corr = base.pct_change().fillna(0).corr(rep.pct_change().fillna(0))
|
||||
rb = (base.iloc[-1] / base.iloc[0] - 1) * 100
|
||||
rr = (rep.iloc[-1] / rep.iloc[0] - 1) * 100
|
||||
flag = "" if (corr > 0.999 and abs(rr - rb) <= max(1.0, abs(rb) * 0.01)) else " <-- MISMATCH"
|
||||
if flag:
|
||||
parity_ok = False
|
||||
print(f" {sid:<10s}{corr:>10.5f}{rb:>14.1f}{rr:>14.1f}{rr-rb:>+9.2f}{flag}")
|
||||
print(f"\n PARITA' {'OK' if parity_ok else 'FALLITA'}.")
|
||||
if not parity_ok:
|
||||
print(" >>> STOP: non procedo col gate su un engine non in parita'.")
|
||||
return
|
||||
|
||||
# --- [3] varianti live-path delle 6 fade ---
|
||||
VARIANTS = {
|
||||
"LIVE": dict(trend_max=None, hurst=True), # produzione oggi
|
||||
"CANDIDATO": dict(trend_max=TREND_MAX, hurst=True), # + trend filter
|
||||
"TREND-ONLY": dict(trend_max=TREND_MAX, hurst=False), # swap hurst->trend
|
||||
"NESSUNO": dict(trend_max=None, hurst=False), # solo EXIT-16 (baseline filtri)
|
||||
"TREND-2.5": dict(trend_max=2.5, hurst=False), # sensibilita' soglia
|
||||
"TREND-3.5": dict(trend_max=3.5, hurst=False),
|
||||
}
|
||||
eqs = {v: {} for v in VARIANTS}
|
||||
ntr = {v: {} for v in VARIANTS}
|
||||
for sid in fade_ids:
|
||||
asset, ents = entries[sid]
|
||||
for v, cfg in VARIANTS.items():
|
||||
tr = build_trades_variant(ents, dfs[asset], mode="exit16",
|
||||
trend_max=cfg["trend_max"],
|
||||
hurst_mask=masks[asset] if cfg["hurst"] else None)
|
||||
eqs[v][sid] = equity_from_trades(dfs[asset], tr)
|
||||
ntr[v][sid] = len(tr)
|
||||
|
||||
# --- [4] PORT06 per variante ---
|
||||
print("\n" + "=" * 100)
|
||||
print(f" [4] PORT06 (pesi cap, OOS da {OOS_DATE}) — fade in path LIVE (exit16+hurst)")
|
||||
print("=" * 100)
|
||||
print(f" {'variante':<12s}{'FULL Sh':>9s}{'FULL DD%':>10s}{'FULL CAGR':>11s}"
|
||||
f" | {'OOS Sh':>8s}{'OOS DD%':>9s}{'OOS CAGR':>10s}")
|
||||
print(" " + "-" * 94)
|
||||
res = {}
|
||||
for v in VARIANTS:
|
||||
members = dict(eq_base)
|
||||
for sid in fade_ids:
|
||||
members[sid] = eqs[v][sid]
|
||||
f, o = port_metrics(members, p)
|
||||
res[v] = (f, o)
|
||||
print(f" {v:<12s}{f['sharpe']:>9.2f}{f['dd']:>10.2f}{f['cagr']:>10.0f}%"
|
||||
f" | {o['sharpe']:>8.2f}{o['dd']:>9.2f}{o['cagr']:>9.0f}%")
|
||||
f_l, o_l = res["LIVE"]
|
||||
f_c, o_c = res["CANDIDATO"]
|
||||
print(" " + "-" * 94)
|
||||
print(f" {'DELTA C-L':<12s}{f_c['sharpe']-f_l['sharpe']:>+9.2f}{f_c['dd']-f_l['dd']:>+10.2f}"
|
||||
f"{f_c['cagr']-f_l['cagr']:>+10.0f}% | {o_c['sharpe']-o_l['sharpe']:>+8.2f}"
|
||||
f"{o_c['dd']-o_l['dd']:>+9.2f}{o_c['cagr']-o_l['cagr']:>+9.0f}%")
|
||||
|
||||
# --- [5] per-sleeve ---
|
||||
print("\n Per-sleeve (FULL): ret% | DD% | n trade [LIVE -> CANDIDATO]")
|
||||
print(f" {'sleeve':<10s}{'ret L%':>10s}{'ret C%':>10s}{'DD L%':>8s}{'DD C%':>8s}"
|
||||
f"{'ntr L':>7s}{'ntr C':>7s}")
|
||||
for sid in fade_ids:
|
||||
el_, ec = eqs["LIVE"][sid], eqs["CANDIDATO"][sid]
|
||||
rl = (el_.iloc[-1] / el_.iloc[0] - 1) * 100
|
||||
rc = (ec.iloc[-1] / ec.iloc[0] - 1) * 100
|
||||
print(f" {sid:<10s}{rl:>10.1f}{rc:>10.1f}{_dd(el_):>8.1f}{_dd(ec):>8.1f}"
|
||||
f"{ntr['LIVE'][sid]:>7d}{ntr['CANDIDATO'][sid]:>7d}")
|
||||
|
||||
# --- GATE ---
|
||||
print("\n" + "=" * 100)
|
||||
print(" GATE (sweep): PROMOSSO se OOS Sharpe >= LIVE-0.02 E OOS DD scende, e FULL non degrada")
|
||||
print("=" * 100)
|
||||
oos_sh_ok = o_c["sharpe"] >= o_l["sharpe"] - 0.02
|
||||
oos_dd_ok = o_c["dd"] < o_l["dd"]
|
||||
full_ok = f_c["sharpe"] >= f_l["sharpe"] - 0.02 and f_c["dd"] <= f_l["dd"] + 0.20
|
||||
promoted = oos_sh_ok and oos_dd_ok and full_ok
|
||||
print(f" OOS Sharpe {o_l['sharpe']:.2f} -> {o_c['sharpe']:.2f} ({'OK' if oos_sh_ok else 'KO'})")
|
||||
print(f" OOS DD% {o_l['dd']:.2f} -> {o_c['dd']:.2f} ({'OK' if oos_dd_ok else 'KO'})")
|
||||
print(f" FULL Sharpe {f_l['sharpe']:.2f} -> {f_c['sharpe']:.2f} | "
|
||||
f"FULL DD {f_l['dd']:.2f} -> {f_c['dd']:.2f} ({'OK' if full_ok else 'KO'})")
|
||||
print("\n VERDETTO: " + (">>> PROMOSSO <<<" if promoted else ">>> BOCCIATO <<<"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user