Files
PythagorasGoal/scripts/analysis/exit_policies/17_wide_sl_trail.py
T
Adriano Dal Pastro ad65a0b344 research(exit-lab): 34 agenti su exit dinamiche → EXIT-16 close-confirm SL PROMOSSO a livello PORT06
23 famiglie esplorate (harness condiviso exit_lab, train/OOS embargo nov-2023,
tutto lo storico 1h 2018-2026) + 10 verifiche avversariali + test PORT06.
'Cavalcare il prezzo' non esiste (4a conferma: oltre il TP=media non c'e' coda).
Scoperta: lo SL intrabar fisso e' il distruttore di valore n.1 delle fade
(stop da wick = falsi negativi). Forma robusta: SL solo su CHIUSURA oltre
sl0±0.5·ATR14 — PORT06 FULL Sharpe 6.47→7.84 DD 4.10→2.60, OOS 8.82→10.06.
Collaterali: bias gap-through dell'engine sugli stop stretti; ramo -2% del
worker morto con sl=0. Diario: docs/diary/2026-06-04-exit-lab.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:16:58 +00:00

87 lines
3.4 KiB
Python

"""EXIT-17 — wide_sl_trail: RESPIRA POI PROTEGGI.
Idea: l'SL fisso all'entrata (sl0) puo' essere troppo stretto -> stoppa fade che
poi sarebbero rientrate alla media. Qui ALLARGHIAMO l'SL iniziale a
sl_w = entry - w*(entry - sl0) (long, w>1 -> stop piu' lontano = piu' respiro)
sl_w = entry + w*(sl0 - entry) (short)
ma attiviamo SUBITO un trailing chandelier a k*ATR dall'estremo favorevole. Lo stop
attivo nel bar j e' il PIU' PROTETTIVO fra l'SL allargato e il chandelier:
long : sl = max(sl_w, max(high[i..j-1]) - k*ATR[j-1])
short: sl = min(sl_w, min(low[i..j-1]) + k*ATR[j-1])
TP fisso tp0 invariato; horizon = max_bars invariato.
Logica: finche' il prezzo NON corre a favore, il rischio e' l'SL allargato (respiro:
si tollera un ritracciamento iniziale piu' ampio nella speranza che la fade rientri).
Appena il prezzo corre a favore, il chandelier sale sopra sl_w e protegge il
profitto come un trail normale. Differenza da EXIT-02 (che usa max(sl0, chand)):
qui il floor iniziale e' PIU' LARGO di sl0, quindi early il rischio per-trade e'
maggiore (peggiora i loser veloci) ma si salvano fade che con sl0 sarebbero state
stoppate giusto prima del rientro.
ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1 (estremo favorevole su [i..j-1]
mantenuto incrementalmente fino a j-1; atr14[j-1]). after_bar non usato.
GRID: w in {1.5, 2.0} x k in {2.0, 3.0} (4 celle).
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from exit_lab import ExitPolicy, evaluate # noqa: E402
class WideSLTrail(ExitPolicy):
name = "wide_sl_trail"
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, w=1.5, k=2.0, **params):
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
self.w = float(w)
self.k = float(k)
self.high = ctx["high"]
self.low = ctx["low"]
self.atr = ctx["atr14"]
# SL iniziale ALLARGATO: piu' lontano dall'entrata (w>1)
if d == 1:
self.sl_w = entry - self.w * (entry - sl0)
else:
self.sl_w = entry + self.w * (sl0 - entry)
# estremo favorevole running (solo barre <= j-1); init = barra d'entrata i
self.run_hi = self.high[i]
self.run_lo = self.low[i]
self.last_seen = i
def _update_running(self, upto: int) -> None:
"""Incorpora le barre (last_seen, upto] nell'estremo favorevole. upto = j-1
-> NON tocca il bar j (anti-look-ahead)."""
while self.last_seen < upto:
self.last_seen += 1
if self.high[self.last_seen] > self.run_hi:
self.run_hi = self.high[self.last_seen]
if self.low[self.last_seen] < self.run_lo:
self.run_lo = self.low[self.last_seen]
def levels(self, j: int):
self._update_running(j - 1) # solo dati <= j-1
a = self.atr[j - 1]
if a is None or a != a: # NaN nei primi 14 bar -> usa l'SL allargato
return self.tp0, self.sl_w, 1.0
if self.d == 1:
chand = self.run_hi - self.k * a
sl = max(self.sl_w, chand) # il piu' protettivo (stop piu' alto)
else:
chand = self.run_lo + self.k * a
sl = min(self.sl_w, chand) # il piu' protettivo (stop piu' basso)
return self.tp0, sl, 1.0
GRID = [
{"w": w, "k": k}
for w in (1.5, 2.0)
for k in (2.0, 3.0)
]
if __name__ == "__main__":
evaluate(WideSLTrail, GRID)