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,335 @@
"""TAIL-RISK AUDIT of EXIT-22 (no_sl) and EXIT-16 (close_confirm_sl).
Hypothesis under test (to REFUTE if possible): removing/softening the intrabar SL
is an artifact whose hidden cost is catastrophic tail risk in crash regimes.
Sections:
(1) Per-trade MAE (maximum adverse excursion, intrabar, leverage 3, % of notional
ret terms == same units as engine `ret`) for base vs no_sl vs close_confirm.
Distribution p50/p95/p99/max per sleeve.
(2) Crash windows: 2020-03-12, 2021-05-19, 2022-06, 2022-11 (FTX). Trades OPEN in
those windows: realized loss + MAE under base / no_sl / close_confirm.
(3) Live worker -2% fallback: with no_sl the strategy emits tp but sl=0 -> the
worker branch `if self.tp and self.sl` is False, falls to `elif self.max_bars`
(fade have max_bars) -> PURE horizon exit, the -2% `else` branch NEVER fires.
So no_sl LIVE has NO stop at all. We simulate what a -2% price stop WOULD have
capped, to quantify the protection that is in fact ABSENT.
(4) Disaster SL at 3x / 4x the base SL distance, intrabar: does it keep almost all
the no_sl gain while cutting the tail?
Run: cd /opt/docker/PythagorasGoal && PYTHONPATH=. uv run python \
scripts/analysis/exit_policies/verify_tail_risk.py
"""
import sys
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import exit_lab as EL # noqa: E402
from exit_lab import ExitPolicy, simulate, load_sleeves, LEV, OOS_START_MS # noqa: E402
DATA = load_sleeves()
SLEEVES = list(DATA.items())
# --------------------------------------------------------------------------- helpers
def _replay_trades(policy_cls, sleeve, params=None, start_ms=None, end_ms=None):
"""Re-run the engine logic but COLLECT per-trade detail incl. MAE.
MAE = worst adverse excursion (in `ret` units == leverage*frac move on notional)
measured on the bars the trade is actually OPEN, from entry bar+1 up to and
INCLUDING the exit bar (the exit bar's wick counts: it's where SL would trigger).
"""
params = params or {}
h, l, c, ts = sleeve["high"], sleeve["low"], sleeve["close"], sleeve["ts_ms"]
n = len(c)
ctx = dict(sleeve)
policy_cls.prepare(ctx, **params)
fee = EL.FEE_RT * LEV
last_exit = -1
out = []
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 = policy_cls(ctx, i, d, entry, tp0, sl0, mb, **params)
horizon = min(int(pol.horizon), EL.HARD_CAP)
fills = []
remaining = 1.0
j = i
worst = 0.0 # most negative excursion in ret units
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
# adverse excursion of THIS bar (before any exit): worst intrabar price
adverse = l[j] if d == 1 else h[j]
exc = (adverse - entry) / entry * d * LEV
worst = min(worst, exc)
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):
fills.append((remaining, c[j])); remaining = 0.0
break
if step == horizon:
fills.append((remaining, c[j])); 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
last_exit = j
out.append({
"i": i, "j": j, "d": d, "entry": entry,
"ts_entry": ts[i], "ts_exit": ts[j],
"ret": ret, "mae": worst, "bars": j - i,
})
return out
def _pct(arr, q):
return np.percentile(arr, q) if len(arr) else float("nan")
# --------------------------------------------------------------------------- (1) MAE dist
def section1():
print("=" * 100)
print("(1) MAE DISTRIBUTION per sleeve (ret units = leverage*move on notional; "
"fee NOT included). Negative = adverse.")
print(" MAE is the worst intrabar excursion BEFORE exit. For base, SL caps it; "
"for no_sl/close_confirm it can run.")
print("=" * 100)
policies = _load_policies()
hdr = f"{'sleeve':<11}{'policy':<16}{'n':>5}{'p50':>9}{'p95':>9}{'p99':>9}{'max':>9}{'realP99':>10}{'realMax':>10}"
for (code, asset), sleeve in SLEEVES:
key = f"{code.split('_')[0]} {asset}"
print(f"\n--- {key}")
print(hdr)
for pname, (cls, prm) in policies.items():
tr = _replay_trades(cls, sleeve, prm)
mae = np.array([t["mae"] for t in tr]) * 100 # to %
rets = np.array([t["ret"] for t in tr]) * 100
print(f"{'':<11}{pname:<16}{len(tr):>5}"
f"{_pct(mae,50):>9.2f}{_pct(mae,5):>9.2f}{_pct(mae,1):>9.2f}{mae.min():>9.2f}"
f"{_pct(rets,1):>10.2f}{rets.min():>10.2f}")
def _load_policies():
"""Return {name: (cls, params)} for base, no_sl, close_confirm(buf0)."""
p22 = Path(__file__).resolve().parents[0] / "22_no_sl.py"
p16 = Path(__file__).resolve().parents[0] / "16_close_confirm_sl.py"
import importlib.util
def _load(path, attr):
spec = importlib.util.spec_from_file_location(path.stem, path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return getattr(mod, attr)
NoSl = _load(p22, "NoSl")
CloseConfirm = _load(p16, "CloseConfirmSl")
return {
"base": (ExitPolicy, {}),
"no_sl": (NoSl, {"mode": "none"}),
"close_confirm0": (CloseConfirm, {"buffer": 0.0}),
}
# --------------------------------------------------------------------------- (2) crashes
CRASHES = {
"2020-03-12 covid": ("2020-03-10", "2020-03-16"),
"2021-05-19 leverage": ("2021-05-17", "2021-05-23"),
"2022-06 3AC/Luna": ("2022-06-10", "2022-06-20"),
"2022-11 FTX": ("2022-11-07", "2022-11-12"),
}
def _ms(s):
return int(pd.Timestamp(s, tz="UTC").value // 1e6)
def section2():
print("\n" + "=" * 100)
print("(2) CRASH WINDOWS — trades OPEN (entry inside window) per policy: realized "
"loss (ret%) and MAE%.")
print("=" * 100)
policies = _load_policies()
for label, (a, b) in CRASHES.items():
lo, hi = _ms(a), _ms(b)
print(f"\n### {label} [{a} .. {b}]")
any_trade = False
for (code, asset), sleeve in SLEEVES:
key = f"{code.split('_')[0]} {asset}"
for pname, (cls, prm) in policies.items():
tr = _replay_trades(cls, sleeve, prm)
win = [t for t in tr if lo <= t["ts_entry"] <= hi]
if not win:
continue
any_trade = True
rets = np.array([t["ret"] for t in win]) * 100
mae = np.array([t["mae"] for t in win]) * 100
worst = min(win, key=lambda t: t["ret"])
print(f" {key:<11}{pname:<16}n{len(win):>3} "
f"sumRet{rets.sum():>8.2f}% worstRet{rets.min():>8.2f}% "
f"worstMAE{mae.min():>8.2f}% avgBars{np.mean([t['bars'] for t in win]):>5.1f}")
if not any_trade:
print(" (no trades opened in window across sleeves)")
# --------------------------------------------------------------------------- (3) -2% fallback
def section3():
print("\n" + "=" * 100)
print("(3) LIVE -2% FALLBACK on no_sl. NOTE: with no_sl the worker has NO stop at "
"all (branch analysis in module docstring).")
print(" Below = what a -2% PRICE stop (==-6% ret at lev3) WOULD cap if it WERE "
"wired. Compares no_sl ret vs a synthetic no_sl+2%stop.")
print("=" * 100)
NoSl = _load_policies()["no_sl"][0]
stop_ret = -0.02 * LEV # -2% price move * leverage = -6% on notional in ret units
hdr = f"{'sleeve':<11}{'no_sl ret%':>12}{'+2%stop ret%':>14}{'Δret pp':>10}{'n capped':>10}{'worst no_sl':>13}{'worst +2%':>11}"
print(hdr)
for (code, asset), sleeve in SLEEVES:
key = f"{code.split('_')[0]} {asset}"
tr = _replay_trades(NoSl, sleeve, {"mode": "none"})
# synthetic: if MAE breaches stop_ret, realize at stop_ret (approx; ignores
# that price may recover — that's the point: a -2% stop locks the loss).
base_rets = np.array([t["ret"] for t in tr])
capped = []
n_cap = 0
fee = EL.FEE_RT * LEV
for t in tr:
if t["mae"] <= stop_ret:
capped.append(stop_ret - fee) # stopped at -2% price, pay fee
n_cap += 1
else:
capped.append(t["ret"])
capped = np.array(capped)
def _compound(rets):
cap = 1000.0
for r in rets:
cap = max(cap + cap * EL.POS * r, 10.0)
return (cap / 1000.0 - 1) * 100
r_nosl = _compound(base_rets)
r_stop = _compound(capped)
print(f"{key:<11}{r_nosl:>12.0f}{r_stop:>14.0f}{r_stop - r_nosl:>10.1f}"
f"{n_cap:>10}{base_rets.min()*100:>13.2f}{capped.min()*100:>11.2f}")
# --------------------------------------------------------------------------- (4) disaster SL
class DisasterSl(ExitPolicy):
"""no_sl behaviour + a FAR intrabar disaster stop at `mult` x base SL distance."""
name = "disaster_sl"
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params):
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
mult = float(params.get("mult", 3.0))
self.sl = entry + mult * (sl0 - entry)
def levels(self, j: int):
return self.tp0, self.sl, 1.0
def _full_metrics(cls, sleeve, prm):
full = simulate(cls, sleeve, prm)
oos = simulate(cls, sleeve, prm, start_ms=OOS_START_MS)
return full, oos
def section4():
print("\n" + "=" * 100)
print("(4) DISASTER SL — no_sl + far intrabar stop at 3x / 4x base SL distance. "
"Keep the gain, cut the tail?")
print("=" * 100)
NoSl = _load_policies()["no_sl"][0]
hdr = (f"{'sleeve':<11}{'policy':<13}"
f"{'FULLret%':>10}{'FULLdd%':>9}{'FULLsh':>8}"
f"{'OOSret%':>9}{'OOSdd%':>8}{'OOSsh':>7}{'worstMAE%':>11}{'nStop':>7}")
print(hdr)
for (code, asset), sleeve in SLEEVES:
key = f"{code.split('_')[0]} {asset}"
rows = [
("base", ExitPolicy, {}),
("no_sl", NoSl, {"mode": "none"}),
("disaster3x", DisasterSl, {"mult": 3.0}),
("disaster4x", DisasterSl, {"mult": 4.0}),
]
print(f"--- {key}")
for pname, cls, prm in rows:
full, oos = _full_metrics(cls, sleeve, prm)
tr = _replay_trades(cls, sleeve, prm)
mae = np.array([t["mae"] for t in tr]) * 100
# count trades that hit the disaster stop (ret near the stop level)
n_stop = 0
if pname.startswith("disaster"):
mult = prm["mult"]
for t, raw in zip(tr, sleeve["signals"]):
pass
# simpler: a stop hit shows up as a large negative ret roughly = stop level
n_stop = int(np.sum(mae <= -2.0 * mult * LEV / LEV * 0)) # placeholder
print(f"{'':<11}{pname:<13}"
f"{full.get('ret_pct',0):>10.0f}{full.get('dd_pct',0):>9.2f}{full.get('sharpe_t',0):>8.2f}"
f"{oos.get('ret_pct',0):>9.0f}{oos.get('dd_pct',0):>8.2f}{oos.get('sharpe_t',0):>7.2f}"
f"{mae.min():>11.2f}{'':>7}")
# --------------------------------------------------------------------------- aggregate
def section5_aggregate():
"""Equal-weight aggregate across the 6 sleeves: does no_sl's tail blow up the
PORTFOLIO worst-trade vs disaster3x? Sum of per-sleeve compounded won't aggregate
DD honestly, so we report the WORST single-trade ret across all sleeves and the
99th pct of the pooled trade distribution."""
print("\n" + "=" * 100)
print("(5) POOLED TRADE DISTRIBUTION across all 6 sleeves (the real tail metric).")
print("=" * 100)
NoSl = _load_policies()["no_sl"][0]
cfgs = [
("base", ExitPolicy, {}),
("no_sl", NoSl, {"mode": "none"}),
("close_confirm0", _load_policies()["close_confirm0"][0], {"buffer": 0.0}),
("disaster3x", DisasterSl, {"mult": 3.0}),
("disaster4x", DisasterSl, {"mult": 4.0}),
]
print(f"{'policy':<16}{'n':>6}{'retP1%':>9}{'retMin%':>9}{'maeP1%':>9}{'maeMin%':>9}{'<-15%cnt':>10}")
for pname, cls, prm in cfgs:
allret, allmae = [], []
for (code, asset), sleeve in SLEEVES:
tr = _replay_trades(cls, sleeve, prm)
allret += [t["ret"] * 100 for t in tr]
allmae += [t["mae"] * 100 for t in tr]
ar = np.array(allret); am = np.array(allmae)
n_bad = int(np.sum(ar < -15.0))
print(f"{pname:<16}{len(ar):>6}{_pct(ar,1):>9.2f}{ar.min():>9.2f}"
f"{_pct(am,1):>9.2f}{am.min():>9.2f}{n_bad:>10}")
if __name__ == "__main__":
section1()
section2()
section3()
section4()
section5_aggregate()