587fbc0f61
I 3 passi: [1] valutazione 2018-INCLUSIVE (il gate IDX2021+ e' cieco al 2018) -> il tail vero del BTC ladder e' il 2018 (-27.7% regime-gated / -50% none); [2] fill maker 0% vs taker 0.10% (Deribit = LIMIT = maker) -> maker leggermente MIGLIORE, harness conservativa, nessun fee-cliff; [3] half-size (la coda 2018 si dimezza sul book). STUDIO SL/TP: sweet spot sl_buf=0.10/tp_buf=0.03 cappa il 2018 a -23.5% (da -27.7) senza intaccare l'edge (oos 5.06). Lezione (conferma prior progetto): SL troppo stretto PEGGIORA (redeploy nel coltello = falso negativo MR), SL da solo senza regime-gate e' erratico -> il regime-gate e' il controllo PRIMARIO della coda, il SL moderato fine-tuna. Config finale: BTC 1h range1.5 rd0.20 ru0.06 L6 sl0.10 tp0.03 half-size, PROMOSSO (OOS 10.86->11.0, corr 0.195). Unico passo residuo: shadow ledger reale (operativo). Diario sez. 8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
128 lines
5.9 KiB
Python
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()
|