bd6232dc00
Crash ETH 2026-06-05: SH01 ETH −15.6% su un trade (exit solo a orizzonte, nessuna protezione). Ricerca con harness dedicato sh01_exit_lab (cache walk-forward, engine fill gap-aware worse(livello,open), parity esatta con explore_lab, train<=2023-11-01): ATR intrabar/close-confirm, %, chandelier, breakeven, giveback, loser-timestop, disaster-cap close+intrabar, swing, vol-regime — NESSUNA passa il gate (ogni stop stretto rompe BTC, ogni stop largo non tocca la coda ETH; nei crash il fill e' al gap). Mitigazione: peso famiglia SHAPE 11.8%->5.9% in PORT06 (FULL 6.47->6.43 DD 4.10->3.96, OOS 8.82->8.58 DD 1.30->1.36) — la prossima coda impatta il conto per meta'. Regression-lock test aggiornato. Diario: docs/diary/2026-06-05-sh01-sl-research.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
204 lines
7.3 KiB
Python
204 lines
7.3 KiB
Python
"""SH01 exit policy 03 — pct_fixed.
|
|
|
|
SL fisso in PERCENTUALE del prezzo d'ingresso: sl = entry * (1 - d*p).
|
|
Griglia p in {0.01, 0.015, 0.02, 0.03, 0.04, 0.05}, modalita' {intrabar, close}
|
|
-> 12 celle. Il livello e' FISSO (deciso a open_trade su close[i]) -> nessun
|
|
look-ahead nei bar successivi (i livelli usano solo dati <= i).
|
|
|
|
Protocollo: grid SOLO sul train; plateau (>=3 celle adiacenti migliorative);
|
|
poi OOS una volta per la config scelta + le 2 vicine.
|
|
|
|
cd /opt/docker/PythagorasGoal && uv run python scripts/analysis/sh01_exit_policies/03_pct_fixed.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
sys.path.insert(0, "/opt/docker/PythagorasGoal")
|
|
|
|
from scripts.analysis.sh01_exit_lab import ( # noqa: E402
|
|
ASSETS, OOS_START_MS, ExitPolicy, load_sleeves, simulate,
|
|
)
|
|
|
|
|
|
class PctFixed(ExitPolicy):
|
|
"""SL fisso a una frazione p del prezzo d'ingresso."""
|
|
|
|
def __init__(self, p: float, mode: str = "intrabar"):
|
|
self.p = p
|
|
self.mode = mode
|
|
self.name = f"pct_fixed p={p:.3f} {mode}"
|
|
|
|
def open_trade(self, ctx, i, d):
|
|
entry = ctx["close"][i]
|
|
sl = entry * (1.0 - d * self.p) # long: sotto; short: sopra
|
|
return {"sl": sl}
|
|
|
|
def levels(self, ctx, i, d, j, st):
|
|
return st["sl"], self.mode
|
|
|
|
|
|
# ----------------------------------------------------------------------------- grid
|
|
|
|
P_GRID = [0.01, 0.015, 0.02, 0.03, 0.04, 0.05]
|
|
MODES = ["intrabar", "close"]
|
|
|
|
|
|
def _row(m):
|
|
return (f"ret={m['ret']:>+7.0f}% dd={m['dd']:>4.0f}% shrp={m['sharpe']:>5.2f} "
|
|
f"worst={m['worst']:>+5.1f}% stop={m['stop_rate']:>4.1f}%")
|
|
|
|
|
|
def main():
|
|
sleeves = load_sleeves()
|
|
|
|
# baseline (no stop)
|
|
print("=" * 110)
|
|
print("BASELINE (orizzonte puro, no SL) — TRAIN:")
|
|
base = {}
|
|
for a in ASSETS:
|
|
m = simulate(sleeves[a], ExitPolicy(), t_hi=OOS_START_MS)
|
|
base[a] = m
|
|
print(f" {a}: {_row(m)}")
|
|
print()
|
|
|
|
# ---------------- grid TRAIN only
|
|
print("=" * 110)
|
|
print("GRID — TRAIN ONLY (selezione qui):")
|
|
train = {}
|
|
for mode in MODES:
|
|
print(f"\n mode={mode}")
|
|
for p in P_GRID:
|
|
pol = PctFixed(p, mode)
|
|
row = {}
|
|
for a in ASSETS:
|
|
m = simulate(sleeves[a], pol, t_hi=OOS_START_MS)
|
|
row[a] = m
|
|
train[(mode, p)] = row
|
|
print(f" p={p:.3f} | BTC {_row(row['BTC'])}")
|
|
print(f" | ETH {_row(row['ETH'])}")
|
|
|
|
# improvement flags vs baseline on TRAIN: ETH gate (sharpe up, dd down, worst less neg)
|
|
# + BTC not degraded (sharpe>=0.95x, ret>=0.80x)
|
|
print("\n" + "=" * 110)
|
|
print("TRAIN improvement check (cell = migliorativa se ETH sharpe^ dd v worst^ AND BTC sharpe>=95% ret>=80%):")
|
|
bE, bB = base["ETH"], base["BTC"]
|
|
improved = {}
|
|
for mode in MODES:
|
|
flags = []
|
|
for p in P_GRID:
|
|
r = train[(mode, p)]
|
|
eth, btc = r["ETH"], r["BTC"]
|
|
eth_ok = (eth["sharpe"] > bE["sharpe"] and eth["dd"] < bE["dd"]
|
|
and eth["worst"] > bE["worst"])
|
|
btc_ok = (btc["sharpe"] >= 0.95 * bB["sharpe"]
|
|
and btc["ret"] >= 0.80 * bB["ret"])
|
|
cell = eth_ok and btc_ok
|
|
improved[(mode, p)] = cell
|
|
flags.append("Y" if cell else (".|E" if not eth_ok else ".|B"))
|
|
print(f" mode={mode:<9s} " + " ".join(f"p={p:.3f}:{f}" for p, f in zip(P_GRID, flags)))
|
|
|
|
# plateau detection: >=3 adjacent p's (same mode) all improved
|
|
print("\nPLATEAU (>=3 p adiacenti migliorativi nella stessa modalita'):")
|
|
plateau_cells = []
|
|
for mode in MODES:
|
|
run = []
|
|
runs = []
|
|
for p in P_GRID:
|
|
if improved[(mode, p)]:
|
|
run.append(p)
|
|
else:
|
|
if len(run) >= 1:
|
|
runs.append(run)
|
|
run = []
|
|
if run:
|
|
runs.append(run)
|
|
for run in runs:
|
|
mark = " <-- PLATEAU" if len(run) >= 3 else ""
|
|
print(f" mode={mode}: run {run} (len {len(run)}){mark}")
|
|
if len(run) >= 3:
|
|
plateau_cells.extend((mode, p) for p in run)
|
|
|
|
if not plateau_cells:
|
|
print("\nNESSUN PLATEAU sul train -> famiglia NON passa. OOS solo informativo.")
|
|
else:
|
|
print(f"\nplateau cells: {plateau_cells}")
|
|
|
|
# ---------------- pick best cell on TRAIN within plateau (or best overall if no plateau)
|
|
def score(cell):
|
|
r = train[cell]
|
|
# ETH train e' il banco di prova (baseline negativo) -> max ETH sharpe,
|
|
# tie-break ETH dd minore, poi BTC sharpe.
|
|
return (r["ETH"]["sharpe"], -r["ETH"]["dd"], r["BTC"]["sharpe"])
|
|
|
|
pool = plateau_cells if plateau_cells else list(train.keys())
|
|
best = max(pool, key=score)
|
|
print(f"\nCHOSEN (train): mode={best[0]} p={best[1]:.3f}")
|
|
|
|
# neighbors (same mode, adjacent p)
|
|
mode_b, p_b = best
|
|
idx = P_GRID.index(p_b)
|
|
neigh = [(mode_b, P_GRID[k]) for k in (idx - 1, idx, idx + 1) if 0 <= k < len(P_GRID)]
|
|
|
|
# ---------------- OOS verdict (chosen + 2 neighbors) — looked at ONCE
|
|
print("\n" + "=" * 110)
|
|
print("OOS VERDICT (config scelta + 2 vicine) — guardato UNA volta:")
|
|
print("\nBaseline OOS:")
|
|
base_oos = {}
|
|
for a in ASSETS:
|
|
m = simulate(sleeves[a], ExitPolicy(), t_lo=OOS_START_MS)
|
|
base_oos[a] = m
|
|
print(f" {a}: {_row(m)}")
|
|
|
|
chosen_oos = None
|
|
for cell in neigh:
|
|
pol = PctFixed(cell[1], cell[0])
|
|
tag = " <== CHOSEN" if cell == best else ""
|
|
print(f"\n mode={cell[0]} p={cell[1]:.3f}{tag}")
|
|
res = {}
|
|
for a in ASSETS:
|
|
tr = simulate(sleeves[a], pol, t_hi=OOS_START_MS)
|
|
oo = simulate(sleeves[a], pol, t_lo=OOS_START_MS)
|
|
res[a] = {"train": tr, "oos": oo}
|
|
print(f" {a} TRAIN {_row(tr)}")
|
|
print(f" {a} OOS {_row(oo)}")
|
|
if cell == best:
|
|
chosen_oos = res
|
|
|
|
# ---------------- gate evaluation on chosen
|
|
print("\n" + "=" * 110)
|
|
print("GATE (tutte e 4, train E oos):")
|
|
r = chosen_oos
|
|
bE_o, bB_o = base_oos["ETH"], base_oos["BTC"]
|
|
|
|
def g(label, cond):
|
|
print(f" [{'PASS' if cond else 'FAIL'}] {label}")
|
|
return cond
|
|
|
|
# a) ETH: sharpe^ dd v worst^ su train E oos
|
|
a_tr = (r["ETH"]["train"]["sharpe"] > bE["sharpe"]
|
|
and r["ETH"]["train"]["dd"] < bE["dd"]
|
|
and r["ETH"]["train"]["worst"] > bE["worst"])
|
|
a_oo = (r["ETH"]["oos"]["sharpe"] > bE_o["sharpe"]
|
|
and r["ETH"]["oos"]["dd"] < bE_o["dd"]
|
|
and r["ETH"]["oos"]["worst"] > bE_o["worst"])
|
|
A = g("a) ETH sharpe^ dd v worst^ (train E oos)", a_tr and a_oo)
|
|
# b) BTC sharpe>=95% ret>=80% baseline (train E oos)
|
|
b_tr = (r["BTC"]["train"]["sharpe"] >= 0.95 * bB["sharpe"]
|
|
and r["BTC"]["train"]["ret"] >= 0.80 * bB["ret"])
|
|
b_oo = (r["BTC"]["oos"]["sharpe"] >= 0.95 * bB_o["sharpe"]
|
|
and r["BTC"]["oos"]["ret"] >= 0.80 * bB_o["ret"])
|
|
B = g("b) BTC sharpe>=95% ret>=80% (train E oos)", b_tr and b_oo)
|
|
# c) ret ETH oos >= 80% baseline
|
|
C = g("c) ret ETH oos >= 80% baseline", r["ETH"]["oos"]["ret"] >= 0.80 * bE_o["ret"])
|
|
# d) plateau
|
|
D = g("d) plateau confermato", bool(plateau_cells) and best in plateau_cells)
|
|
|
|
passes = A and B and C and D
|
|
print(f"\n ==> GATE {'PASS' if passes else 'FAIL'}")
|
|
return passes
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|