cblib causale: spot +1h e DVOL +1 giorno (debito §5.18); §11 rimisurato 0,714 → 0,712, verdetti invariati

- cblib.causale(s, cadenza): spot_series e dvol_series escono causali (asof = ultima chiusura NOTA);
  il DVOL giornaliero era una seconda serie con lo stesso difetto (fino a 24h avanti), verificato contro
  l'API pubblica a 1h. Regolamento ST alle 08:00 invece delle 09:00.
- §11 riprodotto al millesimo su worktree HEAD (0,714 [0,690-0,779], n=19, flag --al) e rimisurato:
  0,712 [0,664-0,732]; solo spot 0,721, solo DVOL 0,693, DVOL orario di controllo 0,717. Mediana onesta
  BTC 1,45 → 2,12, fee 1,51x/1,87x. r0730: 0,73 → 0,71. §75: ETH pre 0,76, BTC durante 0,89 (orario 0,83).
  skew panel ≤0,005. vrp_f_watch: f canonico 0,732 → 0,706, differenza +0,097 [+0,042, +0,140].
- r0909 senza doppio shift, contesto sul DVOL del giorno; r0822 regime su date di calendario; r0901
  join riallineato (bit-exact). Aperti con costo: DVOL orario (−0,04/+0,06 nel crollo), spot 5m (≤25 min).
