ops(live): sorveglianza freschezza feed SKH + correzione dei docstring sulla latenza d'uscita
T1 (TP di SKH01 come limit resting on-book) NON e' stato implementato, per misura.
Leggendo il codice di produzione: TP01 e SKH01 tradano lo stesso strumento con una
sola posizione netta Deribit, quindi un ordine on-book al livello di SKH chiuderebbe
anche quota TP01. Misurati i due ostacoli: (A) segno compatibile nel 97% dei trade
che escono in TP = risolvibile; (B) divergenza modello/live apparentemente bloccante.
Ma (B) non esiste: resample_5m NON scarta il bin 230m in corso (21 barre 5m su 46
nell'ultimo bin) e _skyhook_positions ci itera dentro -> il live rileva gia' SL/TP
intra-barra, entro ~1h dal tocco. I docstring che dicevano "usa solo barre chiuse" e
"latenza fino alla chiusura della barra 230m" erano FALSI e hanno guidato tre analisi
(02/07, 24/07, T1). Corretti sul posto.
Conseguenza: la lente `hourly` sottostima il path live di +0.081 Sharpe FULL di book
(23/23 offset, banda appaiata); il live vero sta sopra il canonical sul FULL, e il fix
richiesto sarebbe un declassamento (+0.054 vs +0.081) in cambio di ordini parziali sul
netto in un percorso con soldi veri.
Cablato invece il problema vero trovato per strada: fresh_5m fallisce in SILENZIO
(fallback al certificato, rigenerato 1x/giorno) -> la latenza d'uscita di SKH01 passa
da ~1h a ~1 giorno senza segnalazione, e nessun controllo esistente scatta (il gate di
staleness guarda il feed di TP01). Stesso schema del feed-freeze del 14/07.
* livefeed.feed_age_minutes: pura, eta' dalla CHIUSURA della barra, None = non
misurata, clamp sugli skew d'orologio
* book_report espone skh_feed_age_min (max fra gli asset)
* book_execute stampa lo stato e allerta su Telegram sopra skh_feed_max_age_min (30m)
Scelta dichiarata: ALLERTA, NON blocca. Bloccare fermerebbe anche TP01 (nettato sullo
stesso strumento) per un guasto di rete; forzare SKH flat chiuderebbe posizioni buone
su un glitch.
Follow-up dichiarato: la stessa barra parziale tocca gli INGRESSI (ent[n-1] da breakout
non confermato). Disaccordo in 1 bin su 112, ma con 3 entry nel campione la taglia non
e' stimabile. E' un cambio di strategia, non di strumentazione -> misura dedicata prima.
Strategia, pesi e cadenza del cron INVARIATI. Nessun ordine inviato. Suite 266 verdi.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ def load_config() -> dict:
|
||||
cfg.setdefault("min_order_usd", 5.0)
|
||||
cfg.setdefault("disaster_sl_pct", 0.30)
|
||||
cfg.setdefault("max_data_age_days", 2.0)
|
||||
cfg.setdefault("skh_feed_max_age_min", 30.0)
|
||||
return cfg
|
||||
|
||||
|
||||
@@ -92,6 +93,29 @@ def _run():
|
||||
print(f" ⚠️ SKH FEED ERRORE (SKH forzato flat!): {r['skh_error']}")
|
||||
notify("⚠️ BOOK — SKH feed fallito (sleeve forzato flat)", {"error": r["skh_error"]})
|
||||
|
||||
# --- FRESCHEZZA del feed 5m usato per il segnale SKH (2026-07-26) --------------------------
|
||||
# `fresh_5m` ricade sul feed certificato IN SILENZIO se il fetch pubblico Deribit fallisce, e
|
||||
# il certificato lo ricostruisce il cron una volta al giorno. Non solleva, non logga: senza
|
||||
# questo controllo la latenza d'uscita di SKH01 passa da ~1h a ~1 GIORNO senza che nulla lo
|
||||
# dica. E' proprio la latenza d'uscita dove vive la qualita' del path live (misura 2026-07-26).
|
||||
# SCELTA DICHIARATA: allerta, NON blocca. Bloccare fermerebbe anche il ribilancio di TP01
|
||||
# (sono nettati sullo stesso strumento) per un guasto di rete; e forzare SKH flat chiuderebbe
|
||||
# posizioni buone su un glitch. La decisione resta all'operatore.
|
||||
skh_age = r.get("skh_feed_age_min")
|
||||
max_skh_age = float(cfg.get("skh_feed_max_age_min", 30.0))
|
||||
if skh_age is None:
|
||||
print(" ⚠️ freschezza feed SKH NON MISURATA (segnale su feed certificato?)")
|
||||
elif skh_age > max_skh_age:
|
||||
print(f" ⚠️ FEED SKH STANTIO: ultima barra 5m di {skh_age:.0f} min fa "
|
||||
f"(soglia {max_skh_age:.0f}) -> le uscite SKH sono in ritardo, NON blocco.")
|
||||
if do_execute:
|
||||
notify("⚠️ BOOK — feed SKH stantio (uscite in ritardo)",
|
||||
{"eta_min": round(skh_age), "soglia_min": round(max_skh_age),
|
||||
"effetto": "SL/TP di SKH01 rilevati in ritardo",
|
||||
"nota": "fresh_5m e' ricaduto sul feed certificato (fetch pubblico KO)"})
|
||||
else:
|
||||
print(f" feed SKH : fresco ({skh_age:.0f} min)")
|
||||
|
||||
if not r["online"]:
|
||||
print(" conto non leggibile (offline) -> stop, non eseguo a cieco.")
|
||||
if do_execute:
|
||||
|
||||
@@ -131,6 +131,50 @@ def sim_equity_exec(ltf: pd.DataFrame, ent: list, mode: str,
|
||||
entry = c[i] if mode == "canonical" else px_hour(close_ts[i])
|
||||
fee_entry = FEE_TAKER_SIDE
|
||||
|
||||
if mode == "fastdetect":
|
||||
# RILEVAZIONE INTRA-BARRA (proposta per il live, 2026-07-26): il livello SL/TP e'
|
||||
# fissato all'INGRESSO da barre chiuse, quindi osservare che il prezzo l'ha gia'
|
||||
# toccato NON e' look-ahead — e' cio' che fa qualunque stop reale. Oggi invece
|
||||
# `_skyhook_positions` guarda solo barre 230m CHIUSE, e il tocco resta invisibile
|
||||
# per una mediana di 115 minuti (p90 210).
|
||||
# Qui: si trova il tocco a 5m, e si esegue al PROSSIMO multiplo orario dopo il tocco
|
||||
# (non al livello: resta un'uscita a mercato del cron).
|
||||
t_end_bar = int(ltf["timestamp"].values[min(i + max_bars, n - 1)]) + MS_LTF
|
||||
a5 = int(np.searchsorted(ts5_close, close_ts[i], side="left"))
|
||||
b5 = int(np.searchsorted(ts5_close, t_end_bar, side="right"))
|
||||
hit_j = None
|
||||
for j5 in range(a5, min(b5 + 1, len(c5))):
|
||||
hs = (l5[j5] <= sl) if direction == 1 else (h5[j5] >= sl)
|
||||
ht = (h5[j5] >= tp) if direction == 1 else (l5[j5] <= tp)
|
||||
if hs or ht:
|
||||
hit_j = j5
|
||||
break
|
||||
# la barra 230m in cui cade il tocco resta quella che chiude il trade (stesse
|
||||
# boundaries del canonico: cambia SOLO il momento dell'esecuzione)
|
||||
for j in range(i + 1, min(i + max_bars + 1, n)):
|
||||
hit_sl = sl is not None and (
|
||||
(direction == 1 and lo[j] <= sl) or (direction == -1 and h[j] >= sl))
|
||||
hit_tp = tp is not None and (
|
||||
(direction == 1 and h[j] >= tp) or (direction == -1 and lo[j] <= tp))
|
||||
if hit_sl or hit_tp:
|
||||
exit_idx, kind = j, ("sl" if hit_sl else "tp")
|
||||
break
|
||||
exit_idx = j
|
||||
kind = "time"
|
||||
else:
|
||||
exit_idx = min(i + max_bars, n - 1)
|
||||
kind = "time"
|
||||
stats[kind] += 1
|
||||
if hit_j is not None and kind in ("sl", "tp"):
|
||||
exit_price = px_hour(int(ts5_close[hit_j])) # primo cron DOPO il tocco
|
||||
else:
|
||||
exit_price = px_hour(close_ts[exit_idx])
|
||||
gross = (exit_price - entry) / entry * direction
|
||||
capital = max(capital + capital * (gross - 2 * FEE_TAKER_SIDE), 1.0)
|
||||
equity[i:exit_idx + 1] = capital
|
||||
busy_until = exit_idx
|
||||
continue
|
||||
|
||||
# --- detection identica al canonico (SL prioritario)
|
||||
exit_idx = min(i + max_bars, n - 1)
|
||||
exit_lvl = c[exit_idx]
|
||||
@@ -218,9 +262,10 @@ def sh3(s: pd.Series) -> tuple[float, float, float, float]:
|
||||
return m_all["sharpe"], m_is["sharpe"], m_ho["sharpe"], m_all["maxdd"]
|
||||
|
||||
|
||||
MODES = ("canonical", "hourly", "onbook_tp", "onbook")
|
||||
MODES = ("canonical", "hourly", "fastdetect", "onbook_tp", "onbook")
|
||||
LABEL = {"canonical": "canonical (backtest, fill-al-livello)",
|
||||
"hourly": "hourly (PATH LIVE DI OGGI)",
|
||||
"fastdetect": "fastdetect (rilevazione INTRA-BARRA, exit a mercato)",
|
||||
"onbook_tp": "onbook_tp (solo TP a limite)",
|
||||
"onbook": "onbook (TP limite + SL stop-market)"}
|
||||
|
||||
@@ -297,7 +342,7 @@ def main() -> None:
|
||||
# per-offset, non la differenza delle mediane (che confronta offset diversi fra loro).
|
||||
print("\n differenze APPAIATE per-offset (mediana [min, max] sui 23 offset):")
|
||||
print(f" {'':>40} {'dFULL':>22} {'dHOLD':>22} {'dDD':>20}")
|
||||
for m in ("canonical", "onbook_tp", "onbook"):
|
||||
for m in ("canonical", "fastdetect", "onbook_tp", "onbook"):
|
||||
d0 = A[m][:, 0] - A["hourly"][:, 0]
|
||||
d2 = A[m][:, 2] - A["hourly"][:, 2]
|
||||
d3 = A[m][:, 3] - A["hourly"][:, 3]
|
||||
@@ -314,7 +359,7 @@ def main() -> None:
|
||||
print(f" recuperato dal solo TP a limite: {dtp:+.3f} = {dtp/dmax:>5.0%}")
|
||||
print(f" recuperato da TP + SL on-book : {dob:+.3f} = {dob/dmax:>5.0%}")
|
||||
print("\n in quanti dei 23 offset la modalita' MIGLIORA il path live di oggi?")
|
||||
for m in ("onbook_tp", "onbook"):
|
||||
for m in ("fastdetect", "onbook_tp", "onbook"):
|
||||
nf = int((A[m][:, 0] - A["hourly"][:, 0] > 0).sum())
|
||||
nh = int((A[m][:, 2] - A["hourly"][:, 2] > 0).sum())
|
||||
nd = int((A[m][:, 3] - A["hourly"][:, 3] < 0).sum())
|
||||
|
||||
Reference in New Issue
Block a user