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>
94 lines
3.8 KiB
Python
94 lines
3.8 KiB
Python
"""EXIT-05 — ratchet: stop a cricchetto sul profitto su close (high-water-mark).
|
|
|
|
Idea: una volta che il trade ha messo a segno profitto (su CLOSE), proteggiamo
|
|
una FRAZIONE f del profitto migliore raggiunto. hwm = miglior close a favore visto
|
|
finora; quando hwm e' a favore dell'entrata, lo stop si porta a
|
|
long : stop = entry + f*(hwm - entry)
|
|
short: stop = entry - f*(entry - hwm)
|
|
Lo stop e' SOLO-STRINGENTE (cricchetto): puo' solo avvicinarsi al prezzo, mai
|
|
allentarsi. Parte da sl0 (protezione iniziale invariata finche' il ratchet non
|
|
supera sl0).
|
|
|
|
Differenza dal trailing chandelier (EXIT-01): qui lo stop e' ancorato all'ENTRY
|
|
+ frazione del profitto su close (non al massimo high - k*ATR). Cattura profitto
|
|
in % del guadagno realizzato; con f<1 lascia un cuscinetto sotto l'hwm.
|
|
|
|
VARIANTI:
|
|
- keep_tp=True : il TP fisso tp0 resta (exit standard alla media) + ratchet di
|
|
backup sullo SL. horizon = max_bars.
|
|
- keep_tp=False: TP RIMOSSO -> ride puro protetto dal cricchetto, horizon=2*mb
|
|
(cap HARD_CAP). Qui il ratchet e' l'unica uscita oltre l'orizzonte.
|
|
|
|
ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
|
|
- hwm calcolato sui close[i .. j-1] (mantenuto incrementalmente, aggiornato col
|
|
bar j-1 prima di calcolare lo stop attivo nel bar j; close[i]=entry e' incluso
|
|
cosi' hwm>=entry sempre, lo stop non scende mai sotto entry una volta armato).
|
|
Nessun TP nella variante ride -> nessun fill parziale. after_bar non usato.
|
|
|
|
GRID: f in {0.3, 0.5, 0.7} x keep_tp in {True, False} (6 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 Ratchet(ExitPolicy):
|
|
name = "ratchet"
|
|
|
|
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, f=0.5, keep_tp=True, **params):
|
|
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
|
|
self.f = float(f)
|
|
self.keep_tp = bool(keep_tp)
|
|
self.close = ctx["close"]
|
|
if not self.keep_tp:
|
|
self.horizon = min(2 * mb, HARD_CAP)
|
|
# high-water-mark del close a favore sullo slice [i..j-1]; il primo bar
|
|
# valutato e' j=i+1 -> slice [i..i] = close[i] = entry (gia' incorporato).
|
|
self.hwm = self.close[i] # = entry
|
|
self._last_seen = i
|
|
self.armed = False # diventa True quando hwm > entry (a favore)
|
|
# stop a cricchetto: parte da sl0, puo' solo stringersi
|
|
self.cur_stop = sl0
|
|
|
|
def levels(self, j: int):
|
|
c = self.close
|
|
d = self.d
|
|
# incorpora i close fino a j-1 (causali, gia' chiusi al poll del bar j)
|
|
while self._last_seen < j - 1:
|
|
self._last_seen += 1
|
|
cv = c[self._last_seen]
|
|
if d == 1:
|
|
if cv > self.hwm:
|
|
self.hwm = cv
|
|
else:
|
|
if cv < self.hwm:
|
|
self.hwm = cv
|
|
tp = self.tp0 if self.keep_tp else None
|
|
# profitto su close del miglior bar a favore (>=0 perche' hwm parte da entry)
|
|
prof = (self.hwm - self.entry) * d
|
|
if prof > 0: # il trade e' andato a favore: arma il ratchet
|
|
self.armed = True
|
|
if d == 1:
|
|
cand = self.entry + self.f * (self.hwm - self.entry)
|
|
if cand > self.cur_stop: # cricchetto: lo stop long solo SALE
|
|
self.cur_stop = cand
|
|
else:
|
|
cand = self.entry - self.f * (self.entry - self.hwm)
|
|
if cand < self.cur_stop: # cricchetto: lo stop short solo SCENDE
|
|
self.cur_stop = cand
|
|
return tp, self.cur_stop, 1.0
|
|
|
|
|
|
GRID = [
|
|
{"f": f, "keep_tp": keep_tp}
|
|
for f in (0.3, 0.5, 0.7)
|
|
for keep_tp in (True, False)
|
|
]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
evaluate(Ratchet, GRID)
|