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>
108 lines
3.7 KiB
Python
108 lines
3.7 KiB
Python
"""SH01 EXIT POLICY 07 — LOSER time-stop condizionale (after_bar).
|
|
|
|
Idea: SH01 esce a orizzonte fisso H=12. Se a un check-point intermedio j=i+m il
|
|
trade e' GIA' in perdita oltre una soglia, esci subito al close invece di tenere
|
|
fino a H. L'ipotesi (lezione exit-lab fade: tagliare i loser presto rischia di
|
|
tagliare anche i winner in drawdown temporaneo): su un orizzonte FISSO di 12 bar
|
|
forse un loser conclamato a meta' corsa raramente recupera, mentre i winner del
|
|
modello partono subito (asimmetria). Il time-stop e' UNA volta sola (al bar m),
|
|
non un trailing: non insegue il prezzo, condiziona solo l'uscita a un istante.
|
|
|
|
Regola (after_bar):
|
|
al bar j == i + m: se (close[j]-entry)/entry * d < -x * ATR14[i] / entry
|
|
esci al close del bar j.
|
|
Equivalente: directional_move[j] < -x*ATR14[i]. x=0.0 => esci se in QUALSIASI
|
|
perdita direzionale al bar m.
|
|
|
|
Griglia m in {2, 3, 4, 6} x x in {0.0, 0.5, 1.0}.
|
|
|
|
ANTI-LOOK-AHEAD: ATR14[i] e entry=close[i] fissati all'ingresso (dati <= i);
|
|
after_bar decide sul close del bar j (dati <= j, eseguibile al poll del tick).
|
|
Nessun indicatore al bar j stesso.
|
|
|
|
cd /opt/docker/PythagorasGoal && uv run python scripts/analysis/sh01_exit_policies/07_loser_timestop.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
|
|
sys.path.insert(0, "/opt/docker/PythagorasGoal")
|
|
|
|
from scripts.analysis.sh01_exit_lab import ( # noqa: E402
|
|
ExitPolicy, evaluate, load_sleeves, simulate, OOS_START_MS,
|
|
)
|
|
|
|
|
|
class LoserTimestop(ExitPolicy):
|
|
def __init__(self, m: int, x: float):
|
|
self.m = int(m)
|
|
self.x = float(x)
|
|
self.name = f"loser_timestop m={m} x={x:g}"
|
|
|
|
def open_trade(self, ctx: dict, i: int, d: int) -> dict:
|
|
return {
|
|
"entry": float(ctx["close"][i]),
|
|
"atr": float(ctx["atr14"][i]),
|
|
}
|
|
|
|
def after_bar(self, ctx: dict, i: int, d: int, j: int, st: dict) -> bool:
|
|
if j != i + self.m:
|
|
return False
|
|
move = (ctx["close"][j] - st["entry"]) * d # directional, in price
|
|
thresh = -self.x * st["atr"]
|
|
return move < thresh
|
|
|
|
|
|
GRID_M = [2, 3, 4, 6]
|
|
GRID_X = [0.0, 0.5, 1.0]
|
|
|
|
|
|
def _fmt(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}% n={m['trades']}")
|
|
|
|
|
|
def main():
|
|
sleeves = load_sleeves()
|
|
|
|
base = {}
|
|
for a in ("BTC", "ETH"):
|
|
ctx = sleeves[a]
|
|
base[a] = {
|
|
"train": simulate(ctx, ExitPolicy(), t_hi=OOS_START_MS),
|
|
"oos": simulate(ctx, ExitPolicy(), t_lo=OOS_START_MS),
|
|
}
|
|
|
|
print("=" * 110)
|
|
print("BASELINE (exit orizzonte puro):")
|
|
for a in ("BTC", "ETH"):
|
|
print(f" {a} TRAIN {_fmt(base[a]['train'])}")
|
|
print(f" {a} OOS {_fmt(base[a]['oos'])}")
|
|
|
|
print("=" * 110)
|
|
print("GRID — TRAIN ONLY (selezione parametri): m x rows")
|
|
train_res = {}
|
|
for mm in GRID_M:
|
|
for xx in GRID_X:
|
|
pol = LoserTimestop(mm, xx)
|
|
row = {}
|
|
for a in ("BTC", "ETH"):
|
|
row[a] = simulate(sleeves[a], pol, t_hi=OOS_START_MS)
|
|
train_res[(mm, xx)] = row
|
|
print(f" m={mm} x={xx:g} | BTC {_fmt(row['BTC'])}")
|
|
print(f" | ETH {_fmt(row['ETH'])}")
|
|
|
|
print("=" * 110)
|
|
print("OOS (verdetto, intera griglia per ispezione plateau):")
|
|
for mm in GRID_M:
|
|
for xx in GRID_X:
|
|
pol = LoserTimestop(mm, xx)
|
|
line_b = simulate(sleeves["BTC"], pol, t_lo=OOS_START_MS)
|
|
line_e = simulate(sleeves["ETH"], pol, t_lo=OOS_START_MS)
|
|
print(f" m={mm} x={xx:g} | BTC OOS {_fmt(line_b)}")
|
|
print(f" | ETH OOS {_fmt(line_e)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|