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>
89 lines
3.4 KiB
Python
89 lines
3.4 KiB
Python
"""EXIT-19 — TP RIMOSSO, exit al CANALE DONCHIAN OPPOSTO (donchian_trail).
|
|
|
|
Idea: la fade attuale esce al TP (sulla media) + SL fisso (ATR dall'entrata) +
|
|
max_bars. Qui TOGLIAMO il TP e usiamo come unica uscita di prezzo uno STOP
|
|
DINAMICO ancorato al canale Donchian OPPOSTO alla direzione del trade:
|
|
|
|
long (d=1): stop(j) = min(low[j-n .. j-1]) (canale inferiore)
|
|
short (d=-1): stop(j) = max(high[j-n .. j-1]) (canale superiore)
|
|
|
|
Per un long che funziona (il prezzo risale verso la media) il canale inferiore
|
|
SALE bar dopo bar -> lo stop segue e blocca profitto; usciamo quando il prezzo
|
|
ritraccia sotto il minimo recente. Simmetrico short.
|
|
|
|
FLOOR a sl0 (mai PEGGIO dello SL originale): il livello attivo e' floorato alla
|
|
protezione iniziale -> non si allenta mai oltre sl0.
|
|
long : stop = max(channel_low, sl0)
|
|
short: stop = min(channel_high, sl0)
|
|
|
|
HORIZON = 4*mb (cap HARD_CAP=240): senza TP la posizione puo' restare a lungo,
|
|
quindi diamo molto piu' respiro al time-stop; l'uscita "naturale" e' il canale.
|
|
|
|
DIFFERENZA da EXIT-18 (swing_stop): qui (a) NON c'e' TP affatto (li' tp0 restava),
|
|
(b) niente cricchetto persistente: lo stop e' il canale RICALCOLATO ogni bar (puo'
|
|
anche allentarsi rispetto al bar prima, ma mai sotto sl0 grazie al floor),
|
|
(c) horizon esteso 4x. E' una uscita puramente trend-following/Donchian innestata
|
|
su un ingresso mean-reversion.
|
|
|
|
ANTI-LOOK-AHEAD (contratto): levels(j) usa SOLO dati con indice <= j-1:
|
|
- min/max sullo slice [j-n .. j-1] (lookback chiuso a j-1, lo[lo:hi] con hi=j);
|
|
- nessun dato del bar j entra nel livello attivo nel bar j;
|
|
- non si guarda mai high/low[j] per decidere lo stop attivo nel bar j.
|
|
|
|
PRIOR dal repo (ladder scartato): il TP della fade sta alla MEDIA, dove il
|
|
movimento e' esaurito; "il runner non corre". Quindi togliendo il TP rischiamo di
|
|
restare in posizione MENTRE il prezzo ristagna/rientra, pagando giveback e fee.
|
|
Il canale opposto dovrebbe limitare il giveback, ma la mean-reversion fa rientrare
|
|
il prezzo prima che il canale si stringa -> probabile uscita PEGGIORE del TP.
|
|
Lo misuriamo senza pregiudizio.
|
|
|
|
GRID: n in {10, 20, 30} (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
|
|
|
|
|
|
class DonchianTrail(ExitPolicy):
|
|
name = "donchian_trail"
|
|
|
|
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, n=20, **params):
|
|
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
|
|
self.n = int(n)
|
|
self.low = ctx["low"]
|
|
self.high = ctx["high"]
|
|
# TP rimosso, horizon esteso 4x (il cap a HARD_CAP lo applica l'engine)
|
|
self.horizon = 4 * mb
|
|
|
|
def levels(self, j: int):
|
|
d = self.d
|
|
# canale opposto causale: slice [lo : j] => indici <= j-1
|
|
lo = max(self.i, j - self.n)
|
|
hi = j
|
|
if hi <= lo:
|
|
# primo bar dopo l'entrata: nessuna finestra -> usa solo sl0 (no TP)
|
|
return None, self.sl0, 1.0
|
|
if d == 1:
|
|
ch = float(np.min(self.low[lo:hi]))
|
|
stop = max(ch, self.sl0) # floor: mai sotto sl0
|
|
else:
|
|
ch = float(np.max(self.high[lo:hi]))
|
|
stop = min(ch, self.sl0) # floor: mai sopra sl0
|
|
return None, stop, 1.0 # TP = None (rimosso)
|
|
|
|
|
|
GRID = [
|
|
{"n": 10},
|
|
{"n": 20},
|
|
{"n": 30},
|
|
]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
evaluate(DonchianTrail, GRID)
|