14522262e6
Reset del progetto su fondamenta verificate dopo la scoperta che l'intera libreria "validata OOS" era artefatto di feed contaminato (print fantasma del feed Cerbero TESTNET + storico Binance/USDT). - Storico ricostruito da Deribit MAINNET (ccxt pubblico, tokenless) e CERTIFICATO (certify_feed.py): BTC/ETH puliti su TUTTA la storia (mediana 2-6 bps vs Coinbase USD), integrita' OHLC + coerenza resample (maxΔ 0.00) + cross-venue OK. Alt esclusi (illiquidi/divergenti: LTC/DOGE 50-82% barre flat; XRP/BNB non certificabili). - Verdetto sul feed pulito: FADE / PAIRS / XS01 / TSM01 morti (ogni portafoglio Sharpe -2.3..-3.0, DD ~40%); solo SH01 e frammenti HONEST con segnale residuo, da ri-validare in isolamento. - Cleanup "restart pulito": strategie, stack live (src/live, src/portfolio, runner/executor, yml, docker), ~100 script ricerca/gate, waste/games/ portfolios, dati non certificati + cache e 60+ diari -> archiviati in Old/ (preservati, non cancellati). Diario consolidato in un unico documento. - Skeleton ricerca tenuto: Strategy ABC + indicatori + src/fractal + src/backtest/engine + load_data; tool dati certificati (rebuild_history, certify_feed, audit_feed, multi_source_check). - Universo dati ATTIVO: solo BTC/ETH (5m/15m/1h); guardrail fisico (load_data su alt -> FileNotFoundError). Esecuzione DISABILITATA, conto flat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
94 lines
3.4 KiB
Python
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)
|