Files
PythagorasGoal/docs/diary/2026-07-26-t1-esecuzione-skh-live.md
Adriano Dal Pastro 031b71bf54 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>
2026-07-25 20:20:01 +00:00

156 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-07-26 — T1 nel live: il fix richiesto NON va fatto, e il perché è una correzione di modello
**Richiesta:** implementare nel live il fix T1 (TP di SKH01 come limit resting on-book).
**Esito: NON implementato**, con i numeri sotto. **Implementato invece** ciò che le misure
sostengono: la sorveglianza della freschezza del feed 5m di SKH01, e la correzione dei docstring
che hanno causato tre settimane di misure sbagliate.
**File toccati (produzione):** `src/live/livefeed.py`, `src/live/book.py`, `src/portfolio/sleeves.py`
(solo docstring), `scripts/live/book_execute.py`, `config/live.json`.
**Test:** `tests/test_skh_feed_freshness.py` (11 casi) · suite **266 verdi**.
**Strategia, pesi, cadenza del cron: INVARIATI.** Nessun ordine inviato.
---
## 1. Cosa ho trovato leggendo il codice di produzione
Il fix T1 nasceva da una simulazione in cui SKH01 esce per conto suo. Il live non funziona così, e
il vincolo era scritto nel codice:
> `book.py`: *«gli exit sono SOFTWARE (no bracket on-book per SKH, sennò chiuderebbero anche la
> quota TP01)»*
TP01 e SKH01 tradano **lo stesso strumento** e Deribit tiene **una sola posizione netta**. Un
ordine on-book al livello di TP di SKH chiuderebbe una fetta della posizione netta, che è per il
75% di TP01. Ho quindi misurato i due ostacoli invece di assumerli:
**(A) Compatibilità di segno — risolvibile.** Un ordine reduce-only può muovere la posizione solo
verso lo zero. Se SKH è short mentre il netto è long (perché TP01 pesa di più), chiudere lo short
significa *aumentare* il netto: non esprimibile. Frequenza sui trade che escono in TP — gli unici
che il fix tocca: **97% compatibili** (SKH long 100%, SKH short 94%). Il 3% si sarebbe potuto
saltare con fallback all'uscita software.
**(B) Divergenza modello/live — sembrava bloccante.** Se il limit riempie a metà barra ma il
modello non "vede" l'uscita, il cron successivo ricalcola il target includendo ancora la quota SKH
e **ricompra ciò che il TP ha appena chiuso**. Misurato assumendo la rilevazione a fine barra 230m:
ritardo mediano **115 min**, p90 **210**, e nel **70% dei TP almeno un cron gira nella finestra**
(mediana 1.9 cron, p90 3.5).
## 2. …e poi ho scoperto che il problema (B) non esiste
Il problema (B) presuppone che il modello veda le uscite solo a barra 230m chiusa. È ciò che dicono
i docstring:
> `sleeves._skyhook_positions`: *«fino all'ultima barra 230m chiusa. Causale: usa solo barre chiuse»*
> `book.py`: *«latenza fino alla chiusura della barra 230m corrente»*
**Sono falsi.** `resample_5m` **non scarta il bin in corso** — aggrega le barre 5m che ci sono
finora — e il ciclo di `_skyhook_positions` itera fino a `n-1`, quindi valuta gli hit di SL/TP
anche sul bin **parziale**, col suo high/low corrente. Verificato sul feed reale: l'ultimo bin 230m
conteneva **21 barre 5m su 46** (105 minuti su 230).
Quindi il live rileva il tocco **entro il primo cron dopo il tocco (~1h)**, non a fine barra. E non
è look-ahead: il livello è fissato all'ingresso da barre chiuse, e osservare che il prezzo l'ha già
toccato è esattamente ciò che fa qualunque stop reale.
### La conseguenza: il path live era modellato male da tre settimane
La lente `hourly` — usata dall'audit del 02/07, dal follow-up del 24/07 e dal mio stesso T1 di
stamattina — assume la rilevazione a fine barra. Il modello fedele è un altro (`fastdetect`:
rilevazione intra-barra a 5m, uscita a mercato al primo cron successivo). Sulla banda appaiata dei
23 offset, **relativo al path live come veniva modellato**:
| modalità | ΔFULL mediana [min,max] | ΔHOLD mediana | offset migliorati (FULL) |
|---|---|---|---|
| **`fastdetect` = il live VERO** | **+0.081** [+0.00,+0.14] | +0.039 | **23/23** |
| `canonical` (tetto teorico del backtest) | +0.054 [0.05,+0.12] | +0.099 | — |
| `onbook_tp` (**il fix richiesto**) | +0.054 [0.03,+0.07] | +0.061 | 19/23 |
| `onbook` (TP+SL on-book) | +0.024 [0.07,+0.11] | +0.031 | 17/23 |
Due letture, entrambe importanti:
1. **Il "0.35 di degrado del path live" è in gran parte un artefatto di modello**, sopra
l'artefatto di ancora già trovato stamattina. Sul FULL il live vero sta **sopra** il canonical.
2. **Il fix richiesto sarebbe un declassamento**: +0.054 contro i +0.081 che il live già fa — e in
cambio si porterebbero in un percorso con soldi veri ordini parziali sul netto, un 3% di casi
non esprimibili, e la riconciliazione fra fill del broker e stato del modello.
**Per questo non l'ho implementato.** Non è una difficoltà tecnica: è che la misura, fatta sulla
lente giusta, dice che peggiorerebbe.
---
## 3. Il problema vero, trovato per strada
`fresh_5m` è il pezzo che tiene bassa la latenza d'uscita. E **fallisce in silenzio**:
```python
try:
tail = _fetch_recent_5m(sym, lookback_days)
except Exception:
return base # ← nessun log, nessun errore, nessuna traccia
```
`_fetch_recent_5m` inghiotte a sua volta le eccezioni (`except Exception: break`). Se il fetch
pubblico Deribit non risponde, il segnale SKH gira sul **feed certificato**, che il cron ricostruisce
**una volta al giorno**. La latenza d'uscita passa da **~1 ora a ~1 giorno**, e niente lo segnala:
il conto è online, la posizione è leggibile, il gate di staleness guarda il feed *di TP01* — nessuno
dei controlli esistenti scatta. È lo stesso schema del feed-freeze del 2026-07-14, su un altro feed.
E conta esattamente quanto il resto di questo lavoro: la latenza d'uscita **è** dove vive la qualità
del path live.
### Cosa ho cablato
- **`livefeed.feed_age_minutes(df5)`** — pura, testabile senza rete: età in minuti dell'ultima barra
5m, misurata dalla sua **chiusura**, con `None` per input non interpretabili (`None` = "non
misurata", da trattare come sospetta: se tornasse `0.0` sembrerebbe fresca) e clamp a zero sugli
skew d'orologio.
- **`book.book_report(live_feed=True)`** avvolge il loader per misurare la freschezza del feed
**effettivamente usato**, e la espone come `skh_feed_age_min` (massimo fra gli asset: se anche uno
solo è stantio, il netto è sospetto).
- **`book_execute`** stampa lo stato e **allerta su Telegram** sopra `skh_feed_max_age_min` (30 min,
in `config/live.json`).
**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 a flat chiuderebbe posizioni
buone su un glitch. Non ho evidenza su quale politica di blocco sia giusta, quindi la decisione
resta all'operatore, informato.
### Docstring corretti
I due docstring falsi sono stati corretti *sul posto*, con la misura e il perché. Erano loro a
sostenere la catena di ragionamenti sbagliata, ed erano la cosa più economica da riparare.
---
## 4. Follow-up dichiarato, non risolto
La stessa barra parziale che migliora le **uscite** tocca anche gli **ingressi**: `ent[n-1]` può
venire da un bin parziale, cioè da un breakout non ancora confermato a fine barra. Se il segnale
evapora, il book apre e richiude — churn puro.
Misurato su **112 bin campionati**: entry sulla barra completa in 3, sulla parziale in 2,
**disaccordo in 1 (~1% dei bin)**. Ma con sole 3 entry nel campione la **taglia dell'effetto non è
stimabile**: so che esiste, non so quanto pesa. Non l'ho toccato — a differenza delle uscite, qui
il comportamento del live **diverge dal backtest in modo sostanziale** (entra su un segnale che il
backtest non avrebbe visto), e sistemarlo è un cambio di strategia, non di strumentazione.
Serve una misura dedicata prima di decidere in che verso.
---
## 5. Lezioni
1. **Prima di implementare una raccomandazione, leggere il codice che dovrà ospitarla.** T1 era
corretto sul suo modello e inapplicabile sull'architettura reale — e leggendo quel codice è
saltato fuori che il modello stesso era sbagliato. La ricerca del mattino non lo poteva sapere:
simulava lo sleeve, non il book nettato.
2. **Un docstring falso costa più di un bug**, perché nessun test lo cattura e tutti ci ragionano
sopra. Questi due hanno guidato tre analisi (02/07, 24/07, T1) verso una latenza che il codice
non aveva.
3. **Un fallback silenzioso è un guasto silenzioso.** `fresh_5m` fa la cosa giusta (mai operare a
cieco) ma non dice di averla fatta: la scelta giusta è tenere il fallback **e** misurarlo.
4. Terzo caso in due giorni in cui **il numero di partenza era peggio di com'era stato scritto**:
la fortuna d'ancora sul degrado (stamattina), la lente `hourly` (qui), il wick indipendente
(ieri). Ogni volta il difetto stava nella **lente**, non nella strategia.