"""EXIT-13 — hurst_exit: TP condizionato al REGIME (rolling-Hurst causale). IDEA. Le fade puntano alla MEDIA: in regime ANTI-PERSISTENTE (Hurst basso) la reversione tende a completarsi -> ha senso tenere il TP pieno tp0. In regime PERSISTENTE/trending (Hurst alto) la reversione spesso NON arriva fino in fondo (il movimento continua, lo stop-loss si concentra li': vedi loss-guard Hurst) -> conviene "prendere quel che c'e'": TP anticipato a meta' strada (entry+tp0)/2. - H[j-1] < h_lo (anti-persistente): tp(j) = tp0 (reversione completa) - H[j-1] >= h_lo (persistente): tp(j) = (entry+tp0)/2 (TP a meta') SL FISSO (sl0) e horizon (max_bars) invariati. ANTI-LOOK-AHEAD. prepare() precalcola H = rolling_hurst(close, window=100, step=6) una volta sullo sleeve. rolling_hurst e' causale: H[k] usa returns[k-window:k] e returns[m]=diff(log(close))[m] dipende da close[m+1], quindi H[k] dipende solo da close <= k. In levels(j) leggo H[j-1] -> solo dati <= j-1. SL/horizon invariati. La decisione del regime e' fissata col bar precedente, il bar j tocca i livelli. NB sul TP a meta'. Lo "step=6" significa che H e' costante a tratti di 6 barre; il regime e' ri-letto ogni bar (j-1) ma cambia valore ogni 6. Il TP a meta' strada e' SEMPRE >= breakeven per costruzione (e' fra entry e tp0, e tp0 e' gia' oltre il margine fee per come la fade lo fissa), quindi non maschera uscite in perdita. GRID: h_lo in {0.45, 0.50, 0.55} (3 celle). """ import sys from pathlib import Path import numpy as np sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from exit_lab import ExitPolicy, evaluate # noqa: E402 sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from src.fractal.indicators import rolling_hurst # noqa: E402 class HurstExit(ExitPolicy): name = "hurst_exit" @classmethod def prepare(cls, ctx, **params): if "hurst100" not in ctx: c = ctx["close"] ctx["hurst100"] = rolling_hurst(c, window=100, step=6) def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params): super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params) self.h = ctx["hurst100"] self.h_lo = float(params.get("h_lo", 0.50)) self.tp_half = (entry + tp0) / 2.0 # TP a meta' strada (persistente) def levels(self, j: int): # SOLO dati <= j-1 hv = self.h[j - 1] if not np.isfinite(hv): return self.tp0, self.sl0, 1.0 if hv < self.h_lo: tp = self.tp0 # anti-persistente: reversione completa else: tp = self.tp_half # persistente: prendi la meta' return tp, self.sl0, 1.0 GRID = [ {"h_lo": 0.45}, {"h_lo": 0.50}, {"h_lo": 0.55}, ] if __name__ == "__main__": evaluate(HurstExit, GRID)