ad65a0b344
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>
85 lines
3.3 KiB
Python
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)
|