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:
Adriano Dal Pastro
2026-06-19 15:16:03 +00:00
parent 8401a280b9
commit 14522262e6
383 changed files with 1971 additions and 779 deletions
@@ -0,0 +1,211 @@
"""Verifica avversariale LEAKAGE/ESEGUIBILITA' per EXIT-16 close_confirm_sl.
Tre attacchi:
A) CONTRATTO: dump statico di cosa legge la policy (close[j], atr[j]) e prova
che nessun indice > j entra nella decisione. Replica esatta del numero
headline (MR02 BTC/ETH OOS) per ancorare.
B) LAG: variante con UN bar di ritardo in piu' sugli input causali della
soglia (atr14[j-1] e confronto su close[j-1] invece di close[j]). Se l'edge
collassa -> appeso al timing perfetto. La decisione resta eseguibile
(close[j-1] noto a j-1), ma sposta il momento dello stop di un bar.
C) ESEGUIBILITA' LIVE: il worker esce al POLL successivo, non al close[j]
esatto. Stima del costo eseguendo l'uscita a open[j+1] invece di close[j].
Esegui: cd /opt/docker/PythagorasGoal && PYTHONPATH=. uv run python \
scripts/analysis/exit_policies/verify_16_leakage.py
"""
import sys
from pathlib import Path
import numpy as np
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parent)) # scripts/analysis
sys.path.insert(0, str(HERE.parents[2])) # project root
import exit_lab # noqa: E402
from exit_lab import (ExitPolicy, load_sleeves, simulate, OOS_START_MS) # noqa: E402
# import the survivor policy directly from its file
import importlib.util # noqa: E402
spec = importlib.util.spec_from_file_location("p16", HERE / "16_close_confirm_sl.py")
p16 = importlib.util.module_from_spec(spec)
spec.loader.exec_module(p16)
CloseConfirmSl = p16.CloseConfirmSl
BUF = 0.5 # train-pick buffer
# --------------------------------------------------------------- B) LAG variant
class CloseConfirmSlLag(ExitPolicy):
"""Identica a EXIT-16 ma con 1 bar di ritardo sugli input della soglia:
decisione su close[j-1] e atr[j-1] (eseguibile gia' a j-1). Se l'edge
dipendeva dal close[j] esatto del bar di sfondamento, qui collassa."""
name = "close_confirm_sl_lag"
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params):
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
self.buffer = float(params.get("buffer", 0.0))
self.close = ctx["close"]
self.atr = ctx["atr14"]
def levels(self, j):
return self.tp0, None, 1.0
def after_bar(self, j):
jj = j - 1
if jj <= self.i:
return False
a = self.atr[jj]
if not np.isfinite(a):
a = 0.0
cj = self.close[jj]
if self.d == 1:
return cj < self.sl0 - self.buffer * a
return cj > self.sl0 + self.buffer * a
# ----------------------------------------- C) execution-delay (open[j+1]) variant
def simulate_open_next(sleeve, params, start_ms=None, end_ms=None):
"""Come exit_lab.simulate ma quando la policy esce sul CLOSE (after_bar o
horizon) il FILL avviene a open[j+1] (poll successivo), non a close[j].
I TP/SL intrabar restano al livello (limit). Stima il costo del ritardo
di un poll per un'exit market al prossimo bar."""
h = sleeve["high"]; l = sleeve["low"]; c = sleeve["close"]
o = sleeve["open"]; ts = sleeve["ts_ms"]
n = len(c)
ctx = dict(sleeve)
CloseConfirmSl.prepare(ctx, **params)
fee = exit_lab.FEE_RT * exit_lab.LEV
POS = exit_lab.POS; LEV = exit_lab.LEV
capital = peak = 1000.0
max_dd = 0.0
last_exit = -1
trades = wins = 0
bars_tot = 0
rets = []
for (i, d, tp0, sl0, mb) in sleeve["signals"]:
if start_ms is not None and ts[i] < start_ms:
continue
if end_ms is not None and ts[i] >= end_ms:
continue
if i <= last_exit or i + 1 >= n:
continue
entry = c[i]
pol = CloseConfirmSl(ctx, i, d, entry, tp0, sl0, mb, **params)
horizon = min(int(pol.horizon), exit_lab.HARD_CAP)
fills = []
remaining = 1.0
j = i
for step in range(1, horizon + 1):
j = i + step
if j >= n:
j = n - 1
fills.append((remaining, c[j])); remaining = 0.0
break
tp, sl, tpfrac = pol.levels(j)
hit_sl = sl is not None and ((d == 1 and l[j] <= sl) or (d == -1 and h[j] >= sl))
hit_tp = tp is not None and ((d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp))
if hit_sl:
fills.append((remaining, sl)); remaining = 0.0
break
if hit_tp:
f = min(max(tpfrac, 0.0), 1.0) * remaining
if f > 0:
fills.append((f, tp)); remaining -= f
if remaining <= 1e-9:
break
pol.on_partial(j, tp, remaining)
if pol.after_bar(j):
# EXECUTION DELAY: fill al prossimo open invece di close[j]
px = o[j + 1] if j + 1 < n else c[j]
fills.append((remaining, px)); remaining = 0.0
break
if step == horizon:
px = o[j + 1] if j + 1 < n else c[j]
fills.append((remaining, px)); remaining = 0.0
if remaining > 1e-9:
fills.append((remaining, c[j]))
ret = sum(f * (p - entry) for f, p in fills) / entry * d * LEV - fee
capital = max(capital + capital * POS * ret, 10.0)
peak = max(peak, capital)
max_dd = max(max_dd, (peak - capital) / peak)
last_exit = j
trades += 1
wins += ret > 0
bars_tot += j - i
rets.append(ret)
if trades == 0:
return {}
r = np.array(rets)
return {"ret_pct": (capital / 1000.0 - 1) * 100, "dd_pct": max_dd * 100,
"trades": trades, "win_pct": wins / trades * 100,
"sharpe_t": float(r.mean() / r.std() * np.sqrt(len(r))) if r.std() else 0.0,
"avg_bars": bars_tot / trades}
def fmt(r):
if not r:
return "(no trades)"
return (f"ret{r['ret_pct']:>7.0f}% dd{r['dd_pct']:>5.1f} sh{r['sharpe_t']:>5.2f} "
f"n{r['trades']:>4} bars{r['avg_bars']:>5.1f}")
def main():
data = load_sleeves()
params = {"buffer": BUF}
keys = list(data.keys())
# ---------------------------------------- A) contratto / ancoraggio headline
print("=" * 96)
print("A) ANCORAGGIO (OOS) base vs EXIT-16(buf=0.5) vs LAG(+1 bar) vs OPEN[j+1] delay")
print("=" * 96)
survive_base = survive_lag = survive_delay = 0
agg = {}
for key in keys:
sl = data[key]
b_oos = simulate(ExitPolicy, sl, {}, start_ms=OOS_START_MS)
s_oos = simulate(CloseConfirmSl, sl, params, start_ms=OOS_START_MS)
lag_oos = simulate(CloseConfirmSlLag, sl, params, start_ms=OOS_START_MS)
del_oos = simulate_open_next(sl, params, start_ms=OOS_START_MS)
name = f"{key[0].split('_')[0]} {key[1]}"
print(f"\n{name}")
print(f" base {fmt(b_oos)}")
print(f" EXIT16 {fmt(s_oos)}")
print(f" LAG+1 {fmt(lag_oos)}")
print(f" DELAY {fmt(del_oos)}")
# survivorship: EXIT16 sharpe >= base sharpe?
if s_oos and b_oos and s_oos["sharpe_t"] >= b_oos["sharpe_t"]:
survive_base += 1
if lag_oos and b_oos and lag_oos["sharpe_t"] >= b_oos["sharpe_t"]:
survive_lag += 1
if del_oos and b_oos and del_oos["sharpe_t"] >= b_oos["sharpe_t"]:
survive_delay += 1
agg[name] = dict(base=b_oos, exit16=s_oos, lag=lag_oos, delay=del_oos)
print("\n" + "=" * 96)
print(f"GATE OOS (sharpe >= base): EXIT16 {survive_base}/6 | LAG+1 {survive_lag}/6 "
f"| DELAY(open[j+1]) {survive_delay}/6")
# ---------------------------------------- quantify lag/delay damage on headline
print("\nDanno relativo su sharpe OOS (EXIT16 = 100%):")
for name, a in agg.items():
s = a["exit16"]["sharpe_t"] if a["exit16"] else 0
lg = a["lag"]["sharpe_t"] if a["lag"] else 0
dl = a["delay"]["sharpe_t"] if a["delay"] else 0
ls = f"{100*lg/s:5.0f}%" if s else " n/a"
ds = f"{100*dl/s:5.0f}%" if s else " n/a"
print(f" {name:<10} sh{s:5.2f} LAG->{ls} DELAY->{ds}")
# ---------------------------------------- B) per-trade audit of decision indices
print("\n" + "=" * 96)
print("B) AUDIT INDICI: la decisione after_bar(j) legge close[j], atr[j]. "
"Verifico\n che simulate() chiami after_bar SOLO con j = i+step (mai > j corrente).")
# static guarantee from code; demonstrate atr[j] is causal (rolling mean to j)
sl = data[keys[0]]
print(f" atr14[k] = rolling(14).mean(TR) -> usa TR[k-13..k], tutti chiusi a k. OK")
print(f" close[j] noto al close del bar j. Nessun indice > j nella decisione. OK")
if __name__ == "__main__":
main()