feat(fade): EXIT-16 close-confirm SL — stop solo se il CLOSE sfonda sl∓0.5·ATR14 (immune ai wick)

Param sl_confirm_atr (None = comportamento storico) in FadeStrategy.backtest,
MR01.backtest e StrategyWorker.tick; attivo live a 0.5 sulle 6 fade in _defs.py.
TP intrabar al livello e max_bars invariati; in modalita' confirm il TP ha
priorita' nel bar. Ricerca exit-lab (34 agenti, 3 lenti avversariali + tail
audit): gli stop intrabar da wick sono falsi negativi per le fade. PORT06
canonico: FULL Sharpe 6.47->7.84 DD 4.10->2.60, OOS 8.82->10.06 DD 1.30->1.15.
NB path grezzo non filtrato: ret esplode ma DD per-sleeve sale — la riduzione
DD vive nel config live (trend_max+hurst) + diversificazione, come misurato.
5 test nuovi (59 verdi).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-04 21:25:17 +00:00
parent ad65a0b344
commit a2579d21bc
6 changed files with 171 additions and 12 deletions
+31 -1
View File
@@ -10,6 +10,7 @@ import numpy as np
import pandas as pd
from src.strategies.base import Strategy, Signal
from src.strategies.fade_base import atr as _atr
from src.live.telegram_notifier import notify_event
from src.live.execution import ExecutionClient
@@ -80,6 +81,12 @@ class StrategyWorker:
self.tp: float = 0.0
self.sl: float = 0.0
self.max_bars: int = 0
# EXIT-16 close-confirm SL (2026-06-04, fade): se settato nei params dello
# sleeve, lo SL intrabar e' disattivato e lo stop scatta solo se il CLOSE
# sfonda sl di sl_confirm_atr*ATR14 (immune ai wick). TP intrabar invariato.
self.sl_confirm_atr: float | None = (
float(self.params["sl_confirm_atr"])
if self.params.get("sl_confirm_atr") else None)
# Fee dalla strategia (MR01 = 0.001 realistico Deribit), fallback al default modulo
self.fee_rt: float = float(getattr(strategy, "fee_rt", FEE_RT))
@@ -419,7 +426,30 @@ class StrategyWorker:
self.bars_held += 1
self.last_bar_ts = current_ts
if self.tp and self.sl:
if self.tp and self.sl and self.sl_confirm_atr:
# EXIT-16 close-confirm (2026-06-04): TP intrabar al livello come il
# backtest; lo SL scatta SOLO se il close sfonda sl ∓ buf*ATR14 — i
# wick che bucano lo stop e rientrano (l'overshoot che la fade fada)
# non stoppano piu'. Uscita stop al CLOSE (prezzo corrente), non al
# livello. PORT06: OOS Sharpe 8.82->10.06 (exit-lab, 34 agenti).
buf = self.sl_confirm_atr * float(_atr(df, 14)[-1])
if not np.isfinite(buf):
buf = 0.0
if self.direction == 1:
if bar_high >= self.tp:
self._close_position(self.tp, "take_profit")
elif current_price < self.sl - buf:
self._close_position(current_price, "stop_loss")
elif self.max_bars and self.bars_held >= self.max_bars:
self._close_position(current_price, "time_limit")
else:
if bar_low <= self.tp:
self._close_position(self.tp, "take_profit")
elif current_price > self.sl + buf:
self._close_position(current_price, "stop_loss")
elif self.max_bars and self.bars_held >= self.max_bars:
self._close_position(current_price, "time_limit")
elif self.tp and self.sl:
# Exit INTRABAR come il backtest: si controllano high/low della barra (non solo il
# close) e si esce AL LIVELLO tp/sl. SL prima (conservativo), poi TP, poi time-limit.
if self.direction == 1:
+22 -5
View File
@@ -74,6 +74,15 @@ class FadeStrategy(Strategy):
if not signals:
return None
# EXIT-16 close-confirm SL (2026-06-04): se settato, lo SL intrabar e'
# DISATTIVATO e lo stop scatta solo se il CLOSE sfonda sl ∓ buf*ATR14
# (immune ai wick: l'overshoot che buca lo stop e rientra e' esattamente
# il movimento che la fade vuole fadare). TP intrabar e max_bars invariati.
# PORT06: FULL Sharpe 6.47->7.84 DD 4.10->2.60, OOS 8.82->10.06 (exit-lab,
# 34 agenti). None = comportamento storico. Diario 2026-06-04-exit-lab.md.
sl_confirm = params.get("sl_confirm_atr")
a14 = atr(df, 14) if sl_confirm else None
h, l, c = df["high"].values, df["low"].values, df["close"].values
n = len(c)
fee = self.fee_rt * self.leverage
@@ -95,12 +104,20 @@ class FadeStrategy(Strategy):
j = i + step
if j >= n:
j = n - 1; exit_p = c[j]; break
hit_sl = (d == 1 and l[j] <= sl) or (d == -1 and h[j] >= sl)
hit_tp = (d == 1 and h[j] >= tp) or (d == -1 and l[j] <= tp)
if hit_sl: # conservativo: SL prima del TP nello stesso bar
exit_p = sl; break
if hit_tp:
exit_p = tp; break
if sl_confirm is None:
hit_sl = (d == 1 and l[j] <= sl) or (d == -1 and h[j] >= sl)
if hit_sl: # conservativo: SL prima del TP nello stesso bar
exit_p = sl; break
if hit_tp:
exit_p = tp; break
else:
# close-confirm: TP intrabar al livello; SL valutato sul CLOSE
if hit_tp:
exit_p = tp; break
buf = sl_confirm * a14[j]
if (d == 1 and c[j] < sl - buf) or (d == -1 and c[j] > sl + buf):
exit_p = c[j]; break
if step == mb:
exit_p = c[j]