Files
PythagorasGoal/scripts/analysis/exit_policies/06_sar_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

85 lines
3.3 KiB
Python

"""EXIT-06 — sar_trail: Parabolic SAR semplificato come trailing stop, TP rimosso.
Idea: come EXIT-01 (cavalcata pura senza TP), ma lo stop trailing non e' un
chandelier a k*ATR fisso bensi' un Parabolic SAR semplificato che ACCELERA: il
fattore af parte a 0.02 e cresce di af_step ad ogni NUOVO estremo favorevole
(cap af_max). Lo stop si avvicina sempre piu' in fretta al prezzo man mano che il
trade corre nella direzione giusta -> protegge i runner stringendo aggressivamente.
sar(j) = sar(j-1) + af * (ep - sar(j-1)) con ep = estremo favorevole (high long / low short)
stop attivo nel bar j = max(sl0, sar) long (min(sl0, sar) short)
af: 0.02 -> +af_step ad ogni nuovo ep -> cap af_max
TP RIMOSSO, horizon = 4*mb (cap HARD_CAP=240).
ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1. L'estremo favorevole `ep` e lo
stato SAR vengono aggiornati incorporando i bar fino a j-1 (gia' chiusi al poll
del bar j). sar(j-1) -> sar(j) usa ep aggiornato a j-1. Nessun dato del bar j.
Nessun TP -> nessun fill parziale; after_bar non usato (uscita solo a SL/orizzonte).
GRID: af_max in {0.1, 0.2} x af_step in {0.02, 0.04} (4 celle).
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from exit_lab import ExitPolicy, evaluate, HARD_CAP
class SarTrail(ExitPolicy):
name = "sar_trail"
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, af_max=0.2, af_step=0.02, **params):
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
self.af_max = float(af_max)
self.af_step = float(af_step)
self.horizon = min(4 * mb, HARD_CAP)
# estremo favorevole (ep) sullo slice [i..j-1]; inizializzato al bar di entrata
self.ep = ctx["high"][i] if d == 1 else ctx["low"][i]
self.af = 0.02
# SAR iniziale: lo stop di partenza (sl0). Il SAR converge verso ep dall'sl0.
self.sar = sl0
self._last_seen = i # ultimo indice gia' incorporato nello stato SAR
self.cur_stop = sl0 # stop monotono (solo si stringe)
def _step(self, idx):
"""Incorpora il bar `idx` (causale, <= j-1) aggiornando ep, af e sar."""
h = self.ctx["high"][idx]
l = self.ctx["low"][idx]
# avanza il SAR di un passo verso l'estremo favorevole corrente
self.sar = self.sar + self.af * (self.ep - self.sar)
# nuovo estremo favorevole? -> accelera
if self.d == 1:
if h > self.ep:
self.ep = h
self.af = min(self.af + self.af_step, self.af_max)
else:
if l < self.ep:
self.ep = l
self.af = min(self.af + self.af_step, self.af_max)
def levels(self, j):
# incorpora i bar fino a j-1 (gia' chiusi al poll del bar j)
while self._last_seen < j - 1:
self._last_seen += 1
self._step(self._last_seen)
if self.d == 1:
if self.sar > self.cur_stop: # lo stop long puo' solo SALIRE (stringersi)
self.cur_stop = self.sar
else:
if self.sar < self.cur_stop: # lo stop short puo' solo SCENDERE
self.cur_stop = self.sar
return None, self.cur_stop, 1.0 # TP rimosso
GRID = [
{"af_max": am, "af_step": st}
for am in (0.1, 0.2)
for st in (0.02, 0.04)
]
if __name__ == "__main__":
evaluate(SarTrail, GRID)