- test: +3 causalita' in test_cb_chain_vrp, anti-doppio-shift in test_r0909; suite 1011/1011.
- revisione fable: 16 segnalazioni, tutte applicate (fra cui un «−1,8% in un'ora» che era +3,9%).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjj5vPEBoAJrB23P6RjKzs
This commit is contained in:
Adriano Dal Pastro
2026-09-09 21:41:23 +00:00
parent bb40e87d13
commit 0a2f780d3c
12 changed files with 401 additions and 73 deletions
+28 -12
View File
@@ -128,7 +128,7 @@ sono all'ancora fortunata; le stime oneste sono la **mediana della banda d'ancor
| TP01 hold-out | 0,31 | **~+0,05** — il valore di TP01 e' il **taglio del DD ~6× vs buy&hold**, non il ritorno | | TP01 hold-out | 0,31 | **~+0,05** — il valore di TP01 e' il **taglio del DD ~6× vs buy&hold**, non il ritorno |
| XS01 standalone | 1,50 / 1,71 / DD 11% | ensemble di fase **1,25 / 1,31 / 10,9%**; maxDD fuori campione **21%** | | XS01 standalone | 1,50 / 1,71 / DD 11% | ensemble di fase **1,25 / 1,31 / 10,9%**; maxDD fuori campione **21%** |
| SKH01 | i numeri di ammissione | de-luckato e' **il meno affidabile dei cinque** (l'ancora regala 2/3 del FULL) | | SKH01 | i numeri di ammissione | de-luckato e' **il meno affidabile dei cinque** (l'ancora regala 2/3 del FULL) |
| VRP01 | f = 1,00 | **f = 0,73** misurato sulle quote reali (IC95 [0,698; 0,780]) | | VRP01 | f = 1,00 | **f = 0,73** misurato sulle quote reali (IC95 [0,698; 0,780], 30/07); **0,71 [0,680; 0,756]** rigirando lo stesso `r0730` con `cblib` causale (09/09 sera, n=27, 0/27 ≥ 1) |
| muro capitale-rendita | $272k (lordo) | **$313k** al netto di fisco **e** funding — banda **[$187k $1,14M]** | | muro capitale-rendita | $272k (lordo) | **$313k** al netto di fisco **e** funding — banda **[$187k $1,14M]** |
| P(≥50 €/g), canale funded, 36 mesi | 42% | **2,6%** [1,5 4,7], P(zero) 40,3% | | P(≥50 €/g), canale funded, 36 mesi | 42% | **2,6%** [1,5 4,7], P(zero) 40,3% |
| XSR01 | Sharpe 1,82 (e' una **terza** lente, divisore fisso 50) | **1,79** alla scoperta / **1,56-1,63** a oggi — lente dei gate, sole barre chiuse. Citare sempre la coppia (lente, ultima barra chiusa) | | XSR01 | Sharpe 1,82 (e' una **terza** lente, divisore fisso 50) | **1,79** alla scoperta / **1,56-1,63** a oggi — lente dei gate, sole barre chiuse. Citare sempre la coppia (lente, ultima barra chiusa) |
@@ -161,8 +161,9 @@ con le classi nell'ordine dichiarato sono 4 + 6 immune. **La leva che aumentereb
peso di SKH01** (37,5/62,5 batte 12/12 episodi ma maxDD 12,1% vs 9,4%): vincolato (§3), gate peso di SKH01** (37,5/62,5 batte 12/12 episodi ma maxDD 12,1% vs 9,4%): vincolato (§3), gate
`weights_tilt_null` fallisce su una condizione **in-sample** (delta 0,0026, 26/07; cache segnali ferma `weights_tilt_null` fallisce su una condizione **in-sample** (delta 0,0026, 26/07; cache segnali ferma
al 25/07). **Crollo CATTURATO a quote vere (1-5/06/2026, archivio bite, `r0909_crash_catturato.py`)**: al 25/07). **Crollo CATTURATO a quote vere (1-5/06/2026, archivio bite, `r0909_crash_catturato.py`)**:
f_net del put credit spread **0,74 = 0,714 del rally**; put δ−0,10 costa **1,92×** il modello all'ask ma f_net del put credit spread **0,76 [0,71-0,86] = 0,712 del rally** (rimisurato la sera del 09/09 con spot e DVOL
paga come il modello nel crollo (0,94-1,04); f al peggior MTM 1,08 (il 2,58 della prima stesura era un causali, debito 18; era 0,74 = 0,714); put δ−0,10 costa **1,98×** il modello all'ask ma paga come il modello nel
crollo (0,99-1,04); f al peggior MTM 1,08 (il 2,58 della prima stesura era un
artefatto di quote oltre la larghezza). **NON e' il f di stress della regola del 19/06**: crollo a vol artefatto di quote oltre la larghezza). **NON e' il f di stress della regola del 19/06**: crollo a vol
BASSA (picco DVOL al 29°/46° percentile), gate IV-rank di VRP01 CHIUSO, famiglia INVERSE, 4 strutture. BASSA (picco DVOL al 29°/46° percentile), gate IV-rank di VRP01 CHIUSO, famiglia INVERSE, 4 strutture.
Diario `2026-09-09-crolli-opzioni-monete.md`. Diario `2026-09-09-crolli-opzioni-monete.md`.
@@ -435,14 +436,29 @@ d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per
`usde_convert.cuscino_richiesto_usd`, P1) ogni ora e allerta sotto zero; e la riconversione USDE→USDC che `usde_convert.cuscino_richiesto_usd`, P1) ogni ora e allerta sotto zero; e la riconversione USDE→USDC che
ripristina il cuscino e' `usde_convert --quota <minore>`, che esiste ma non e' automatica. ripristina il cuscino e' `usde_convert --quota <minore>`, che esiste ma non e' automatica.
18. ⚠️ **`cblib.spot_series` + `asof(ts)` guarda UN'ORA AVANTI** (trovato in revisione il 09/09): il 18. **RIPARATO (2026-09-09 sera).** `cblib.spot_series` + `asof(ts)` guardava **un'ora avanti** (feed
feed 1h e' etichettato all'**apertura** (la barra 20:00 chiude col 5m delle 20:55), quindi 1h etichettato all'apertura: verificato, chiusura 1h a T == chiusura 5m a T+55 nel 100% delle barre)
`S.asof(ts)` restituisce la chiusura di ts+1h. Tocca ogni numero di `cblib` che usa lo spot **e `dvol_series` fino a 24 ore avanti** (la riga DVOL del giorno D e' la chiusura delle 23:00 di D:
(il f 0,714 di §11 compreso; anche `ST = S.asof(exp)` e' il prezzo di un'ora DOPO la scadenza). verificato contro la risoluzione 1h dell'API pubblica) — seconda serie con lo stesso difetto, trovata
Nella misura del 09/09 e' innocuo (ambo le gambe ITM) e giocava contro l'autore; `r0909_crash_catturato` riparando la prima (D6, quinta occorrenza). Ora `cblib.causale(s, cadenza)` rietichetta le chiusure
usa `spot_causale` (indice +1h). **Non riparato in `cblib`**: cambierebbe numeri pubblicati — va all'istante in cui sono note e le due serie escono **gia' causali** (spot +1h, DVOL +1 giorno);
riparato con la rimisura di §11, dichiarando lo scarto. D6, quarta occorrenza dell'etichetta. `r0909` non ri-sposta. **§11 rimisurato allo stesso taglio (22/08, n=19)**: prima riprodotto al
millesimo su worktree HEAD (0,714 [0,690-0,779]), poi **0,712 [0,664-0,732]** — le due correzioni
hanno **segno opposto** (solo spot 0,721 · solo DVOL 0,693 · entrambe 0,712; M26) e un DVOL **orario**
di controllo da' 0,717. Il verdetto di §11 non cambia; cambiano i numeri di contorno (Sharpe canonico
BTC 32,58 → 29,61, mediana onesta 1,45 → **2,12**; ETH 3,90 → 7,64; fee 1,44×/1,80× → 1,51×/1,87×;
il regolamento e' ora il prezzo delle 08:00, non delle 09:00: P&L +3,3%). Sul crollo (§75): ETH pre
0,74 → **0,76 [0,71-0,86]**, BTC durante 0,79 → **0,89** (DVOL orario 0,83: nel crollo il DVOL
giornaliero stantio vale 0,04/+0,06 di f). `vrp_f_watch` (cron): f canonico 0,732 → **0,706**, differenza
col candidato +0,054 → **+0,097 [+0,042, +0,140]** — ora esclude lo zero e il criterio (b) e'
soddisfatto, (a) no (28/40). ⚠️ **Resta aperto, con costo misurato**: il DVOL nel feed e' giornaliero,
quindi causale = **stantio fino a 23 ore** (mediana 0,2 pt di vol, max 3 pt; nel crollo 0,06 di f):
la via giusta e' un feed DVOL **orario** (API pubblica, gratuito, `resolution=3600`) con la sua riga
in `cron_daily` — non fatto, e' una decisione sul feed; e lo spot causale resta **stantio ≤25 min** (snapshot
al :25, chiusura al :00; il feed 5m certificato lo ridurrebbe a ≤5). Trovati in revisione altri due lettori:
**`r0730_vrp_real_quotes`** (il f 0,73 di §2 → **0,71**, dichiarato in §2) e **`r0901_btc_collar`** (join
per giorno: riallineato con `causale(·, "-1D")`, bit-exact a prima). `options_vrp_calibrate.spot_series`
(copia propria, numeri del 20/06) **non toccato**. Diario `2026-09-09b-debito-18-cblib-causale.md`.
--- ---
## 6. Le regole di prim'ordine ## 6. Le regole di prim'ordine
@@ -660,7 +676,7 @@ uv run python scripts/live/<script>.py --help # uso e flag (d
uv run python scripts/live/trades_db.py --reconcile # incrocio delle 3 fonti sui fill uv run python scripts/live/trades_db.py --reconcile # incrocio delle 3 fonti sui fill
uv run python scripts/live/journal.py # voce del giorno (numeri + lettura) uv run python scripts/live/journal.py # voce del giorno (numeri + lettura)
uv run python scripts/live/analista.py --secco # analisi del giorno, senza salvare uv run python scripts/live/analista.py --secco # analisi del giorno, senza salvare
uv run pytest # test (1008, tutti verdi al 09/09) uv run pytest # test (1011, tutti verdi al 09/09 sera)
``` ```
```python ```python
@@ -0,0 +1,182 @@
# 2026-09-09b — Debito §5.18 riparato: `cblib` causale (spot +1h, DVOL +1 giorno) e §11 rimisurato
**Richiesta dell'operatore:** *"ripara il debito §5.18 su cblib e rimisura §11"*. Revisione col modello
`fable` (agente fresco), come da regola.
**Esito in una riga:** riparato in `cblib` — spot e DVOL escono **gia' causali** — e §11 rimisurato allo
stesso taglio: **0,714 [0,690-0,779] → 0,712 [0,664-0,732]** (n=19). Il numero non si muove perche' le due
correzioni hanno **segno opposto**; **nessun verdetto cambia**; riparando la prima serie ne e' uscita una
**seconda con lo stesso difetto** (il DVOL giornaliero, fino a 24 ore avanti); il DVOL orario resta un
passo aperto **con costo misurato**. Libro, config, cron: invariati. Nessun ordine.
## 0. Il difetto, verificato prima di ripararlo (D6: si controlla col lag, non si assume)
| serie | come e' etichettata | prova | look-ahead di `asof(ts)` |
|---|---|---|---|
| feed 1h certificato | all'**apertura**: la barra T contiene la chiusura di T+1h | chiusura 1h a T == chiusura 5m a **T+55** nel **100%** delle barre (BTC ed ETH, 1-5/06); a T5 nell'1,7% / 0% | **+1 ora** |
| `dvol_{asset}.parquet` (giornaliero, `fetch_dvol`, `resolution=86400`) | al giorno D contiene la **chiusura delle 23:00 di D** | API pubblica a risoluzione 1h, 1-4/06: `close` 1D del giorno D == `close` 1h delle 23:00 di D, e `open` 1D di D+1 == `close` di D | **fino a +24 ore** |
La seconda riga non era nel debito: e' uscita controllando la convenzione dell'altra serie di
contesto che `cblib` legge con lo stesso `asof`. Il sleeve (`_vrp_weekly_asset`) **non ha il difetto**:
lavora a cadenza giornaliera e accosta la chiusura del giorno D al DVOL del giorno D, che a quella
cadenza e' coerente. Il difetto e' solo di chi entra **a un'ora** con una serie giornaliera.
## 1. La riparazione
`cblib.causale(s, cadenza)` sposta le **etichette** (mai i valori) dall'apertura all'istante in cui la
chiusura e' nota. `spot_series``causale(·, "1h")`, `dvol_series``causale(·, "1D")`. Conseguenze:
- `asof(ts)` = ultima chiusura **conosciuta** a ts (prima della prima chiusura nota: NaN, non un valore).
- il regolamento `ST = S.asof(exp)` alle 08:00 e' ora la chiusura della barra 07:00, cioe' **il prezzo
delle 08:00** — prima era la chiusura della barra 08:00, cioe' il prezzo delle **09:00**, un'ora dopo la
scadenza. Deribit regola su una TWAP dei 30 minuti prima delle 08:00: l'approssimazione e' ora dalla
parte giusta dell'ora.
- `r0909_crash_catturato` **non ri-sposta** (lo `spot_causale` locale della mattina e' rimosso: applicato
due volte darebbe la chiusura di **due** ore prima — lo stesso errore col segno opposto). Test di
non-regressione sul sorgente.
- chi usa `cblib`: `r0822_vrp_real_quotes` (§11), `r0822_skew` (§5), `r0909` (§75), **`vrp_f_watch`**
(cron giornaliero, `data/vrp_f_watch/state.json`), e — trovati dal revisore, non da me —
**`r0730_vrp_real_quotes`** (il f 0,73 [0,706-0,805] di CLAUDE.md §2: rigirato stampa **0,71
[0,680-0,756]**, n=27, 0/27 ≥ 1, f gamba lunga 1,66 → 1,49; verdetto invariato, numero dichiarato in §2)
e **`r0901_btc_collar`** (join del DVOL **per giorno**: con la serie causale avrebbe accostato l'IV di D
al DVOL di D1 — riallineato con `causale(·, "-1D")`, bit-exact a prima, §71 non si muove).
`options_vrp_calibrate.py` ha una **copia propria** di `spot_series` (numeri del 20/06): **non toccato**,
dichiarato.
- la sezione di REGIME di `r0822` (date di calendario: «ultimo giorno aperto», IV-rank per anno) e la
tabella di contesto di `r0909` usano `causale(V, "-1D")`: li' serve il DVOL **del** giorno, non
l'etichetta causale — altrimenti ogni data stampata slitta di un giorno (trovato dal revisore).
- `r0822_vrp_real_quotes` ha ora il flag `--al <data>` (taglio delle scadenze) per riprodurre una misura
a una data: senza, "riprodurre il 22/08" era impossibile perche' la catena cresce ogni ora.
## 2. Prima di pubblicare il numero nuovo, la macchina ha riprodotto quello vecchio (M23)
Worktree di HEAD (`git worktree add`, `data/raw` in symlink), stesso script con `--al 2026-08-22`:
**pooled 0,714 (IC95 [0,690, 0,779], n=19)** — al millesimo, IC compreso. Poi lo stesso comando sul
codice riparato.
## 3. Scomposizione: due correzioni di segno opposto (M26) e un controllo orario
| taglio | spot | DVOL | pooled f | IC95 | n |
|---|---|---|---|---|---|
| 22/08 | grezzo | grezzo (com'era) | **0,714** | [0,690, 0,779] | 19 |
| 22/08 | causale | grezzo | 0,721 | [0,685, 0,758] | 19 |
| 22/08 | grezzo | causale (giornaliero) | 0,693 | [0,670, 0,748] | 19 |
| 22/08 | causale | causale (giornaliero) = **`cblib` oggi** | **0,712** | [0,664, 0,732] | 19 |
| 22/08 | causale | causale **orario** (controllo, API pubblica) | 0,717 | [0,667, 0,732] | 19 |
| 09/09 | grezzo | grezzo | 0,720 | [0,717, 0,952] | 23 |
| 09/09 | causale | causale (giornaliero) | **0,712** | [0,677, 0,741] | 23 |
| 09/09 | causale | causale orario | 0,726 | [0,680, 0,738] | 23 |
Il DVOL di fine giornata **alzava** f: nel rally la vol scende dentro il giorno, la chiusura di D sta
**sotto** quella di D1 (+1,05 pt di vol mediana su 19/19 trade, misura del revisore) → denominatore piu'
basso → f piu' alto; causale: 0,021. Lo spot di un'ora dopo, invece, **non e' un meccanismo ma rumore**:
lo scarto per trade ha segno misto (mediana 0,02% BTC / 0,11% ETH, banda 0,6…+0,9%) e sposta f fino a
**±0,15 per trade** (lo spread δ−0,28/0,10 ha delta netto ~0,18: 0,5% di spot vale 15-20% del credito
modellato); il +0,007 netto e' la somma di 19 segni. ⚠️ Corollario: lo spot causale resta **stantio fino
a 25 minuti** (snapshot al :25, chiusura nota al :00), cioe' ±0,05-0,10 di f per osservazione; il feed
**5m certificato** lo porterebbe a ≤5 minuti. Non fatto: dichiarato in §6. Il DVOL giornaliero causale e' **stantio fino a
23 ore** (agli ingressi delle 08:00: 8 ore): scarto mediano dall'orario **0,2 punti di vol**, max 1,1
(22/08) / 3,0 (oggi). Su §11 vale 0,005 di f.
## 4. Cosa cambia in §11 (taglio 22/08, stesso campione)
| grandezza | prima | dopo | perche' |
|---|---|---|---|
| f pooled | 0,714 [0,690-0,779] | **0,712 [0,664-0,732]** | §3 |
| f BTC / ETH | 0,706 / 0,715 | 0,720 / 0,692 | |
| Sharpe canonico BTC (titolo, non citabile) | 32,58 | 29,61 | regolamento alle 08:00 |
| **mediana onesta** BTC (9 ancore) | 1,45 | **2,12** | banda [0,19, 29,61] (era [0,35, 32,58]) |
| Sharpe canonico ETH → mediana onesta | 3,90 → 2,41 | 7,64 → 3,35 | una settimana ETH cambia taglia: 26/06 27,38 → 12,84 $ (BTC: 31/07 19,88 → 31,69, 14/08 12,94 → 8,68) |
| media BTC fra le ancore | 1,35% → +11,22% | 0,66% → +11,44% | **cambia ancora segno**; sd 11,1× → 9,2× |
| forfait fee vs listino | 1,44× / 1,80× | 1,51× / 1,87× | credito modellato a DVOL causale |
| peggior mark: mediana / minimo / minimo fra le vincenti | 6,4% / 91,0% / 56,9% | 6,4% / 91,5% / 55,2% | |
| P&L somma 19 settimane | $2.733 | $2.822 (+3,3%) | tutto dal regolamento |
| sottostante nel campione | +25,1% / +44,2% | +21,1% / +40,1% | il vecchio regolamento del 21/08 era il prezzo delle **09:00**, che sta **+3,9%** sopra quello delle 08:00 (76.311 → 79.316): un numero a due estremi pesa un'ora intera. (La prima stesura diceva «−1,8%»: avevo letto l'ora dopo, 09:00 → 10:00. Corretto dal revisore) |
**Verdetto di §11: SCARTATO, per le stesse quattro ragioni** (il campione non contiene la strategia; il
titolo non sopravvive all'ora d'ingresso; la famiglia con storia non e' quella eseguibile; lo spread e'
il costo dominante). Nessuna delle quattro dipende da un'ora di spot. Il testo del verdetto nello script
riporta i numeri nuovi con i vecchi accanto.
## 5. Effetti collaterali, misurati (tutto cio' che legge `cblib`)
**§75, crollo catturato** (`r0909`, rigirato): ETH pre-crollo f_net **0,74 [0,65-0,82] → 0,76
[0,71-0,86]**; peggior MTM 1,08 [1,07-1,31] → 1,08 [1,06-1,27]; put δ−0,10 1,92× → 1,98×; al picco 0,94 →
0,99; BTC durante **0,79 → 0,89 [0,74-1,05]**, ETH durante 0,71 → 0,78. Con il DVOL **orario** BTC durante fa **0,83**: nel crollo
il DVOL sale di 5-6 punti al giorno, quindi il giornaliero di fine giornata (com'era) sottostima f e
quello del giorno prima (causale) lo sovrastima — **0,04/+0,06 di f e' il prezzo della stantiezza in un
crollo**, ed e' l'argomento per il feed orario. DVOL "pre" 37,3 → **36,4** (BTC): il 37,3 era la chiusura
del **primo giorno di crollo** (01/06, 3,5%), non del giorno prima — IV-rank pre 0,05 → 0,03. Picco e
percentili **invariati** (49,1 / 69,2; 29° / 46°) dopo aver corretto la tabella di contesto (§7).
Verdetto invariato.
**§5, skew** (`r0822_skew`, rigirato, 66 s): la scomposizione sul panel si muove di **≤0,005** per fattore
(term 0,880 · skew 0,917 · spread 0,910 · fit 0,995 · f_tot 0,729); Q1/Q2 (lag prezzo/skew) **non si
toccano** (serie proprie ancorate a `ts_max`). Agli ingressi il controllo `f_markfit` passa da 1,045 a
**0,991** e le osservazioni che lo superano da 22/28 a **25/28**: l'ora di spot in piu' stava **nel
controllo**, non nell'effetto — riparare ha reso il controllo piu' pulito, non il risultato diverso.
**`vrp_f_watch`** (sorvegliante in `cron_daily`, criteri pre-registrati il 30/07 e **non toccati**):
| | prima (state.json del 09/09 mattina) | dopo |
|---|---|---|
| coppie | 28 | 28 |
| f canonico | 0,732 [0,702-0,784] | **0,706 [0,678-0,745]** |
| f candidato | 0,803 [0,741-0,842] | 0,805 [0,771-0,840] |
| differenza | +0,054 [0,027, +0,105], 18/28 > 0 | **+0,097 [+0,042, +0,140]**, 21/28 > 0 |
| ampiezza IC95 (b ≤ 0,12) | 0,131 — no | **0,098 — OK** |
| coppie (a ≥ 40) | no | no |
| `ready` | False | False |
Lo `state.json` e' stato **riscritto** dal mio lancio (21:14Z, senza `--quiet`; nessun Telegram: notifica
solo a `ready`); il «prima» completo e' la colonna qui sopra (n_positive 18, `esclude_zero` False), e il
cron lo rigenera domani. La differenza ora **esclude lo zero**. Non decide niente: (a) non e' soddisfatto e il candidato resta
bocciato sul deflated-Sharpe, che non dipende da f. Si registra perche' fra un mese qualcuno leggera'
"esclude lo zero" e deve sapere da quando e perche'.
## 6. Cosa resta aperto, col suo costo
| voce | costo di non farlo | costo di farlo |
|---|---|---|
| **DVOL orario** nel feed (`fetch_dvol --res 3600`, riga in `cron_daily`, `cblib` che lo legge) | su §11 0,005 di f; **su un crollo ±0,05**; ogni misura oraria su opzioni a DVOL giornaliero eredita lo scarto | un fetch pubblico gratuito (3.163 barre da maggio in 2 chiamate), una riga di cron, e una decisione: e' un feed nuovo dentro il perimetro (D1, certificazione, backup) |
| **spot causale stantio ≤25 min** (snapshot :25, chiusura :00): ±0,05-0,10 di f per osservazione | `cblib.spot_series` sul feed **5m** certificato (`causale(·, "5m")`): cambia di nuovo ogni numero di `cblib`, quindi con rimisura dichiarata |
| `options_vrp_calibrate.spot_series` (copia, 20/06) | i numeri del 20/06 (f 0,73) restano con l'ora in piu'; §11 li ha gia' sostituiti | riscriverlo su `cblib` |
| la tabella pubblicata di §5 (x0,869 / x0,904 / x0,920) | ≤0,005 per fattore: sotto la risoluzione del campione (14+14 scadenze) | nulla: nota aggiunta |
## 7. Errori dell'autore in sessione
- **`asi8` in microsecondi** scrivendo il test della causalita' (pandas 3): i timestamp sintetici finivano
nel 1970 e il test falliva per la fixture, non per il codice. E' la trappola del debito 1, ripagata
scrivendo un test *sul* debito 18. Corretto con `(idx epoch) // Timedelta(ms)`, la convenzione di
`load_tf`.
- **La tabella di contesto di `r0909` si e' spostata di un giorno** dopo la riparazione: mostrava il DVOL
del giorno D1 accanto al prezzo del giorno D, e il picco del crollo cambiava (69,2 → 68,3, 46° → 43°)
non per una misura ma per un'etichetta. Trovato **col diff degli output**, non da un test: una tabella
per umani non ha test. Ora il contesto usa la chiusura *del* giorno (`causale(V, "-1D")`, dichiarato
nel commento) e `dvol_pre` / `ivr_pre` restano causali.
- Il verdetto §7 di `r0822_vrp_real_quotes` era **prosa con numeri cablati**: sarebbe rimasto a 0,714 per
sempre (N11 al contrario). Aggiornato con i vecchi accanto; il difetto di forma resta (i numeri del
verdetto andrebbero calcolati, non scritti).
## 8. Revisione (`fable`, agente fresco) — 16 segnalazioni, verdetto «correggi e committa»
Ha **riverificato in proprio** la convenzione delle due serie (1h vs 5m su 120 barre: 100%; DVOL contro
l'API oraria) e riprodotto ogni numero dei documenti lanciando gli script. Applicate: **#2** «−1,8% in
un'ora» era falso (avevo letto 09:00 → 10:00; il fatto e' +3,9% 08:00 → 09:00) · **#3** due delle tre
settimane «ETH» erano BTC · **#4** la parentesi sul DVOL era invertita (la chiusura di D e' piu' BASSA,
non piu' alta) · **#5** lo spot non e' un meccanismo ma rumore, e resta stantio ≤25 min · **#6/#7** due
lettori di `cblib` non dichiarati: `r0730` (0,73 → 0,71, ora in CLAUDE.md §2) e `r0901` (riallineato) ·
**#8** le date di calendario di `r0822` §1 slittavano di un giorno · **#9** il test anti-doppio-shift non
catturava `causale(spot_series, "1h")`: ora conta i `causale(` · **#10** ancora 0,74 → 0,76 nel test
d'integrazione · **#11** teste di §75 e memoria 20 §5 con i numeri vecchi come correnti (M28) · **#12**
«±0,05» → «−0,04/+0,06» · **#13** `state.json` riscritto: dichiarato · **#15** lo skew agli ingressi cambia
piu' del panel (correlazioni che cambiano segno): dichiarato in §5. Non applicate: nessuna. Confermati
senza rilievo (#1, #14, #16): verso e taglia degli shift, nessun doppio spostamento, `ST` alle 08:00
coerente con la TWAP 07:30-08:00, tutti i numeri di §3-§5.
## 9. Test
`tests/test_cb_chain_vrp.py`: +3 (causale; `spot_series` causale sul feed 1h con controllo positivo sulla
serie grezza; `dvol_series` causale sul giornaliero). `tests/test_r0909_crash_catturato.py`: il test dello
shift locale diventa "lo script usa le serie di `cblib` senza ri-spostarle" (sorgente + controllo positivo
su `cblib.causale`). Suite intera: **1011 passati, 0 falliti** (erano 1008), rilanciata dopo le correzioni della revisione: 1011/1011.
+5 -4
View File
@@ -701,7 +701,7 @@ chi lo riapre deve battere il motivo, non ripetere l'esperimento.
**0,93** (alto) e **supera 1 in backwardation** mentre `f_skew` resta piatto -> **la parte maggiore **0,93** (alto) e **supera 1 in backwardation** mentre `f_skew` resta piatto -> **la parte maggiore
del difetto si annulla proprio dove il gate fa tradare il sleeve**, quindi f=0,73 e' plausibilmente del difetto si annulla proprio dove il gate fa tradare il sleeve**, quindi f=0,73 e' plausibilmente
**conservativo li'**. Non misurato (0/22 ingressi passano il gate) e in un crash i due pezzi vanno **conservativo li'**. Non misurato (0/22 ingressi passano il gate) e in un crash i due pezzi vanno
in **direzioni contrarie**. **`VRP_CFG["f"]` NON cambiato.** Replica indipendente del f: **0,714**. in **direzioni contrarie**. **`VRP_CFG["f"]` NON cambiato.** Replica indipendente del f: **0,714** (0,712 dal 09/09 sera, `cblib` causale; il 0,73 del 30/07 stampa 0,71).
📌 **(7) CHIUSURE DI FAMIGLIA.** **Il filone funding si chiude sul QUARTO lato** (affollamento): 📌 **(7) CHIUSURE DI FAMIGLIA.** **Il filone funding si chiude sul QUARTO lato** (affollamento):
su **53.430 ore / 3 anni** non predice ne' direzione ne' volatilita'. E i futures datati **non sono su **53.430 ore / 3 anni** non predice ne' direzione ne' volatilita'. E i futures datati **non sono
un quinto lato**: il loro basis **E'** il funding del perp (+7,33% implicito contro **+6,48%** un quinto lato**: il loro basis **E'** il funding del perp (+7,33% implicito contro **+6,48%**
@@ -1532,7 +1532,7 @@ una maschera a caso della stessa frequenza**.
3,38%: crollo con TP01 a 0,505 (lo squeeze di §33 e' sulla lente hourly). 37,5/62,5 batte 12/12 episodi 3,38%: crollo con TP01 a 0,505 (lo squeeze di §33 e' sulla lente hourly). 37,5/62,5 batte 12/12 episodi
ma maxDD 12,1% vs 9,4%; gate fallisce in-sample (cache `skh_sigtab` ferma al 25/07). **(2) Crollo ma maxDD 12,1% vs 9,4%; gate fallisce in-sample (cache `skh_sigtab` ferma al 25/07). **(2) Crollo
catturato**: l'archivio bite copre con bid/ask il 1-5/06/2026 (BTC 25%, ETH 30%), scadenza 19/06, inverse. catturato**: l'archivio bite copre con bid/ask il 1-5/06/2026 (BTC 25%, ETH 30%), scadenza 19/06, inverse.
ETH pre-crollo (4 strutture): f_net **0,74 = 0,714 del rally**; f a scadenza 1,04 ma **tautologico** (ambo le ETH pre-crollo (4 strutture): f_net **0,76 [0,71-0,86] = 0,712 del rally** (rimisurato la sera con `cblib` causale; era 0,74 = 0,714); f a scadenza 1,04 ma **tautologico** (ambo le
gambe ITM ⇒ solo f_net); peggior MTM **1,08** dopo il taglio alla larghezza — la prima stesura diceva 2,58 con gambe ITM ⇒ solo f_net); peggior MTM **1,08** dopo il taglio alla larghezza — la prima stesura diceva 2,58 con
"IV corta esplosa a 108 per lo skew": **artefatto** (quote 158% di spread a T→0; la IV 108 e' del 18/06, a T→0); "IV corta esplosa a 108 per lo skew": **artefatto** (quote 158% di spread a T→0; la IV 108 e' del 18/06, a T→0);
put δ−0,10 costa **1,92×** il modello (punto nuovo, sopra l'1,27 di §46) e paga come il modello (0,94-1,04); put δ−0,10 costa **1,92×** il modello (punto nuovo, sopra l'1,27 di §46) e paga come il modello (0,94-1,04);
@@ -1544,7 +1544,8 @@ una maschera a caso della stessa frequenza**.
Coinbase delisto' XRP 2021-01→2023-07, riferimento Bitstamp prima); 5m flat 27-61%. Dato in Coinbase delisto' XRP 2021-01→2023-07, riferimento Bitstamp prima); 5m flat 27-61%. Dato in
`data/raw/alt_xrp_*`. **(4) Venue**: 32 perp USDC, liquidi ≥$10M/g solo XRP/ETH/BTC/SOL; XS01 13/19 (APT `data/raw/alt_xrp_*`. **(4) Venue**: 32 perp USDC, liquidi ≥$10M/g solo XRP/ETH/BTC/SOL; XS01 13/19 (APT
inactive); `BTCDVOL_USDC` future esiste ma vol 0 / spread 12%; PAXG perp non misurato (9 mesi, $0,18M/g). inactive); `BTCDVOL_USDC` future esiste ma vol 0 / spread 12%; PAXG perp non misurato (9 mesi, $0,18M/g).
**Debito nuovo (§5.18)**: `cblib.spot_series` + `asof` guarda un'ora avanti (feed 1h etichettato **Debito §5.18** (`cblib.spot_series` + `asof` un'ora avanti) **riparato la sera stessa**, insieme al DVOL
all'apertura). **Errori dell'autore smontati dal revisore `fable`**: il 2,6× (artefatto) e "8/12 GUADAGNA" (fino a 24h avanti): §11 0,714 → 0,712, verdetti invariati; `vrp_f_watch` f canonico 0,732 → 0,706 e la
differenza col candidato +0,097 [+0,042, +0,140] ora esclude lo zero (criterio (a) 28/40 ancora aperto). **Errori dell'autore smontati dal revisore `fable`**: il 2,6× (artefatto) e "8/12 GUADAGNA"
(l'ordine delle classi era il verdetto: immune → POSITIVO da' 4+6). Nessun cambio a libro/pesi/config. (l'ordine delle classi era il verdetto: immune → POSITIVO da' 4+6). Nessun cambio a libro/pesi/config.
+7 -2
View File
@@ -164,8 +164,13 @@
con quell'etichetta restituisce la chiusura di **ts+1h**: un look-ahead di un'ora senza eccezioni e con quell'etichetta restituisce la chiusura di **ts+1h**: un look-ahead di un'ora senza eccezioni e
senza NaN (D6, quarta occorrenza). Colpisce `cblib.spot_series` — cioe' il f 0,714 di §11 e ogni numero senza NaN (D6, quarta occorrenza). Colpisce `cblib.spot_series` — cioe' il f 0,714 di §11 e ogni numero
di `cblib` con lo spot, compreso `ST = S.asof(exp)` che e' il prezzo di un'ora DOPO la scadenza. di `cblib` con lo spot, compreso `ST = S.asof(exp)` che e' il prezzo di un'ora DOPO la scadenza.
`r0909_crash_catturato.spot_causale` sposta l'indice di +1h (chiusura = tempo in cui e' nota); `cblib` **RIPARATO la sera stessa in `cblib`** (`causale(s, cadenza)`: spot +1h, e **anche il DVOL
**non e' riparato** (cambierebbe numeri pubblicati: va fatto con la rimisura di §11). Debito CLAUDE.md §5.18. giornaliero, +1 giorno** — la riga del giorno D e' la chiusura delle 23:00 di D, verificato contro la
risoluzione 1h dell'API pubblica: seconda serie con lo stesso difetto, quinta occorrenza di D6). §11
riprodotto al millesimo su worktree HEAD e rimisurato: 0,714 → **0,712** (le correzioni hanno segno
opposto; DVOL orario di controllo 0,717). Il DVOL causale da feed giornaliero e' **stantio fino a 23 ore**
(0,2 pt mediana, 3 pt max; nel crollo ±0,05 di f): il feed DVOL orario e' il passo aperto. Diario
`2026-09-09b-debito-18-cblib-causale.md`.
- **XRP** (`XRP/USDC:USDC`, dal 2022-03-16) ricostruito il 2026-09-09 con `rebuild_history --asset XRP` e - **XRP** (`XRP/USDC:USDC`, dal 2022-03-16) ricostruito il 2026-09-09 con `rebuild_history --asset XRP` e
spostato in **`data/raw/alt_xrp_*.parquet`** (namespace di ricerca come SOL: fuori dal feed attivo, non spostato in **`data/raw/alt_xrp_*.parquet`** (namespace di ricerca come SOL: fuori dal feed attivo, non
rinfrescato dal cron, `load_data("XRP")` fallisce, presidiato da `test_sol_leg` via `DERIBIT_INSTR`). rinfrescato dal cron, `load_data("XRP")` fallisce, presidiato da `test_sol_leg` via `DERIBIT_INSTR`).
+25 -4
View File
@@ -197,6 +197,18 @@ lati contro il **93%** di BTC_USDC, e il suo miglior bid sta a **un tick**.
DVOL alla mediana del 7°/11° percentile storico, sottostante +25%/+44%. DVOL alla mediana del 7°/11° percentile storico, sottostante +25%/+44%.
- ✅ **Replica indipendente del f del 30/07 su campione piu' lungo: f = 0,714 pooled** (IC95 - ✅ **Replica indipendente del f del 30/07 su campione piu' lungo: f = 0,714 pooled** (IC95
[0,690, 0,779], **0/19 osservazioni >= 1,0**). Attraversare lo spread costa **~10% del credito**. [0,690, 0,779], **0/19 osservazioni >= 1,0**). Attraversare lo spread costa **~10% del credito**.
**RIMISURATO il 2026-09-09 sera** (debito §5.18: `cblib` dava lo spot di un'ora DOPO e il DVOL di
fino a 24 ore DOPO l'ingresso): allo stesso taglio (22/08, n=19) **f = 0,712 [0,664, 0,732]** — le due
correzioni hanno segno opposto (solo spot 0,721, solo DVOL 0,693) e un DVOL orario di controllo da'
0,717; a oggi (n=23) 0,712 [0,677, 0,741]. **Il verdetto non cambia.** Cambiano i numeri di
contorno: Sharpe canonico BTC 32,58 → 29,61 con mediana onesta **2,12** (era 1,45) e banda
[0,19, 29,61]; ETH 3,90 → 7,64 (mediana onesta 3,35); media BTC fra le ancore 0,66% → +11,44%
(cambia ancora segno); forfait fee 1,51× (BTC) / 1,87× (ETH); il regolamento e' il prezzo delle
08:00 e non delle 09:00 (P&L +3,3%); sottostante nel campione +21% / +40% (era +25% / +44%: il vecchio
regolamento del 21/08 era il prezzo delle 09:00, **+3,9%** sopra quello delle 08:00 — quanto pesa un'ora su
un numero a due estremi). Lo stesso `cblib` rigira anche `r0730`: il f 0,73 [0,706-0,805] del 30/07 stampa
ora **0,71 [0,680-0,756]** (n=27, 0/27 ≥ 1).
Diario `2026-09-09b-debito-18-cblib-causale.md`.
- ⚠️ **Correzione a un numero pubblicato:** il forfait fee del sleeve sovrastima il listino vero di - ⚠️ **Correzione a un numero pubblicato:** il forfait fee del sleeve sovrastima il listino vero di
**1,44x (BTC) / 1,80x (ETH)**, non di "~2x". Il segno (conservativo) regge, la taglia no. **1,44x (BTC) / 1,80x (ETH)**, non di "~2x". Il segno (conservativo) regge, la taglia no.
- ⚠️ **Rischio sul MINIMO, non sulle chiusure:** peggior mark infra-settimana mediana 6,4% del - ⚠️ **Rischio sul MINIMO, non sulle chiusure:** peggior mark infra-settimana mediana 6,4% del
@@ -374,6 +386,13 @@ nuovo: **va decisa PRIMA del 23/10, non quel giorno.**
(il collettore li accumula da solo), revisione comunque **2027-02-22**. (il collettore li accumula da solo), revisione comunque **2027-02-22**.
- ✅ Confound di modello **ESCLUSO e misurato**: `f_markfit` = **0,992** (la mark IV di Deribit - ✅ Confound di modello **ESCLUSO e misurato**: `f_markfit` = **0,992** (la mark IV di Deribit
riprezza i propri mid entro l'1%) -> RR/BF sono un fatto di **prezzo**, non l'output del fit. riprezza i propri mid entro l'1%) -> RR/BF sono un fatto di **prezzo**, non l'output del fit.
📌 **Rigirato il 2026-09-09 sera con spot e DVOL causali** (debito §5.18): la scomposizione sul panel si
muove di **≤0,005** per fattore (term 0,880 · skew 0,917 · spread 0,910 · fit 0,995 · f_tot 0,729) e Q1/Q2
non si toccano (serie proprie); agli **ingressi** il controllo `f_markfit` passa da 1,045 a **0,991** e
le osservazioni che lo superano da 22/28 a **25/28** — l'ora di spot in piu' stava nel controllo, non
nell'effetto. Agli ingressi (n=28) si muove di piu': f_term 0,910 → 0,875, f_tot 0,732 → 0,706, e le
correlazioni `corr(f_tot_mid, rr7)` +0,21 → 0,06 e `atm7` +0,02 → +0,34 **cambiano segno/ordine** — sono
22-25 punti, dichiarate (P12), non usate.
- ✅ **Controllo positivo cablato nello script:** con l'ancora **assunta** a :30 invece del `ts_max` - ✅ **Controllo positivo cablato nello script:** con l'ancora **assunta** a :30 invece del `ts_max`
osservato compare un **falso lead** a +15m (0,102 contro 0,031) — *"e' l'errore che avrei osservato compare un **falso lead** a +15m (0,102 contro 0,031) — *"e' l'errore che avrei
pubblicato senza quel controllo"*. pubblicato senza quel controllo"*.
@@ -3997,7 +4016,7 @@ nuovo: quello che guadagna nei crolli e' gia' in produzione, e la leva che lo au
`scripts/research/r0909_crash_catturato.py`. **Nessun ordine.** L'archivio bite copre con bid/ask il crollo `scripts/research/r0909_crash_catturato.py`. **Nessun ordine.** L'archivio bite copre con bid/ask il crollo
1-5/06/2026 (BTC 25%, ETH 30%; scadenza 19/06, 14-21 DTE, famiglia INVERSE); il campione di §11 partiva 1-5/06/2026 (BTC 25%, ETH 30%; scadenza 19/06, 14-21 DTE, famiglia INVERSE); il campione di §11 partiva
dal fondo. ETH, ingressi pre-crollo (286 snapshot = **4 strutture**): f_net **0,74 = 0,714 del rally**; dal fondo. ETH, ingressi pre-crollo (286 snapshot = **4 strutture**): f_net **0,76 [0,71-0,86] = 0,712 del rally** (era 0,74 = 0,714 prima della riparazione di §5.18, la sera stessa; put 1,98× e picco 0,99 idem);
f a scadenza 1,04 ma **tautologico** nell'83% (ambo le gambe ITM ⇒ solo f_net); peggior MTM **1,08** f a scadenza 1,04 ma **tautologico** nell'83% (ambo le gambe ITM ⇒ solo f_net); peggior MTM **1,08**
dopo il taglio alla larghezza; put δ−0,10 costa **1,92×** il modello (punto nuovo sopra l'1,27 di §46) dopo il taglio alla larghezza; put δ−0,10 costa **1,92×** il modello (punto nuovo sopra l'1,27 di §46)
e paga come il modello (0,94-1,04); ingressi durante il crollo bimodali (perde il 46%). BTC pre-crollo e paga come il modello (0,94-1,04); ingressi durante il crollo bimodali (perde il 46%). BTC pre-crollo
@@ -4006,9 +4025,11 @@ IV-rank di VRP01 CHIUSO in tutte le ore. ⇒ la condizione di §3 e' soddisfatta
sostanza: non si riapre**. Servono un crollo con IV-rank > 0,30 e la famiglia USDC-lineare. sostanza: non si riapre**. Servono un crollo con IV-rank > 0,30 e la famiglia USDC-lineare.
🚨 **Due conclusioni dell'autore smontate in revisione:** «MTM 2,58× per lo skew (IV 108)» era un 🚨 **Due conclusioni dell'autore smontate in revisione:** «MTM 2,58× per lo skew (IV 108)» era un
artefatto (quote con 158% di spread a T→0 oltre la larghezza; la IV 108 e' del 18/06 a T→0); «f a artefatto (quote con 158% di spread a T→0 oltre la larghezza; la IV 108 e' del 18/06 a T→0); «f a
scadenza 1,04 = il modello prezza bene» era una tautologia. **Debito nuovo (CLAUDE.md §5.18):** scadenza 1,04 = il modello prezza bene» era una tautologia. **Debito §5.18 RIPARATO la sera stessa** in `cblib`
`cblib.spot_series` + `asof` guarda un'ora avanti (feed 1h etichettato all'apertura); qui corretto con (spot +1h, e anche il DVOL giornaliero, +1 giorno): ETH pre-crollo f_net **0,76 [0,71-0,86]** (era 0,74),
`spot_causale`, in `cblib` no (toccherebbe il 0,714 di §11). BTC durante **0,89 [0,74-1,05]** (era 0,79; con DVOL orario 0,83 — nel crollo il DVOL giornaliero stantio
vale 0,04/+0,06 di f), put δ−0,10 1,98× (era 1,92), DVOL pre 36,4 / 49,6 (il 37,3 di BTC era la chiusura del
PRIMO giorno di crollo: contaminato). Picco e percentili invariati. Verdetto invariato.
**VERDETTO: `CATTURATO, MA A VOL BASSA E FUORI DAL GATE: f_net NON CAMBIA COL REGIME`** — nessuna riapertura. **VERDETTO: `CATTURATO, MA A VOL BASSA E FUORI DAL GATE: f_net NON CAMBIA COL REGIME`** — nessuna riapertura.
## 76 — XRP-TERZA-GAMBA (l'unica altra moneta liquida di Deribit, coi meccanismi congelati) ## 76 — XRP-TERZA-GAMBA (l'unica altra moneta liquida di Deribit, coi meccanismi congelati)
+3
View File
@@ -9,6 +9,9 @@ lontana, che il 30/07 aveva misurato sottoprezzata ~2.3x.
piu' mispriced in RELATIVO (f_long 5.85 contro 2.23) ma pesa il **3.2%** del premio corto invece piu' mispriced in RELATIVO (f_long 5.85 contro 2.23) ma pesa il **3.2%** del premio corto invece
del **18.4%**, quindi sul credito NETTO l'effetto e' minore: f_net **0.852** contro **0.718**. del **18.4%**, quindi sul credito NETTO l'effetto e' minore: f_net **0.852** contro **0.718**.
Meccanismo giusto, segno della conclusione sbagliato. Meccanismo giusto, segno della conclusione sbagliato.
(Numeri del 30/07, misurati con `cblib` che leggeva spot e DVOL di UN'ORA / fino a 24 ORE dopo
l'ingresso — debito §5.18, riparato la sera del 09/09: da allora la tabella stampata da questo
script e' la fonte, e il f canonico e' passato da 0,732 a 0,706 sulle stesse 28 coppie.)
E' pero' una misura su **16 coppie**: la differenza appaiata e' **+0.109 con IC95 E' pero' una misura su **16 coppie**: la differenza appaiata e' **+0.109 con IC95
[-0.047, +0.193]**, cioe' **compatibile con zero**. Serve piu' campione, e "quando ce ne sara' [-0.047, +0.193]**, cioe' **compatibile con zero**. Serve piu' campione, e "quando ce ne sara'
+31 -2
View File
@@ -13,6 +13,10 @@ Regola che vale per ogni misura fatta con questo modulo: il f della sola gamba c
dello spread. Il modello sbaglia soprattutto sull'ala che si COMPRA (piu' OTM, IV piu' alta per dello spread. Il modello sbaglia soprattutto sull'ala che si COMPRA (piu' OTM, IV piu' alta per
skew, prezzata dal modello a vol ATM) -> misurare una gamba sola da' la risposta sbagliata con skew, prezzata dal modello a vol ATM) -> misurare una gamba sola da' la risposta sbagliata con
segno rassicurante. segno rassicurante.
Seconda regola (09/09, debito §5.18): spot e DVOL escono da qui gia' CAUSALI — `asof(ts)` da'
l'ultima chiusura nota a ts. Chi costruisce una serie di prezzi altrove usi `causale()`, non
l'indice del feed com'e' (etichettato all'apertura: D6).
""" """
from __future__ import annotations from __future__ import annotations
@@ -65,23 +69,48 @@ def puts(asset: str, df: pd.DataFrame | None = None) -> pd.DataFrame:
return d[(d["asset"] == asset) & (d["option_type"] == "P")] return d[(d["asset"] == asset) & (d["option_type"] == "P")]
def causale(s: pd.Series, cadenza: str) -> pd.Series:
"""Rietichetta una serie di CHIUSURE dall'APERTURA della barra all'istante in cui la chiusura
e' nota (apertura + cadenza), cosi' `s.asof(ts)` restituisce l'ultima chiusura CONOSCIUTA a ts
e mai una futura. I valori non si toccano, solo le etichette.
🚨 Perche' esiste (debito §5.18, 09/09): i feed del progetto sono etichettati all'apertura
la barra 1h `20:00` chiude col 5m delle 20:55 (verificato: chiusura 1h a T == chiusura 5m a
T+55 nel 100% delle barre) e la riga DVOL del giorno D e' la chiusura delle 23:00 di D
(verificato contro la risoluzione 1h dell'API pubblica). Senza questo spostamento `asof(ts)`
dava lo spot di UN'ORA dopo e il DVOL di FINO A 24 ORE dopo l'ingresso (D6, quinta occorrenza)."""
out = s.copy()
out.index = out.index + pd.Timedelta(cadenza)
return out
@lru_cache(maxsize=8) @lru_cache(maxsize=8)
def spot_series(asset: str) -> pd.Series: def spot_series(asset: str) -> pd.Series:
"""Chiusure 1h del feed certificato, etichettate all'istante in cui sono NOTE (apertura + 1h).
`spot_series(a).asof(ts)` = ultima chiusura oraria conosciuta a `ts`. Conseguenza sul
regolamento: `asof(exp)` alle 08:00 e' la chiusura della barra 07:00, cioe' il prezzo delle
08:00 prima era il prezzo delle 09:00, un'ora DOPO la scadenza."""
from scripts.analysis.research_lab import load_tf from scripts.analysis.research_lab import load_tf
px = load_tf(asset, "1h") px = load_tf(asset, "1h")
s = pd.Series(px["close"].values.astype(float), s = pd.Series(px["close"].values.astype(float),
index=pd.to_datetime(px["timestamp"], unit="ms", utc=True)).sort_index() index=pd.to_datetime(px["timestamp"], unit="ms", utc=True)).sort_index()
s.index = pd.DatetimeIndex(s.index).as_unit("ns") # le quote bite hanno microsecondi s.index = pd.DatetimeIndex(s.index).as_unit("ns") # le quote bite hanno microsecondi
return s return causale(s, "1h")
@lru_cache(maxsize=8) @lru_cache(maxsize=8)
def dvol_series(asset: str) -> pd.Series: def dvol_series(asset: str) -> pd.Series:
"""DVOL giornaliero (chiusura), etichettato all'istante in cui e' NOTO (giorno + 1D).
`dvol_series(a).asof(ts)` = ultima chiusura giornaliera del DVOL conosciuta a `ts`: dentro il
giorno D e' la chiusura di D1, quindi vecchia fino a 23 ore. E' il prezzo della causalita'
con un feed giornaliero; l'alternativa (DVOL orario dall'API pubblica) non e' nel feed."""
d = pd.read_parquet(RAW / f"dvol_{asset.lower()}.parquet") d = pd.read_parquet(RAW / f"dvol_{asset.lower()}.parquet")
s = pd.Series(d["close"].values.astype(float), s = pd.Series(d["close"].values.astype(float),
index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)).sort_index() index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)).sort_index()
s.index = pd.DatetimeIndex(s.index).as_unit("ns") s.index = pd.DatetimeIndex(s.index).as_unit("ns")
return s return causale(s, "1D")
# ------------------------------------------------------------------ prezzo # ------------------------------------------------------------------ prezzo
+20 -12
View File
@@ -310,11 +310,15 @@ def boot_ci(x: np.ndarray, n: int = 4000, seed: int = 822) -> tuple[float, float
def main() -> None: def main() -> None:
ap = argparse.ArgumentParser() ap = argparse.ArgumentParser()
ap.add_argument("--no-live", action="store_true", help="non interrogare il venue") ap.add_argument("--no-live", action="store_true", help="non interrogare il venue")
ap.add_argument("--al", default=None,
help="data di taglio UTC (riproduzione): esclude le scadenze >= questa data; default adesso")
args = ap.parse_args() args = ap.parse_args()
print(__doc__.split("\n\n")[0]) print(__doc__.split("\n\n")[0])
puts = load_puts() puts = load_puts()
today = pd.Timestamp.now(tz="UTC") today = pd.Timestamp(args.al, tz="UTC") if args.al else pd.Timestamp.now(tz="UTC")
print(f"\n taglio dei trade: scadenze < {today:%Y-%m-%d %H:%M}Z"
+ (" (--al: riproduzione)" if args.al else " (adesso)"))
venue = venue_read(not args.no_live) venue = venue_read(not args.no_live)
# ------------------------------------------------------------ §1 REGIME # ------------------------------------------------------------ §1 REGIME
@@ -323,7 +327,7 @@ def main() -> None:
"misurato 4 volte). Se nel campione quel gate non scatta mai, il campione non contiene\n" "misurato 4 volte). Se nel campione quel gate non scatta mai, il campione non contiene\n"
"la strategia — contiene la sua astensione.\n") "la strategia — contiene la sua astensione.\n")
for a in ASSETS: for a in ASSETS:
V = CB.dvol_series(a) V = CB.causale(CB.dvol_series(a), "-1D") # calendario: il DVOL DEL giorno D (la serie di cblib e' causale: sta a D+1)
win = V[(V.index >= puts["ts"].min()) & (V.index <= puts["ts"].max())] win = V[(V.index >= puts["ts"].min()) & (V.index <= puts["ts"].max())]
hist = V[V.index < win.index[0]].to_numpy(float) hist = V[V.index < win.index[0]].to_numpy(float)
print(f" {a}: DVOL nel campione min {win.min():.1f} / mediana {win.median():.1f} / " print(f" {a}: DVOL nel campione min {win.min():.1f} / mediana {win.median():.1f} / "
@@ -372,7 +376,7 @@ def main() -> None:
print(" Regola del 19/06: «rivalutare quando la catena cattura un crash». Un criterio senza\n" print(" Regola del 19/06: «rivalutare quando la catena cattura un crash». Un criterio senza\n"
" numero non e' un criterio, quindi eccolo (IV-rank espandente causale, come nel sleeve):") " numero non e' un criterio, quindi eccolo (IV-rank espandente causale, come nel sleeve):")
for a in ASSETS: for a in ASSETS:
V = CB.dvol_series(a) V = CB.causale(CB.dvol_series(a), "-1D") # idem: date di calendario, non etichette causali
vals = V.to_numpy(float) vals = V.to_numpy(float)
ivr = np.full(len(vals), np.nan) ivr = np.full(len(vals), np.nan)
for i in range(1, len(vals)): for i in range(1, len(vals)):
@@ -753,13 +757,17 @@ def main() -> None:
# ------------------------------------------------------------ §7 VERDETTO # ------------------------------------------------------------ §7 VERDETTO
hr("§7 VERDETTO") hr("§7 VERDETTO")
print(""" SCARTATO — su questi dati la domanda non e' decidibile. Quattro motivi, indipendenti print(""" (numeri della stesura del 22/08, RIMISURATI il 09/09 con spot e DVOL causali — debito §5.18:
erano 0.714 [0.690, 0.779] · 32.58 / 1.45 / -0.35 · 3.90 -> 2.41 · -1.35% -> +11.22% · +25% / +44%.
Il verdetto non cambia: le quattro ragioni non dipendono da un'ora di spot.)
SCARTATO su questi dati la domanda non e' decidibile. Quattro motivi, indipendenti
l'uno dall'altro: ciascuno da solo basterebbe. l'uno dall'altro: ciascuno da solo basterebbe.
1. IL CAMPIONE NON CONTIENE LA STRATEGIA. 0 settimane su 19 passano il gate IV-rank>0.30 1. IL CAMPIONE NON CONTIENE LA STRATEGIA. 0 settimane su 19 passano il gate IV-rank>0.30
che E' l'alpha di VRP01 il sleeve sarebbe stato FLAT per l'intero campione. La DVOL che E' l'alpha di VRP01 il sleeve sarebbe stato FLAT per l'intero campione. La DVOL
mediana sta al 7 (BTC) / 11 (ETH) percentile della storia 2021+, e il sottostante e' mediana sta al 7 (BTC) / 11 (ETH) percentile della storia 2021+, e il sottostante e'
salito del +25% / +44%: vol implicita bassa E rialzo forte = il regime MIGLIORE salito del +21% / +40%: vol implicita bassa E rialzo forte = il regime MIGLIORE
possibile per vendere put, non uno neutro. Le settimane sono 10 per asset, non 16 possibile per vendere put, non uno neutro. Le settimane sono 10 per asset, non 16
(prima del 2026-06-09 l'archivio seguiva solo la finestra 13-20 DTE: quella 4-10 DTE (prima del 2026-06-09 l'archivio seguiva solo la finestra 13-20 DTE: quella 4-10 DTE
non esiste). Esito: 10/10 vincenti su BTC, e la lente giusta non e' lo Sharpe ma la non esiste). Esito: 10/10 vincenti su BTC, e la lente giusta non e' lo Sharpe ma la
@@ -770,13 +778,13 @@ def main() -> None:
regime di vol bassa persistente, non davanti a un muro. regime di vol bassa persistente, non davanti a un muro.
2. IL TITOLO NON SOPRAVVIVE ALL'ORA D'INGRESSO. E' un max-of-k mai dichiarato, misurato 2. IL TITOLO NON SOPRAVVIVE ALL'ORA D'INGRESSO. E' un max-of-k mai dichiarato, misurato
qui per la prima volta su questa struttura: su 9 ancore lo Sharpe canonico BTC 32.58 qui per la prima volta su questa struttura: su 9 ancore lo Sharpe canonico BTC 29.61
sta all'89 percentile, la MEDIANA ONESTA e' 1.45 e la banda tocca il NEGATIVO (-0.35). sta all'89 percentile, la MEDIANA ONESTA e' 2.12 e la banda tocca il NEGATIVO (-0.19).
Su ETH 3.90 -> 2.41. Il numero da citare per BTC e' 1.45, non 32.58. Su ETH 7.64 -> 3.35. Il numero da citare per BTC e' 2.12, non 29.61.
E il meccanismo non e' quello che avevo scritto prima di guardarlo. Non e' solo la E il meccanismo non e' quello che avevo scritto prima di guardarlo. Non e' solo la
vol campionaria a ballare: fra le ancore la MEDIA settimanale BTC va da -1.35% a vol campionaria a ballare: fra le ancore la MEDIA settimanale BTC va da -0.66% a
+11.22% del rischio CAMBIA SEGNO e la deviazione standard di 11x (ETH: media 2.9x, +11.44% del rischio CAMBIA SEGNO e la deviazione standard di 9.2x (ETH: media 2.1x,
sd 2.4x). Spostare l'ora d'ingresso cambia lo snapshot e quindi quali strike combaciano sd 3.2x). Spostare l'ora d'ingresso cambia lo snapshot e quindi quali strike combaciano
col delta bersaglio: non e' lo stesso trade un'ora dopo, e' UN'ALTRA STRUTTURA. A 10 col delta bersaglio: non e' lo stesso trade un'ora dopo, e' UN'ALTRA STRUTTURA. A 10
settimane questo parametro di disturbo domina il segnale che si vorrebbe misurare. settimane questo parametro di disturbo domina il segnale che si vorrebbe misurare.
@@ -793,7 +801,7 @@ def main() -> None:
4. LO SPREAD E' IL COSTO DOMINANTE, E SULLA FAMIGLIA ESEGUIBILE E' PEGGIO. Attraversare 4. LO SPREAD E' IL COSTO DOMINANTE, E SULLA FAMIGLIA ESEGUIBILE E' PEGGIO. Attraversare
il bid/ask costa ~10% del credito sugli INVERSE; nella regione di strike che serve lo il bid/ask costa ~10% del credito sugli INVERSE; nella regione di strike che serve lo
spread relativo mediano e' 24-37% sugli inverse e 29-59% sulle USDC, con open interest spread relativo mediano e' 24-37% sugli inverse e 29-59% sulle USDC, con open interest
~0. Il f del credito netto si replica indipendentemente a 0.714 (IC95 [0.690, 0.779], ~0. Il f del credito netto si replica indipendentemente a 0.712 (IC95 [0.664, 0.732],
0/19 osservazioni >= 1.0) conferma dello 0.73 del 30/07 su un campione piu' lungo e 0/19 osservazioni >= 1.0) conferma dello 0.73 del 30/07 su un campione piu' lungo e
con un percorso diverso. con un percorso diverso.
+2 -1
View File
@@ -167,7 +167,8 @@ def calibra_catena() -> dict:
skew[nm] = (np.array(xs), np.array(ys)) skew[nm] = (np.array(xs), np.array(ys))
# struttura a termine: IV ATM a DTE t contro il DVOL 30g dello stesso istante # struttura a termine: IV ATM a DTE t contro il DVOL 30g dello stesso istante
dv = cblib.dvol_series("BTC") dv = cblib.causale(cblib.dvol_series("BTC"), "-1D") # join per GIORNO: il DVOL del giorno D — la serie di
# cblib e' causale dal 09/09 sera (la chiusura di D sta a D+1); senza questo il join accosterebbe l'IV di D al DVOL di D1
a = q[q["ad"].between(0.45, 0.55)].copy() a = q[q["ad"].between(0.45, 0.55)].copy()
a["giorno"] = a["ts"].dt.floor("D") a["giorno"] = a["ts"].dt.floor("D")
dvd = dv.copy() dvd = dv.copy()
+14 -15
View File
@@ -33,8 +33,10 @@ Un episodio e' un episodio: da' la TAGLIA di f nello stress, non la sua distribu
DUE CORREZIONI DELLA REVISIONE (fable, 09/09), applicate: DUE CORREZIONI DELLA REVISIONE (fable, 09/09), applicate:
* il feed 1h e' etichettato all'APERTURA (la barra 20:00 chiude alle 21:00): `S.asof(ts)` * il feed 1h e' etichettato all'APERTURA (la barra 20:00 chiude alle 21:00): `S.asof(ts)`
guardava un'ora AVANTI. Qui lo spot e' `spot_causale` = chiusura dell'ultima barra chiusa a ts. guardava un'ora AVANTI. Corretto la mattina del 09/09 qui con uno `spot_causale` locale;
`cblib.spot_series` ha lo stesso difetto (e quindi il f 0,714 di §11): registrato, non toccato. la sera dello stesso giorno la riparazione e' entrata in `cblib` (`causale`, debito §5.18) e
copre anche il DVOL giornaliero (fino a 24 ore avanti): questo script usa le serie di `cblib`
COME SONO, senza ri-spostarle.
* il costo di chiusura `(ask corta bid lunga)·spot` puo' superare la LARGHEZZA dello spread * il costo di chiusura `(ask corta bid lunga)·spot` puo' superare la LARGHEZZA dello spread
(una quota da 158% di spread relativo a T0): uno spread a rischio definito non costa mai piu' (una quota da 158% di spread relativo a T0): uno spread a rischio definito non costa mai piu'
della larghezza per chiudere (si lascia regolare). Il MTM e' tagliato alla larghezza e le ore della larghezza per chiudere (si lascia regolare). Il MTM e' tagliato alla larghezza e le ore
@@ -76,13 +78,6 @@ def leg_fee(prem_base: float, spot: float) -> float:
return min(FEE_UNDERLYING * spot, FEE_PREMIUM_CAP * prem_base * spot) return min(FEE_UNDERLYING * spot, FEE_PREMIUM_CAP * prem_base * spot)
def spot_causale(S: pd.Series) -> pd.Series:
"""Il feed 1h e' etichettato all'apertura: la chiusura della barra T e' nota a T+1h. Spostando
l'indice di +1h, `asof(ts)` restituisce l'ultima chiusura CONOSCIUTA a ts, mai una futura."""
out = S.copy(); out.index = out.index + pd.Timedelta(hours=1)
return out
def carica() -> pd.DataFrame: def carica() -> pd.DataFrame:
a = pd.read_parquet(ARCHIVIO) a = pd.read_parquet(ARCHIVIO)
a["ts"] = pd.to_datetime(a["ts"], utc=True) a["ts"] = pd.to_datetime(a["ts"], utc=True)
@@ -213,7 +208,7 @@ def misura() -> dict:
out = {"trades": {}, "contesto": {}, "sleeve": {}} out = {"trades": {}, "contesto": {}, "sleeve": {}}
for asset in ASSETS: for asset in ASSETS:
seg = a[a["asset"] == asset] seg = a[a["asset"] == asset]
S, V = spot_causale(CB.spot_series(asset)), CB.dvol_series(asset) S, V = CB.spot_series(asset), CB.dvol_series(asset) # gia' causali (cblib, dal 09/09 sera)
Q = Quote(seg, S, V) Q = Quote(seg, S, V)
rows = [] rows = []
for ts in sorted(seg["ts"].unique()): for ts in sorted(seg["ts"].unique()):
@@ -227,17 +222,21 @@ def misura() -> dict:
# contesto: DVOL, RV5 e RV1 sul crollo # contesto: DVOL, RV5 e RV1 sul crollo
px = S[(S.index >= pd.Timestamp("2026-05-20", tz="UTC")) & (S.index <= EXP)] px = S[(S.index >= pd.Timestamp("2026-05-20", tz="UTC")) & (S.index <= EXP)]
pd_ = px.resample("1D").last().dropna() # chiusura del giorno = ultima chiusura nota entro le 24:00 pd_ = px.resample("1D").last().dropna() # chiusura del giorno = ultima chiusura nota entro le 24:00
hist_all = V[V.index < CROLLO[0]] # CONTESTO per giorno: la serie di cblib e' causale (la chiusura del giorno D sta a D+1);
# qui si vuole "il DVOL DEL giorno D" accanto al prezzo del giorno D, quindi si toglie lo
# spostamento SOLO per la tabella e per il picco del crollo. `dvol_pre` e `ivr_pre` restano
# sulla serie causale: sono cio' che si sapeva alle 00:00 del primo giorno di crollo.
dvg = CB.causale(V, "-1D")
hist_all = dvg[dvg.index < CROLLO[0]]
lr = np.log(pd_).diff() lr = np.log(pd_).diff()
rv5 = lr.rolling(5).std() * np.sqrt(365.25) * 100 rv5 = lr.rolling(5).std() * np.sqrt(365.25) * 100
dv = V[(V.index >= pd.Timestamp("2026-05-20", tz="UTC")) & (V.index <= EXP)] dv = dvg[(dvg.index >= pd.Timestamp("2026-05-20", tz="UTC")) & (dvg.index <= EXP)]
hist = V[V.index < CROLLO[0]]
out["contesto"][asset] = dict( out["contesto"][asset] = dict(
spot=pd_, ret=lr, rv5=rv5, dvol=dv, spot=pd_, ret=lr, rv5=rv5, dvol=dv,
dvol_max_crollo=float(dv[(dv.index >= CROLLO[0]) & (dv.index <= CROLLO[1])].max()), dvol_max_crollo=float(dv[(dv.index >= CROLLO[0]) & (dv.index <= CROLLO[1])].max()),
dvol_pre=float(V.asof(CROLLO[0])), dvol_pre=float(V.asof(CROLLO[0])),
pctl_picco=float((hist_all < V[(V.index >= CROLLO[0]) & (V.index <= CROLLO[1])].max()).mean()), pctl_picco=float((hist_all < dv[(dv.index >= CROLLO[0]) & (dv.index <= CROLLO[1])].max()).mean()),
ivr_pre=float((hist < V.asof(CROLLO[0])).mean()), ivr_pre=float((hist_all < V.asof(CROLLO[0])).mean()),
rv5_max=float(rv5[(rv5.index >= CROLLO[0]) & (rv5.index <= CROLLO[1] + pd.Timedelta(days=3))].max()), rv5_max=float(rv5[(rv5.index >= CROLLO[0]) & (rv5.index <= CROLLO[1] + pd.Timedelta(days=3))].max()),
caduta=float(pd_[CROLLO[0]:CROLLO[1]].min() / pd_[:CROLLO[0]].iloc[-1] - 1.0), caduta=float(pd_[CROLLO[0]:CROLLO[1]].min() / pd_[:CROLLO[0]].iloc[-1] - 1.0),
) )
+60 -1
View File
@@ -23,7 +23,8 @@ sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research")) sys.path.insert(0, str(ROOT / "scripts" / "research"))
sys.path.insert(0, str(ROOT / "scripts" / "analysis")) sys.path.insert(0, str(ROOT / "scripts" / "analysis"))
from cblib import bs_put, f_factors, pick_legs # noqa: E402 import cblib # noqa: E402
from cblib import bs_put, causale, f_factors, pick_legs # noqa: E402
from certify_cb_chain import ( # noqa: E402 from certify_cb_chain import ( # noqa: E402
crossed_rate, hollow_rate, monotonicity_violations, worst_day_hollow, crossed_rate, hollow_rate, monotonicity_violations, worst_day_hollow,
) )
@@ -196,3 +197,61 @@ def test_nel_sleeve_f_moltiplica_il_credito_NETTO():
assert SL.VRP_CFG["f"] == 1.0, ( assert SL.VRP_CFG["f"] == 1.0, (
"il sleeve gira ancora a f=1.0: se cambia, aggiornare il diario 2026-07-30 e CLAUDE.md" "il sleeve gira ancora a f=1.0: se cambia, aggiornare il diario 2026-07-30 e CLAUDE.md"
) )
# ===========================================================================================
# C. CAUSALITA' delle serie di contesto (debito §5.18, 09/09): `asof(ts)` non guarda avanti
# ===========================================================================================
def test_causale_rietichetta_alla_chiusura_e_non_tocca_i_valori():
"""Il feed e' etichettato all'APERTURA: la chiusura della barra T e' nota a T+cadenza.
DEVE FALLIRE SE: qualcuno "semplifica" togliendo lo spostamento, o sposta i VALORI."""
idx = pd.date_range("2026-06-01", periods=5, freq="h", tz="UTC")
S = pd.Series([10.0, 11.0, 12.0, 13.0, 14.0], index=idx)
Sc = causale(S, "1h")
assert list(Sc.index) == list(idx + pd.Timedelta(hours=1))
assert Sc.asof(idx[2]) == 11.0, "a 02:00 e' nota la chiusura della barra 01:00"
assert S.asof(idx[2]) == 12.0, "il difetto che la funzione ripara (controllo positivo)"
assert np.isnan(Sc.asof(idx[0])), "prima della prima chiusura nota non c'e' spot"
assert list(Sc.values) == list(S.values), "i VALORI non si toccano, solo le etichette"
def test_spot_series_e_causale_sul_feed_1h(monkeypatch):
"""`cblib.spot_series(a).asof(ts)` = ultima chiusura ORARIA nota a ts. Controllo positivo:
la serie grezza del feed (etichettata all'apertura) a ts da' la chiusura di ts+1h."""
import scripts.analysis.research_lab as rl
idx = pd.date_range("2026-06-01", periods=6, freq="h", tz="UTC")
epoch = pd.Timestamp("1970-01-01", tz="UTC") # NON `asi8 // 10**6`: in pandas 3 e' in `us` (D6)
px = pd.DataFrame({"timestamp": ((idx - epoch) // pd.Timedelta(milliseconds=1)).astype("int64"),
"close": [100.0, 101.0, 102.0, 103.0, 104.0, 105.0]})
monkeypatch.setattr(rl, "load_tf", lambda asset, tf: px)
cblib.spot_series.cache_clear()
try:
S = cblib.spot_series("ZZZ")
ts = idx[3] + pd.Timedelta(minutes=17) # 03:17: la barra 03:00 e' ancora aperta
assert S.asof(ts) == 102.0, "a 03:17 l'ultima chiusura nota e' quella della barra 02:00"
assert S.asof(idx[3]) == 102.0, "a 03:00 esatte la barra 02:00 e' appena chiusa"
assert S.asof(idx[5] + pd.Timedelta(hours=1)) == 105.0, "l'ultima chiusura arriva un'ora dopo"
grezza = pd.Series(px["close"].values, index=idx)
assert grezza.asof(ts) == 103.0, "il difetto riparato: la serie del feed a 03:17 da' la chiusura delle 04:00"
finally:
cblib.spot_series.cache_clear()
def test_dvol_series_e_causale_sul_feed_giornaliero(monkeypatch, tmp_path):
"""La riga DVOL del giorno D e' la chiusura delle 23:00 di D: dentro D non e' ancora nota.
`cblib.dvol_series(a).asof(ts in D)` deve dare la chiusura di D1."""
days = pd.date_range("2026-06-01", periods=4, freq="D", tz="UTC")
epoch = pd.Timestamp("1970-01-01", tz="UTC")
pd.DataFrame({"timestamp": ((days - epoch) // pd.Timedelta(milliseconds=1)).astype("int64"),
"close": [36.0, 37.0, 43.0, 47.0]}).to_parquet(tmp_path / "dvol_zzz.parquet")
monkeypatch.setattr(cblib, "RAW", tmp_path)
cblib.dvol_series.cache_clear()
try:
V = cblib.dvol_series("ZZZ")
ts = days[2] + pd.Timedelta(hours=10) # 03/06 10:00
assert V.asof(ts) == 37.0, "dentro il 03/06 l'ultimo DVOL noto e' la chiusura del 02/06"
assert V.asof(days[3]) == 43.0, "alle 00:00 del 04/06 e' nota la chiusura del 03/06"
assert np.isnan(V.asof(days[0] + pd.Timedelta(hours=5))), "nel primo giorno non c'e' storia"
finally:
cblib.dvol_series.cache_clear()
+24 -20
View File
@@ -8,7 +8,7 @@ numero con quel peso ha bisogno di guardie sul MECCANISMO, non solo sull'esito:
nei casi degeneri (T0, sig=0 intrinseco): se diverge, il denominatore di ogni `f` e' un nei casi degeneri (T0, sig=0 intrinseco): se diverge, il denominatore di ogni `f` e' un
altro modello e il rapporto non significa piu' niente; altro modello e il rapporto non significa piu' niente;
* **lo spot dev'essere CAUSALE.** Il feed 1h e' etichettato all'APERTURA: la barra 20:00 chiude * **lo spot dev'essere CAUSALE.** Il feed 1h e' etichettato all'APERTURA: la barra 20:00 chiude
alle 21:00, quindi `S.asof(ts)` guardava un'ora AVANTI. `spot_causale` sposta l'indice di alle 21:00, quindi `S.asof(ts)` guardava un'ora AVANTI. `cblib.causale` sposta l'indice di
+1h. E' un look-ahead di un'ora dentro una misura di stress piccolo e letale; +1h. E' un look-ahead di un'ora dentro una misura di stress piccolo e letale;
* **il costo di chiusura si TAGLIA alla larghezza.** Uno spread a rischio definito non costa * **il costo di chiusura si TAGLIA alla larghezza.** Uno spread a rischio definito non costa
mai piu' della larghezza per chiudersi: si lascia regolare. Senza il taglio il "peggior MTM" mai piu' della larghezza per chiudersi: si lascia regolare. Senza il taglio il "peggior MTM"
@@ -96,25 +96,28 @@ def _trade(d_short: float, d_long: float, bid_s=0.030, ask_s=0.032, bid_l=0.010,
# =========================================================================================== # ===========================================================================================
# spot_causale — il look-ahead di un'ora # lo spot e il DVOL arrivano da cblib GIA' causali: qui non si ri-spostano (doppio shift = un'ora
# di RITARDO invece di un'ora di anticipo, stesso errore col segno opposto)
# =========================================================================================== # ===========================================================================================
def test_spot_causale_restituisce_l_ultima_chiusura_CONOSCIUTA(): def test_lo_script_usa_le_serie_di_cblib_senza_ri_spostarle():
"""🚨 Il feed 1h e' etichettato all'APERTURA: la barra 01:00 chiude alle 02:00. Senza lo """🚨 Il 09/09 mattina lo script riparava da solo il look-ahead con `spot_causale`; la sera la
spostamento, `asof(02:00)` restituiva la chiusura delle 02:00 cioe' un'ora di futuro riparazione e' entrata in `cblib.spot_series` / `dvol_series` (debito §5.18). Applicare lo
dentro un ingresso. DEVE FALLIRE SE: qualcuno "semplifica" togliendo lo shift. spostamento DUE volte darebbe la chiusura di due ore prima. DEVE FALLIRE SE: qualcuno
reintroduce uno shift locale, o `cblib` smette di essere causale."""
`cblib.spot_series` ha ancora il difetto (e quindi il f 0,714 di §11): dichiarato nel import inspect
docstring dello script, non toccato qui.""" import cblib
idx = pd.date_range("2026-06-01", periods=5, freq="h", tz="UTC") src = inspect.getsource(C.misura)
S = pd.Series([10.0, 11.0, 12.0, 13.0, 14.0], index=idx) assert "CB.spot_series(asset)" in src and "CB.dvol_series(asset)" in src
Sc = C.spot_causale(S) assert "spot_causale" not in src and "Timedelta(hours=1)" not in src
# l'UNICO `causale(` ammesso in misura() e' quello del contesto, e toglie un giorno al DVOL per la
assert list(Sc.index) == list(idx + pd.Timedelta(hours=1)) # tabella di calendario: un `causale(CB.spot_series(...), "1h")` sarebbe il doppio shift
assert Sc.asof(idx[2]) == 11.0, "a 02:00 e' nota la chiusura della barra 01:00" assert src.count("causale(") == 1 and 'CB.causale(V, "-1D")' in src
assert S.asof(idx[2]) == 12.0, "il difetto che la funzione ripara (controllo positivo)" assert not hasattr(C, "spot_causale"), "lo shift locale e' stato rimosso: vive in cblib.causale"
assert np.isnan(Sc.asof(idx[0])), "prima della prima chiusura nota non c'e' spot, non c'e' 10.0" # cblib e' causale (controllo positivo sul difetto che ripara)
assert Sc.iloc[0] == 10.0 and S.iloc[0] == 10.0, "i VALORI non si toccano, solo le etichette" idx = pd.date_range("2026-06-01", periods=3, freq="h", tz="UTC")
S = pd.Series([1.0, 2.0, 3.0], index=idx)
assert cblib.causale(S, "1h").asof(idx[1]) == 1.0 and S.asof(idx[1]) == 2.0
# =========================================================================================== # ===========================================================================================
@@ -327,7 +330,8 @@ def test_trade_CONTA_le_ore_tagliate_invece_di_nasconderle():
def test_f_net_e_il_credito_reale_su_quello_modellato(): def test_f_net_e_il_credito_reale_su_quello_modellato():
"""Il credito reale si incassa al BID della corta e si paga all'ASK della lunga: e' la """Il credito reale si incassa al BID della corta e si paga all'ASK della lunga: e' la
convenzione conservativa di `cblib.f_factors`, e vale 0,74 su ETH nel crollo.""" convenzione conservativa di `cblib.f_factors`, e vale 0,76 su ETH nel crollo (0,74 con lo spot
e il DVOL non causali della mattina del 09/09: i due numeri vanno cambiati insieme)."""
t = _trade(C.SHORT_D, C.LONG_D) t = _trade(C.SHORT_D, C.LONG_D)
assert t["cred_real"] == pytest.approx((0.030 - 0.012) * 2000.0) assert t["cred_real"] == pytest.approx((0.030 - 0.012) * 2000.0)
T = (C.EXP - TS0).total_seconds() / 86400.0 / 365.25 T = (C.EXP - TS0).total_seconds() / 86400.0 / 365.25
@@ -477,7 +481,7 @@ def test_integrazione_la_misura_del_09_09_si_riproduce():
eth = s["ETH"] eth = s["ETH"]
assert eth["n_pre"] > 200, "il campione pre-crollo si e' svuotato" assert eth["n_pre"] > 200, "il campione pre-crollo si e' svuotato"
assert eth["f_net"] == pytest.approx(0.74, abs=0.03) assert eth["f_net"] == pytest.approx(0.76, abs=0.03)
assert eth["f_exp"] == pytest.approx(1.04, abs=0.03) assert eth["f_exp"] == pytest.approx(1.04, abs=0.03)
assert eth["q_itm_pieno"] == pytest.approx(0.83, abs=0.10), ( assert eth["q_itm_pieno"] == pytest.approx(0.83, abs=0.10), (
"la quota di trade a f_exp tautologico e' cambiata: cambia la lettura del 1,04") "la quota di trade a f_exp tautologico e' cambiata: cambia la lettura del 1,04")