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.8 KiB
Python
94 lines
3.8 KiB
Python
"""EXIT-05 — ratchet: stop a cricchetto sul profitto su close (high-water-mark).
|
|
|
|
Idea: una volta che il trade ha messo a segno profitto (su CLOSE), proteggiamo
|
|
una FRAZIONE f del profitto migliore raggiunto. hwm = miglior close a favore visto
|
|
finora; quando hwm e' a favore dell'entrata, lo stop si porta a
|
|
long : stop = entry + f*(hwm - entry)
|
|
short: stop = entry - f*(entry - hwm)
|
|
Lo stop e' SOLO-STRINGENTE (cricchetto): puo' solo avvicinarsi al prezzo, mai
|
|
allentarsi. Parte da sl0 (protezione iniziale invariata finche' il ratchet non
|
|
supera sl0).
|
|
|
|
Differenza dal trailing chandelier (EXIT-01): qui lo stop e' ancorato all'ENTRY
|
|
+ frazione del profitto su close (non al massimo high - k*ATR). Cattura profitto
|
|
in % del guadagno realizzato; con f<1 lascia un cuscinetto sotto l'hwm.
|
|
|
|
VARIANTI:
|
|
- keep_tp=True : il TP fisso tp0 resta (exit standard alla media) + ratchet di
|
|
backup sullo SL. horizon = max_bars.
|
|
- keep_tp=False: TP RIMOSSO -> ride puro protetto dal cricchetto, horizon=2*mb
|
|
(cap HARD_CAP). Qui il ratchet e' l'unica uscita oltre l'orizzonte.
|
|
|
|
ANTI-LOOK-AHEAD: levels(j) usa SOLO dati <= j-1:
|
|
- hwm calcolato sui close[i .. j-1] (mantenuto incrementalmente, aggiornato col
|
|
bar j-1 prima di calcolare lo stop attivo nel bar j; close[i]=entry e' incluso
|
|
cosi' hwm>=entry sempre, lo stop non scende mai sotto entry una volta armato).
|
|
Nessun TP nella variante ride -> nessun fill parziale. after_bar non usato.
|
|
|
|
GRID: f in {0.3, 0.5, 0.7} x keep_tp in {True, False} (6 celle).
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from exit_lab import ExitPolicy, evaluate, HARD_CAP # noqa: E402
|
|
|
|
|
|
class Ratchet(ExitPolicy):
|
|
name = "ratchet"
|
|
|
|
def __init__(self, ctx, i, d, entry, tp0, sl0, mb, f=0.5, keep_tp=True, **params):
|
|
super().__init__(ctx, i, d, entry, tp0, sl0, mb, **params)
|
|
self.f = float(f)
|
|
self.keep_tp = bool(keep_tp)
|
|
self.close = ctx["close"]
|
|
if not self.keep_tp:
|
|
self.horizon = min(2 * mb, HARD_CAP)
|
|
# high-water-mark del close a favore sullo slice [i..j-1]; il primo bar
|
|
# valutato e' j=i+1 -> slice [i..i] = close[i] = entry (gia' incorporato).
|
|
self.hwm = self.close[i] # = entry
|
|
self._last_seen = i
|
|
self.armed = False # diventa True quando hwm > entry (a favore)
|
|
# stop a cricchetto: parte da sl0, puo' solo stringersi
|
|
self.cur_stop = sl0
|
|
|
|
def levels(self, j: int):
|
|
c = self.close
|
|
d = self.d
|
|
# incorpora i close fino a j-1 (causali, gia' chiusi al poll del bar j)
|
|
while self._last_seen < j - 1:
|
|
self._last_seen += 1
|
|
cv = c[self._last_seen]
|
|
if d == 1:
|
|
if cv > self.hwm:
|
|
self.hwm = cv
|
|
else:
|
|
if cv < self.hwm:
|
|
self.hwm = cv
|
|
tp = self.tp0 if self.keep_tp else None
|
|
# profitto su close del miglior bar a favore (>=0 perche' hwm parte da entry)
|
|
prof = (self.hwm - self.entry) * d
|
|
if prof > 0: # il trade e' andato a favore: arma il ratchet
|
|
self.armed = True
|
|
if d == 1:
|
|
cand = self.entry + self.f * (self.hwm - self.entry)
|
|
if cand > self.cur_stop: # cricchetto: lo stop long solo SALE
|
|
self.cur_stop = cand
|
|
else:
|
|
cand = self.entry - self.f * (self.entry - self.hwm)
|
|
if cand < self.cur_stop: # cricchetto: lo stop short solo SCENDE
|
|
self.cur_stop = cand
|
|
return tp, self.cur_stop, 1.0
|
|
|
|
|
|
GRID = [
|
|
{"f": f, "keep_tp": keep_tp}
|
|
for f in (0.3, 0.5, 0.7)
|
|
for keep_tp in (True, False)
|
|
]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
evaluate(Ratchet, GRID)
|