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>
100 lines
4.2 KiB
Python
100 lines
4.2 KiB
Python
"""EXIT-23 — sl_tp_ride: LOCK AL TP E CAVALCA.
|
|
|
|
Idea (variante stretta di EXIT-09). Le fade escono al TP fisso (alla media). Qui
|
|
si tiene il TP fisso ATTIVO come exit normale finche' il prezzo non lo SUPERA in
|
|
chiusura. Quando close[j-1] supera tp0 a favore:
|
|
- TP RIMOSSO (None): non si esce piu' al livello fisso;
|
|
- SL spostato esattamente a tp0 (il profitto del TP e' LOCKATO: non si esce mai
|
|
peggio del TP originale);
|
|
- da tp0 in poi un trail chandelier k*ATR ancorato all'estremo favorevole visto
|
|
dal bar di superamento in poi, SOLO-STRINGENTE (cricchetto, mai si allenta).
|
|
|
|
Differenza dal 09: qui il floor e' ESATTAMENTE tp0 (cosi' come in 09, ma 09 ha
|
|
horizon 3*mb fisso e griglia su k 1/2/3); qui il lock scatta subito al primo
|
|
superamento in chiusura e la griglia esplora ANCHE l'horizon. State machine:
|
|
|
|
FASE A (armed=False): tp=tp0, sl=sl0 -> identica a base.
|
|
FASE B (armed): tp=None, sl = max(tp0, fav_high - k*atr) (long, cricchetto)
|
|
= min(tp0, fav_low + k*atr) (short, cricchetto)
|
|
|
|
ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
|
|
- trigger di arma su close[j-1] (gia' chiuso al poll del bar j);
|
|
- estremo favorevole post-superamento su slice [arm_idx .. j-1] (incrementale);
|
|
- atr14[j-1] (indice causale).
|
|
Nessun fill parziale (tp_frac sempre 1.0). after_bar non usato.
|
|
|
|
GRID: k in {1.5, 2.5} x horizon in {2*mb, 4*mb} (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 # noqa: E402
|
|
|
|
|
|
class SlTpRide(ExitPolicy):
|
|
name = "sl_tp_ride"
|
|
|
|
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, k=2.5, hmult=4, **params):
|
|
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
|
|
self.k = float(k)
|
|
self.horizon = min(int(hmult) * mb, HARD_CAP)
|
|
self.close = ctx["close"]
|
|
self.high = ctx["high"]
|
|
self.low = ctx["low"]
|
|
self.atr = ctx["atr14"]
|
|
self.armed = False
|
|
self.arm_idx = None
|
|
self.fav_high = None
|
|
self.fav_low = None
|
|
self._last_seen = i # ultimo close esaminato per il trigger di arma
|
|
self._fav_seen = None # ultimo bar incorporato nell'estremo favorevole
|
|
self.cur_stop = None # stop trailing monotono in FASE B (floor tp0)
|
|
|
|
def levels(self, j: int):
|
|
c = self.close
|
|
d = self.d
|
|
# ---- FASE A: controlla l'arma sui close fino a j-1 (causali) -------------
|
|
if not self.armed:
|
|
while self._last_seen < j - 1:
|
|
self._last_seen += 1
|
|
cv = c[self._last_seen]
|
|
crossed = (cv > self.tp0) if d == 1 else (cv < self.tp0)
|
|
if crossed:
|
|
self.armed = True
|
|
self.arm_idx = self._last_seen
|
|
self.fav_high = self.high[self.arm_idx]
|
|
self.fav_low = self.low[self.arm_idx]
|
|
self._fav_seen = self.arm_idx
|
|
self.cur_stop = self.tp0 # SL spostato a tp0: profitto lockato
|
|
break
|
|
if not self.armed:
|
|
return self.tp0, self.sl0, 1.0 # FASE A: TP/SL fissi
|
|
|
|
# ---- FASE B: TP rimosso, trail chandelier con floor a tp0 ----------------
|
|
while self._fav_seen < j - 1:
|
|
self._fav_seen += 1
|
|
if self.high[self._fav_seen] > self.fav_high:
|
|
self.fav_high = self.high[self._fav_seen]
|
|
if self.low[self._fav_seen] < self.fav_low:
|
|
self.fav_low = self.low[self._fav_seen]
|
|
a = self.atr[j - 1]
|
|
if a == a: # non-NaN
|
|
if d == 1:
|
|
cand = max(self.fav_high - self.k * a, self.tp0)
|
|
if cand > self.cur_stop: # cricchetto: solo sale
|
|
self.cur_stop = cand
|
|
else:
|
|
cand = min(self.fav_low + self.k * a, self.tp0)
|
|
if cand < self.cur_stop: # cricchetto: solo scende
|
|
self.cur_stop = cand
|
|
return None, self.cur_stop, 1.0 # TP rimosso in FASE B
|
|
|
|
|
|
GRID = [{"k": k, "hmult": h} for k in (1.5, 2.5) for h in (2, 4)]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
evaluate(SlTpRide, GRID)
|