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>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""EXIT-09 — tp_extend_momentum: cavalca il momentum OLTRE il TP.
|
||||
|
||||
Idea: le fade escono al TP fisso (alla media). Ma quando il movimento NON e'
|
||||
esaurito e sfonda il TP in chiusura, vogliamo provare a cavalcarlo invece di
|
||||
prendere solo il TP. State machine a 2 fasi:
|
||||
|
||||
FASE A (default, TP intatto): tp = tp0, sl = sl0. Exit standard alla media.
|
||||
Finche' close[j-1] NON ha superato tp0 in chiusura, ci si comporta come base.
|
||||
|
||||
FASE B (armata quando close[j-1] supera tp0 a favore):
|
||||
- TP RIMOSSO (None): non si esce piu' al livello fisso;
|
||||
- attiva un trail "chandelier" a k*ATR ancorato all'ESTREMO favorevole visto
|
||||
DAL bar di superamento in poi (high per long, low per short);
|
||||
- FLOOR a tp0: lo stop trailing non scende mai sotto tp0 (long) / non sale mai
|
||||
sopra tp0 (short), cosi' NON si esce MAI peggio del TP originale. Lo stop e'
|
||||
inoltre solo-stringente (cricchetto): puo' solo avvicinarsi al prezzo.
|
||||
|
||||
Trigger di arma (su close[j-1], a favore):
|
||||
long : close[j-1] > tp0 -> arma
|
||||
short: close[j-1] < tp0 -> arma
|
||||
|
||||
Trail attivo (FASE B), con floor a tp0:
|
||||
long : stop = max( tp0, fav_high_post - k*atr14[j-1] ) (cricchetto: solo sale)
|
||||
short: stop = min( tp0, fav_low_post + k*atr14[j-1] ) (cricchetto: solo scende)
|
||||
|
||||
Orizzonte esteso a 3*mb (cap HARD_CAP=240) per dare spazio al runner post-TP.
|
||||
|
||||
ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
|
||||
- il trigger di arma legge close[j-1] (gia' chiuso al poll del bar j);
|
||||
- l'estremo favorevole post-superamento e' sullo slice [arm_idx .. j-1]
|
||||
(mantenuto incrementalmente, incorporando i bar fino a j-1);
|
||||
- atr14[j-1] (indice causale).
|
||||
Nessun fill parziale (tp_frac sempre 1.0). after_bar non usato.
|
||||
|
||||
GRID: k in {1.0, 2.0, 3.0} (3 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 TpExtendMomentum(ExitPolicy):
|
||||
name = "tp_extend_momentum"
|
||||
|
||||
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, k=2.0, **params):
|
||||
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
|
||||
self.k = float(k)
|
||||
self.horizon = min(3 * mb, HARD_CAP)
|
||||
self.close = ctx["close"]
|
||||
self.high = ctx["high"]
|
||||
self.low = ctx["low"]
|
||||
self.atr = ctx["atr14"]
|
||||
# FASE A finche' armed=False. Si arma quando close[j-1] supera tp0 a favore.
|
||||
self.armed = False
|
||||
self.arm_idx = None # bar dal quale si ancora l'estremo favorevole
|
||||
self.fav_high = None # estremo favorevole post-superamento (long)
|
||||
self.fav_low = None # estremo favorevole post-superamento (short)
|
||||
self._last_seen = i # ultimo close gia' esaminato per il trigger
|
||||
self._fav_seen = None # ultimo bar gia' incorporato nell'estremo
|
||||
# stop trailing monotono in FASE B: parte dal floor tp0, solo si stringe
|
||||
self.cur_stop = None
|
||||
|
||||
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:
|
||||
# arma: ancora l'estremo favorevole DAL bar di superamento
|
||||
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 # floor di partenza = tp0
|
||||
break
|
||||
if not self.armed:
|
||||
return self.tp0, self.sl0, 1.0 # ancora FASE A: TP/SL fissi
|
||||
|
||||
# ---- FASE B -> TP rimosso, trail chandelier ancorato post-superamento -----
|
||||
# incorpora i bar [arm_idx .. j-1] nell'estremo favorevole (dati causali)
|
||||
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 = self.fav_high - self.k * a
|
||||
cand = max(cand, self.tp0) # floor: mai sotto il TP
|
||||
if cand > self.cur_stop: # cricchetto: solo sale
|
||||
self.cur_stop = cand
|
||||
else:
|
||||
cand = self.fav_low + self.k * a
|
||||
cand = min(cand, self.tp0) # floor: mai sopra il TP
|
||||
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} for k in (1.0, 2.0, 3.0)]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
evaluate(TpExtendMomentum, GRID)
|
||||
Reference in New Issue
Block a user