Files
PythagorasGoal/scripts/analysis/exit_policies/11_z_overshoot.py
T
Adriano Dal Pastro ad65a0b344 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>
2026-06-04 21:16:58 +00:00

94 lines
3.4 KiB
Python

"""EXIT-11 — z_overshoot: TP OLTRE la media (overshoot di z_off deviazioni std).
PRIOR (vincolante): le fade puntano alla MEDIA. Oggi il TP e' la SMA_n al close[i-1].
Ipotesi di questa famiglia: la reversione spesso NON si ferma esattamente alla media,
ma la attraversa (overshoot). Spostando il TP un po' OLTRE la media nella direzione
del trade catturiamo l'overshoot quando c'e', al prezzo di mancare alcuni TP che si
fermano alla media (che poi rientrano o vengono presi da max_bars/SL).
tp(j) = SMA_n[j-1] + d * z_off * STD_n[j-1]
long (d=+1): si compra sotto la media -> TP = media + z_off*std (sopra la media: piu' lontano).
short (d=-1): si vende sopra la media -> TP = media - z_off*std (sotto la media: piu' lontano).
In ENTRAMBI i casi il TP si allontana di z_off*std OLTRE la media nella direzione
del profitto -> target piu' ambizioso. NB: lo z e' un overshoot ADDIZIONALE rispetto
alla media corrente; la media stessa si muove (come EXIT-10) quindi il target insegue
la media + overshoot.
CAP ONESTO (anti exit-in-perdita): se la media + overshoot finisce contro l'entry
(media e' gia' scappata oltre entry contro di noi), il TP non deve diventare un'uscita
in perdita mascherata. Floor a breakeven+fee come EXIT-10:
long : tp(j) = max(tp(j), entry*(1+0.002))
short: tp(j) = min(tp(j), entry*(1-0.002))
SL FISSO (sl0) invariato; horizon = max_bars invariato.
ANTI-LOOK-AHEAD: prepare() precalcola sma_n = rolling(n).mean() e std_n =
rolling(n).std(ddof=0) su close (causali: ogni valore dipende solo da close <= indice).
In levels(j) si legge sma_n[j-1] e std_n[j-1] -> solo dati <= j-1, mai il bar j.
SL e horizon invariati. OK per costruzione.
GRID: n in {20, 50} x z_off in {0.25, 0.5} (4 celle).
"""
import sys
from pathlib import Path
import numpy as np
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from exit_lab import ExitPolicy, evaluate # noqa: E402
class ZOvershoot(ExitPolicy):
name = "z_overshoot"
@classmethod
def prepare(cls, ctx, **params):
n = int(params.get("n", 50))
kmean = f"sma_{n}"
kstd = f"std_{n}"
if kmean not in ctx or kstd not in ctx:
c = pd.Series(ctx["close"])
ctx[kmean] = c.rolling(n).mean().values
ctx[kstd] = c.rolling(n).std(ddof=0).values
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, **params):
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
n = int(params.get("n", 50))
self.z_off = float(params.get("z_off", 0.5))
self.sma = ctx[f"sma_{n}"]
self.std = ctx[f"std_{n}"]
# cap onesto: il TP non puo' diventare un'uscita in perdita
if d == 1:
self.cap = entry * (1.0 + 0.002)
else:
self.cap = entry * (1.0 - 0.002)
def levels(self, j: int):
# SOLO dati <= j-1
m = self.sma[j - 1]
s = self.std[j - 1]
if not (np.isfinite(m) and np.isfinite(s)):
# warmup non pronto -> ricadi sul TP fisso d'entrata
return self.tp0, self.sl0, 1.0
tp = m + self.d * self.z_off * s
if self.d == 1:
tp = max(tp, self.cap)
else:
tp = min(tp, self.cap)
return tp, self.sl0, 1.0
GRID = [
{"n": 20, "z_off": 0.25},
{"n": 20, "z_off": 0.5},
{"n": 50, "z_off": 0.25},
{"n": 50, "z_off": 0.5},
]
if __name__ == "__main__":
evaluate(ZOvershoot, GRID)