031b71bf54
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>
156 lines
8.5 KiB
Markdown
156 lines
8.5 KiB
Markdown
# 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.
|