"""EXIT-07 — TP dinamico che DECADE col tempo (verso breakeven). PRIOR (vincolante, ieri SCARTATO un ladder 80/20): il TP delle fade sta alla MEDIA, e li' il movimento e' esaurito -> il prezzo spesso NON arriva al TP e il trade muore a max_bars (close) o sullo SL. Idea di questa famiglia: invece di tenere il TP fisso al target ambizioso (la media), farlo DECADERE col passare delle barre verso un target piu' modesto (breakeven che copre le fee, o meta' strada). Cosi' un trade che s'e' avvicinato ma non ha toccato il TP pieno puo' essere chiuso in profitto/pareggio prima di scadere a max_bars potenzialmente in perdita. SL FISSO (sl0) invariato. horizon = max_bars invariato. TP attivo nel bar j: frac(j) = clamp( ((j - i) / mb) * speed , 0, 1 ) tp(j) = tp0 + frac(j) * (target_fin - tp0) - frac dipende SOLO da j (indice di tempo) e da costanti note all'entrata (i, mb, speed) -> NESSUN dato > j-1 -> anti-look-ahead OK per costruzione. - long (d=+1): tp0 > entry; target_fin <= tp0 -> il TP SCENDE verso entry. - short (d=-1): tp0 < entry; target_fin >= tp0 -> il TP SALE verso entry. - speed>1: il TP raggiunge il target finale prima della fine (clamp a 1). target_fin (param "target"): - "breakeven": entry*(1 + d*0.003) -> copre 0.3% (~ fee 0.10%RT + margine). Per long sta sopra entry, per short sotto: chiudere li' NON e' in perdita. - "halfway" : (entry + tp0)/2 -> meta' strada fra entry e il TP pieno. GRID: speed in {0.5, 1.0, 1.5} x target in {breakeven, halfway} (6 celle). """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from exit_lab import ExitPolicy, evaluate # noqa: E402 class TpDecay(ExitPolicy): name = "tp_decay" def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params): super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params) self.speed = float(params.get("speed", 1.0)) target = str(params.get("target", "breakeven")) if target == "breakeven": self.target_fin = entry * (1.0 + d * 0.003) elif target == "halfway": self.target_fin = 0.5 * (entry + tp0) else: raise ValueError(f"target sconosciuto: {target}") # mb puo' essere 0 in teoria; protezione self.mb_eff = max(int(mb), 1) def levels(self, j: int): # frac dipende SOLO dal tempo (j - i), mb, speed: costanti note all'entrata frac = ((j - self.i) / self.mb_eff) * self.speed if frac < 0.0: frac = 0.0 elif frac > 1.0: frac = 1.0 tp = self.tp0 + frac * (self.target_fin - self.tp0) return tp, self.sl0, 1.0 GRID = [ {"speed": 0.5, "target": "breakeven"}, {"speed": 1.0, "target": "breakeven"}, {"speed": 1.5, "target": "breakeven"}, {"speed": 0.5, "target": "halfway"}, {"speed": 1.0, "target": "halfway"}, {"speed": 1.5, "target": "halfway"}, ] if __name__ == "__main__": evaluate(TpDecay, GRID)