fix(live): EXIT-16 confirm-SL valutato sul close di barra COMPLETATA + alert STALE_FEED

Il worker valutava il confirm sul prezzo della candela IN FORMAZIONE ad ogni poll,
reintroducendo la wick-sensitivity che EXIT-16 elimina (audit crash ETH 2026-06-05:
2 stop su 3 erano wick-stop che il backtest validato non avrebbe preso in quel
momento). Ora il confirm usa SOLO il close dell'ultima barra completata (riga -1 =
candela in corso finche' now < ts[-1]+bar_ms), buf dall'ATR della stessa barra,
fill al prezzo corrente (~ stress lag_close_exit OK in exit-lab), TP intrabar
invariato. Concausa feed-gap: non mitigabile lato exit (fill reali ~ sim);
entry-guard post-flat TESTATA e BOCCIATA (skippare i segnali dopo barre flat
peggiora tutti gli sleeve ETH: la candela-gap e' l'overshoot che la fade fada).
Aggiunto alert Telegram STALE_FEED (>=2 barre 1h flat -> notifica + gap % al
risveglio, dedup per episodio, solo osservabilita').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-06-05 18:32:14 +00:00
parent ff2ff6de7d
commit 2da3457ca7
7 changed files with 243 additions and 5 deletions
+19 -5
View File
@@ -430,22 +430,36 @@ class StrategyWorker:
# 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])
# non stoppano piu'. PORT06: OOS Sharpe 8.82->10.06 (exit-lab, 34 agenti).
#
# FIX 2026-06-05: il confirm va valutato sul close di barra COMPLETATA,
# come nel backtest (fade_base: c[j] di bar chiusi) — NON sul prezzo
# della barra in formazione, che reintroduce la wick-sensitivity che
# EXIT-16 elimina (audit live: 2 stop su 3 del 2026-06-05 erano scattati
# su dip intrabar che il backtest avrebbe ignorato in quel momento).
# L'ultima riga del df e' la candela in corso se non e' ancora trascorsa
# la sua durata; il fill resta al prezzo corrente (lag di poll, stress
# lag_close_exit superato in exit-lab). Il buf usa l'ATR della stessa
# barra completata.
ts_arr = df["timestamp"].values.astype("int64")
bar_ms = int(np.median(np.diff(ts_arr[-50:]))) if len(ts_arr) > 1 else 0
now_ms = int(time.time() * 1000)
k = -1 if now_ms >= ts_arr[-1] + bar_ms else -2
confirm_close = float(c[k])
buf = self.sl_confirm_atr * float(_atr(df, 14)[k])
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:
elif confirm_close < 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:
elif confirm_close > 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")
+2
View File
@@ -17,6 +17,8 @@ NOTIFY_EVENTS = {
# esecuzione REALE (shadow su Deribit testnet)
"REAL_EXEC_LIVE", # primo ordine reale verificato di un worker (conferma "e' vivo")
"REAL_OPEN_FAIL", # un'apertura reale NON si e' verificata (problema da guardare)
"STALE_FEED", # feed flat/fermo da >= N barre 1h (worker ciechi: il prossimo
# prezzo reale puo' gappare, come ETH 2026-06-05 1655->1600)
}