research(vrp): il f del credito netto e' 0.73 sulle quote reali, non 1.0

Prima integrazione della catena opzioni Deribit mainnet accumulata da
cerbero-bite (/opt/docker/cerbero-bite, dal 2026-06-09: entrambe le ali,
1g-3mesi, oraria, con book_depth). E' l'unica fonte di prezzi opzioni VERI
del progetto, e non e' ricostruibile a posteriori: Deribit non serve book
storici, un'ora non raccolta e' persa.

VRP01 prezza entrambe le gambe con BS su DVOL ATM (VRP_CFG f=1.0 sul credito
NETTO). Misurato agli STESSI strike su 8/8 scadenze settimanali con entrambe
le gambe quotate (delta -0.270/-0.099 contro target -0.280/-0.100):

  f gamba corta   1.02   <- replica la calibrazione del 20/06
  f gamba lunga   2.30   <- l'ala che si COMPRA
  f credito NETTO 0.73   IC95% [0.698, 0.780], 0/15 osservazioni >= 1.0

Meccanismo, non rumore: IV(corta)-DVOL +0.8pp ma IV(lunga)-DVOL +7.5pp -> il
modello prezza a vol ATM anche l'ala comprata. Il difetto non e' nel premio
incassato ma nella protezione comprata, cioe' proprio il "defined-risk" per
cui v2 fu promosso.

Conseguenza standalone (solo f): 1.00 -> FULL 1.08 / HOLD +0.58; 0.80 -> 0.51
/ -0.02; 0.73 -> 0.31 / -0.23. Book 5-sleeve: FULL -0.069, HOLD -0.103, DD
invariato = dentro la banda d'ancora, ma ~meta' del contributo LOO di VRP01
era il prezzo che il modello si faceva da solo.

VRP_CFG["f"] NON cambiato: 15 osservazioni, 7 settimane, e 0/8 passano il
gate IV-rank>0.30 -> il f e' misurato nel regime in cui il sleeve sta FLAT.
Caveat quantificato, non nuovo parametro. Il criterio del 19/06 (rivalutare
quando cerbero-bite cattura un crash) e' intatto.

Book, pesi, cron, config INVARIATI.

Regole nuove congelate nei test:
- il f di una struttura multi-gamba non e' il f di una sua gamba (misurare la
  sola gamba venduta da' la risposta sbagliata con segno rassicurante);
- un guasto IN CORSO si misura al GIORNO PEGGIORE, non in media (la prima
  stesura della certificazione diluiva un guasto di 2 giorni da 51.7% a 13.5%);
