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,227 @@
|
||||
"""GATE DIP01 + PORT06: estendere EXIT-16 (close-confirm SL) a DIP01 (sweep punto 9).
|
||||
|
||||
DIP01 e' l'unico sleeve BTC con esecuzione REALE round-trip, e gira ancora col
|
||||
branch SL intrabar wick-sensitive. EXIT-16 e' stato validato SULLE FADE: estenderlo
|
||||
a una strategia honest richiede la validazione sul grid proprio di DIP01, con
|
||||
engine GAP-AWARE (lezione exit-lab: l'engine canonico filla gli stop "al livello"
|
||||
anche su gap-through -> bias PRO stop intrabar stretti; il confronto onesto filla
|
||||
lo SL a worse(livello, open)).
|
||||
|
||||
Protocollo:
|
||||
[1] parita': replay engine 'orig' (fill al livello) == equity canonica DIP01_BTC
|
||||
[2] grid 3x3x2 (z_in x sl_atr x max_bars) su BTC (deployato) ed ETH (robustezza):
|
||||
orig GAP-AWARE vs EXIT-16(buf 0.5), ret/DD/Sharpe train (pre-OOS) e OOS
|
||||
[3] plateau buffer {0.4, 0.5, 0.75, 1.0} sulla cella canonica
|
||||
[4] gate PORT06: DIP01_BTC exit16 innestato nel canonico, pesi cap
|
||||
-> PROMOSSO se OOS Sharpe non peggiora E FULL/DD non degradano materialmente.
|
||||
|
||||
NB hurst_max NON valutato: il gate trendmax (2026-06-07) ha mostrato che il
|
||||
loss-guard Hurst e' ridondante-dannoso POST-EXIT-16 (stesso regime target).
|
||||
|
||||
uv run python scripts/analysis/dip01_exit16_impact.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 scripts.analysis.strategy_research import atr
|
||||
from scripts.analysis.combine_portfolio import _norm, IDX, metrics, SPLIT, OOS_DATE
|
||||
from scripts.analysis._port06_gate_common import port_metrics
|
||||
from scripts.portfolios._defs import PORTFOLIOS
|
||||
|
||||
FEE_RT, LEV, POS, INIT = 0.001, 3.0, 0.15, 1000.0
|
||||
BUFFER = 0.5
|
||||
GRID_Z = (2.0, 2.5, 3.0)
|
||||
GRID_SL = (2.0, 2.5, 3.0)
|
||||
GRID_MB = (24, 48)
|
||||
CANON = dict(n=50, z_in=2.5, sl_atr=2.5, max_bars=24)
|
||||
|
||||
|
||||
def dip_entries(df, n=50, z_in=2.5, sl_atr=2.5, max_bars=24):
|
||||
"""Entries DIP01 == honest_improve2.dip_market_gated (market_n=0): crossing
|
||||
di z sotto -z_in. Ritorna [{i, tp, sl, mb}] (long-only)."""
|
||||
c = df["close"].values
|
||||
ma = pd.Series(c).rolling(n).mean().values
|
||||
sd = pd.Series(c).rolling(n).std().values
|
||||
a = atr(df, 14)
|
||||
z = (c - ma) / np.where(sd == 0, np.nan, sd)
|
||||
out = []
|
||||
for i in range(n + 14, len(c)):
|
||||
if np.isnan(z[i]) or np.isnan(a[i]):
|
||||
continue
|
||||
if z[i] <= -z_in and z[i - 1] > -z_in:
|
||||
out.append({"i": i, "tp": ma[i], "sl": c[i] - sl_atr * a[i],
|
||||
"mb": max_bars})
|
||||
return out
|
||||
|
||||
|
||||
def dip_trades(ents, df, mode, buffer=BUFFER):
|
||||
"""Engine exit DIP01 (long-only), non-overlap come il canonico.
|
||||
|
||||
mode="orig" : SL intrabar fill AL LIVELLO (== canonico, per la parita')
|
||||
mode="orig_gap" : SL intrabar fill a worse(livello, open[j]) — gap-aware
|
||||
mode="exit16" : SL intrabar OFF; TP intrabar al livello (priorita' nel bar);
|
||||
stop solo se close[j] < sl - buffer*ATR14[j], fill a close[j]
|
||||
"""
|
||||
h, l, c, o = df["high"].values, df["low"].values, df["close"].values, df["open"].values
|
||||
n = len(c)
|
||||
a = atr(df, 14)
|
||||
fee = FEE_RT * LEV
|
||||
out = []
|
||||
last = -1
|
||||
for e in ents:
|
||||
i = e["i"]
|
||||
if i <= last or i + 1 >= n:
|
||||
continue
|
||||
tp, sl, mb = e["tp"], e["sl"], e["mb"]
|
||||
exit_p = c[min(i + mb, n - 1)]
|
||||
j = min(i + mb, n - 1)
|
||||
for k in range(1, mb + 1):
|
||||
j = i + k
|
||||
if j >= n:
|
||||
j = n - 1
|
||||
exit_p = c[j]
|
||||
break
|
||||
if mode in ("orig", "orig_gap"):
|
||||
if l[j] <= sl:
|
||||
exit_p = sl if mode == "orig" else min(sl, o[j])
|
||||
break
|
||||
if h[j] >= tp:
|
||||
exit_p = tp
|
||||
break
|
||||
if k == mb:
|
||||
exit_p = c[j]
|
||||
else: # exit16
|
||||
if h[j] >= tp:
|
||||
exit_p = tp
|
||||
break
|
||||
aj = a[j] if np.isfinite(a[j]) else 0.0
|
||||
if c[j] < sl - buffer * aj:
|
||||
exit_p = c[j]
|
||||
break
|
||||
if k == mb:
|
||||
exit_p = c[j]
|
||||
ret = (exit_p - c[i]) / c[i] * LEV - fee
|
||||
out.append((i, j, ret))
|
||||
last = j
|
||||
return out
|
||||
|
||||
|
||||
def daily_equity(df, trades):
|
||||
"""Equity giornaliera con la convenzione CANONICA honest (_daily_equity su punti
|
||||
trade-exit). NB: la serie a punti-trade reindexata su IDX ancora il primo valore
|
||||
al PRIMO trade dentro IDX (bfill), non al capitale portato avanti da prima —
|
||||
convenzione discutibile ma e' quella di build_everything: per la parita' (e il
|
||||
confronto col PORT06 canonico) va replicata esattamente."""
|
||||
from scripts.analysis.honest_improve2 import _daily_equity
|
||||
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
|
||||
cap = INIT
|
||||
eq_ts, eq_v = [], []
|
||||
for i, j, ret in sorted(trades, key=lambda t: t[1]):
|
||||
cap = max(cap + cap * POS * ret, 10.0)
|
||||
eq_ts.append(ts.iloc[j])
|
||||
eq_v.append(cap)
|
||||
return _norm(_daily_equity(eq_ts, eq_v, IDX))
|
||||
|
||||
|
||||
def cell_metrics(eq):
|
||||
dr = eq.pct_change().fillna(0.0)
|
||||
return metrics(dr), metrics(dr, lo=SPLIT)
|
||||
|
||||
|
||||
def main():
|
||||
p = PORTFOLIOS["PORT06"]
|
||||
print("=" * 104)
|
||||
print(" GATE DIP01 EXIT-16 (close-confirm 0.5 ATR) — grid gap-aware + PORT06")
|
||||
print(f" OOS da {OOS_DATE} | fee {FEE_RT*100:.2f}%RT x lev{LEV:.0f} | pos {POS}")
|
||||
print("=" * 104)
|
||||
|
||||
print("\n[1] build_everything() canonico (cache)...")
|
||||
from src.portfolio.sleeves import all_sleeve_equities
|
||||
eq_base = dict(all_sleeve_equities())
|
||||
|
||||
dfs = {a: load_data(a, "1h") for a in ("BTC", "ETH")}
|
||||
|
||||
# --- parita' ---
|
||||
ents = dip_entries(dfs["BTC"], **CANON)
|
||||
rep = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "orig"))
|
||||
base = eq_base["DIP01_BTC"]
|
||||
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
|
||||
print(f"\n[1] PARITA' orig vs canonico: corr={corr:.5f} ret {rb:+.0f}% vs {rr:+.0f}%")
|
||||
if not (corr > 0.999 and abs(rr - rb) <= max(1.0, abs(rb) * 0.01)):
|
||||
print(" >>> PARITA' FALLITA: STOP.")
|
||||
return
|
||||
|
||||
# --- [2] grid gap-aware ---
|
||||
for asset in ("BTC", "ETH"):
|
||||
df = dfs[asset]
|
||||
print(f"\n[2] GRID {asset} — orig GAP-AWARE vs EXIT-16 (train | OOS: ret% e Sharpe)")
|
||||
print(f" {'cella':<16s}{'tr retO':>9s}{'tr retE':>9s} {'oos retO':>9s}{'oos retE':>9s}"
|
||||
f" {'oos ShO':>8s}{'oos ShE':>8s} {'ddO':>6s}{'ddE':>6s} esito")
|
||||
wins_tr = wins_oos = cells = 0
|
||||
for z in GRID_Z:
|
||||
for slm in GRID_SL:
|
||||
for mb in GRID_MB:
|
||||
ents = dip_entries(df, n=50, z_in=z, sl_atr=slm, max_bars=mb)
|
||||
eo = daily_equity(df, dip_trades(ents, df, "orig_gap"))
|
||||
ee = daily_equity(df, dip_trades(ents, df, "exit16"))
|
||||
fo, oo = cell_metrics(eo)
|
||||
fe, oe = cell_metrics(ee)
|
||||
tr_o = fo["ret"] - oo["ret"]; tr_e = fe["ret"] - oe["ret"] # ~train (full-oos, approssimato su ret composti: usare segni)
|
||||
# train ret esatto: equity al SPLIT
|
||||
tr_o = (eo.iloc[SPLIT] / eo.iloc[0] - 1) * 100
|
||||
tr_e = (ee.iloc[SPLIT] / ee.iloc[0] - 1) * 100
|
||||
cells += 1
|
||||
w_tr = tr_e >= tr_o
|
||||
w_oos = oe["ret"] >= oo["ret"]
|
||||
wins_tr += w_tr
|
||||
wins_oos += w_oos
|
||||
tag = ("OK" if (w_tr and w_oos) else "tr-" if w_oos else "oos-" if w_tr else "KO")
|
||||
print(f" z{z} sl{slm} mb{mb:<3d}{tr_o:>9.0f}{tr_e:>9.0f} "
|
||||
f"{oo['ret']:>9.0f}{oe['ret']:>9.0f} {oo['sharpe']:>8.2f}{oe['sharpe']:>8.2f}"
|
||||
f" {fo['dd']:>6.1f}{fe['dd']:>6.1f} {tag}")
|
||||
print(f" -> EXIT-16 >= orig-gap: train {wins_tr}/{cells}, OOS {wins_oos}/{cells}")
|
||||
|
||||
# --- [3] plateau buffer (BTC, cella canonica) ---
|
||||
print("\n[3] Plateau buffer EXIT-16 (BTC, cella canonica):")
|
||||
ents = dip_entries(dfs["BTC"], **CANON)
|
||||
for buf in (0.4, 0.5, 0.75, 1.0):
|
||||
ee = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "exit16", buffer=buf))
|
||||
fe, oe = cell_metrics(ee)
|
||||
print(f" buf {buf:<5}FULL ret {fe['ret']:>+7.0f}% DD {fe['dd']:>5.1f} Sh {fe['sharpe']:>5.2f}"
|
||||
f" | OOS ret {oe['ret']:>+6.0f}% DD {oe['dd']:>5.1f} Sh {oe['sharpe']:>5.2f}")
|
||||
|
||||
# --- [4] gate PORT06 ---
|
||||
ee = daily_equity(dfs["BTC"], dip_trades(ents, dfs["BTC"], "exit16"))
|
||||
members_b = dict(eq_base)
|
||||
members_e = dict(eq_base)
|
||||
members_e["DIP01_BTC"] = ee
|
||||
f_b, o_b = port_metrics(members_b, p)
|
||||
f_e, o_e = port_metrics(members_e, p)
|
||||
print("\n" + "=" * 104)
|
||||
print(f" [4] PORT06 (pesi cap {p.caps}) — DIP01_BTC orig vs EXIT-16")
|
||||
print("=" * 104)
|
||||
print(f" {'variante':<10s}{'FULL Sh':>9s}{'FULL DD%':>10s}{'CAGR':>6s} | {'OOS Sh':>7s}{'OOS DD%':>8s}{'CAGR':>6s}")
|
||||
for nm, (f, o) in (("BASE", (f_b, o_b)), ("EXIT-16", (f_e, o_e))):
|
||||
print(f" {nm:<10s}{f['sharpe']:>9.2f}{f['dd']:>10.2f}{f['cagr']:>5.0f}% | "
|
||||
f"{o['sharpe']:>7.2f}{o['dd']:>8.2f}{o['cagr']:>5.0f}%")
|
||||
|
||||
oos_ok = o_e["sharpe"] >= o_b["sharpe"] - 0.02 and o_e["dd"] <= o_b["dd"] + 0.20
|
||||
full_ok = f_e["sharpe"] >= f_b["sharpe"] - 0.02 and f_e["dd"] <= f_b["dd"] + 0.20
|
||||
promoted = oos_ok and full_ok
|
||||
print(f"\n GATE: OOS {'OK' if oos_ok else 'KO'} | FULL {'OK' if full_ok else 'KO'}")
|
||||
print(" VERDETTO: " + (">>> PROMOSSO <<<" if promoted else ">>> BOCCIATO <<<"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user