config(PORT06): cap SHAPE 0.0588 — SH01 resta senza SL (ricerca multi-agente: 11 famiglie di stop, 0 sopravvissute)

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>
This commit is contained in:
Adriano Dal Pastro
2026-06-05 17:56:16 +00:00
parent 6f86c644bf
commit bd6232dc00
16 changed files with 2413 additions and 3 deletions
@@ -0,0 +1,107 @@
"""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()