"""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()