Files
Adriano Dal Pastro 14522262e6 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>
2026-06-19 15:20:59 +00:00

159 lines
6.5 KiB
Python

"""XS01 phase-tranching — PRE-REGISTRATO: il roll non-sovrapposto di XS01 (entry a i,
exit a i+hold, re-entry) ha una FASE arbitraria (dipende dal primo indice valido).
Se l'esito dipende dalla fase, c'e' timing-luck: dividere il book in K sub-book
sfasati di hold/K barre (capitale 1/K ciascuno) e' un ensemble di fase che riduce
la varianza SENZA parametri fittati (K=2 e K=3 riportati entrambi, nessun best-pick).
Test (griglia fissata qui, completa):
[1] SENSIBILITA' DI FASE: xsec_sim base con offset di partenza 0..hold-1
-> dispersione di Sharpe/ret/DD (FULL e OOS). Se la dispersione e' piccola,
il tranching non serve (verdetto onesto, fine).
[2] TRANCHING: K in {2, 3} sub-book sfasati equal-capital -> Sharpe/DD/ret
FULL e OOS vs la MEDIA e il WORST delle fasi singole.
Criterio (tutti necessari): il tranched riduce la dispersione (per costruzione) e
il suo Sharpe OOS >= worst-fase OOS con DD <= mediana fasi; fee identiche (il
tranching NON cambia il turnover per unita' di capitale).
uv run python scripts/analysis/xs01_tranche_research.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 scripts.strategies.XS01_cross_sectional import (
aligned_panel, UNIVERSE, LB, HOLD, FEE_RT, LEV, POS, OOS_FRAC)
def xsec_trades(phase: int = 0, lb: int = LB, hold: int = HOLD, fee_rt: float = FEE_RT,
split_frac: float = 0.0, M=None):
"""Replica ESATTA della logica di xsec_sim ma parte da max(lb, split)+phase e
ritorna la lista trade (i, j, net) — stessa matematica, fee = 2*fee_rt."""
C = M[UNIVERSE].values
n = len(C)
logC = np.log(C)
split = int(n * split_frac)
fee = 2 * fee_rt
out = []
last = -1
i = max(lb, split) + phase
while i < n - hold:
if i <= last:
i += 1
continue
dm = logC[i] - logC[i - lb]
dm = dm - dm.mean()
gw = np.sum(np.abs(dm))
if gw < 1e-9:
i += 1
continue
w = -dm / gw
book = float(np.sum(w * (logC[i + hold] - logC[i])))
out.append((i, i + hold, book - fee))
last = i + hold
i += 1
return out
def equity_from(trades, ts, pos=POS, lev=LEV, weight=1.0):
"""Equity compounding a punti-exit (convenzione xsec_sim), peso per tranche."""
cap = 1000.0
eq_ts, eq_v = [], []
for i, j, net in sorted(trades, key=lambda t: t[1]):
cap = max(cap + cap * pos * lev * net * weight, 10.0)
eq_ts.append(ts[j])
eq_v.append(cap)
return pd.Series(eq_v, index=pd.DatetimeIndex(eq_ts))
def metrics(trades, ts, yrs_span):
rets = [t[2] * POS for t in trades]
n = len(rets)
sharpe = float(np.mean(rets) / np.std(rets) * np.sqrt(n / yrs_span)) if n > 1 and np.std(rets) > 0 else 0.0
eq = equity_from(trades, ts)
pk = eq.cummax()
dd = float(((pk - eq) / pk).max() * 100) if len(eq) else 0.0
ret = float((eq.iloc[-1] / 1000 - 1) * 100) if len(eq) else 0.0
return dict(n=n, ret=ret, sharpe=sharpe, dd=dd)
def combined_metrics(branches, ts, yrs_span):
"""K tranche -> un'unica equity: pesa ogni trade 1/K sul capitale comune."""
K = len(branches)
allt = sorted([t for b in branches for t in b], key=lambda t: t[1])
cap = 1000.0
eq_ts, eq_v, rets = [], [], []
for i, j, net in allt:
rets.append(net * POS / K)
cap = max(cap + cap * POS * LEV * net / K, 10.0)
eq_ts.append(ts[j])
eq_v.append(cap)
eq = pd.Series(eq_v, index=pd.DatetimeIndex(eq_ts))
pk = eq.cummax()
n = len(rets)
sharpe = float(np.mean(rets) / np.std(rets) * np.sqrt(n / yrs_span)) if n > 1 and np.std(rets) > 0 else 0.0
return dict(n=n, ret=float((eq.iloc[-1] / 1000 - 1) * 100),
sharpe=sharpe, dd=float(((pk - eq) / pk).max() * 100))
def regression_lock(M=None):
"""xsec_trades(phase=0) DEVE riprodurre xsec_sim (n trade e ret totale):
la copia e' load-bearing (la usano gate e validate_xsec_worker) — senza
lock eseguito un fix all'engine canonico non si propagherebbe e la
validazione live certificherebbe un engine stantio (code-review 2026-06-11)."""
from scripts.strategies.XS01_cross_sectional import xsec_sim
if M is None:
M = aligned_panel()
ts = pd.to_datetime(M.index, unit="ms", utc=True)
tr = xsec_trades(phase=0, M=M)
eq = equity_from(tr, ts)
ref = xsec_sim()
n_ok = len(tr) == ref["trades"]
ret = (eq.iloc[-1] / 1000 - 1) * 100 if len(eq) else 0.0
ret_ok = abs(ret - ref["ret"]) < 1e-6 * max(1.0, abs(ref["ret"]))
if not (n_ok and ret_ok):
raise AssertionError(f"REGRESSION-LOCK FALLITO: trades {len(tr)} vs {ref['trades']}, "
f"ret {ret:.6f} vs {ref['ret']:.6f}")
print(f" regression-lock OK: {len(tr)} trade, ret {ret:+.2f}% == xsec_sim")
def run():
M = aligned_panel()
ts = pd.to_datetime(M.index, unit="ms", utc=True)
n = len(M)
print("=" * 96)
print(" XS01 phase-tranching — sensibilita' di fase + ensemble K=2/3 | griglia pre-registrata")
print("=" * 96)
regression_lock(M)
for tag, split in (("FULL", 0.0), ("OOS", 1 - OOS_FRAC)):
yrs_span = (ts[-1] - ts[max(int(n * split), LB)]).days / 365.25 or 1
rows = []
for ph in range(HOLD):
tr = xsec_trades(phase=ph, split_frac=split, M=M)
rows.append(metrics(tr, ts, yrs_span))
sh = np.array([r["sharpe"] for r in rows])
dd = np.array([r["dd"] for r in rows])
rt = np.array([r["ret"] for r in rows])
print(f"\n [{tag}] sensibilita' di fase (12 fasi):")
print(f" Sharpe: min {sh.min():.2f} | med {np.median(sh):.2f} | max {sh.max():.2f} | std {sh.std():.2f}")
print(f" ret%: min {rt.min():+.0f} | med {np.median(rt):+.0f} | max {rt.max():+.0f}")
print(f" DD%: min {dd.min():.1f} | med {np.median(dd):.1f} | max {dd.max():.1f}")
for K in (2, 3):
branches = [xsec_trades(phase=int(p), split_frac=split, M=M)
for p in np.linspace(0, HOLD, K, endpoint=False)]
m = combined_metrics(branches, ts, yrs_span)
print(f" K={K} tranched: Sharpe {m['sharpe']:.2f} | ret {m['ret']:+.0f}% | "
f"DD {m['dd']:.1f}% | trade {m['n']}")
print("\n criterio: tranched-Sharpe OOS >= worst-fase OOS e DD <= mediana fasi; "
"se la dispersione di fase e' gia' piccola -> NON SERVE (verdetto onesto).")
if __name__ == "__main__":
run()