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:
+29
-5
@@ -21,9 +21,19 @@ deribit_book_sleeves = 0.75*TP01 + 0.25*SKH01):
|
||||
|
||||
GLI EXIT DI SKH01 SONO IMPLICITI: _skyhook_positions() replica il book (ingressi + SL/TP/max_bars +
|
||||
non-overlap) sulla feed certificata fresca -> quando il trade va chiuso ritorna 'flat' -> skh_sign=0
|
||||
-> il target netto si aggiorna -> il reconciler chiude la quota SKH. NB: gli exit sono SOFTWARE
|
||||
(no bracket on-book per SKH, sennò chiuderebbero anche la quota TP01) -> latenza fino alla chiusura
|
||||
della barra 230m corrente. Solo il disaster-SL (-30%) resta on-book, sulla posizione NETTA.
|
||||
-> il target netto si aggiorna -> il reconciler chiude la quota SKH. Gli exit sono SOFTWARE (no
|
||||
bracket on-book per SKH, sennò chiuderebbero anche la quota TP01). Solo il disaster-SL (-30%) resta
|
||||
on-book, sulla posizione NETTA.
|
||||
|
||||
⚠️ CORREZIONE 2026-07-26 — questo docstring diceva "latenza fino alla chiusura della barra 230m
|
||||
corrente". E' FALSO, e l'errore e' costato tre settimane di misure sbagliate (audit 02/07, follow-up
|
||||
24/07): `resample_5m` NON scarta il bin 230m in corso, e il loop di `_skyhook_positions` ci itera
|
||||
dentro usando il suo high/low CORRENTE -> il tocco di SL/TP e' visto **entro il primo cron dopo il
|
||||
tocco (~1h)**, non a fine barra (mediana 115 min, p90 210 di ritardo in piu'). Misurato: modellare
|
||||
il live come "detection a fine barra" (lente `hourly`) lo SOTTOSTIMA di +0.081 di Sharpe FULL sul
|
||||
book, in 23/23 offset. Diario `2026-07-26-t1-esecuzione-skh-live.md`.
|
||||
La latenza reale dipende quindi dalla FRESCHEZZA del feed 5m, non dalla griglia 230m -> vedi
|
||||
`skh_feed_age_min` nel report e l'allerta in scripts/live/book_execute.py.
|
||||
|
||||
Questo modulo NON invia nulla: costruisce solo il report/ordine. L'invio è in scripts/live/book_execute.py
|
||||
(doppio gate). Causale: usa solo barre chiuse (eredita la causalità di TP01 e di _skyhook_positions).
|
||||
@@ -118,9 +128,18 @@ def book_report(offline: bool = False, equity_override: float | None = None,
|
||||
equity = sh["equity"]
|
||||
cap = _cap(equity=equity, real_equity=sh.get("real_equity"), eq_fallback=sh.get("eq_fallback"))
|
||||
load5m = None
|
||||
feed_ages: dict[str, float | None] = {}
|
||||
if live_feed:
|
||||
from src.live.livefeed import fresh_5m
|
||||
load5m = fresh_5m
|
||||
from src.live.livefeed import feed_age_minutes, fresh_5m
|
||||
|
||||
def load5m(a: str):
|
||||
"""Wrapper che MISURA la freschezza del feed effettivamente usato per il segnale.
|
||||
`fresh_5m` ricade sul certificato in SILENZIO se il fetch pubblico fallisce: senza
|
||||
questa misura la latenza d'uscita di SKH01 passerebbe da ~1h a ~1 giorno senza che
|
||||
nulla lo segnali (vedi feed_age_minutes)."""
|
||||
df = fresh_5m(a)
|
||||
feed_ages[a] = feed_age_minutes(df)
|
||||
return df
|
||||
skh_error = None
|
||||
try:
|
||||
skh = _skyhook_positions(load5m=load5m)
|
||||
@@ -154,5 +173,10 @@ def book_report(offline: bool = False, equity_override: float | None = None,
|
||||
cap_per_asset=cap, weights=dict(TP01=W_TP01, SKH01=W_SKH),
|
||||
assets=assets, orders=orders, skh_error=skh_error, pos_error=sh.get("pos_error"),
|
||||
eq_fallback=sh.get("eq_fallback"),
|
||||
# eta' (minuti) del feed 5m REALMENTE usato per il segnale SKH: None se non misurata
|
||||
# (live_feed=False) o non interpretabile. Vale il MASSIMO fra gli asset: se anche uno solo
|
||||
# e' stantio, il segnale netto e' sospetto.
|
||||
skh_feed_age_min=(max((v for v in feed_ages.values() if v is not None), default=None)
|
||||
if feed_ages else None),
|
||||
flat=all(abs(x["net_target"]) < FLAT_USD for x in assets),
|
||||
)
|
||||
|
||||
+24
-1
@@ -70,8 +70,31 @@ def merge_tail(base: pd.DataFrame, tail: pd.DataFrame) -> pd.DataFrame:
|
||||
return merged
|
||||
|
||||
|
||||
def feed_age_minutes(df5: pd.DataFrame, now_ms: int | None = None) -> float | None:
|
||||
"""Eta' in MINUTI dell'ultima barra 5m del feed passato. None se non interpretabile.
|
||||
|
||||
⚠️ E' la metrica che dice se `fresh_5m` ha davvero attaccato la coda fresca o se e' ricaduta
|
||||
in SILENZIO sul certificato: il fallback non solleva e non logga, quindi senza questa misura
|
||||
un fetch pubblico rotto e' invisibile. Conta perche' la latenza d'uscita di SKH01 e' dove vive
|
||||
la qualita' del path live (misurata il 2026-07-26): col feed fresco l'uscita e' rilevata entro
|
||||
~1 ora dal tocco del livello, sul solo certificato si allunga fino a ~1 GIORNO (il rebuild
|
||||
certificato gira 1x/giorno). PURA, testabile senza rete."""
|
||||
if df5 is None or len(df5) == 0 or "timestamp" not in df5:
|
||||
return None
|
||||
try:
|
||||
last = int(df5["timestamp"].iloc[-1])
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
now = int(pd.Timestamp.now(tz="UTC").timestamp() * 1000) if now_ms is None else int(now_ms)
|
||||
return max(0.0, (now - (last + 5 * 60_000)) / 60_000.0)
|
||||
|
||||
|
||||
def fresh_5m(asset: str, lookback_days: int = 12) -> pd.DataFrame:
|
||||
"""Feed 5m certificato + coda recente effimera (in-memory). Fallback al certificato su errore."""
|
||||
"""Feed 5m certificato + coda recente effimera (in-memory). Fallback al certificato su errore.
|
||||
|
||||
NB: il fallback e' SILENZIOSO per scelta (mai operare a cieco > mai operare). Chi esegue deve
|
||||
misurare la freschezza di cio' che riceve con `feed_age_minutes` — vedi book.book_report, che
|
||||
la espone come `skh_feed_age_min`, e book_execute, che allerta."""
|
||||
base = load_data(asset, "5m")
|
||||
sym = DERIBIT_SYMBOL.get(asset)
|
||||
if sym is None:
|
||||
|
||||
@@ -239,7 +239,23 @@ def _skyhook_returns() -> pd.Series:
|
||||
def _skyhook_positions(load5m=None) -> dict:
|
||||
"""Stato corrente del book Skyhook per asset (introspezione live): se c'e' un trade APERTO ORA
|
||||
-> dir/entry/sl/tp/barre-trascorse; altrimenti 'flat'. Replica la logica non-overlap di
|
||||
entry+exit (TP/SL/max_bars) fino all'ultima barra 230m chiusa. Causale: usa solo barre chiuse.
|
||||
entry+exit (TP/SL/max_bars).
|
||||
|
||||
⚠️ CORREZIONE 2026-07-26 — qui c'era scritto "fino all'ultima barra 230m CHIUSA. Causale: usa
|
||||
solo barre chiuse". E' FALSO ed e' stato creduto per settimane: `resample_5m` NON scarta il bin
|
||||
230m in corso (verificato: 21 barre 5m su 46 nell'ultimo bin), e il ciclo qui sotto itera fino a
|
||||
`n-1`, quindi valuta gli hit di SL/TP anche sul bin PARZIALE, col suo high/low corrente.
|
||||
Conseguenze, entrambe reali:
|
||||
* USCITE (bene): il tocco del livello e' visto entro il primo cron dopo il tocco (~1h) invece
|
||||
che a fine barra 230m -> il path live e' MIGLIORE di come lo modellavano l'audit 02/07 e il
|
||||
follow-up 24/07 (+0.081 Sharpe FULL di book sulla banda appaiata, 23/23 offset).
|
||||
NON e' look-ahead: il livello e' fissato all'INGRESSO da barre chiuse, e osservare che il
|
||||
prezzo l'ha gia' toccato e' esattamente cio' che fa uno stop reale.
|
||||
* INGRESSI (da tenere d'occhio): anche `ent[n-1]` puo' venire da un bin PARZIALE, cioe' da un
|
||||
breakout non ancora confermato a fine barra -> se il segnale evapora il book apre e richiude.
|
||||
Misurato su 112 bin campionati: disaccordo parziale-vs-completa in 1 (~1%), ma con sole 3
|
||||
entry nel campione la TAGLIA non e' stimabile -> follow-up dichiarato, non risolto.
|
||||
Diario `2026-07-26-t1-esecuzione-skh-live.md`.
|
||||
|
||||
load5m: callable(asset)->df5 opzionale (per il live: feed certificato + coda fresca effimera,
|
||||
vedi src/live/livefeed.fresh_5m). Default = feed certificato su disco (load_data)."""
|
||||
|
||||
Reference in New Issue
Block a user