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,198 @@
|
||||
"""VERIFY EXIT-16 close_confirm_sl — lente OVERFIT/ROBUSTEZZA (avversariale).
|
||||
|
||||
Ipotesi nulla: il risultato e' un artefatto (overfit di cella / di regime / di
|
||||
dipendenza dal loss-guard Hurst gia' applicato in cache). Tre test:
|
||||
|
||||
(1) JITTER PARAMETRI: buffer fuori griglia {0.4, 0.6, 0.75, 1.0} + ponte verso la
|
||||
base con SL fisso a 3x/4x ATR (no_sl come limite). Il plateau tiene?
|
||||
(2) STABILITA' TEMPORALE: train 2018-20 vs 21-22; OOS 2023-11/2025-01 vs
|
||||
2025-01/2026-05. Il miglioramento e' in OGNI finestra o concentrato?
|
||||
(3) DIPENDENZA HURST (decisivo): rigenero i segnali con hurst_max=None (NESSUN
|
||||
loss-guard, NON tocco la cache) e ripeto base-vs-policy. Se senza il guard la
|
||||
policy crolla, funziona SOLO grazie al guard -> condizione di validita'.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[3]))
|
||||
|
||||
import exit_lab # noqa: E402
|
||||
from exit_lab import ExitPolicy, simulate, OOS_START_MS, _atr14 # noqa: E402
|
||||
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"cc16", str(Path(__file__).resolve().parent / "16_close_confirm_sl.py"))
|
||||
cc16 = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(cc16)
|
||||
CloseConfirmSl = cc16.CloseConfirmSl
|
||||
|
||||
from src.data.downloader import load_data # noqa: E402
|
||||
from src.live.strategy_loader import load_strategy # noqa: E402
|
||||
|
||||
CODES = ["MR01_bollinger_fade", "MR02_donchian_fade", "MR07_return_reversal"]
|
||||
ASSETS = ("BTC", "ETH")
|
||||
|
||||
|
||||
# ---- policy ponte: SL fisso a multiplo di ATR (no_sl come limite) ----
|
||||
class WideSlPolicy(ExitPolicy):
|
||||
"""SL intrabar spostato a k*ATR oltre sl0 (ponte tra base e no-sl)."""
|
||||
name = "wide_sl"
|
||||
|
||||
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params):
|
||||
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
|
||||
self.k = float(params.get("k_atr", 2.0))
|
||||
self.atr = ctx["atr14"]
|
||||
|
||||
def levels(self, j: int):
|
||||
a = self.atr[j - 1] if np.isfinite(self.atr[j - 1]) else 0.0
|
||||
# sl0 e' sotto (long) / sopra (short) l'entry; allarga di k*atr
|
||||
sl = self.sl0 - self.k * a if self.d == 1 else self.sl0 + self.k * a
|
||||
return self.tp0, sl, 1.0
|
||||
|
||||
|
||||
def sub(cls, sleeve, g, s, e):
|
||||
return simulate(cls, sleeve, g, start_ms=s, end_ms=e)
|
||||
|
||||
|
||||
def fmt(r):
|
||||
if not r:
|
||||
return " n/a"
|
||||
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 build_signals(hurst_max):
|
||||
"""Rigenera sleeve in memoria con hurst_max dato (None = no guard). NON tocca cache."""
|
||||
out = {}
|
||||
params = dict(trend_max=3.0, ema_long=200, hurst_max=hurst_max, min_tp_frac=0.0015)
|
||||
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 main():
|
||||
data = exit_lab.load_sleeves()
|
||||
keys = list(data.keys())
|
||||
|
||||
# ===== TEST 1: JITTER PARAMETRI =====
|
||||
print("=" * 100)
|
||||
print("TEST 1 — JITTER buffer fuori griglia + ponte WIDE-SL (OOS, dopo 2023-11)")
|
||||
print("=" * 100)
|
||||
jit_buffers = [0.4, 0.6, 0.75, 1.0]
|
||||
all_pos = True
|
||||
for (code, asset), sleeve in data.items():
|
||||
key = f"{code.split('_')[0]} {asset}"
|
||||
base = sub(ExitPolicy, sleeve, {}, OOS_START_MS, None)
|
||||
line = f"{key:<10} BASE {fmt(base)}"
|
||||
print(line)
|
||||
for b in jit_buffers:
|
||||
r = sub(CloseConfirmSl, sleeve, {"buffer": b}, OOS_START_MS, None)
|
||||
better = r and base and r["sharpe_t"] >= base["sharpe_t"] - 0.10
|
||||
all_pos &= bool(better)
|
||||
print(f" buf={b:<4} {fmt(r)} {'OK' if better else 'WORSE'}")
|
||||
print()
|
||||
print(f"JITTER buffer: tutte >= base-0.10 sharpe? {all_pos}\n")
|
||||
|
||||
print("-" * 100)
|
||||
print("PONTE WIDE-SL: SL intrabar fisso allargato a k*ATR (k grande -> verso no-sl)")
|
||||
print("-" * 100)
|
||||
for (code, asset), sleeve in data.items():
|
||||
key = f"{code.split('_')[0]} {asset}"
|
||||
base = sub(ExitPolicy, sleeve, {}, OOS_START_MS, None)
|
||||
print(f"{key:<10} BASE(k=0) {fmt(base)}")
|
||||
for k in [1.5, 3.0, 4.0]:
|
||||
r = sub(WideSlPolicy, sleeve, {"k_atr": k}, OOS_START_MS, None)
|
||||
print(f" k={k:<4} {fmt(r)}")
|
||||
print()
|
||||
|
||||
# ===== TEST 2: STABILITA' TEMPORALE =====
|
||||
print("=" * 100)
|
||||
print("TEST 2 — STABILITA' TEMPORALE (base vs policy buffer=0.5)")
|
||||
print("=" * 100)
|
||||
ms = lambda d: int(pd.Timestamp(d, tz="UTC").value // 1e6)
|
||||
windows = [
|
||||
("TRAIN 2018-20", None, ms("2021-01-01")),
|
||||
("TRAIN 2021-22", ms("2021-01-01"), OOS_START_MS),
|
||||
("OOS 23/11-25/01", OOS_START_MS, ms("2025-01-01")),
|
||||
("OOS 25/01-26/05", ms("2025-01-01"), None),
|
||||
]
|
||||
win_verdict = {w[0]: 0 for w in windows}
|
||||
win_total = {w[0]: 0 for w in windows}
|
||||
for (code, asset), sleeve in data.items():
|
||||
key = f"{code.split('_')[0]} {asset}"
|
||||
print(f"\n{key}")
|
||||
for wname, s, e in windows:
|
||||
b = sub(ExitPolicy, sleeve, {}, s, e)
|
||||
p = sub(CloseConfirmSl, sleeve, {"buffer": 0.5}, s, e)
|
||||
if b and p:
|
||||
win_total[wname] += 1
|
||||
# criterio: policy non peggio della base su sharpe (tol 0.15)
|
||||
imp = p["sharpe_t"] >= b["sharpe_t"] - 0.15
|
||||
win_verdict[wname] += int(imp)
|
||||
tag = "OK " if imp else "BAD"
|
||||
else:
|
||||
tag = "n/a"
|
||||
print(f" {wname:<18} base {fmt(b)}")
|
||||
print(f" {'':<18} pol {fmt(p)} -> {tag}")
|
||||
print("\nPer-finestra (policy >= base-0.15 sharpe):")
|
||||
for w in windows:
|
||||
wn = w[0]
|
||||
print(f" {wn:<18} {win_verdict[wn]}/{win_total[wn]} sleeve OK")
|
||||
|
||||
# ===== TEST 3: DIPENDENZA HURST (DECISIVO) =====
|
||||
print("\n" + "=" * 100)
|
||||
print("TEST 3 — DIPENDENZA dal loss-guard HURST (DECISIVO)")
|
||||
print("Rigenero i segnali con hurst_max=None (NO guard, regime trending incluso).")
|
||||
print("Se la policy crolla -> funziona SOLO grazie al guard.")
|
||||
print("=" * 100)
|
||||
print("Generazione segnali SENZA hurst (puo' richiedere ~1-2 min)...")
|
||||
data_nohurst = build_signals(hurst_max=None)
|
||||
|
||||
n_guard = sum(len(s["signals"]) for s in data.values())
|
||||
n_nohurst = sum(len(s["signals"]) for s in data_nohurst.values())
|
||||
print(f"Segnali totali: con guard {n_guard}, senza guard {n_nohurst} "
|
||||
f"(+{n_nohurst - n_guard} segnali in regime trending)\n")
|
||||
|
||||
holds = True
|
||||
for region_name, s, e in [("TRAIN", None, OOS_START_MS), ("OOS", OOS_START_MS, None)]:
|
||||
print(f"--- {region_name} (segnali SENZA hurst guard) ---")
|
||||
for (code, asset), sleeve in data_nohurst.items():
|
||||
key = f"{code.split('_')[0]} {asset}"
|
||||
b = sub(ExitPolicy, sleeve, {}, s, e)
|
||||
p = sub(CloseConfirmSl, sleeve, {"buffer": 0.5}, s, e)
|
||||
if b and p:
|
||||
imp = p["sharpe_t"] >= b["sharpe_t"] - 0.15
|
||||
ddimp = p["dd_pct"] <= b["dd_pct"] + 1.0
|
||||
holds &= bool(imp)
|
||||
tag = "OK " if imp else "POLICY WORSE"
|
||||
else:
|
||||
tag = "n/a"
|
||||
print(f" {key:<10} base {fmt(b)}")
|
||||
print(f" {'':<10} pol {fmt(p)} -> {tag}")
|
||||
print()
|
||||
print(f"TEST 3 verdict: policy regge SENZA il guard (>= base-0.15 sharpe ovunque)? {holds}")
|
||||
print("Se False -> la tesi 'SL dannoso' dipende dal guard (condizione di validita').")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user