- una riga presente non e' un dato presente.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-30 19:51:52 +00:00
parent 7d64dd4c2b
commit 8c18e82f1a
6 changed files with 983 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
# 2026-07-30 — VRP01 contro le quote REALI: il f del credito netto è 0.73, non 1.0
**Esito:** nessun cambio a book, pesi, cron o config. Un'ipotesi di VRP01 è **falsificata con
misura**, e il dataset che l'ha falsificata entra nel progetto (estrattore + certificazione +
harness + test). Il muro dichiarato per il deploy — il *f* di stress — resta in piedi, intatto.
## Da dove viene il dato
`/opt/docker/cerbero-bite` accumula dal 2026-06-09 la catena opzioni Deribit **mainnet**: entrambe
le ali, scadenze 1g→3 mesi, ora per ora, con la profondità top-3 del book. È la fonte che il
progetto cita dal 20/06 (`options_vrp_calibrate.py`) e che finora era stata usata una volta sola,
via export manuale in `/tmp`.
Il motivo per cui quel dato si **memorizza** invece di richiederlo quando serve: una catena opzioni
non è ricostruibile a posteriori — Deribit non serve book storici. Un'ora non raccolta è persa per
sempre. Stessa asimmetria dello storico spot certificato, con l'aggravante che qui non esiste un
secondo venue da cui recuperarla.
## La domanda
`src/portfolio/sleeves.py:136``VRP_CFG` gira a **`f=1.0`**, applicato al credito **NETTO**:
```python
net_prem = (_bs_put(S0, Ks, T, sig) - _bs_put(S0, Kl, T, sig)) * cfg["f"]
```
cioè assume che il mercato paghi esattamente ciò che Black-Scholes su DVOL-ATM dice, **per entrambe
le gambe**. Il caveat di ammissione ("premio MODELLATO su DVOL ATM, skew non esplicito") era
dichiarato dal 19/06 e mai quantificato, per mancanza di quote vere.
## D1 — copertura: la struttura c'è
Finestra settimanale 4-10 DTE: **8 scadenze su 8** hanno **entrambe** le gambe quotate, per
entrambi gli asset. Delta realizzati **0.270** (target 0.280) e **0.099** (target 0.100): la
struttura misurata è quella del sleeve, non una sua approssimazione.
## D2 — il numero: f = 0.73
Confronto agli **stessi strike** (così il rapporto isola l'errore di *prezzo*, non la scelta dello
strike), fill conservativo (vendi al bid, compri all'ask):
| grandezza | mediana | p25 | p75 |
|---|---|---|---|
| f gamba **corta** (venduta) | **1.02** | 0.88 | 1.12 |
| f gamba **lunga** (comprata) | **2.30** | 1.68 | 2.87 |
| **f credito NETTO** ← quello che entra nel book | **0.73** | 0.71 | 0.82 |
| f credito netto, a mid | 0.80 | 0.79 | 0.89 |
IC95% bootstrap sulla mediana **[0.698, 0.780]**; **0 osservazioni su 15** arrivano a 1.0.
⚠️ **La calibrazione del 20/06 non è contraddetta, è replicata**: misurava la **sola gamba corta** e
trovava f ≈ 1.0; qui la stessa quantità, con implementazione indipendente, dà **1.02**. La
differenza è che VRP01 è uno *spread*, e il problema sta nell'altra gamba.
## Il meccanismo (senza il quale il numero sarebbe un artefatto)
**IV(corta) DVOL = +0.8 pp**, **IV(lunga) DVOL = +7.5 pp**. Il modello prezza entrambe le gambe
a vol ATM; l'ala più OTM — quella che si **compra** — sta 8 punti sopra per skew, quindi costa
**~2.3× il modello**, e il credito netto si comprime del 27%.
Colpisce esattamente il punto su cui VRP01-v2 era stato promosso: *"(a) defined-risk taglia la coda
(worst-week 16.6%→−7.4%)"*. Nel modello quell'assicurazione è comprata a vol ATM, cioè quasi
gratis. Nel mercato costa più del doppio. **Il difetto non è nel premio incassato, è nella
protezione comprata.**
## Conseguenza, misurata
VRP01 standalone sostituendo **solo** `f` (replica sana a f=1.0: 1.08 / 0.58 / 11.8% contro i
1.10 / 0.60 / 12% pubblicati):
| f | Sh FULL | DD FULL | CAGR | Sh HOLD-OUT |
|---|---|---|---|---|
| **1.00** (assunto) | 1.08 | 11.8% | 8.2% | **+0.58** |
| **0.80** (misurato a mid) | 0.51 | 14.4% | 3.6% | **0.02** |
| **0.73** (misurato, fill conservativo) | 0.31 | 15.3% | 2.0% | **0.23** |
Book a 5 sleeve (VRP01 al 12%), ancore canoniche: FULL 2.215 → **2.146**0.069),
HOLD 2.347 → **2.244**0.103), DD invariato.
**Lettura onesta: il book non crolla** — l'effetto è dentro la banda d'ancora (canonico 2.22 contro
mediana de-luckata 1.95). Ma il LOO del 26/07 attribuiva a VRP01 **+0.122 di FULL**: **circa metà di
quel contributo era il prezzo che il modello si faceva da solo.** L'audit d'ancora aveva corretto la
fortuna di calendario; l'ottimismo di *prezzo* non l'aveva corretto nessuno.
## D3 — ciò che solo questo dato permette
Le stesse due gambe sono riquotate **~164 volte per trade** (orarie) fino a scadenza. Da qui:
- il **50%-profit-take scatterebbe in 13/15 settimane** → la regola di gestione diventa testabile;
- **peggior mark infra-settimana** (N=13 trade con path): mediana **6.9%** del capitale, minimo
**81.7%**, su un trade che è finito **in utile**.
⚠️ Il sleeve marca solo a scadenza: quel minimo **non esiste nei suoi numeri**. È la stessa classe
di errore della lente wick accoppiata del 25/07 — *un rischio valutato sul minimo non si misura
sulle chiusure*. Il DD 12% di VRP01 è un DD di chiusure settimanali.
## D4 — ciò che il dato NON dice, ed è la ragione per cui si raccoglie
**Nella finestra raccolta VRP01 non avrebbe aperto una sola posizione.** IV-rank mediano 0.12 (ETH)
e 0.08 (BTC) contro il gate >0.30: **0 settimane su 8** per entrambi. Il massimo di DVOL raccolto
sta al **24° percentile** (ETH) e **28°** (BTC) della storia.
Quindi il f = 0.73 è misurato nel regime in cui il sleeve **sta flat**. Lo skew è strutturale e in
stress si irripidisce, quindi la direzione è robusta — ma **la taglia in stress resta non
misurata**, e il criterio dichiarato il 19/06 ("rivalutare quando cerbero-bite cattura un crash")
non è stato toccato da questo lavoro.
## Il difetto trovato nel dato, e la certificazione che ora c'è
Il feed ha un guasto **in corso** dal 29/07 05:00 UTC: il collettore persiste la riga anche quando
il ticker fallisce (rate-limit per-IP), con `bid`/`ask`/`iv`/`delta` NULL. Il **conteggio righe non
cambia** — 13.000/giorno prima e dopo — quindi ogni controllo di copertura basato sulle righe dice
"tutto bene". Tasso di quote vuote per settimana: 0.4 · 0.7 · 2.2 · 1.5 · 0.5 · 0.4 · 0.3 → **22%**.
`scripts/analysis/fetch_cb_chain.py` certifica quattro difetti (quote vuote, book incrociato,
premio non monotono nello strike, `depth==0` ambiguo by design) ed è il posto dove è stata
imparata la lezione seguente.
⚠️ **La prima stesura della certificazione confrontava "tutto il campione" con "ultimi 7 giorni" —
e diluiva un guasto di 2 giorni al 13.5%**, cioè commetteva a 7 giorni lo stesso errore che
dichiarava di voler evitare a 3 mesi. Con la statistica giusta (**giorno peggiore** della finestra)
lo stesso dato dice **51.7%** e lo status passa da `DEGRADATO` a `QUOTE-VUOTE`.
**REGOLA: un guasto IN CORSO non si misura con una media su una finestra — si misura al suo giorno
peggiore. Qualunque finestra è abbastanza lunga da nascondere un guasto abbastanza giovane.**
Congelata in `test_giorno_peggiore_non_si_lascia_diluire_dalla_media`.
## Cosa NON è stato fatto, e perché
**`VRP_CFG["f"]` non è stato cambiato.** La misura sta su 15 osservazioni, 7 settimane, un solo
regime — per giunta il regime in cui il sleeve è flat. Sostituire 1.0 con 0.73 significherebbe
propagare in tutto il book un numero misurato dove il book non opera. La mossa corretta è il
**caveat quantificato**: i numeri 1.10 / 0.60 / 12% si citano d'ora in poi con "a f=1.0; f misurato
sulle quote reali 0.73 [0.70, 0.78], a cui l'hold-out standalone è 0.23".
Che VRP01 stia al 12% e non sia deployato è, retroattivamente, la decisione giusta **per il motivo
giusto**: non "short-vol da modello per prudenza", ma "il modello sovrastima il credito del 27% e
lo fa sulla gamba che doveva proteggere".
## Regole nuove
1. **Il *f* di una struttura multi-gamba non è il *f* di una sua gamba.** Misurare la sola gamba
venduta dà la risposta sbagliata **con segno rassicurante** (1.02 invece di 0.73). Ogni futura
calibrazione su quote reali di una struttura a N gambe si fa sul **netto**.
Congelata in `test_il_f_della_gamba_corta_non_e_il_f_dello_spread` (3 ali, identità algebrica
`f_net = (1 f_long·k)/(1 k)` — una soglia sola sarebbe stata una fixture fortunata).
2. **Un guasto in corso si misura al giorno peggiore, non in media** (sopra).
3. **Una riga presente non è un dato presente.** Il difetto delle quote vuote è invisibile a
qualunque controllo che conti righe: va contato ciò che è **quotato**, non ciò che è **scritto**.
È la terza occorrenza della stessa famiglia dopo la contabilità a 3 stati di `paper_dvolspread`
(26/07) e il silenzio letto come zero di `fresh_5m` (26/07).
## File
- `scripts/analysis/fetch_cb_chain.py` — estrazione + certificazione → `data/raw/cb_chain.parquet`
- `scripts/research/cblib.py` — harness (loader, `pick_legs`, `f_factors`, `close_cost_path`)
- `scripts/research/r0730_vrp_real_quotes.py` — lo studio (D1-D4, meccanismo, sweep f, `--book`)
- `tests/test_cb_chain_vrp.py` — 14 casi (rilevatori + controlli positivi + guardia di convenzione)