Files
PythagorasGoal/Old/scripts/analysis/ladder_sltp_study.py
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

128 lines
5.9 KiB
Python

"""LADDER SL/TP STUDY (2026-06-18) — i 3 passi pre-deploy + studio di SL e TP da aggiungere.
Contesto: dopo clean_feed.py i Price Ladder BTC/ETH sono candidati VERI (PROMOSSO, DD gate
2021+ ~11-15%), MA il tail REALE e' il 2018 (-44/-52%) che il gate (IDX 2021+) NON vede. SL/TP
sono la leva per domarlo. Prior del progetto: gli stop su mean-reversion sono falsi negativi
(EXIT-16/SH01/pairs z-stop) -> ma un grid in un BEAR sostenuto (2018, niente rimbalzo) e' il
caso in cui un catastrophe-SL genuinamente aiuta. Questo studio distingue i due regimi.
Fa i 3 passi:
1. VALUTAZIONE 2018-INCLUSIVE: metriche standalone su TUTTA la storia (2018+) + DD per anno
(il gate del progetto e' cieco al 2018; qui no).
2. FILL maker vs taker: il grid e' LIMIT -> su Deribit fill MAKER ~0%; confronto 0% vs 0.10%
RT (la harness e' conservativa). E' la preparazione backtest dello shadow ledger (la parte
live = deploy operativo a parte).
3. HALF-SIZE: il candidato finale a meta' size (prudenza coda).
E studia SL (sl_buf, = catastrophe stop sotto il range) x TP (tp_buf) sul tail 2018 vs edge 2021+.
uv run python scripts/analysis/ladder_sltp_study.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.analysis.ladder_search import regime_mask, _gate
from scripts.analysis.grid_game_gate import grid_mtm
OOS_DATE = pd.Timestamp("2024-10-12", tz="UTC")
def fm(eqd: pd.Series) -> dict:
"""Metriche su TUTTA la storia (2018+), niente reindex a IDX 2021+."""
def sh(s):
r = s.pct_change().fillna(0.0)
return float(r.mean() / r.std() * np.sqrt(365)) if r.std() > 0 else 0.0
def dd(s):
c = s / s.iloc[0]
return float(((c - c.cummax()) / c.cummax()).min() * 100)
oos = eqd[eqd.index >= OOS_DATE]
peryear = {int(y): round(dd(g), 1) for y, g in eqd.groupby(eqd.index.year)}
return {"full_sh": round(sh(eqd), 2), "full_dd": round(dd(eqd), 1),
"oos_sh": round(sh(oos), 2) if len(oos) > 5 else 0.0,
"dd2018": peryear.get(2018, 0.0), "dd2022": peryear.get(2022, 0.0),
"peryear": peryear}
def run(asset, tf, rd, ru, levels, sl_buf, tp_buf, max_bars, regime, tmax, fee_side=0.0005):
mask = regime_mask(asset, tf, trend_max=tmax) if regime == "range" else None
eqd, st = grid_mtm(asset, tf=tf, range_down=rd, range_up=ru, levels=levels,
sl_buf=sl_buf, tp_buf=tp_buf, max_bars=max_bars,
deploy_mask=mask, fee_side=fee_side)
m = fm(eqd); m["trades"] = st["trades"]; m["eqd"] = eqd
return m
# candidati base (i migliori del re-gate pulito)
BASES = {
"BTC 1h L6 range1.5": dict(asset="BTC", tf="1h", rd=0.20, ru=0.06, levels=6,
max_bars=720, regime="range", tmax=1.5),
"BTC 1h L3 none": dict(asset="BTC", tf="1h", rd=0.08, ru=0.06, levels=3,
max_bars=720, regime="none", tmax=2.0),
}
def sltp_sweep(name, base):
print(f"\n{'='*104}\n SL/TP SWEEP — {name} (full=2018+, dd2018=tail vero, oos=2024-10+; fee 0.10% RT)\n{'='*104}")
print(f" {'sl_buf':>7}{'tp_buf':>7}{'trades':>8}{'full_sh':>9}{'full_dd':>9}{'dd2018':>8}{'dd2022':>8}{'oos_sh':>8}")
best = None
for slb in (0.06, 0.08, 0.10, 0.12, 0.15, 0.20):
for tpb in (0.03, 0.05, 0.08):
m = run(**base, sl_buf=slb, tp_buf=tpb)
star = ""
# criterio: tail 2018 contenuto (>-25%) E oos edge preservato (sh>3) E full edge ok
if m["dd2018"] > -25 and m["oos_sh"] > 3 and m["full_sh"] > 1.5:
star = " <-- tail-capped + edge"
if best is None or m["dd2018"] > best[1]["dd2018"]:
best = ((slb, tpb), m)
print(f" {slb:>7.2f}{tpb:>7.2f}{m['trades']:>8}{m['full_sh']:>9.2f}"
f"{m['full_dd']:>9.1f}{m['dd2018']:>8.1f}{m['dd2022']:>8.1f}{m['oos_sh']:>8.2f}{star}")
return best
def main():
print("LADDER SL/TP STUDY — 3 passi pre-deploy + SL/TP da aggiungere\n")
# passo 1: valutazione 2018-inclusive dei due base (sl/tp correnti)
print("[1] VALUTAZIONE 2018-INCLUSIVE (sl/tp correnti) — DD per anno (il gate IDX2021+ e' cieco al 2018):")
for name, base in BASES.items():
m = run(**base, sl_buf=0.12, tp_buf=0.05)
print(f" {name:<22} full_sh {m['full_sh']:>5.2f} full_dd {m['full_dd']:>6.1f} "
f"oos_sh {m['oos_sh']:>5.2f} | DD/anno {m['peryear']}")
# passo SL/TP: sweep su entrambi
winners = {}
for name, base in BASES.items():
winners[name] = sltp_sweep(name, base)
# passo 2+3: maker vs taker + half-size + gate 2021+, sul miglior (sl,tp) del candidato regime-gated
name = "BTC 1h L6 range1.5"
w = winners.get(name)
print(f"\n{'='*104}\n [2+3] FILL maker/taker + HALF-SIZE + GATE 2021+ — {name}\n{'='*104}")
if w is None:
print(" nessun (sl,tp) ha cappato il tail 2018 sotto -25% mantenendo l'edge: vedi sweep sopra.")
# usa comunque lo sl piu' stretto per il confronto fill
(slb, tpb) = (0.06, 0.05)
else:
(slb, tpb), _ = w
print(f" miglior (sl_buf,tp_buf) per tail 2018 + edge = ({slb}, {tpb})")
base = BASES[name]
for fee, lab in ((0.0005, "taker 0.10% RT"), (0.0, "maker 0% (Deribit limit)")):
m = run(**base, sl_buf=slb, tp_buf=tpb, fee_side=fee)
g = _gate(m["eqd"])
print(f" fee={lab:<26} oos_sh {m['oos_sh']:>5.2f} dd2018 {m['dd2018']:>6.1f} "
f"gate½ {g['verdict_half']} (OOS {g['base_oos_sh']}->{g['half_oos_sh']}, corr {g['max_corr_existing']})")
print("\n NB half-size: il gate 'half' E' gia' a meta' size (vedi grid_game_gate). La coda 2018")
print(" standalone va dimezzata sul book a half. Lo shadow ledger reale (fill intrabar/maker)")
print(" resta il passo OPERATIVO finale, non backtestabile qui.")
if __name__ == "__main__":
main()