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:
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user