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,245 @@
|
||||
"""ADVERSARIAL VERIFY — EXIT-02 trail_atr_keep_tp, LENTE OVERFIT/ROBUSTEZZA.
|
||||
|
||||
Tesi del sopravvissuto: lo SL intrabar fisso distrugge valore nelle fade; il
|
||||
Chandelier trail (k=1.5) + TP fisso migliora Sharpe/DD ovunque (6/6 train, 5/6 OOS).
|
||||
|
||||
Ipotesi nulla del verificatore: e' un artefatto. Tre attacchi:
|
||||
(1) JITTER parametri: k vicini non provati (1.25/1.75) + ponte SL fisso a 3x/4x
|
||||
ATR (no_sl). Il plateau tiene o e' una cresta?
|
||||
(2) STABILITA' TEMPORALE: train 2018-20 vs 21-22, OOS 23-11/25-01 vs 25-01/26-05.
|
||||
Il miglioramento c'e' in OGNI finestra o concentrato in un regime?
|
||||
(3) DIPENDENZA HURST (decisivo): i segnali in cache hanno hurst_max=0.55 (toglie
|
||||
il regime trending). Rigenero i segnali SENZA hurst (hurst_max=None) IN MEMORIA
|
||||
(non tocco la cache) e ripeto base-vs-policy: la tesi "SL dannoso" regge anche
|
||||
dove gli stop servivano (regime persistente)?
|
||||
|
||||
cd /opt/docker/PythagorasGoal && PYTHONPATH=. uv run python \
|
||||
scripts/analysis/exit_policies/verify_02_overfit.py
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
HERE = Path(__file__).resolve()
|
||||
sys.path.insert(0, str(HERE.parents[1])) # scripts/analysis
|
||||
sys.path.insert(0, str(HERE.parents[3])) # project root
|
||||
|
||||
import exit_lab # noqa: E402
|
||||
from exit_lab import (ExitPolicy, simulate, load_sleeves, OOS_START_MS, # noqa: E402
|
||||
CODES, ASSETS, LIVE_PARAMS, _atr14)
|
||||
from importlib import import_module # noqa: E402
|
||||
|
||||
mod = import_module("exit_policies.02_trail_atr_keep_tp")
|
||||
TrailATRKeepTP = mod.TrailATRKeepTP
|
||||
|
||||
from src.data.downloader import load_data # noqa: E402
|
||||
from src.live.strategy_loader import load_strategy # noqa: E402
|
||||
|
||||
SLEEVE_KEYS = [(c, a) for c in CODES for a in ASSETS]
|
||||
|
||||
|
||||
# --------------------------------------------------------------- fixed-SL bridge
|
||||
class FixedSLmultATR(ExitPolicy):
|
||||
"""Ponte fra base (SL=sl0) e no_sl: SL fisso a m*ATR(entry) dall'entrata,
|
||||
TP fisso. Se il trail (k piccolo) batte uno SL fisso GIA' largo (3x/4x),
|
||||
allora il guadagno e' nel trailing, non solo nell'allontanare lo SL."""
|
||||
name = "fixed_sl_mult_atr"
|
||||
|
||||
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params):
|
||||
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
|
||||
m = float(params.get("m", 3.0))
|
||||
a = ctx["atr14"][i]
|
||||
if a is None or a != a:
|
||||
self.sl = sl0
|
||||
else:
|
||||
self.sl = entry - m * a if d == 1 else entry + m * a
|
||||
|
||||
def levels(self, j: int):
|
||||
return self.tp0, self.sl, 1.0
|
||||
|
||||
|
||||
# --------------------------------------------------------------- no-SL bridge
|
||||
class NoSL(ExitPolicy):
|
||||
"""Solo TP fisso + horizon, NESSUNO stop. Isola: il valore e' nel TOGLIERE
|
||||
lo stop (qualsiasi) o nel TRAIL dinamico? Se NoSL ~ trail, il driver e'
|
||||
'niente SL'; se il trail batte NoSL, il trail aggiunge."""
|
||||
name = "no_sl"
|
||||
|
||||
def levels(self, j: int):
|
||||
return self.tp0, None, 1.0
|
||||
|
||||
|
||||
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']:>6.2f} "
|
||||
f"n{r['trades']:>4} bars{r['avg_bars']:>5.1f}")
|
||||
|
||||
|
||||
def _summary(rows):
|
||||
"""rows: list of (sleeve_key, base_dict, pol_dict). Ritorna conteggi miglioramento."""
|
||||
sh_up = dd_dn = ret_up = n = 0
|
||||
for _, b, p in rows:
|
||||
if not b or not p:
|
||||
continue
|
||||
n += 1
|
||||
sh_up += p["sharpe_t"] > b["sharpe_t"]
|
||||
dd_dn += p["dd_pct"] < b["dd_pct"]
|
||||
ret_up += p["ret_pct"] > b["ret_pct"]
|
||||
return sh_up, dd_dn, ret_up, n
|
||||
|
||||
|
||||
# ============================================================= TEST 1: JITTER
|
||||
def test_jitter(data):
|
||||
print("\n" + "=" * 78)
|
||||
print("TEST 1 — JITTER: k vicini (1.25/1.75) + ponte SL fisso 3x/4x ATR + NoSL")
|
||||
print("=" * 78)
|
||||
print("\n[1a] Trail k in {1.25, 1.5, 1.75} — plateau o cresta? (OOS)")
|
||||
for k in (1.25, 1.5, 1.75):
|
||||
rows = []
|
||||
for key in SLEEVE_KEYS:
|
||||
sl = data[key]
|
||||
b = simulate(ExitPolicy, sl, start_ms=OOS_START_MS)
|
||||
p = simulate(TrailATRKeepTP, sl, {"k": k}, start_ms=OOS_START_MS)
|
||||
rows.append((key, b, p))
|
||||
sh, dd, ret, n = _summary(rows)
|
||||
print(f" k={k:<5} OOS: Sharpe-up {sh}/{n} DD-down {dd}/{n} ret-up {ret}/{n}")
|
||||
|
||||
print("\n[1b] SL fisso a m*ATR dall'entrata (m=3,4) — uno stop largo basta? (OOS)")
|
||||
for m in (3.0, 4.0):
|
||||
rows = []
|
||||
for key in SLEEVE_KEYS:
|
||||
sl = data[key]
|
||||
b = simulate(ExitPolicy, sl, start_ms=OOS_START_MS)
|
||||
p = simulate(FixedSLmultATR, sl, {"m": m}, start_ms=OOS_START_MS)
|
||||
rows.append((key, b, p))
|
||||
sh, dd, ret, n = _summary(rows)
|
||||
print(f" m={m:<5} OOS: Sharpe-up {sh}/{n} DD-down {dd}/{n} ret-up {ret}/{n}")
|
||||
|
||||
print("\n[1c] NoSL (solo TP+horizon) — il driver e' 'togliere lo SL'? (OOS)")
|
||||
rows = []
|
||||
for key in SLEEVE_KEYS:
|
||||
sl = data[key]
|
||||
b = simulate(ExitPolicy, sl, start_ms=OOS_START_MS)
|
||||
p = simulate(NoSL, sl, start_ms=OOS_START_MS)
|
||||
rows.append((key, b, p))
|
||||
sh, dd, ret, n = _summary(rows)
|
||||
print(f" NoSL OOS: Sharpe-up {sh}/{n} DD-down {dd}/{n} ret-up {ret}/{n}")
|
||||
|
||||
print("\n[1d] dettaglio per sleeve: base vs k=1.5 vs NoSL vs SLx3 (OOS)")
|
||||
for key in SLEEVE_KEYS:
|
||||
sl = data[key]
|
||||
b = simulate(ExitPolicy, sl, start_ms=OOS_START_MS)
|
||||
t = simulate(TrailATRKeepTP, sl, {"k": 1.5}, start_ms=OOS_START_MS)
|
||||
ns = simulate(NoSL, sl, start_ms=OOS_START_MS)
|
||||
f3 = simulate(FixedSLmultATR, sl, {"m": 3.0}, start_ms=OOS_START_MS)
|
||||
tag = f"{key[0].split('_')[0]} {key[1]}"
|
||||
print(f" {tag:<10} base sh{b.get('sharpe_t',0):>6.2f} | trail1.5 sh{t.get('sharpe_t',0):>6.2f} "
|
||||
f"| NoSL sh{ns.get('sharpe_t',0):>6.2f} | SLx3 sh{f3.get('sharpe_t',0):>6.2f}")
|
||||
|
||||
|
||||
# ============================================================= TEST 2: TEMPORAL
|
||||
def test_temporal(data):
|
||||
print("\n" + "=" * 78)
|
||||
print("TEST 2 — STABILITA' TEMPORALE (Sharpe base -> trail k=1.5)")
|
||||
print("=" * 78)
|
||||
W = [
|
||||
("train 2018-20", None, int(pd.Timestamp("2021-01-01", tz="UTC").value // 1e6)),
|
||||
("train 2021-22", int(pd.Timestamp("2021-01-01", tz="UTC").value // 1e6), OOS_START_MS),
|
||||
("OOS 23-11/25-01", OOS_START_MS, int(pd.Timestamp("2025-01-01", tz="UTC").value // 1e6)),
|
||||
("OOS 25-01/26-05", int(pd.Timestamp("2025-01-01", tz="UTC").value // 1e6), None),
|
||||
]
|
||||
for label, s, e in W:
|
||||
rows = []
|
||||
for key in SLEEVE_KEYS:
|
||||
sl = data[key]
|
||||
b = simulate(ExitPolicy, sl, start_ms=s, end_ms=e)
|
||||
p = simulate(TrailATRKeepTP, sl, {"k": 1.5}, start_ms=s, end_ms=e)
|
||||
rows.append((key, b, p))
|
||||
sh, dd, ret, n = _summary(rows)
|
||||
# mediana del delta-Sharpe
|
||||
deltas = [p["sharpe_t"] - b["sharpe_t"] for _, b, p in rows if b and p]
|
||||
med = float(np.median(deltas)) if deltas else 0.0
|
||||
print(f" {label:<20} Sharpe-up {sh}/{n} DD-down {dd}/{n} ret-up {ret}/{n} "
|
||||
f"median dSharpe {med:+.2f}")
|
||||
|
||||
|
||||
# ============================================================= TEST 3: HURST
|
||||
def _build_sleeves_no_hurst():
|
||||
"""Rigenera i segnali SENZA il loss-guard Hurst (hurst_max=None), IN MEMORIA.
|
||||
Replica esattamente load_sleeves() ma con LIVE_PARAMS modificati."""
|
||||
params = dict(LIVE_PARAMS)
|
||||
params["hurst_max"] = None
|
||||
out = {}
|
||||
for code in CODES:
|
||||
strat = load_strategy(code)
|
||||
for asset in ASSETS:
|
||||
df = load_data(asset, "1h")
|
||||
ts = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
|
||||
sigs = strat.generate_signals(df, ts, **params)
|
||||
h = df["high"].values.astype(float)
|
||||
l = df["low"].values.astype(float)
|
||||
c = df["close"].values.astype(float)
|
||||
out[(code, asset)] = {
|
||||
"signals": [(int(s.idx), int(s.direction), float(s.metadata["tp"]),
|
||||
float(s.metadata["sl"]), int(s.metadata["max_bars"]))
|
||||
for s in sigs],
|
||||
"open": df["open"].values.astype(float),
|
||||
"high": h, "low": l, "close": c,
|
||||
"ts_ms": df["timestamp"].values.astype(np.int64),
|
||||
"atr14": _atr14(h, l, c),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def test_hurst(data):
|
||||
print("\n" + "=" * 78)
|
||||
print("TEST 3 — DIPENDENZA DAL FILTRO HURST (decisivo)")
|
||||
print("=" * 78)
|
||||
print("Rigenero segnali con hurst_max=None (loss-guard OFF -> include il regime")
|
||||
print("trending/persistente dove gli stop dovrebbero servire). Confronto base->trail.")
|
||||
nh = _build_sleeves_no_hurst()
|
||||
|
||||
# quanti segnali in piu' (il guard ne toglieva)
|
||||
print("\n segnali: con-guard -> senza-guard")
|
||||
for key in SLEEVE_KEYS:
|
||||
ng = len(data[key]["signals"])
|
||||
nn = len(nh[key]["signals"])
|
||||
tag = f"{key[0].split('_')[0]} {key[1]}"
|
||||
print(f" {tag:<10} {ng:>4} -> {nn:>4} (+{nn-ng})")
|
||||
|
||||
for scope, s, e in [("TRAIN", None, OOS_START_MS), ("OOS", OOS_START_MS, None)]:
|
||||
print(f"\n [{scope}] base vs trail k=1.5 — SENZA hurst guard")
|
||||
rows = []
|
||||
for key in SLEEVE_KEYS:
|
||||
sl = nh[key]
|
||||
b = simulate(ExitPolicy, sl, start_ms=s, end_ms=e)
|
||||
p = simulate(TrailATRKeepTP, sl, {"k": 1.5}, start_ms=s, end_ms=e)
|
||||
rows.append((key, b, p))
|
||||
tag = f"{key[0].split('_')[0]} {key[1]}"
|
||||
print(f" {tag:<10} base {_fmt(b)}")
|
||||
print(f" {'':<10} trail{_fmt(p)}")
|
||||
sh, dd, ret, n = _summary(rows)
|
||||
print(f" --> Sharpe-up {sh}/{n} DD-down {dd}/{n} ret-up {ret}/{n}")
|
||||
|
||||
# contro-prova: con-guard sugli STESSI scope, per isolare l'effetto guard
|
||||
print("\n [CONTROLLO] stesso confronto CON hurst guard (cache):")
|
||||
for scope, s, e in [("TRAIN", None, OOS_START_MS), ("OOS", OOS_START_MS, None)]:
|
||||
rows = []
|
||||
for key in SLEEVE_KEYS:
|
||||
sl = data[key]
|
||||
b = simulate(ExitPolicy, sl, start_ms=s, end_ms=e)
|
||||
p = simulate(TrailATRKeepTP, sl, {"k": 1.5}, start_ms=s, end_ms=e)
|
||||
rows.append((key, b, p))
|
||||
sh, dd, ret, n = _summary(rows)
|
||||
print(f" [{scope}] con-guard --> Sharpe-up {sh}/{n} DD-down {dd}/{n} ret-up {ret}/{n}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
data = load_sleeves()
|
||||
test_jitter(data)
|
||||
test_temporal(data)
|
||||
test_hurst(data)
|
||||
print("\nDONE")
|
||||
Reference in New Issue
Block a user