Compare commits
53 Commits
ee5c6ed539
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 79afe41ec2 | |||
| 8f2e6c5c2c | |||
| 0a2f780d3c | |||
| bb40e87d13 | |||
| 9f55652d99 | |||
| 95424f0755 | |||
| de83909db9 | |||
| 1279ea605a | |||
| 835e0c8666 | |||
| 861cc7fc27 | |||
| 2f701b8469 | |||
| bc43218a12 | |||
| d95fabf060 | |||
| 1c0549080c | |||
| ec8478308f | |||
| 05816c49f9 | |||
| 5bcf212923 | |||
| 8d391ed29a | |||
| 62025e7840 | |||
| 4705d71ceb | |||
| fd7595e819 | |||
| c8be83ef6e | |||
| 30286ea033 | |||
| 41f83b5dd8 | |||
| 7b8e35633d | |||
| f151316bdf | |||
| c250c04371 | |||
| a45794d2cb | |||
| 3cf884fa39 | |||
| 035bd086a2 | |||
| fc24ee5f52 | |||
| bb33df472f | |||
| 993d17556a | |||
| 2beb11b764 | |||
| 32e1649dcb | |||
| 418f523be7 | |||
| 21fe289383 | |||
| 801bd13f10 | |||
| c21a0d4058 | |||
| 31f82e1730 | |||
| cb40ab3e20 | |||
| 05a698a83e | |||
| a6b5756cc0 | |||
| e5052a690f | |||
| 631e854b29 | |||
| 562e95338b | |||
| c4c82a8fbd | |||
| e7a5fee2fd | |||
| 3034bfdc58 | |||
| d597fc64e8 | |||
| a51844875b | |||
| f10d847816 | |||
| 4f91b25b72 |
@@ -17,8 +17,8 @@ chi la riapre deve battere il motivo, non ripetere la misura.
|
||||
| `docs/memory/40-produzione-e-deploy.md` | esecutore, tripwire, monitor, libro di bordo, PRIIPs/UCITS | tocchi cio' che gira con soldi veri |
|
||||
| `docs/memory/50-dati-e-feed.md` | difetti del dato trovati e riparati, catena opzioni | tocchi i feed |
|
||||
| `docs/memory/60-metodo-e-gate.md` | i gate codificati in `altlib.py` | valuti un candidato |
|
||||
| `docs/research/RESULTS-0822.md` | registro per filone §1-69 | vuoi il dettaglio di un filone |
|
||||
| `docs/diary/` (123 voci) | la sessione originale, per data | vuoi il contesto completo |
|
||||
| `docs/research/RESULTS-0822.md` | registro per filone §1-77 | vuoi il dettaglio di un filone |
|
||||
| `docs/diary/` (146 voci) | la sessione originale, per data | vuoi il contesto completo |
|
||||
|
||||
---
|
||||
|
||||
@@ -32,7 +32,13 @@ Documento di fondazione: `docs/diary/2026-06-19-deribit-history.md`.
|
||||
- Lo storico e' **ricostruito da Deribit mainnet e certificato**. Universo affidabile = **solo
|
||||
BTC/ETH** (tutti i TF); gli alt sono esclusi (illiquidi/divergenti/non certificabili).
|
||||
- Tutto il codice pre-reset e' archiviato in `Old/` (preservato in git, non cancellato).
|
||||
- **L'esecuzione e' ARMATA e LIVE** su Deribit mainnet dal 2026-06-20. Capitale reale **~$635**
|
||||
- 🚨 **Il README e' rimasto fermo al 2026-06-04 fino al 2026-09-02**, cioe' *quindici giorni prima*
|
||||
del reset: per 75 giorni la prima pagina del repo ha pubblicato la libreria pre-reset e i suoi
|
||||
numeri (PORT06 Sharpe 7,84/10,06, CAGR ~79%) come risultati correnti, mentre il progetto li aveva
|
||||
gia' dichiarati artefatti. Riscritto contro lo stato vero. **La lezione non e' "aggiornare il
|
||||
README": e' che un reset invalida anche i documenti che nessuno rilegge** — l'inventario di cosa
|
||||
cita numeri morti va fatto il giorno del reset, non quando qualcuno ci inciampa.
|
||||
- **L'esecuzione e' ARMATA e LIVE** su Deribit mainnet dal 2026-06-20. Capitale reale **~$4.495** dal 04/09 (era ~$635 fino al 25/08, ~$2.060 fino al 04/09)
|
||||
(NON i €2.000 nominali del paper trader).
|
||||
- Si riparte dalla ricerca di strategie NUOVE, su dati certi, con la metodologia della §8.
|
||||
|
||||
@@ -67,12 +73,56 @@ lorda massima 1,0x**, e nessun livello di capitale cambia il profilo di rischio
|
||||
`min_order_usd` 5 · `disaster_sl_pct` 0.30 on-book sulla posizione **netta** · staleness-gate feed
|
||||
2 giorni (blocca) · `skh_feed_max_age_min` 30 (allerta, **non** blocca) · alert Telegram.
|
||||
|
||||
📌 **Modello di margine: `Cross: Standard Margin` (X:SM) dal 2026-08-31**, cambiato dall'operatore. Prima era **Segregated** (S:SM), e sotto quello l'USDE **non faceva margine**: e' il motivo per cui la misura dell'haircut trovava $0,0000 accantonati — si cercava un parametro non in vigore. Il passaggio e' verificabile dal gateway: `available_funds` USDC e' passato da ~$1.400 a **$2.011,09**, contro $2.010,90 attesi con haircut 5% (scarto **$0,19** — seconda conferma indipendente del 5%). ⚠️ **Cosa ha comprato, misurato**: **$611** di margine utilizzabile — inutile a 0,28x di leva, serve solo sopra ~1,4x che non e' autorizzata — e **+11 USDE (~$11)** di capienza sul tetto. **Cosa e' costato**: sotto segregato l'USDE era **ring-fenced** dalle perdite del libro (solo il silo USDC rispondeva delle posizioni); sotto cross **l'intero conto risponde**. Non morde oggi (perdita massima plausibile ~$617 col disaster-SL, dentro il solo USDC), ma il rischio strutturale e' cambiato verso: **se un giorno il cross non servisse piu', tornare a S:SM ri-recinta l'USDE**. 📌 **X:PM valutato e SCARTATO il 31/08 senza provarlo** — la risposta era gia' nella pagina margini: **Available Balance $1.935,14 contro $2.011,73** di X:SM (−$76,59) e margini piu' alti su entrambe le misure, **MM 3,43% contro 0,37% (~9×)**. Il PM e' basato su scenari e premia il rischio che si **compensa**: due long direzionali nudi sono il suo caso peggiore, e paga lo scenario di stress invece dell'aliquota piatta. **Cosa lo riapre**: il deploy di VRP01 o di un altro sleeve di opzioni — allora il rischio inizia a compensarsi e il confronto cambia di segno. ✅ **Confermato il 31/08 a X:SM attivo** e su misure pulite: la KB da' l'USDe al 5% sotto entrambi i modelli, quindi l'haircut si cancella e l'inversa della riga CROSS da' il margine di POSIZIONE — **$88,23 sotto PM contro $11,64 sotto SM, 7,6×**, e 9,3× la MM. La riga X:SM era una **proiezione di un modello inattivo** e oggi si riproduce al centesimo ⇒ anche la riga X:PM era affidabile: decidere senza provare era corretto. 🚨 **L'«IM %» della pagina margini NON e' il margine delle posizioni**: e' `(margin_balance − available)/margin_balance`, e da noi e' **74% haircut** (e' salita da 2,13% a 2,15% *perche' abbiamo comprato USDE*, non perche' sia salito il rischio). La **MM** e' pulita e ha base diversa. 📌 **La pagina margini non serve piu' come schermata**: la riga CROSS **e'** `available_funds` del gateway (scarto $0,01) e `r0831_margini_conto.live()` la ricostruisce, haircut compreso. 📌 **L'equity che il book somma e' la RICCHEZZA, non la capacita' di margine** — giusta per accorgersi di un'uscita di fondi (il difetto del 26/08), sbagliata se la si usasse per dire quanto margine c'e'.
|
||||
|
||||
📌 **Dal 2026-08-26 l'equity del book somma USDC + USDE** (valutato
|
||||
all'indice pubblico `usde_usdc`): il conto tiene **3.088,7 USDE, quota 68,7%** (**IDONEO** dal 28/08;
|
||||
500 → 644 il 30/08 → **3.088,7 il 06/09** su autorizzazione dell'operatore *«usa piu' USDE»* e *«cerca la %
|
||||
massima raggiungibile»*, dopo che il versamento di **$2.410,14 del 04/09** aveva dimezzato la quota a 14,6%).
|
||||
🚨 **Il TETTO del venue del 30-31/08 (~31,2-31,9%) il 06/09 NON C'ERA**: sonda a saldo crescente
|
||||
(`r0906_usde_tetto_sonda.py`, passi 100→20→4→1, stesso metodo del 31/08) da 31,8% a **68,7% senza un solo
|
||||
rifiuto**, 2.434 USDE in 39 ordini @ 1,0005 (~$1,2 di spread); si e' fermata al **cuscino di regolamento**
|
||||
(70% = `2 × 0,5 × disaster_sl_pct`, derivato da config), cioe' al NOSTRO limite, non al venue. **«Il 70% non
|
||||
e' raggiungibile ne' ora ne' mai» (31/08) e' falsificato**; il tetto e' **sparito, non scalato** — e alle
|
||||
20:58 dello stesso giorno avevo scritto qui «terza conferma della frazione» sulla base di *nessun rifiuto
|
||||
fino al 31,8%*, che dimostra ≥, non =. Non spiegato (D5): puo' tornare. `venue_cap_frac` → **null**,
|
||||
`quota_max_frac` → **0,85** (il valore che l'operatore aveva scelto il 30/08 per questo scenario).
|
||||
⚠️ **Nessuno sorveglia il cuscino USDC**: `usde_watch` allerta solo sulla quota; una perdita del libro
|
||||
(che si regola in USDC) fa salire la quota da sola — a −$58 di USDC il cuscino e' gia' scoperto, a −30%
|
||||
la quota passa il 98%. GATE USDE-01 in §4). ⚠️ **Ogni versamento abbassa la quota**: dopo un bonifico si
|
||||
rilancia `usde_convert --quota 0.70` (che si ferma al cuscino), non si aspetta che qualcuno se ne accorga. Autorita' dei parametri: sezione `usde` di `config/live.json`
|
||||
(haircut **5%** — ✅ divergenza chiusa il 31/08, la config diceva 10% ed era sbagliato (`r0831_margini_conto.py`); oggi comunque **non si applica**, vedi sopra — tetto ALLERTA quota 50%, soglie depeg 0,99/0,95 coi criteri in `_nota_usde`), letta
|
||||
da `src/live/usde.py` — shadow (sizing orario) e `usde_watch` (sorveglianza 12:35 UTC, cron
|
||||
proprio, sotto `monitor_health`) derivano entrambi da li'. ⚠️ Il difetto d'origine: `shadow._equity`
|
||||
leggeva SOLO il conto USDC — la prima conversione avrebbe causato un falso "USCITA DI FONDI −24%"
|
||||
e vendite indesiderate. Chi aggiunge una valuta di collaterale DEVE passare da `usde.py`/shadow.
|
||||
|
||||
🚨 **Una capacita' PUBBLICATA dal venue non e' una capacita' del CONTO: si verifica sul conto, prima che entri in un piano — e costa un ordine da $1.** Due affermazioni di Deribit smentite dal conto in due giorni: la **APR USDC 3,40%** (l'Italia e' nella lista delle giurisdizioni escluse → i nostri $1.412 di USDC rendono **zero**) e «**all Deribit users are permitted to buy and sell BUIDL**» (11 ordini rifiutati, fino a **1 unita' a limite 1,0100 contro ask 1,0004 con 10.730 di profondita'**, tenendo zero BUIDL e $1.412 disponibili → **GATE BUIDL-01 chiuso: NON ENTRABILE**, costo $0, diario `2026-08-31-buidl-test-eligibilita`). ⇒ **L'USDE e' l'unico collaterale a rendimento che il conto puo' usare**, ed e' al suo tetto: le altre tre vie sono chiuse per **giurisdizione** (USDC), **permesso** (BUIDL) e perche' **non e' un dollaro** (stETH 2,23%, che e' ETH: coprirlo e' CC01, gia' parcheggiato a scala ~$20k+). 📌 **`not_enough_funds_in_currency` e' il messaggio GENERICO di Deribit per "non puoi acquisire altra di questa valuta"** — permesso o tetto, mai i fondi. Chi lo incontra salti subito a *questa valuta la posso acquisire, e fino a quanto?* invece di inseguire taglia, cadenza e prezzo come il 30/08.
|
||||
|
||||
⚠️ **Il nome "disaster-SL −30%" inganna:** e' **rotolante e simmetrico**, non un massimo di perdita
|
||||
per trade. Su cicli a ingresso proprio lascia passare perdite oltre −30% senza scattare, e il
|
||||
peggior DD dall'ingresso misurato e' **−60,6% BTC / −61,5% ETH**. Dettaglio in `40-produzione`.
|
||||
|
||||
📌 **Soldi fermi (01-02/09):** on-venue **chiuso con l'elenco intero** — Deribit remunera 4 valute su 50,
|
||||
le quattro gia' chiuse il 31/08. L'USDC sul conto **non e' fermo** (base di sizing + cuscino). Il fermo
|
||||
vero sta fuori (~€6k in XEON = lo split-cassa): le opzioni sicure valgono **+€52-62/anno** (BOT 12,5% /
|
||||
conto deposito 26%), sUSDe rende 2x ma e' **lo stesso emittente dell'USDE** e distrugge lo split.
|
||||
✅ **L'operatore ha scelto di VERSARE** (piano 27/07: **€5.000 dentro, ~€1.000 fuori** — la protezione
|
||||
venue satura a qualunque quota > 0). Nessuna azione di config. **In attesa di importo e data**; al
|
||||
deposito va **una riga di giornale**, non ricostruita dopo. `r0901d_soldi_fermi.py`.
|
||||
|
||||
**Candidati in forward-monitor** (nessuno nel book): XSR01 · DVOLSPREAD · STATARB · PREVDAY.
|
||||
|
||||
📌 **Revisione SETTIMANALE in sola lettura (dal 2026-09-09).** Lunedi' 06:15 UTC `scripts/cron_review.sh` →
|
||||
`scripts/live/revisione.py` → `src/live/revisione.py`: un revisore **diverso dall'autore** (`claude-fable-5-1`)
|
||||
rilegge CLAUDE.md, config, crontab, commit, `monitor_health`, 7 giorni di giornale e di diari, stato dei sorveglianti
|
||||
(~195k caratteri), e scrive `docs/revisioni/<data>.md` **firmato** + la Sintesi su Telegram. **Propone, non esegue:**
|
||||
l'unico file che scrive e' il rapporto; le decisioni vincolanti (§3) non le ripropone; un'idea «nuova» la propone solo
|
||||
citando la memoria che ha ucciso la simile. Il rapporto e' opinione di un lettore fallibile (P13): guardia sui numeri
|
||||
come `analista`. Un modello muto produce un rapporto che lo dice, e un 🚨 (P5). Deciso dall'operatore il 09/09 sera
|
||||
(«fai») dopo aver **scartato** autoregolazione dei parametri e generazione automatica di strategie: la ricerca non e'
|
||||
il vincolo binding dal 26/07, e un generatore e' una grid search senza fondo (M3).
|
||||
|
||||
---
|
||||
|
||||
## 2. I numeri da citare — e quelli da NON citare
|
||||
@@ -88,10 +138,12 @@ 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 |
|
||||
| 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) |
|
||||
| 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]** |
|
||||
| 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) |
|
||||
| soffitto direzionale BTC/ETH | ~1,3 | **~1,15** col funding dentro |
|
||||
| performance del libro LIVE | «+243%» di equity (e' **96,3% un bonifico**) · e **+11,98%** (lettura 06/09 20:47Z, PRIMA della dichiarazione del versamento di prova) | **TWR +7,83%** spezzato sui TRE movimenti — +8,14% fino al 25/08 08:00, −0,62% / −0,12% / +0,46% dopo (lettura 06/09 21:14Z); trading da arming **+$62,45**. Dal 02/09 lo stampa `trades_db.py --report` (debito 14 chiuso). 🚨 **Il 06/09 l'operatore ha dichiarato un versamento di PROVA di €25 il 25/08 alle 08:00Z**, ~$24,9: +3,3% su $647, **sotto la soglia del 10%** del rilevatore, quindi per 12 giorni e' stato contato come trading (un terzo del «trading da arming» e 4 punti di TWR). Ora sta in `data/live/movimenti_dichiarati.jsonl`, letto da `journal.movimenti_capitale` (fonte `dichiarato`, importo dell'operatore, il mercato dell'ora resta nel rendimento). **Regola: ogni versamento, anche di prova, va dichiarato li' il giorno stesso** — il rilevatore vede solo i salti ≥10% |
|
||||
|
||||
📌 **Il libro a k=1 rende MENO dell'S&P 500** (15,19% contro 17,40%, stessa finestra): il vantaggio
|
||||
sta nello **Sharpe** (1,35 vs 0,89), e **senza leva non si converte in rendimento**. A iso-rischio
|
||||
@@ -109,6 +161,23 @@ tutto il drift del libro). A 10 anni i bonifici fanno il **59%** del risultato.
|
||||
un tetto di 1,0x. **Il cap non ha mai morso: il vincolo e' il segnale.** Riempire quel vuoto con
|
||||
l'ETF e' stato **misurato e SCARTATO** il 25/08 (`20-ondate`).
|
||||
|
||||
📌 **Il libro nei crolli (misurato 09/09, `r0909_libro_nei_crolli.py`): nel GIORNO del crollo PERDE**
|
||||
(−0,48%/g sui 160 giorni ≤ −5%, positivo nel 18%); per finestra di 20 giorni e' immune-o-positivo e
|
||||
**dal 2022 chiude > 0 in 8/8 peggiori finestre**, tutto dalla gamba **short di SKH01** (108% dei
|
||||
guadagni; marcata all'USCITA del trade, quindi arriva *dopo* il giorno). Le 2 finestre perse (2019,
|
||||
maggio 2021) sono TP01 long dentro un trend non ancora girato. Beta +0,0769 (riproduce §46). ⚠️ Un
|
||||
solo aggettivo ("guadagna"/"immune") e' l'ordine delle regole: la prima stesura diceva 8/12 GUADAGNA,
|
||||
con le classi nell'ordine dichiarato sono 4 + 6 immune. **La leva che aumenterebbe il guadagno e' il
|
||||
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
|
||||
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,76 [0,71-0,86] = 0,712 del rally** (rimisurato la sera del 09/09 con spot e DVOL
|
||||
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
|
||||
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`.
|
||||
|
||||
🚨 **Tre baseline diverse girano sotto lo stesso nome "libro 75/25"**, e lo spread fra loro (0,12 di
|
||||
Sharpe, 1,7pp di maxDD) e' **piu' grande di quasi tutti gli effetti che il progetto misura**:
|
||||
`hourly` **1,683 / 11,22%** (la lente di **ogni muro e ogni traiettoria**) · `canonical` 1,800 / 9,56%
|
||||
@@ -138,18 +207,37 @@ CLAUDE.md", la risposta e' la colonna *cosa la riapre*.
|
||||
| decisione | data | non si ri-discute | cosa la riapre |
|
||||
|---|---|---|---|
|
||||
| **100% Deribit fino a $20k** — accettato P(perso tutto) 10/18/34/64% a p=0,5/1/2/5% invece di 0/0/4/27% | 26/07 | la soglia $3k | **$20k**, o un cambio di piano, o `p` che diventa stimabile invece che assunto |
|
||||
| **SOL escluso** — universo direzionale resta BTC/ETH (presidiato da un test) | 22/08 | SOL su un book a 2 gambe con questi dati | ~3 anni di storia SOL certificata (**non prima del 2028**), o un meccanismo che non sia TP01/SKH01 congelati |
|
||||
| **Niente short-vol da modello in deploy** — e niente long-vol scalp da modello | 19/06 | — | un `f` di stress reale misurato su un crash catturato |
|
||||
| **SOL escluso** — universo direzionale resta BTC/ETH (presidiato da un test) | 22/08 | SOL su un book a 2 gambe con questi dati | ~3 anni di storia SOL certificata (**non prima del 2028**), o un meccanismo che non sia TP01/SKH01 congelati. **XRP misurato il 09/09 con lo stesso harness: diluisce uguale** (hold-out −0,169 in 0/24, un anno buono, 5m flat 27-61%); su Deribit i perp liquidi (≥$10M/g) sono SOLO BTC/ETH/XRP/SOL — `r0909_deribit_universo.py` |
|
||||
| **Niente short-vol da modello in deploy** — e niente long-vol scalp da modello | 19/06 | — | un `f` di stress reale misurato su un crash catturato **con IV-rank > 0,30 e sulla famiglia USDC-lineare** — il crollo del 1-5/06/2026 (misurato 09/09) e' catturato ma a vol bassa, sotto il gate e inverse: soddisfa la forma, non la sostanza |
|
||||
| **Non de-esporre il weekend** (porta il 38% del gross di TP01 nel 31% del tempo) | 17/07 | — | ogni proposta "risk-off weekend" parte **REFUTED** salvo null de-levering superato |
|
||||
| **Peso 75/25 confermato 3 volte** (24/07, 26/07, 24/07-follow-up) | 26/07 | — | `weights_tilt_null` superato, che finora **fallisce** |
|
||||
| **Fee Deribit: ≤5 bps/lato → non si tocca nulla; >10 bps → rivedere il peso SKH01** (regola decisa *prima* di vedere il numero) | 26/07 | — | il sorvegliante `fee_watch` allerta da solo |
|
||||
| **Gradino di leva NON autorizzato oggi.** 1,25x autorizzabile *a condizione*; **1,50x BOCCIATO** (peggior giorno strutturale 21,48% > 20%); **k max difendibile 1,40** | 23/08 | il 1,50x | costruire prima una **chiave di scala esplicita** in config **e** rifare `r0726_fee_sensitivity` |
|
||||
| **GTAA01: decisione tenere/bloccare RINVIATA a $15k di book** — sotto quella soglia lo sleeve **non e' deployabile** (`GTAA_MIN_CAPITAL` $3.000 allocati al peso 20%), quindi finora e' manutenzione senza beneficio incassabile. Contributo misurato a iso-rischio **+0,095 / +0,124** di Sharpe secondo la fase: **reale e sempre positivo**, e delle stesse dimensioni dello spread fra le baseline (0,12) | 28/08 | «GTAA01 non contribuisce» — la misura dice il contrario | **il book a $15k**, che il giornale sorveglia da solo (`journal.SOGLIE_CAPITALE`) |
|
||||
| **Gradino di leva NON autorizzato oggi.** 1,25x autorizzabile *a condizione*; **1,50x BOCCIATO** (peggior giorno strutturale 21,48% > 20%); **k max difendibile 1,40** | 23/08 | il 1,50x | ✅ la **chiave di scala e' costruita** (01/09, inerte a 1,00): restano **GATE SCALA-01** (§4) e il rifacimento di `r0726_fee_sensitivity` al nuovo lordo — la sua conclusione *"liquidation fee 1% irrilevante"* era condizionata a lordo ≤1x, a 1,25x costerebbe **1,25%** |
|
||||
|
||||
⚠️ **La regola *"ogni cambio di scala passa dal cap di config, non da `target_vol`"* NON E'
|
||||
IMPLEMENTABILE COME SCRITTA:** verificato che in `config/live.json` **non esiste una chiave di scala**
|
||||
— servirebbe toccare `WEIGHT`/`W_TP01`/`W_SKH` in `src/live/book.py`, cioe' codice su un percorso con
|
||||
soldi veri. Specifica pronta in `docs/research/SPEC-scale-key.md` (`GATE SCALA-01`, 9 condizioni, con
|
||||
il **tetto sul PRODOTTO** `n_asset · frac · scala · disaster_sl_pct ≤ 0,50` — non sulla singola chiave).
|
||||
✅ **La chiave di scala ESISTE dal 2026-09-01** (era il debito che rendeva la regola *"ogni cambio di
|
||||
scala passa dal cap di config"* non implementabile). `book_scale_k`, letta da `src/live/book._scala`
|
||||
e applicata **DOPO il clamp** in `book_net_target` — l'ordine non e' di comodo: applicandola PRIMA il
|
||||
cap se la mangerebbe proprio nei giorni di massima convinzione (k_eff tornerebbe a 1,00 a ogni k),
|
||||
cioe' sarebbe un cambio di **FORMA** travestito da cambio di taglia, che la curva `g(k)` con cui il
|
||||
gradino viene autorizzato **non descrive**. 🚨 **La chiave e' INERTE: assente = 1,00 = il libro di
|
||||
sempre, bit-exact** (T7, `max|diff| = 0.0`) — `config/live.json` **non e' stato toccato**. Guardie:
|
||||
`LEVA_LORDA_MAX` 1,25 e `SCALA_LADDER` (1,00 · 1,25) sono **costanti di CODICE, non di config** (un
|
||||
tetto nello stesso file della chiave sarebbe un lucchetto con la chiave attaccata: il gradino a 1,50
|
||||
richiede una modifica di codice, quindi una review); il tetto e' **sul PRODOTTO**
|
||||
`n_asset · frac · scala` e l'invariante del disaster-SL e' `n_asset · frac · scala · disaster_sl_pct
|
||||
≤ 0,50` (la guardia che morde per **prima**: da' scala ≤ 1,67x e scatta se qualcuno allarga lo stop —
|
||||
🚨 e **`disaster_sl_pct` NON si sostituisce con la distanza di un pavimento di put**: darebbe 2,28x
|
||||
sulla carta, ma la put limita UNA finestra e i drawdown di BTC sono grind di 290-818 giorni, su 8
|
||||
finestre −33,6% → 77% dell'equity a 2,28x. Misurato in §72, scritto qui prima che sia comodo);
|
||||
una scala fuori scaletta o fuori tetto **NON viene tagliata** — `book_execute` si ferma, non invia e
|
||||
allerta (`ScalaNonAutorizzata`); e **la scala vive solo sul percorso fidato** (equity reale
|
||||
illeggibile ⇒ 1,00, cosi' "il fallback non e' piu' permissivo" e' vero per costruzione).
|
||||
Sorvegliante `src/live/scale_watch.py` (giornaliero in `cron_daily`, 3 domande / 3 azioni / 3 stati,
|
||||
una allerta per streak, marcatore scritto solo dopo invio riuscito), giornale append-only
|
||||
`data/live/scale_history.jsonl`. Test **T1-T11 in `tests/test_book_scale.py` (18, tutti verdi)**.
|
||||
📌 **Cio' che il sorvegliante NON puo' fare: impedire la modifica** — la rende visibile entro 24h e
|
||||
attribuibile. Come `venue_watch`: non protegge il saldo, compra tempo.
|
||||
|
||||
---
|
||||
|
||||
@@ -160,13 +248,24 @@ sull'hold-out; cambiarne le soglie guardando l'esito, pure.
|
||||
|
||||
| gate | data | criterio | stato |
|
||||
|---|---|---|---|
|
||||
| **STATARB** | 2026-09-27 | soglie 24/07 (non toccate) + diagnostica statica sempre-short | 🚨 **si ribalta col difetto `advance()`**: riparata → RITIRO, attuale → candidato |
|
||||
| **XSR01** | 2026-10-23 | Sharpe ≥1,0 **E** haircut a $5.000 ≤40% (se sfonda → ritiro a prescindere), poi `weights_tilt_null` e capitale ≥$5k | 🚨 legge un monitor **mal tarato** (vedi §5.3) |
|
||||
| **DVOLSPREAD** | kill 2026-10-24 se Sh<−0,50; decisione 2027-01-24 | (a) Sharpe>0 (b) marginale ADDS+robust_oos+insample_edge (c) **DSR ≥0,95** (d) `weights_tilt_null`; **veto** se barre attive <80% → si estende, non si decide | il veto **non scatta** su una serie che registra 2 min/giorno (§5.1) |
|
||||
| **USDE-01 (eligibilita' reward)** | 2026-08-29 | ≥1 reward entro il 29/08 → IDONEO e si apre la decisione di QUOTA (dell'operatore; tetto allerta 50%, mai 100% — R1 emittente non recuperabile); zero → si riconverte e la pista si chiude | ✅ **CHIUSO IDONEO** il 2026-08-28 12:35Z: primo reward **+0,052055 USDE** rilevato per delta al netto dei trade, coi criteri pre-registrati il 26/08 PRIMA dell'esito. **QUOTA DECISA il 30/08** (l'operatore ha anticipato il rinvio: *"porta in usde tutto il capitale che non viene usato"*): bersaglio **70%**, `usde.quota_target` in config. Tasso a 4 finestre (30/08): **3,26% annuo, banda bootstrap [1,13% – 4,51%]** — 3/4 hanno pagato (0,00 · 3,80 · 4,51 · 4,51%), e la banda larga 3,4 punti **e' il risultato**: a n=4 il tasso non e' misurato. `usde_watch.rendimento()` lo stampa **col suo n**; `quota_da_decidere()` lo chiede ogni giorno da lunedi'. 🚨 **L'EV non puo' scegliere la quota** — e' lineare in q, quindi il suo argmax e' sempre un angolo (0% o 100%): ogni quota interna nasce da un criterio sulla **coda**, dichiarato non ottimizzato (M8). Il resto e' misurato (`r0830_usde_quota.py`, diario 30/08): **hazard di pareggio = l'APR** (EV-positiva sse Ethena sta sotto il 3,3%/anno, numero **assunto** come il `p` di Deribit) · l'haircut **non morde a nessuna quota** fino al 90%, nemmeno a leva 1,50x con USDE marcato a 0,95 (29× di cuscino): **il vincolo non e' il margine** · la resa di oggi vale **$16/anno**, meno di UN mese di versamento ⇒ la leva binding resta il **bonifico** · gia' a 24,2% l'evento emittente vale **3,1× il maxDD dell'intero libro**. 📌 **Aspettare costa $1,33**: alzare a 50% fra 4 settimane invece che subito lascia sul tavolo $1,33 di resa (conversione reversibile a ~6 bps) e porta il campione da **4 a 32 finestre**, stringendo la banda ~2,8×. 🚨 **DUE FATTI DEL 30/08 CHE CAMBIANO IL GATE** (diario `2026-08-30-usde-quota-tetto-venue`). **(a) TETTO DEL VENUE ≈31-32% dell'equity — e' una FRAZIONE, non un livello.** 🚨 **SUPERATO IL 06/09: il
|
||||
tetto non c'era piu'** — quota portata al **68,7%** con 39 ordini senza rifiuti, fermata dal cuscino di
|
||||
regolamento (§1). Quanto segue e' la misura del 30-31/08, vera allora, tenuta perche' il tetto puo' tornare. Ogni acquisto oltre il tetto e' rifiutato con `not_enough_funds_in_currency` pur avendo **$1.400 disponibili**: messaggio **fuorviante**, che ha fatto inseguire tre ipotesi sbagliate (taglia, cadenza, prezzo). Che sia un tetto e non un problema d'ordine e' provato a saldo neutro (BUY rifiutato → SELL OK → BUY riaccettato → BUY rifiutato); che sia una **frazione** e' provato il 31/08 lasciando scendere l'equity di $8: **il tetto e' sceso con lei** — 30/08 in [644,18·645,18) con equity $2.063,79 = **31,21-31,26%**, 31/08 in [643,18·644,18) con equity $2.055,56 = **31,29-31,34%** (0,05pp di scarto, dentro il rumore dell'equity che oscilla ±$2-8/ora). ⚠️ **Ri-sondato il 31/08 dopo il passaggio a X:SM** (l'ipotesi era che il tetto dipendesse dal modello segregato): **[654,18-655,18) con equity $2.054,90 = 31,84-31,88%**. Il modello ENTRA nel tetto ma **non lo spiega** — ha comprato **+0,55pp = +11 USDE (~$11)**, non le centinaia che servirebbero. ⇒ **il tetto SCALA col conto**: la quota resta ~31% e il valore in dollari cresce col capitale, ma **il 70% non e' raggiungibile ne' ora ne' mai**. 🚨 **Deribit non lo documenta**: l'articolo ufficiale *Yield/reward bearing coins* descrive un **Cap ETHENA** che diluisce il **TASSO** a livello di exchange (`User APR = min(bal_exchange, cap)/bal_exchange × Ethena APR`) e **nessun limite sulle quantita' detenibili** da un conto. Ora e' in config (`usde.venue_cap_frac` 0.312, bordo basso dei bracket) e `usde_convert` **rifiuta il piano prima di sparare** invece di scoprirlo dal venue. **(b) L'USDC NON frutta SUL NOSTRO CONTO — misurato, e la premessa del gate e' RESTAURATA.** `public/get_currencies` dice USDC 3,4000% (USDE 4,1071%) e per qualche ora quel numero e' stato scritto qui come se fosse nostro: **era un listino del venue, non un accredito**. Deribit paga i reward USDC **una volta al MESE** («paid out as a single monthly payment early in the following month», accrual 00:00 UTC sulla **minima equity**) — non ogni giorno come l'USDE, ed e' per questo che una sorveglianza giornaliera non poteva vederli. Verificato sui **due confini di mese a libro FLAT**, dove `equity == balance == USDC` e ogni scalino sarebbe un accredito: **29/06→08/07 equity 598,06 costante, 237/237 ore ferme, 0 scalini** (attesi $0,45) e **31/07→03/08 equity 596,92 costante per 4 giorni** (attesi **$1,72 contro una risoluzione di $0,01, 172×**). **Zero misurato, non zero sotto soglia.** ⇒ il guadagno di tenere USDE e' il **tasso pieno ~4,1%**, non uno spread: **~$26/anno** sui $644. 🚨 **Il motivo ora e' DOCUMENTATO, e non e' MiCA come avevo ipotizzato**: l'articolo ufficiale elenca le giurisdizioni escluse dai reward USDC e **l'ITALIA c'e'** (con Canada e Giappone: non e' una lista MiCA, e' policy di giurisdizione Deribit). ⚠️ **E la finestra di pagamento e' piu' larga di quella che avevo controllato**: «within the **first two weeks** of the following month». Rifatta la verifica sulle due settimane vere: **luglio e' conclusivo** (atteso $1,72 contro un'escursione TOTALE dell'equity di **$0,48** in 1-16/08, zero scalini compatibili), **giugno NO** (il libro ha iniziato a operare a meta' mese e il rumore e' della taglia del segnale). Quindi: **una finestra conclusiva piu' la lista ufficiale**, non due finestre — il «172× la risoluzione» del 30/08 era sovra-affermato. 📌 **Un tasso pubblicato non e' un tasso incassato: si verifica sul CONTO** — ed e' costato una query su una serie che avevamo gia'. Strumento: **`scripts/live/balance_watch.py`** + `cron_balance.sh` (orario :42, sola lettura) registra il **balance a 8 decimali**, la serie che mancava. 📌 **Il vincolo che l'analisi del margine non aveva guardato**: P&L e funding dei perp USDC-lineari **si regolano in USDC**, quindi la quota massima difendibile non e' data dall'haircut ma dal **cuscino di regolamento** = disaster-SL sulla massima esposizione lorda (`n_asset × frac × disaster_sl_pct` = **30% dell'equity**), che lascia esattamente il **70%** — coincidenza per costruzione, zero slack (M8: non e' un argmax). 📌 **Il costo del NON averlo e' ora documentato e vale il criterio**: un'equity negativa in una valuta paga una *collateral fee* dello **0,05% AL GIORNO** — **18,25%/anno**, addebitata al secondo, **4,3× la resa USDE che si starebbe comprando** (fonte: KB *Cross collateral specifications*). Il ribilanciamento automatico NON salva: scatta solo a **$1M** assoluti o al **100% della cross equity**, soglie che a $2k non si toccano mai — si sanguina la fee e basta. Il 30/08 avevo scritto «Deribit lo finanzia a interesse» **senza il numero**: il numero e' questo, e rende il cuscino piu' giustificato, non meno. Attrezzo: **`scripts/live/usde_convert.py`** (dry-run di default, guardie proprie; NON passa da `execution.ALLOWED`, che resta ai soli due perp) |
|
||||
| **STATARB** | 2026-09-27 | soglie 24/07 (non toccate) + diagnostica statica sempre-short | ✅ monitor riparato e rigenerato il 26/08: la serie vera da' Sharpe **−1,61** a oggi → coi criteri invariati punta al **RITIRO**. Si legge il 27/09, non prima |
|
||||
| **XSR01** | 2026-10-23 | Sharpe ≥1,0 **E** haircut ≤40% — dal 26/08 il numero decisivo e' `r0826_xsr_haircut.py` a **pavimento $10**, citato con la frazione di ordini eseguiti — poi `weights_tilt_null` e **capitale ≥$20k** | ✅ **riscritto il 26/08 su decisione dell'operatore** (dichiarato prima dell'esito, stringe: diario `2026-08-26-xsr01-gate-riscritto`): capitale allineato a "100% Deribit fino a $20k"; serie forward rigenerata (31 barre vere, Sharpe −2,72). Sotto la lente RENDITA il sleeve resta **eguagliato da un conto remunerato** (📌 sotto) |
|
||||
| **DVOLSPREAD** | kill 2026-10-24 se Sh<−0,50; decisione 2027-01-24 | (a) Sharpe>0 (b) marginale ADDS+robust_oos+insample_edge (c) **DSR ≥0,95** (d) `weights_tilt_null`; **veto** se barre attive <80% → si estende, non si decide | ✅ serie rigenerata il 26/08 (−4,41 a oggi, era −14,73 sulla serie rotta); il veto barre-attive ora conta barre VERE e la guardia PREMATURO sorveglia il monitor |
|
||||
| **GATE PROP-01** | chiuso 22-23/08 | (a) listino 13/13 ✅ (b) ancora SKH01 23/23 ✅ (c) economica+sopravvivenza 2/2 ✅ | **chiuso 3/3** — ma il numero operativo e' sceso 42% → 7,8% → 4,4% → 2,6% |
|
||||
| **GATE PREVDAY-01** | scritto 23/08 | 10 condizioni | **8/10**: la cella che GIRA non e' quella che la selezione onesta sceglie, e quella viva **fallisce il DSR** (0,905) |
|
||||
| **GATE SCALA-01** (portare `book_scale_k` da 1,00 a 1,25) | non prima del **2026-10-01** | 9 condizioni **tutte necessarie** (`SPEC-scale-key` §6.2): A1 gradino di `SCALA_LADDER` · A2 k0 in produzione ≥180g col criterio passato ogni giorno · A3 **C1-C5 di WORST-DAY ricalcolati OGGI**, non ereditati · A4 invariante disaster-SL · A5 tetto sul prodotto · **A6 ANTI-RECENCY** (il criterio deve passare anche **escludendo gli ultimi 90 giorni**) · A7 `r0726_fee_sensitivity` rifatto · A8 la modifica e' **una riga di config + una di giornale, zero codice** · A9 T1-T11 verdi | ◐ **A8/A9 FATTI il 01/09** (chiave inerte + 18 test); **A2 non maturo** — la specifica impone ≥30 giorni a 1,00 col sorvegliante attivo *prima* di eseguire il gate: «l'unico modo di scoprire che il sorvegliante e' rotto mentre la leva e' ancora 1,00». 📌 **A6 oggi NON morde** (k ammesso ~3,5x contro un gradino di 1,25x): il vincolo che morde e' il **disaster-SL**. E' scritto ora **perche' non e' comodo per nessuno**, non quando servira' |
|
||||
| **`edge_watch`** (kill del book live) | continuo | **(A)** Sharpe rolling 36m < −0,5 → si riapre `weights_tilt_null`+DSR, il book **non si spegne da solo**; **(B)** in un anno con DD buy&hold >10%, il DD di TP01 deve restare <75% di quello | stato 27/07: Sharpe 36m +1,51, protezione **8/8** |
|
||||
|
||||
📌 **XSR01 sotto la lente RENDITA (25/08): il meccanismo vale ~0.** Mescolarne i rendimenti da' lo
|
||||
stesso muro ($476-484k contro $478,7k), e un **conto remunerato** allo stesso tasso (3,72%, vol 0, corr 0,
|
||||
nessun secondo venue) da' **$477,6k** — identico dentro la risoluzione MC, e **meglio** al 4,0%. Cio' che
|
||||
si compra e' un **drift scorrelato**, non la scorrelazione (a drift zero il muro SALE). E l'haircut lo
|
||||
mangia: **non pareggia un conto al 4% nemmeno a haircut ZERO**; pareggia il 2% solo sotto il 46%.
|
||||
Per un 25% sopra il C\* vero servono **$60.000** di conto. `r0825_xsr_rendita.py`.
|
||||
|
||||
📌 **Un gate superato non e' un numero confermato:** differenze e livelli ereditano la fortuna
|
||||
d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per niente).
|
||||
|
||||
@@ -174,47 +273,91 @@ d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per
|
||||
|
||||
## 5. Debiti aperti — difetti noti NON riparati
|
||||
|
||||
1. 🚨 **`advance()` registra una frazione del giorno → 4 monitor su 6 sono rotti.**
|
||||
`fetch_hyperliquid`/`resample_tf` scrivono la barra del giorno **in corso**, `advance()` la consuma
|
||||
e ci porta sopra `last_ts` → `paper_statarb` **4 min/giorno**, `paper_dvolspread` 2, `paper_xsr` 41.
|
||||
✅ **La finestra non va persa** (misurato due volte: 0 barre chiuse cambiate su 1.761.259) → l'azione
|
||||
e' *"riparare **e rigenerare**"*, e **nessuna data di gate si sposta**. Guardia raccomandata e non
|
||||
cablata: `ts_ultima_barra + cadenza ≤ mtime`, grazia 5 min.
|
||||
2. 🚨 **Il trasporto degli allarmi e' un punto singolo di guasto** — 6,9% di invii falliti (2/29).
|
||||
Fatte il 23/08: retry opzionale in `send()` e registrazione del motivo nel punto in cui veniva
|
||||
ingoiato. **Resta:** `alerted=True` marcato **prima** dell'invio → un 🚨 perso e' perso per
|
||||
l'episodio intero (gli episodi storici durano 200-2.324 ore). Cambiarlo **cambia il comportamento**
|
||||
→ decisione dell'operatore.
|
||||
3. 🚨 **Il gate XSR01 del 23/10 legge un monitor tarato sul pavimento del venue sbagliato**
|
||||
(C* vero $15.000-20.000, non ~$3.000). Due sole vie pulite: riscrivere la soglia **adesso**
|
||||
dichiarando che la si riscrive prima di vedere l'esito, **oppure** lasciarla e **citare il difetto
|
||||
alla decisione**. Decisione dell'operatore.
|
||||
4. ❓ **Due domande al commercialista, aperte** (valgono ~$22k di muro e ~€30/mese di versamento):
|
||||
(a) i derivati Deribit (inverse, margine e regolamento in cripto, sede extra-UE) stanno in
|
||||
`c-sexies` (33%) o `c-quater` (26%)? E il collaterale sconta il 2‰ al 31/12? (b) i payout di una
|
||||
prop firm: **ne' `c-sexies` ne' `c-quater`** — le fonti convergono su **lavoro autonomo**, nessuna
|
||||
prassi ufficiale. *Non sono pareri fiscali: sono domande da porre.*
|
||||
5. ⚠️ **`test_leva_massima_da_config_resta_sotto_o_uguale_a_1x` va cancellato con nota, non
|
||||
aggiustato:** misura `frac · n_asset` mentre con una chiave di scala la grandezza vera diventerebbe
|
||||
`frac · n_asset · scala` → **continua a passare e smette di controllare**.
|
||||
1. ✅ **RIPARATO E RIGENERATO (2026-08-26).** `advance()` consumava la barra del giorno in corso:
|
||||
ora tutti e 6 i monitor filtrano con `src/live/paper_guard.nuove_chiuse` (barra chiusa =
|
||||
`ts + cadenza ≤ adesso`), le 4 serie rotte sono **rigenerate dallo stesso `start_ts`**
|
||||
(`scripts/live/paper_regen.py`, vecchi file in `*.pre_regen_20260826.*`): statarb
|
||||
**+1,95 → −1,61**, dvolspread −14,73 → −4,41, xsr −4,98 → −2,72, prevday +0,39 → +0,40.
|
||||
**Nessuna data di gate si sposta.** La guardia e' cablata: `monitor_health` stato **PREMATURO**
|
||||
(ultima barra che chiude dopo l'mtime, grazia 5 min; `open_labeled=False` per collect_chain).
|
||||
`paper_portfolio` NON rigenerato (GTAA su ADJUSTED_LAST: replay ≠ serie registrata, P12) —
|
||||
storia pre-fix dichiarata corrotta, coda non chiusa tolta. ⚠️ Nel fix, D6 pagata di nuovo:
|
||||
`asi8` in pandas 3 e' in `us`, non `ns` — blindato con test su tre risoluzioni.
|
||||
2. ✅ **RISOLTO (2026-08-28, decisione dell'operatore).** Il trasporto degli allarmi era un punto
|
||||
singolo di guasto — 6,9% di invii falliti (2/29) e `alerted=True` marcato **prima** dell'invio,
|
||||
quindi un 🚨 perso era perso per l'**episodio intero** (200-2.324 ore). Ora `venue_watch.run_once`
|
||||
riceve il **sender iniettato** e il marcatore «gia' detto» si scrive **solo dopo un invio
|
||||
riuscito**; su fallimento si disfano i soli marcatori — non le misure — e per il lock **le ore
|
||||
continuano a correre**, cosi' una manutenzione che sfora la grazia sale ad ALERT anche col
|
||||
trasporto giu'. `notify()` accetta `tentativi` (venue_watch: 3) e l'esito finisce nel log.
|
||||
⚠️ Resta scoperto **ogni altro chiamante di `notify()`**: la disciplina «commit dopo l'invio»
|
||||
e' oggi solo in `venue_watch`.
|
||||
3. ✅ **RISOLTO (2026-08-26, decisione dell'operatore: opzione "riscrivere adesso").** Gate
|
||||
XSR01 riscritto in modo dichiarato, prima dell'esito, in direzione che stringe: capitale
|
||||
$5k → **$20k** (riconcilia con la decisione 26/07, che e' posteriore e governa), gamba haircut
|
||||
letta da `r0826_xsr_haircut.py` a pavimento $10 con la frazione di ordini eseguiti accanto
|
||||
(misurata: haircut FULL −1,1% con **22% di eseguiti** — la guardia da sola era vacua), soglie
|
||||
numeriche INVARIATE. Diario `2026-08-26-xsr01-gate-riscritto.md`.
|
||||
4. ◐ **(a) RISPOSTA TROVATA ALLA FONTE (2026-08-28), (b) resta aperta.** *Non sono pareri fiscali.*
|
||||
**(a) I derivati stanno in `c-quater` → 26%, non in `c-sexies` → 33%.** Circolare AdE **30/E del
|
||||
27/10/2023**, §2.3.2 e §3, verbatim: «In generale, i redditi derivanti da **contratti derivati,
|
||||
ancorche' aventi come sottostante cripto-attivita'**, costituiscono redditi diversi ai sensi
|
||||
dell'articolo 67, comma 1, **lettera c-quater)**» e «qualora le cripto-attivita' costituiscano il
|
||||
sottostante di un contratto derivato (ad es. contract for difference o **future**) i redditi
|
||||
derivanti da tali strumenti finanziari costituiscono redditi diversi ai sensi dell'articolo
|
||||
c-quater)». La circolare conferma esplicitamente che la riforma 2023 non sposta nulla: «tali
|
||||
contratti **non rientrano** nell'ambito di applicazione della lettera c-sexies». ⚠️ Conseguenza:
|
||||
**le minusvalenze su derivati NON si compensano con le plusvalenze cripto** (categorie diverse) e
|
||||
non c'e' franchigia. **Il collaterale sconta il 2‰**: Deribit non e' intermediario residente,
|
||||
quindi non applica il bollo e scatta l'*imposta sul valore delle cripto-attivita'* sul valore al
|
||||
31/12, in RW (a $2.068 fa **~$4/anno**: obbligo dichiarativo, non un costo). 📌 E la conversione
|
||||
**USDC→USDE non e' fattispecie realizzativa** — §3.1: «dal 1° gennaio 2023 non costituisce
|
||||
fattispecie realizzativa lo scambio di una cripto-valuta con un'altra», col valore di carico che
|
||||
si trasferisce. **Resta da chiedere:** se il perpetual *inverse* regolato in cripto sia il caso
|
||||
letterale della circolare (che nomina CFD e future) o serva un passaggio in piu'.
|
||||
**(b) I payout di una prop firm: ancora aperta.** Le fonti convergono su **lavoro autonomo**
|
||||
(partita IVA, ATECO, forfettario 5% per i primi 5 anni poi 15%, imponibile da coefficiente di
|
||||
redditivita'), **non** redditi diversi al 26%. **Nessuna prassi ufficiale**, e il coefficiente
|
||||
esatto dipende dal codice ATECO.
|
||||
5. ✅ **FATTO (2026-08-26):** `test_leva_massima_da_config...` cancellato con nota in
|
||||
`tests/test_fee_sensitivity.py` — misurava `frac · n_asset`, con una chiave di scala avrebbe
|
||||
continuato a passare smettendo di controllare. ✅ **CHIUSO il 2026-09-01:** la chiave esiste e la
|
||||
sostituzione e' cablata — **T2** (tetto sul PRODOTTO `n_asset · frac · scala ≤ LEVA_LORDA_MAX`) e
|
||||
**T3** (invariante disaster-SL) in `tests/test_book_scale.py`, che importano i bersagli da
|
||||
`src.live.book` invece di ridichiararli (P1). Il vecchio test resta **cancellato**, non rilassato,
|
||||
e **T11 lo verifica**.
|
||||
6. ⚠️ **TLT ha 13,5 anni di storia in meno** (parte 2016-02 invece del 2002) → **GTAA01 gira su
|
||||
CINQUE gambe prima del 2016**, e l'assente e' quella obbligazionaria. Non e' un fetch da rifare (IB
|
||||
ritorna 0 barre). ⇒ in-sample e hold-out di GTAA01 **non sono la stessa strategia**.
|
||||
7. ⚠️ **Il docstring di `book_execute.py` prescrive "ogni ~230 minuti", il cron gira OGNI ORA.**
|
||||
**Il docstring e' sbagliato e la configurazione che gira e' quella giusta** — chi lo "correggesse"
|
||||
sposterebbe il libro sulla riga 4h, dove BTC **raddoppia** gli scatti del disaster-SL.
|
||||
7. ✅ **RIPARATO (2026-09-02).** Il docstring di `book_execute.py` prescriveva "ogni ~230 minuti"
|
||||
mentre il cron gira **ogni ora** (`47 * * * *`): era il docstring a essere sbagliato, e chi
|
||||
avesse "corretto" il **CRON** verso il docstring avrebbe spostato il libro dalla riga 1h (rotolante:
|
||||
BTC 0 scatti / ETH 1 in 7-8 anni) alla riga 4h (BTC 2, contro 1 del pavimento); la peggiore
|
||||
misurata e' 24h (ETH 5, con 2 in 30 giorni) — `r0823_sl_anchor.py`. Ora il docstring dichiara
|
||||
**CADENZA: ORARIA** con la ragione (giro idempotente: latenza SKH01 ≤1h; il rotolante e'
|
||||
**controllato** ogni ora e ri-ancorato solo oltre la tolleranza di `ensure_disaster_sl`, mark
|
||||
+5,263%/−4,762% o taglia >10%, quindi lo stop siede fra **−33,5% e −26,5%** dal mark corrente —
|
||||
«si ri-ancora ogni ora» era una frase sbagliata, corretta in revisione il 02/09; e «girare piu'
|
||||
fitto non costa ordini» pure: 22 dei 48 ordini live sono ri-taglie |Δ|≤$10, deadband in valuta
|
||||
assoluta, C2) e il divieto esplicito. **`tests/test_book_cadenza.py` (10 test)** tiene
|
||||
d'accordo le tre dichiarazioni — docstring, riga in `cron_book.sh`, **crontab installata**
|
||||
(letta con `crontab -l`, **tutte** le righe attive: due righe sono due esecuzioni; tre stati —
|
||||
illeggibile ⇒ SALTATO, leggibile senza questo progetto ⇒ SALTATO, leggibile col progetto ma senza
|
||||
`cron_book.sh` attivo ⇒ **ROSSO**) — verifica 60 min < `skyhook.LTF_MIN` 230 < 240 (riga 4h),
|
||||
minuto ≠ :00 **e fuori dallo slot di release** (`venue_probe.in_release_window`, col :07 come
|
||||
controllo positivo). Diario `2026-09-02b-debito-7-cadenza-docstring.md`.
|
||||
8. ⚠️ **Il Rulebook Deribit** (ADL, perdita socializzata, *emergency powers*, conti dormienti) **non
|
||||
lo sorveglia nessuno**; `MAINT_GRACE_HOURS`=2 **presume** gli annunci invece di leggerli.
|
||||
✅ *Ridotto in parte il 2026-08-25:* `src/live/venue_probe.py` interroga l'API **pubblica**
|
||||
Deribit e distingue **manutenzione / venue giu' / NOSTRO gateway / non vedo**, e lo slot di
|
||||
release (**martedi' 09:00 UTC**) e' ora una costante dichiarata e testata. Resta scoperto il
|
||||
Rulebook vero e proprio: la sonda legge se il venue *risponde*, non cosa il venue *annuncia*.
|
||||
9. ⚠️ **`tests/test_wave_0726.py::test_t1_canonical_riproduce_il_backtest_ufficiale` FALLISCE**
|
||||
(dal ~24-25/08): Sharpe hold-out SKH01 `canonical` **1,9223** contro la banda cablata
|
||||
`1.3 < h < 1.9`. **Il codice non e' cambiato, sono cambiati i DATI** — `data/raw/` e' gitignored
|
||||
e il cron lo ricostruisce ogni notte, quindi la finestra si allunga e il numero deriva (verso
|
||||
l'**alto**). **La banda non e' stata allargata**: si guarda sotto prima di toccarla (lezione
|
||||
07/08). Suite: **730 passati, 1 fallito** (25/08).
|
||||
9. ✅ **INDAGATO E RIPARATO (2026-08-26, `r0826_skh_band_drift.py`).** Il dato regge (taglio
|
||||
al 02/07 riproduce l'audit: 1,6376; in-sample 1,424 identico su ogni taglio); la deriva era la
|
||||
finestra hold-out che si allunga — e 📌 **la SOLA settimana 15-22/08 vale +0,35 di Sharpe
|
||||
hold-out** (rally ETH con SKH01 long): il numero e' fragile, coerente con §2. Il test ora
|
||||
**taglia il feed al 02/07** e verifica la riproduzione stretta (±0,02): si rompe solo se
|
||||
cambiano codice o dato sulla finestra congelata. Suite: **775 passati, 0 falliti** (26/08).
|
||||
10. ⚠️ **Nessuna tabella pubblicata prima del 22/08 contiene fisco E funding insieme.** L'unica
|
||||
congiunta e' quella in `30-piano-capitale-fisco.md` — le altre vanno lette con lo sconto.
|
||||
11. 🚨 **Le credenziali Deribit esistono SOLO dentro il gateway** (`cerbero-mcp`): in locale c'e'
|
||||
@@ -230,11 +373,102 @@ d'ancora in modo diverso (nella differenza si cancella in parte, nel livello per
|
||||
da **$334 a $2.500/asset — ~7,5x di leva** su un conto da $668, per giunta sul ramo `eq_fallback`
|
||||
che **allerta e NON blocca**. ✅ Riparato il 2026-08-25 con una fixture **autouse** in
|
||||
`tests/conftest.py` (strutturale: non si chiede a ogni autore di ricordarsene — quella scommessa
|
||||
ha gia' perso il 26/07 e il 21/08) + guardia in `test_cap_watermark.py`. **Resta il principio:
|
||||
un test non deve poter scrivere in `data/live/`; oggi e' deviato solo il watermark, non
|
||||
`trades.db` ne' `book_executions.jsonl`.**
|
||||
ha gia' perso il 26/07 e il 21/08) + guardia in `test_cap_watermark.py`. ✅ **Esteso il
|
||||
2026-08-26:** deviati anche `trades.db` (wrapper su `connect` — il default e' catturato alla
|
||||
definizione, patchare la costante non bastava) e `docs/journal/`; per `book_executions.jsonl`
|
||||
(caricato ad-hoc via importlib, irraggiungibile da conftest) impronta a inizio suite e verifica
|
||||
a fine suite, col falso positivo possibile (fill reale del cron :47) dichiarato nel messaggio.
|
||||
13. ⚠️ **GTAA01 gira su una finestra che ROTOLA, e la fase di ribilanciamento cambia da sola ogni
|
||||
notte** (misurato 2026-08-28, `r0828_gtaa_band_phase.py`). IB serve una finestra di **30 anni**
|
||||
e SPY e' al muro: la sua prima barra avanza di un giorno di borsa ogni notte (1996-07-02 il
|
||||
24/06 → 1996-09-04 il 28/08). `gtaa._gated_returns` ribilancia su `i % every == 0`, cioe' sulla
|
||||
**posizione nell'array**: se la prima barra scivola si ri-fasa ogni decisione di trent'anni.
|
||||
Effetto misurato: su una finestra **chiusa** (pre-2015) lo Sharpe si e' mosso fino a **0,076**
|
||||
a codice fermo, e la banda scelta al buio cambia fra 25% / 40% / 60% da una notte all'altra.
|
||||
**Ha gia' ucciso due criteri del gate (A)** (07/08 e 28/08). **Quantificato e innocuo OGGI**
|
||||
(D5): a livello di portafoglio l'escursione sulle 5 fasi vale **0,029** di Sharpe, dentro la
|
||||
banda dichiarata [1,81-2,12]. QQQ e IWM arriveranno allo stesso muro **fra 2,5 e 3,7 anni**.
|
||||
Riparazione (ancorare la fase al calendario) rinviata con la decisione su GTAA01: cambierebbe
|
||||
**tutti** i numeri registrati dello sleeve.
|
||||
14. ✅ **RIPARATO (2026-09-02).** `trades_db.py --report` stampava `e1/e0-1` sulla serie grezza —
|
||||
«**+243%**», per il **96,3% un bonifico** (il versamento di $1.399,39 del 25/08 11:47Z; misurato
|
||||
il 01/09, diario `2026-09-01-stato-trades.md`). La riparazione esisteva gia' nel giornale
|
||||
(`journal.movimenti_capitale`) e **non aveva attraversato il confine fra i due lettori della
|
||||
stessa serie** — variante di P1. Ora **`journal.rendimento_twr`** e' la funzione che entrambi
|
||||
chiamano: spezza la serie sui movimenti CERTI (gli ambigui restano dentro, dichiarati) e il
|
||||
report stampa **TWR +10,61% = +11,61% × −0,89%**, `trading da arming +$50,95`, i movimenti
|
||||
elencati, e il delta $ grezzo etichettato «movimenti INCLUSI» — il `%` grezzo **non compare piu'**.
|
||||
Test: 5 in `test_journal.py` (fra cui la riproduzione del +10,80% del diario 01/09, M23) + 2 in
|
||||
`test_trades_report.py` sul testo stampato, con `connect()` deviato in tmp. Diario
|
||||
`2026-09-02-debito-14-report-twr.md`. ⚠️ **Due limiti dichiarati dalla revisione del 02/09** (D5): (a) **l'intervallo che contiene un movimento certo esce INTERO dal rendimento** — il suo P&L di mercato finisce in `certi` e fuori da `trading`, con errore massimo pari a META' del movimento riconosciuto per costruzione del classificatore (il 25/08: ~$0,5 su $1.399); non si stima, si dichiara (P12); (b) **a base di equity zero `twr` E `trading` sono None**: il salto 0→X del primo versamento e' invisibile al classificatore e `trading` varrebbe l'intero conto. 🚨 **Seconda tornata (02/09, 15:30Z): il classificatore era CIECO ~23 ore al
|
||||
giorno.** Il feed 1h certificato si ferma alle 00:00 (rebuild 00:30): per le letture successive
|
||||
`asof` dava la stessa barra a t0 e t1, mercato "fermo" = 0, e **qualunque calo ≥10% del giorno
|
||||
sarebbe stato un «movimento»** — un crash o un depeg USDE stampato come prelievo, nel giornale
|
||||
(dal 25/08) e nel report. Riprodotto su copia del DB. Ora un salto fuori dalla copertura del feed
|
||||
(o con feed assente) e' **«mercato non misurabile»** ⇒ `ambiguo` con la ragione, e `twr`/`trading`
|
||||
sono **None** con motivo (prima, a feed assente, `e1/e0-1` tornava sotto l'etichetta TWR). Il
|
||||
«limite ereditato +10% a mercato fermo» scritto qui la mattina era un **artefatto della fixture
|
||||
piatta**, non un limite del classificatore (con mercato misurato il trading non puo' superare
|
||||
2·max_mkt): cancellato. E `pnl_giorno` ora **chiama** `rendimento_twr` (prima l'affermazione
|
||||
«la funzione che entrambi chiamano» era falsa: il giornale rifaceva `cum − certi`); la pagina
|
||||
stampa il TWR accanto al cumulato e la testata Telegram dell'analista qualifica il cumulato
|
||||
«di cui versati». `leva_tetto` del classificatore include la scala e il tetto di codice (P1).
|
||||
|
||||
15. ⚠️ **Il bound di mercato del classificatore dei movimenti guarda solo BTC/ETH, ma dal 26/08 il
|
||||
31% dell'equity e' USDE** (trovato in revisione il 02/09). Un depeg dell'USDE a BTC/ETH fermi:
|
||||
−1,5% (indice 0,95) resta sotto soglia e nel P&L; una rottura piena (−31%) supera 2× un mercato
|
||||
"fermo" e diventerebbe un **«movimento» scorporato come prelievo**, nel giornale e nel report —
|
||||
l'invenzione silenziosa che P12 vieta. Il bound corretto somma `quota_usde × |Δ indice usde_usdc|`,
|
||||
ma l'indice era registrato **una volta al giorno** (`usde_watch`, 12:35Z). ◐ **Primo passo fatto
|
||||
il 02/09:** `balance_watch` (orario, sola lettura) registra ora `usde_usdc` a ogni campione —
|
||||
None con la ragione se l'indice non e' leggibile, mai 1,0. **Manca il cablaggio nel bound**, che
|
||||
si fa quando la serie ha storia. Intanto: chi legge un «movimento» negativo senza bonifico in
|
||||
uscita **lo verifichi contro `balance_watch.jsonl`** prima di crederci.
|
||||
|
||||
16. ✅ **RIPARATO (2026-09-02).** Nessuno dei **18** script di `scripts/live/` usava argparse: un
|
||||
flag sconosciuto non era un errore, era **il ramo `else`**. Costo misurato lo stesso giorno
|
||||
durante una revisione: `trades_db.py --help` non stampava l'uso, cadeva in `sync()` e riscriveva
|
||||
`meta.ultimo_sync`; sugli altri due sarebbe stato peggio — `journal.py --help` avrebbe scritto
|
||||
pagina e riga di DB, `analista.py --help` avrebbe speso una chiamata al modello e mandato un
|
||||
Telegram. Ora `src/live/cli.valida` e' la **prima istruzione** di ogni `__main__`: `--help`
|
||||
stampa l'uso ed esce **0**, un flag ignoto esce **2** con l'elenco di quelli previsti (P4).
|
||||
Niente argparse: cambierebbe messaggi, codici d'uscita e `--help` di script che il cron gia'
|
||||
chiama — qui si lascia intatto cio' che gira. **`tests/test_cli_flag.py` (30)**: l'elenco degli
|
||||
script si **deriva dalla cartella** (uno nuovo senza guardia fa fallire il test), verifica che
|
||||
`valida` sia la prima istruzione, che l'uso documenti i flag accettati, e che **i flag che il
|
||||
cron usa davvero restino accettati** (P15/P16). Diario `2026-09-02c-flag-e-pulizia.md`.
|
||||
|
||||
17. ⚠️ **Nessuno sorveglia il CUSCINO USDC di regolamento** (aperto il 2026-09-06, quando la quota USDE e'
|
||||
salita al 68,7%). P&L e funding dei perp si regolano in USDC: una perdita del libro consuma il cuscino e
|
||||
fa salire la quota USDE da sola, senza alcun ordine. `usde_watch` allerta solo su `quota > quota_max_frac`
|
||||
(0,85): a 68,7% lo slack sul cuscino e' **$58**, e un saldo USDC negativo costa lo 0,05%/giorno. Il
|
||||
sorvegliante giusto legge `USDC − cuscino_richiesto` (derivato da config come in
|
||||
`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.
|
||||
|
||||
18. ✅ **RIPARATO (2026-09-09 sera).** `cblib.spot_series` + `asof(ts)` guardava **un'ora avanti** (feed
|
||||
1h etichettato all'apertura: verificato, chiusura 1h a T == chiusura 5m a T+55 nel 100% delle barre)
|
||||
**e `dvol_series` fino a 24 ore avanti** (la riga DVOL 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, trovata
|
||||
riparando la prima (D6, quinta occorrenza). Ora `cblib.causale(s, cadenza)` rietichetta le chiusure
|
||||
all'istante in cui sono note e le due serie escono **gia' causali** (spot +1h, DVOL +1 giorno);
|
||||
`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
|
||||
@@ -259,7 +493,7 @@ nei file di memoria.
|
||||
- **M5** **Null del de-levering (7 occorrenze):** ogni claim "meno drawdown" si testa a **iso-rischio** prima di crederci — e' il primo test, non l'ultimo. Se la variante e' una pura ri-scalatura il null e' **degenere** (`sh(k·base) ≡ sh(base)`): serve iso-peso sullo Sharpe.
|
||||
- **M6** Un diversificatore a **basso CAGR** si giudica a **iso-rischio**, mai a iso-nozionale.
|
||||
- **M7** Su offset/ancore appaiate la statistica e' la **mediana delle differenze**, non la differenza delle mediane. E **se si de-lucka una strategia va de-luckato anche il suo DEGRADO**.
|
||||
- **M8** Un **argmax dentro un plateau non e' una decisione**; e la mediana da sola puo' nascondere il fatto decisivo (p90 su, p10 giu' = si compra dipendenza dall'ancora, non Sharpe).
|
||||
- **M8** Un **argmax dentro un plateau non e' una decisione**; e la mediana da sola puo' nascondere il fatto decisivo (p90 su, p10 giu' = si compra dipendenza dall'ancora, non Sharpe). E un **argmax sul BORDO della griglia non e' una cella: e' una pendenza** — si segue fino al suo limite prima di leggerne il valore, perche' il limite puo' essere un oggetto diverso da quello che si stava studiando (COLLAR01: le 3 celle vincenti su 48 stavano tutte sul bordo, e il limite era una **covered call**, cioe' fuori dalla domanda posta).
|
||||
- **M9** Un contributo positivo si **scompone per anno** prima di crederci: "24/24 ancore positive" puo' essere **un anno solo**.
|
||||
- **M10** **Due punti non fanno una tendenza** nemmeno quando la meccanica sembra spiegarla.
|
||||
- **M11** Un **blocco dichiarato e' un'ipotesi, non un fatto**: si ri-verifica prima di costruirci sopra o di rimandare (3 occorrenze, ogni volta il blocco non esisteva).
|
||||
@@ -280,12 +514,13 @@ nei file di memoria.
|
||||
- **M26** **Non contare su una compensazione fra correzioni misurate separatamente** — la si misura, e in piu' di una **moneta** (un'interazione piccola cambia segno con la moneta).
|
||||
- **M27** Prima di dichiarare che un campo di un dataset e' sbagliato, **aprire il codice che lo produce**. E una **fonte normativa citata in un commento si verifica come un numero**.
|
||||
- **M28** Quando **due affermazioni della stessa memoria si contraddicono, la contraddizione e' informazione**: una delle due e' stata scritta guardando i dati.
|
||||
- **M29** 🚨 Un edge da **opzioni prezzate a modello** si **riprezza alla volatilita' REALIZZATA** prima di crederci: se sparisce, non era la struttura — era il **premio di varianza**, cioe' VRP01, che il progetto ha gia'. Su BTC il **DVOL sta il 32% sopra la RV-forward** a 7 giorni (**77% dei giorni**), quindi *qualunque* struttura che vende vol **stampa per costruzione del prezzatore**: in COLLAR01 il **72-100%** del drift della cella migliore era questo, e l'angolo cadeva da Sharpe **1,471 a 0,511** — che e' il **0,47** gia' pubblicato di VRP01. Vale anche al contrario: un edge che *compra* vol a modello e' sottostimato dallo stesso fattore.
|
||||
|
||||
### Sui costi e l'eseguibilita'
|
||||
- **C1** Il costo di un venue va modellato nella sua **FORMA** (fisso vs proporzionale), non solo nel livello: un pavimento fisso e' una **tassa regressiva** e due venue a pari "costo medio" danno esiti **opposti** al variare del capitale.
|
||||
- **C2** Un parametro d'esecuzione espresso in **valuta assoluta** ha effetto che dipende dal capitale: il controllo non e' lo Sharpe ma la **quota di tempo a mercato**.
|
||||
- **C3** Prima di misurare il rendimento a un capitale dato, misurare il **lotto minimo del venue** — e sapere **quale famiglia di strumenti** si sta guardando (inverse vs USDC-lineare: lotti 7,5× diversi).
|
||||
- **C4** Un **fattore di errore si giudica moltiplicato per il suo peso** (5,85 al 3% fa meno danno di 2,23 al 18%). Il `f` di una struttura multi-gamba **non e' il `f` di una sua gamba**.
|
||||
- **C4** Un **fattore di errore si giudica moltiplicato per il suo peso** (5,85 al 3% fa meno danno di 2,23 al 18%). Il `f` di una struttura multi-gamba **non e' il `f` di una sua gamba**. E **il segno dell'asimmetria di `f` dipende dal VERSO della gamba**: *"un roll anticipato non paga `f`"* (§46) vale per una copertura **solo LONG** — se c'e' una **gamba VENDUTA da ricomprare**, uscire prima paga `f` *esattamente* sulla parte che a scadenza si regolava gratis, e piu' spesso. **L'asimmetria si INVERTE** (COLLAR01: uscire al 50% del tempo costa 5,6 punti di drift col gate forte, 10,2 col largo).
|
||||
- **C5** **L'open interest NON misura la negoziabilita'** (su Deribit USDC sono quasi anti-correlati). Prima di credere a un rapporto estremo su un prezzo piccolo, **contare i tick**.
|
||||
- **C6** La negoziabilita' **sul conto reale** va verificata quando lo sleeve entra in **RICERCA**, non quando entra nel book (PRIIPs costo': 5 settimane di misure). *"Non posso comprare" ≠ "non posso vedere"*: il segnale puo' girare su una serie che non si puo' negoziare.
|
||||
- **C7** Si cerca per **ISIN, non per ticker**; si prende la linea nella **valuta del proprio saldo**; un costo di conversione e' proprieta' della **coppia strumento-conto**, non dello strumento.
|
||||
@@ -419,7 +654,9 @@ src/portfolio/ → portfolio.py (combina N sleeve + weights_tilt_n
|
||||
src/backtest/harness.py → harness ONESTO (load BTC/ETH, backtest_signals no-leakage, OOS)
|
||||
src/live/ → book.py (esecutore netto TP01+SKH01), shadow.py, deribit.py,
|
||||
livefeed.py, venue_watch.py, monitor_health.py, notifier.py,
|
||||
tradesdb.py · journal.py · analista.py (libro di bordo)
|
||||
scale_watch.py (chiave di scala: 3 domande, 3 stati),
|
||||
tradesdb.py · journal.py · analista.py (libro di bordo),
|
||||
revisione.py (revisione settimanale, sola lettura, revisore fable)
|
||||
src/data/eq_splits.py, eq_crosscheck.py → guardie sul feed equity
|
||||
scripts/research/ → ricerca (r<data>_*.py). Harness condiviso: alt/altlib.py
|
||||
scripts/analysis/ → SOLO tool sui dati certificati: rebuild_history, certify_feed,
|
||||
@@ -427,11 +664,12 @@ scripts/analysis/ → SOLO tool sui dati certificati: rebuild_history
|
||||
scripts/live/ → book_execute · venue_watch · fee_watch · edge_watch ·
|
||||
monitor_health · collect_chain · paper_* · trades_db ·
|
||||
journal · analista
|
||||
scripts/cron_{book,daily,chain,opt_snapshot,vol_term}.sh
|
||||
scripts/cron_{book,daily,chain,opt_snapshot,vol_term,review}.sh
|
||||
docs/memory/ → LA MEMORIA (indice in testa a questo file)
|
||||
docs/research/ → BRIEF-0822 · RESULTS-0822 (§1-69) · SPEC-scale-key
|
||||
docs/diary/YYYY-MM-DD.md → una voce per esperimento (123)
|
||||
docs/research/ → BRIEF-0822 · RESULTS-0822 (§1-77) · SPEC-scale-key
|
||||
docs/diary/YYYY-MM-DD.md → una voce per esperimento (146)
|
||||
docs/journal/YYYY-MM-DD.md → libro di bordo, una voce al giorno, 4 livelli per provenienza
|
||||
docs/revisioni/YYYY-MM-DD.md → revisione settimanale firmata (lunedi'), opinione del revisore
|
||||
data/live/trades.db → i trade allineati col tempo (dentro il perimetro di backup)
|
||||
Old/ → ARCHIVIO pre-reset
|
||||
VERSION → semver
|
||||
@@ -445,11 +683,13 @@ uv run python scripts/analysis/rebuild_history.py --asset BTC ETH # storico da
|
||||
uv run python scripts/analysis/certify_feed.py [--local] # certifica i feed
|
||||
uv run python scripts/portfolio/run_portfolio.py # report del portafoglio (ricerca)
|
||||
uv run python scripts/live/paper_portfolio.py # avanza il paper (forward-only)
|
||||
uv run python scripts/live/trades_db.py --report # stato libro di bordo + P&L
|
||||
uv run python scripts/live/trades_db.py --report # stato libro di bordo + P&L (TWR, non e1/e0)
|
||||
uv run python scripts/live/<script>.py --help # uso e flag (dal 02/09: un flag ignoto esce 2)
|
||||
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/analista.py --secco # analisi del giorno, senza salvare
|
||||
uv run pytest # test (731: 730 ok, 1 noto §5.9)
|
||||
uv run python scripts/live/revisione.py --secco # taglia del materiale della revisione settimanale (niente modello)
|
||||
uv run pytest # test (1031, tutti verdi al 09/09 sera)
|
||||
```
|
||||
|
||||
```python
|
||||
|
||||
@@ -1,423 +1,153 @@
|
||||
# PythagorasGoal
|
||||
|
||||
Sistema di riconoscimento pattern frattali e predizione per il trading di criptovalute (BTC, ETH), ispirato al framework teorico di Serleto & Malanga (*Pythagoras Trading Prediction*).
|
||||
Ricerca e esecuzione di strategie algoritmiche su BTC/ETH, con **un libro che gira con soldi veri**
|
||||
su Deribit mainnet dal 20 giugno 2026.
|
||||
|
||||
## Obiettivo
|
||||
> 🚨 **v2.0.0 — RESET del 2026-06-19. Tutto ciò che questo README diceva prima è archiviato in
|
||||
> `Old/` e non è fidato.** L'intera libreria di strategie "validata out-of-sample" (le famiglie
|
||||
> FADE/HONEST/PAIRS/TSMOM/SHAPE, i portafogli PORT01-06, gli Sharpe fra 6 e 10) era un **artefatto
|
||||
> di uno storico contaminato**: print fantasma di un feed *testnet* più storico Binance/USDT.
|
||||
> Ri-testate sul feed reale ricostruito da Deribit mainnet, **perdono ogni anno**. Documento di
|
||||
> fondazione: `docs/diary/2026-06-19-deribit-history.md`.
|
||||
>
|
||||
> Questo README descrive il progetto **dopo** il reset. È stato riscritto il 2026-09-02, dopo
|
||||
> essere rimasto fermo al 4 giugno — quindici giorni prima del reset — mentre pubblicava i numeri
|
||||
> che il progetto aveva già dichiarato falsi.
|
||||
|
||||
Partendo da un capitale iniziale di €1.000, raggiungere un profitto medio di €50 al giorno entro 6–8 mesi, tramite un portafoglio di strategie algoritmiche poco correlate fra loro — mean-reversion, trend/rotazione e spread market-neutral — validate out-of-sample e fee-aware.
|
||||
**L'autorità sui numeri e sulle decisioni è `CLAUDE.md`**, e il racconto completo sta in
|
||||
`docs/memory/`. Questo file è l'ingresso, non la fonte.
|
||||
|
||||
## Risultati
|
||||
---
|
||||
|
||||
> ⚠️ **Revisione 2026-05-28.** La famiglia squeeze-breakout (SQ/MT/ML/AD/CM/PD, con
|
||||
> accuracy storiche dichiarate 76-82%) è stata **scartata**: quei numeri erano un
|
||||
> **artefatto di look-ahead**. I backtest decidevano la direzione dalla candela di
|
||||
> breakout `close[i]` ma entravano a `close[i-1]` — impossibile dal vivo. Sotto
|
||||
> ingresso onesto (`close[i]`) e fee reali, l'edge sparisce e tutte perdono, anche
|
||||
> a fee zero. Dettagli e prove: `scripts/analysis/oos_validation.py`.
|
||||
## Cos'è vivo, adesso
|
||||
|
||||
Dopo una validazione **out-of-sample, fee-aware** di molte famiglie di strategie,
|
||||
emergono cinque famiglie con edge netto reale, tutte radicate nella stessa lezione
|
||||
(in cripto la **mean-reversion** funziona, la continuazione no) o nella diversificazione:
|
||||
| | |
|
||||
|---|---|
|
||||
| libro live | **TP01 + SKH01 a 75/25**, nettati in software su una sola posizione per asset (50/50 BTC/ETH) |
|
||||
| venue | Deribit mainnet, perpetual lineari USDC. Esecuzione **armata** dal 2026-06-20 |
|
||||
| capitale | ~$2.050 (USDC + USDE), non i €2.000 nominali di nessun paper trader |
|
||||
| cadenza | **oraria**, minuto `:47` (`scripts/cron_book.sh`) |
|
||||
| leva | lorda ~0,28x su un tetto di 1,00x. Il cap non ha mai morso: **il vincolo è il segnale** |
|
||||
| protezione on-book | un solo disaster-SL rotolante al −30% sulla posizione netta |
|
||||
|
||||
| Famiglia | Meccanismo | Strategie | Profilo (netto OOS) |
|
||||
|----------|-----------|-----------|---------------------|
|
||||
| **FADE** | mean-reversion intraday 1h (long/short, BTC/ETH) | MR01 Bollinger, MR02 Donchian, MR07 Return-reversal | Acc 52-55%, DD 18-34% |
|
||||
| **HONEST** | long-only multi-regime multi-crypto | DIP01 dip-buy, TR01 EMA-trend, ROT02 dual-momentum | CAGR 31-56%, DD 15-27% |
|
||||
| **PAIRS** | spread reversion *market-neutral* (2 gambe) | PR01 ETH/BTC, LTC/ETH, ADA/ETH, BTC/LTC, ETH/SOL | Sharpe 2.0-4.4, corr col mercato ~0.05 |
|
||||
| **TSMOM** | time-series momentum multi-orizzonte | TSM01 (3/6/12m + risk-off) | diversificatore, DD 15-22% |
|
||||
| **SHAPE** | ML walk-forward su feature di *forma* del prezzo | SH01 (LogisticRegression, orizzonte 12 barre) | diversificatore, corr +0.08 col resto |
|
||||
**Non** sono nel libro live: XS01, VRP01, GTAA01, XSR01. Vivono nel portafoglio di **ricerca**
|
||||
(paper, 5 sleeve), che è una serie diversa da quella che gira — quasi tutti i numeri di portafoglio
|
||||
del progetto sono su quella, non su questa.
|
||||
|
||||
Tutti i numeri sono **netti** dopo fee realistiche (Deribit 0.10% RT single-leg, 0.20%
|
||||
RT/coppia sui pairs), leva 3x, su finestra held-out. Le strategie sono robuste su griglia
|
||||
parametri, sweep fee 0.00-0.20% RT e — per i pairs — validate con **walk-forward** e
|
||||
config universale (niente cherry-picking).
|
||||
## I numeri, con la loro lente
|
||||
|
||||
### Portafoglio combinato (la vera leva anti-drawdown)
|
||||
Ogni numero va scritto con la lente e la banda che gli appartengono. La tabella completa
|
||||
("non citare" ↔ "citare") è in `CLAUDE.md` §2; qui i tre che contano.
|
||||
|
||||
Le famiglie sono **quasi scorrelate fra loro** (~0.05). Combinandole in un unico
|
||||
portafoglio equipesato il drawdown crolla sotto quello di ogni singola sleeve:
|
||||
| grandezza | valore onesto |
|
||||
|---|---|
|
||||
| rendimento del libro live | **TWR +10,6%** dall'armamento, spezzato sul versamento del 25/08: +11,61% prima, −0,9% dopo |
|
||||
| crescita di trading | **+$50** in 71 giorni, su 30 round-trip chiusi e $0,87 di fee totali |
|
||||
| Sharpe del portafoglio di ricerca | **1,95** [1,81 – 2,12] full, **1,54** [1,11 – 1,91] hold-out |
|
||||
|
||||
| Portafoglio | CAGR | Max DD | Sharpe |
|
||||
|-------------|------|--------|--------|
|
||||
| FADE (6 sleeve) | ~46% | 8% | 3.9 |
|
||||
| HONEST (3 sleeve) | ~46% | 13% | 2.2 |
|
||||
| **MASTER** (FADE + HONEST, 9) | ~47% | **5%** | 4.2 |
|
||||
| **MASTER + PAIRS + TSM01** (15) | ~67% | ~5% | ~6 |
|
||||
| **PORT06 live** (17 sleeve, cap pairs 33%, leva 2×, config EXIT-16) | ~79% | **2.6%** | 7.8 (FULL) / 10.1 (OOS) |
|
||||
⚠️ Il rendimento dell'equity **non** è la performance: il 96% della crescita del conto è un
|
||||
bonifico. Chi legge una percentuale su questo progetto deve sapere se è un TWR o un rapporto fra
|
||||
due saldi — è stato un difetto reale, riparato il 2026-09-02.
|
||||
|
||||
> 🔎 **Numeri sobri (anti-overfit).** L'OOS singolo cade nel regime favorevole 2024-25:
|
||||
> i valori di Sharpe/DD sopra sono ottimistici di circa il 50%. Da pianificare per le
|
||||
> decisioni: **Sharpe atteso ~5**, **worst-drawdown su 90 giorni ~6%**, profilo che regge
|
||||
> a leva 2x con slippage raddoppiato. Configurazione raccomandata: equal-weight, leva 2x,
|
||||
> con un cap sull'allocazione ai pairs (~30-35%, poiché concentrano ~57% del rischio).
|
||||
> Tutto resta da confermare nel paper trading live.
|
||||
## La riga che ordina tutto il resto
|
||||
|
||||
## Come funziona
|
||||
> La ricerca ha smesso di essere il vincolo il 2026-07-26, e **sei ondate successive lo hanno
|
||||
> confermato invece che ribaltarlo**. I vincoli sono **il capitale che entra** e **il conto che non
|
||||
> sparisce**.
|
||||
|
||||
### MR01 — Bollinger Fade (mean-reversion)
|
||||
Misurato: il miglior candidato nuovo vale **+0,046 €/giorno**; versare €500/mese invece di €250
|
||||
porta la probabilità di arrivare al traguardo in 20 anni **dal 14% all'85%**. E €100/mese in più
|
||||
equivalgono a **+4,07%/anno di drift**, cioè più di tutta la leva autorizzabile.
|
||||
|
||||
La strategia attiva sfrutta il fatto, emerso dai dati, che su BTC/ETH a 1h gli estremi
|
||||
di prezzo **rientrano verso la media** più di quanto proseguano:
|
||||
## Metodo — cosa deve superare una strategia nuova
|
||||
|
||||
1. **Bollinger Bands** (window `n`, `k` deviazioni standard) sul close.
|
||||
2. **Entry** — quando il close esce *sotto* la banda inferiore → **long** (o *sopra* la superiore → **short**). Ingresso a `close[i]`, eseguibile dal vivo.
|
||||
3. **Take-profit** alla media mobile (il rientro atteso).
|
||||
4. **Stop-loss** a `sl_atr × ATR` oltre l'estremo; **time-limit** a `max_bars`.
|
||||
Sei requisiti, nessuno negoziabile (`CLAUDE.md` §8, gate in `scripts/research/alt/altlib.py`):
|
||||
|
||||
Nessun look-ahead: direzione e livelli sono calcolati con dati fino a `close[i]`.
|
||||
1. **ingresso eseguibile** — direzione e prezzo da dati fino a `close[i]`, mai l'estremo di una candela;
|
||||
2. **backtest netto** dopo fee Deribit realistiche, più la leva;
|
||||
3. **out-of-sample** held-out, robustezza su griglia, sweep fee;
|
||||
4. **liquidità e plausibilità** — un edge su un book fermo o su wick fantasma non è un edge;
|
||||
5. i **gate**: `marginal_vs_tp01` (Sharpe marginale, non assoluto), `study_family_honest` con
|
||||
deflated-Sharpe ≥ 0,95, `day_boundary_robust`, `anchor_luck_band`, `weights_tilt_null`;
|
||||
6. codice, test e diario.
|
||||
|
||||
### Le altre famiglie
|
||||
Il progetto ha **73 regole di prim'ordine** (`CLAUDE.md` §6) — sul dato, sul metodo, sui costi,
|
||||
sulla produzione e sul piano — ognuna pagata almeno una volta. La più
|
||||
ricorrente, cinque occorrenze: *un sorvegliante deve derivare il proprio bersaglio dal codice
|
||||
sorvegliato, mai ridichiararlo — un controllo puntato su una configurazione diversa da quella che
|
||||
gira passa sempre, e non sta controllando niente.*
|
||||
|
||||
- **FADE** (oltre MR01): MR02 fada la rottura del canale Donchian verso il centro;
|
||||
MR07 fada il movimento di barra estremo misurato in deviazioni standard dei
|
||||
rendimenti. Stessa logica di reversione, indicatori indipendenti.
|
||||
- **HONEST** (long-only, multi-crypto): DIP01 compra i dip estremi e rivende al
|
||||
recupero; TR01 segue il trend con incrocio di EMA su un paniere; ROT02 ruota ogni
|
||||
giorno sui tre asset col momentum più forte, andando in cash quando BTC è sotto la
|
||||
sua media (risk-off). Coprono i regimi di trend e rotazione, complementari alle fade.
|
||||
- **PAIRS** (market-neutral): scommette sul rientro verso la media del log-ratio fra
|
||||
due cripto (z-score). Long su una, short sull'altra: l'esposizione netta al mercato è
|
||||
quasi nulla (correlazione ~0.02), il che la rende un diversificatore eccellente.
|
||||
- **TSMOM**: tiene gli asset con momentum positivo persistente su più orizzonti
|
||||
(3/6/12 mesi), con overlay risk-off. Rende meno ma è poco correlato, utile in ensemble.
|
||||
## Il dato
|
||||
|
||||
### Perché lo squeeze breakout è stato abbandonato
|
||||
- **La verità è Deribit mainnet**, perché è dove si esegue. Binance è un audit indipendente, mai
|
||||
un'ancora per "ripulire": è USDT, ~10 bps fuori, fino al 3% sotto depeg.
|
||||
- Universo certificato: **solo BTC/ETH**, ogni timeframe. Gli alt sono esclusi.
|
||||
- Lo storico si aggiorna **solo** con `rebuild_history.py` e si certifica **sempre** con
|
||||
`certify_feed.py`. Il vecchio downloader è la causa del reset.
|
||||
- La catena opzioni si **raccoglie** ogni ora (`collect_chain.py`, minuto `:25`): un'ora non
|
||||
raccolta è persa per sempre.
|
||||
|
||||
L'ipotesi originale era opposta — *continuazione* dopo la compressione di volatilità
|
||||
(Bollinger dentro Keltner → breakout direzionale). Su dati storici sembrava dare
|
||||
76-82% di accuracy, ma era un **artefatto di look-ahead**: il backtest entrava a
|
||||
`close[i-1]` con direzione decisa da `close[i]`. Replicando l'esecuzione reale
|
||||
(ingresso a `close[i]`) l'edge collassa al ~47% (lancio di moneta) e i costi fanno
|
||||
il resto. Il test sui breakout intra-barra a 5m conferma che il movimento *rientra*
|
||||
subito (mean-reversion), giustificando MR01. Tutta la famiglia squeeze è in `scripts/waste/`.
|
||||
|
||||
### Lezione metodologica
|
||||
|
||||
Ogni nuova strategia deve passare: (1) **ingresso eseguibile** senza look-ahead,
|
||||
(2) backtest **netto** dopo fee realistiche (0.10% RT Deribit), (3) validazione
|
||||
**out-of-sample** + robustezza su griglia parametri + sweep fee. Strumenti in
|
||||
`scripts/analysis/` (`strategy_research.py`, `oos_validation.py`, `intrabar_test.py`).
|
||||
|
||||
## Struttura progetto
|
||||
## Struttura
|
||||
|
||||
```
|
||||
PythagorasGoal/
|
||||
├── src/
|
||||
│ ├── data/ # Download e gestione dati (Cerbero MCP + Binance)
|
||||
│ ├── fractal/ # Indicatori frattali: Hurst, Higuchi FD, self-similarity
|
||||
│ ├── backtest/ # Motore di backtesting con fee e metriche
|
||||
│ ├── strategies/ # Classe base Strategy ABC + indicatori condivisi
|
||||
│ │ ├── base.py # Strategy, Signal, BacktestResult, YearlyStats
|
||||
│ │ └── indicators.py # keltner_ratio, detect_squeezes, ema, atr, rv, corr
|
||||
│ ├── live/ # Paper trading live su Deribit testnet
|
||||
│ │ ├── multi_runner.py # Orchestratore multi-strategia (strategie + pairs)
|
||||
│ │ ├── strategy_worker.py # Worker single-leg con stato persistente
|
||||
│ │ ├── pairs_worker.py # Worker a 2 gambe per i pairs (market-neutral)
|
||||
│ │ ├── strategy_loader.py # Import dinamico classi Strategy
|
||||
│ │ ├── cerbero_client.py # Client HTTP per Cerbero MCP
|
||||
│ │ ├── signal_engine.py # Squeeze + ML real-time (legacy) + validazione OOS
|
||||
│ │ └── telegram_notifier.py
|
||||
│ └── portfolio/ # Portafogli di prima classe (capitale condiviso, backtest + live)
|
||||
│ ├── base.py # SleeveSpec, Portfolio (.backtest), load_active_portfolio
|
||||
│ ├── weighting.py # Schemi di ponderazione: equal, cap, inverse_vol, cluster_rp, manual
|
||||
│ ├── sleeves.py # Builder unificato equity-per-sleeve (fonte unica, parità report)
|
||||
│ ├── ledger.py # PortfolioLedger: PnL/DD aggregati, persistenza e resume
|
||||
│ └── runner.py # PortfolioRunner live (Cerbero v2, sizing, ribilancio giornaliero)
|
||||
├── scripts/
|
||||
│ ├── strategies/ # Strategie con edge validato OOS (FADE, HONEST, PAIRS, TSMOM + portafogli)
|
||||
│ ├── portfolios/ # Definizioni PORT01-06 e report run() dei portafogli di prima classe
|
||||
│ ├── waste/ # Strategie scartate (squeeze SQ/MT/ML/AD/CM/PD, MR03, ROT01, W01-W28)
|
||||
│ └── analysis/ # Ricerca/validazione OOS fee-aware, gestione rischio, report
|
||||
├── strategies.yml # Config multi-strategy paper trader
|
||||
├── data/
|
||||
│ ├── raw/ # Parquet OHLCV (gitignored, ~70 MB)
|
||||
│ └── regime/ # DVOL + funding (Deribit mainnet) + cache feature regime (gitignored)
|
||||
├── VERSION # versione semver (cotta nell'immagine, mostrata nei msg Telegram)
|
||||
├── docs/
|
||||
│ ├── diary/ # Diario di ricerca giornaliero
|
||||
│ └── specs/ # Specifiche di design
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
└── pyproject.toml
|
||||
src/
|
||||
data/downloader.py load_data(asset, tf) sui parquet certificati
|
||||
strategies/ trend_portfolio.py (TP01) · skyhook.py (SKH01) · base · indicators
|
||||
portfolio/ portfolio.py (N sleeve + weights_tilt_null) · sleeves.py · gtaa.py
|
||||
backtest/harness.py backtest onesto, senza look-ahead
|
||||
live/ book.py (esecutore netto) · deribit · livefeed · usde
|
||||
venue_watch · venue_probe · venue_news · monitor_health · scale_watch
|
||||
tradesdb · journal · analista · notifier · cli
|
||||
scripts/
|
||||
live/ book_execute · trades_db · journal · analista · balance_watch
|
||||
paper_* (forward-monitor) · usde_watch · usde_convert · fee_watch
|
||||
research/ r<data>_*.py — un file per esperimento, harness in alt/altlib.py
|
||||
analysis/ rebuild_history · certify_feed · audit_feed · multi_source_check
|
||||
cron_{book,daily,chain,balance,usde,opt_snapshot,vol_term}.sh
|
||||
docs/
|
||||
memory/ LA MEMORIA — 6 file, indicizzati in testa a CLAUDE.md
|
||||
research/ RESULTS-0822 (§1-77, un registro per filone) · BRIEF-0822 · SPEC-scale-key
|
||||
diary/ una voce per esperimento (144)
|
||||
journal/ libro di bordo, una voce al giorno, 4 livelli per provenienza
|
||||
tests/ 1008, tutti verdi
|
||||
Old/ archivio pre-reset — consultabile, non fidato
|
||||
```
|
||||
|
||||
## Strategie attive
|
||||
|
||||
Le strategie single-asset estendono `src.strategies.base.Strategy`
|
||||
(`generate_signals() → backtest()`); i pairs hanno un worker dedicato a 2 gambe.
|
||||
|
||||
| Codice | Script | Famiglia | Descrizione |
|
||||
|--------|--------|----------|-------------|
|
||||
| **MR01** | `MR01_bollinger_fade.py` | FADE | Fada la banda di Bollinger, TP alla media, SL ad ATR |
|
||||
| **MR02** | `MR02_donchian_fade.py` | FADE | Fada la rottura del canale Donchian, TP al centro |
|
||||
| **MR07** | `MR07_return_reversal.py` | FADE | Fada il movimento di barra estremo (z dei rendimenti) |
|
||||
| **DIP01** | `DIP01_dip_reversion.py` | HONEST | Dip-buy long-only su z-score estremo |
|
||||
| **TR01** | `TR01_ema_trend.py` | HONEST | EMA 20/100 trend-following su paniere cripto (4h) |
|
||||
| **ROT02** | `ROT02_dual_momentum.py` | HONEST | Rotazione cross-sectional top-3 + risk-off (1d) |
|
||||
| **PR01** | `PR01_pairs_reversion.py` | PAIRS | Spread reversion market-neutral su 5 coppie |
|
||||
| **TSM01** | `tsmom_research.py` | TSMOM | Time-series momentum multi-orizzonte + risk-off |
|
||||
| **SH01** | `SH01_shape_ml.py` | SHAPE | LogisticRegression walk-forward su 17 feature di forma, orizzonte 12 barre (diversificatore) |
|
||||
|
||||
Le fade applicano tre protezioni live: un **filtro trend** (`trend_max`/`ema_long`,
|
||||
salta i segnali col prezzo troppo esteso rispetto alla EMA200), un **loss-guard Hurst**
|
||||
(`hurst_max=0.55`, salta i segnali in regime persistente/trending dove si concentrano gli stop-loss
|
||||
— dimezza il drawdown del portafoglio, calcolato dalle sole close) e l'**EXIT-16 close-confirm SL**
|
||||
(`sl_confirm_atr=0.5`, 2026-06-04: lo stop scatta solo se la barra *chiude* oltre `sl ∓ 0.5·ATR14` —
|
||||
gli stop intrabar da wick erano falsi negativi, l'overshoot che buca lo stop è proprio il movimento
|
||||
che la fade fada; a livello PORT06 porta l'OOS Sharpe da 8.82 a 10.06). Più un filtro `min_tp_frac`
|
||||
che scarta i micro-scalp col take-profit entro il costo delle fee. Le tre protezioni sono
|
||||
complementari: Hurst toglie il regime tossico, il trend-filter gli ingressi sovra-estesi, il
|
||||
close-confirm i falsi stop. Portafogli pronti: `PORT01`
|
||||
(honest), `PORT02` (fade), `PORT03` (master fade+honest), **`PORT06`** (master esteso, default live).
|
||||
|
||||
**Scartate** (in `scripts/waste/`): la famiglia squeeze (SQ01-04, ML01, MT01, PD01,
|
||||
CM01, AD01 — artefatto di look-ahead), MR03 Keltner (debole/ridondante con MR01) e
|
||||
ROT01 (dominata da ROT02).
|
||||
|
||||
### Comandi utili
|
||||
## Comandi
|
||||
|
||||
```bash
|
||||
# Backtest di una strategia
|
||||
uv run python scripts/strategies/MR01_bollinger_fade.py
|
||||
uv run python scripts/strategies/PR01_pairs_reversion.py
|
||||
|
||||
# Ricerca e validazione fee-aware out-of-sample
|
||||
uv run python scripts/analysis/strategy_research.py # screening famiglie + deep-dive fade
|
||||
uv run python scripts/analysis/strategy_research_v2.py # MR02 / MR03 / MR07
|
||||
uv run python scripts/analysis/oos_validation.py # perche' la famiglia squeeze e' scartata
|
||||
uv run python scripts/analysis/pairs_research.py # ricerca + verifica no-look-ahead dei pairs
|
||||
|
||||
# Gestione rischio, combinazione, report
|
||||
uv run python scripts/analysis/risk_management.py # filtro trend + portafoglio fade
|
||||
uv run python scripts/analysis/combine_portfolio.py # combinare fade + honest
|
||||
uv run python scripts/analysis/combine_v2.py # master esteso con pairs + TSM01
|
||||
uv run python scripts/analysis/report_families.py # report per anno di tutte le famiglie
|
||||
|
||||
# Validazione dei worker live (replay == backtest)
|
||||
uv run python scripts/analysis/validate_worker_mr01.py # worker single-leg su MR01
|
||||
uv run python scripts/analysis/validate_worker_pairs.py # worker a 2 gambe sui pairs
|
||||
uv run python scripts/analysis/live_smoke_pairs.py # smoke test feed live reale dei pairs
|
||||
uv sync # dipendenze
|
||||
uv run python scripts/analysis/rebuild_history.py --asset BTC ETH # storico da Deribit mainnet
|
||||
uv run python scripts/analysis/certify_feed.py # certifica i feed
|
||||
uv run python scripts/live/trades_db.py --report # stato del libro + TWR
|
||||
uv run python scripts/live/journal.py # voce del giorno
|
||||
uv run python scripts/live/analista.py --secco # analisi del giorno, senza salvare
|
||||
uv run python scripts/portfolio/run_portfolio.py # portafoglio di ricerca
|
||||
uv run pytest # 1008 test
|
||||
```
|
||||
|
||||
## Paper Trading Live
|
||||
Ogni script di `scripts/live/` accetta `--help` e **rifiuta un flag che non conosce** (esce 2): fino
|
||||
al 2026-09-02 un flag sbagliato eseguiva l'azione di default, e su tre script quell'azione scriveva.
|
||||
|
||||
Il multi-strategy runner esegue N strategie in parallelo su dati live da Cerbero MCP,
|
||||
ognuna con €1000 USDC virtuali indipendenti. Gestisce due tipi di worker:
|
||||
## Gate aperti
|
||||
|
||||
- **Single-leg** (`strategy_worker.py`): per le strategie direzionali. Se un `Signal`
|
||||
porta `tp`/`sl`/`max_bars` in `metadata` (come le fade), chiude su take-profit /
|
||||
stop-loss / time-limit; altrimenti usa il fallback `hold_bars`/stop -2%.
|
||||
- **Due gambe** (`pairs_worker.py`): per i pairs market-neutral. Apre long su una gamba
|
||||
e short sull'altra, esce sul rientro dello z-score o per time-limit, conta le fee su
|
||||
entrambe le gambe. Validato: il replay storico coincide *esattamente* col backtest.
|
||||
Un gate si decide **alla data**, coi criteri scritti **prima**. Elenco completo in `CLAUDE.md` §4.
|
||||
|
||||
### Avvio
|
||||
| gate | data | dove punta oggi |
|
||||
|---|---|---|
|
||||
| STATARB | 2026-09-27 | ritiro (Sharpe −1,61 sulla serie vera) |
|
||||
| SCALA-01 — leva 1,00 → 1,25 | non prima del 2026-10-01 | chiave costruita e **inerte**; 9 condizioni, 2 fatte |
|
||||
| XSR01 | 2026-10-23 | sotto la lente rendita il meccanismo vale ~0 |
|
||||
| DVOLSPREAD | kill 2026-10-24 | serie −4,41 |
|
||||
| GTAA01 tenere/bloccare | al book a $15k | contributo reale +0,10/+0,12 di Sharpe, non incassabile sotto la soglia |
|
||||
|
||||
```bash
|
||||
# Locale
|
||||
uv run python -m src.live.multi_runner
|
||||
## Obiettivo, e cosa lo rende difficile
|
||||
|
||||
# Docker
|
||||
docker compose up -d
|
||||
```
|
||||
Il target dichiarato è **€50/giorno partendo da €1.000**. Non è raggiungibile a questo capitale:
|
||||
servono ~$313k (banda [$187k – $1,14M]) o **€1.733/mese per dieci anni** per averlo al 90% di
|
||||
probabilità. La leva non è la scorciatoia — quella difendibile vale quanto €100-150/mese di
|
||||
versamento. La via è target-vol, capitale e tempo.
|
||||
|
||||
### Configurazione
|
||||
|
||||
Le strategie attive sono definite in `strategies.yml`:
|
||||
|
||||
```yaml
|
||||
defaults:
|
||||
capital: 1000
|
||||
position_size: 0.15
|
||||
leverage: 3
|
||||
|
||||
strategies: # strategie single-leg
|
||||
- name: MR01_bollinger_fade
|
||||
asset: BTC
|
||||
tf: 1h
|
||||
enabled: true
|
||||
params: { bb_window: 50, k: 2.5, sl_atr: 2.0, max_bars: 24, trend_max: 3.0, ema_long: 200 }
|
||||
|
||||
pairs: # strategie a 2 gambe (market-neutral)
|
||||
- name: PR01_pairs_reversion
|
||||
a: ETH
|
||||
b: BTC
|
||||
tf: 1h
|
||||
enabled: true
|
||||
params: { n: 50, z_in: 2.0, z_exit: 0.75, max_bars: 72, jump_max: 0.08 }
|
||||
```
|
||||
|
||||
Per aggiungere una strategia: nuova riga in `strategies.yml` (sezione `strategies` o
|
||||
`pairs`), poi `docker compose restart`. Lo storico delle strategie esistenti rimane intatto.
|
||||
|
||||
### Persistenza
|
||||
|
||||
Ogni strategia ha la sua directory in `data/paper_trades/`:
|
||||
|
||||
```
|
||||
data/paper_trades/
|
||||
MR01_bollinger_fade__BTC__1h/
|
||||
trades.jsonl # Storico trade append-only
|
||||
status.json # Stato corrente (resume al restart, include tp/sl/max_bars)
|
||||
```
|
||||
|
||||
Notifiche Telegram per ogni trade (richiede `TELEGRAM_BOT_TOKEN` e `TELEGRAM_CHAT_ID` in `.env`).
|
||||
|
||||
## Paper Trading a Portafoglio
|
||||
|
||||
Accanto al multi-strategy runner originale — in cui ogni strategia gestisce autonomamente il proprio conto virtuale da €1.000 — il progetto dispone ora di un **paper trader a portafoglio** (`src/portfolio/`) che tratta l'insieme delle strategie come un unico organismo con un capitale condiviso.
|
||||
|
||||
### Come funziona
|
||||
|
||||
La definizione di un portafoglio (`SleeveSpec` + schema di peso) ha due facce sulla stessa sorgente dati:
|
||||
|
||||
- **Backtest** (`.backtest()`): ricostruisce le equity-curve di ogni sleeve tramite il builder unificato in `sleeves.py`, le pondera secondo lo schema scelto e calcola le metriche aggregate (CAGR, Sharpe, max DD). La parità con i report prodotti da `report_families.py` è garantita dalla fonte unica.
|
||||
- **Live** (`PortfolioRunner`): ogni ora il runner scarica le candele aggiornate via Cerbero v2, calcola i pesi correnti, avvia i worker appropriati per ogni sleeve attiva e registra il PnL aggregato nel ledger (`data/portfolios/{code}/`). Il ledger persiste tra i riavvii.
|
||||
|
||||
### Schemi di ponderazione
|
||||
|
||||
Il modulo `weighting.py` mette a disposizione cinque schemi: `equal` (default), `cap` (tetto per famiglia — p.es. `pairs: 0.33` per limitare la concentrazione), `inverse_vol` (pesi inversamente proporzionali alla volatilità storica), `cluster_rp` (equal tra cluster naturali poi inverse-vol all'interno del cluster) e `manual` (pesi liberi). Lo schema si specifica in `portfolios.yml` insieme al codice portafoglio e alla leva.
|
||||
|
||||
### Portafoglio di default: PORT06
|
||||
|
||||
La configurazione raccomandata è **PORT06** (`scripts/portfolios/PORT06_master_shape.py`): portafoglio master esteso che include tutte e sei le famiglie (FADE, HONEST, PAIRS, TSMOM, SHAPE), con schema `cap` che limita i pairs al 33% del capitale per moderare la loro concentrazione di rischio. Backtest canonico (dati al 2026-05-28): Sharpe 6.47 (FULL) / 8.82 (OOS), drawdown massimo 4.10% (FULL) / 1.30% (OOS), leva 2×; **con la config live attuale (EXIT-16 close-confirm): Sharpe 7.84 / 10.06, DD 2.60% / 1.15%**.
|
||||
|
||||
### Scope live
|
||||
|
||||
Il runner esegue **tutti e 17 gli sleeve** di PORT06: **fade** (MR01, MR02, MR07 × BTC/ETH),
|
||||
**honest** (DIP01, TR01-basket 4h, ROT02-rotation 1d), **pairs** (PR01, cinque coppie),
|
||||
**TSMOM** (TSM01 1d) e **shape** (SH01 × BTC/ETH). Worker dedicati: `StrategyWorker` (single-leg, fade/
|
||||
dip/**shape**), `PairsWorker` (2 gambe), `BasketTrendWorker`, `RotationWorker`, `TsmomWorker`. Il runner
|
||||
fetcha 1h da Cerbero v2 e resampla a 4h/1d; il pool di capitale, il ribilancio giornaliero e il ledger
|
||||
sono validati == backtest.
|
||||
|
||||
> **SH01 (2026-06-01):** gira come `StrategyWorker` normale (il walk-forward è interno a
|
||||
> `generate_signals`). Il vecchio `MLWorkerWrapper` usava il `SignalEngine` **squeeze scartato** —
|
||||
> rimosso. **Loss-guard Hurst (2026-06-02):** le fade saltano i segnali in regime persistente
|
||||
> (rolling-Hurst ≥ 0.55), dove si concentrano gli stop-loss — dimezza il drawdown del portafoglio
|
||||
> (FULL 4.1%→2.4%; stop-loss fade −67% in numero, perdite totali −68%). Calcolato dalle sole close,
|
||||
> attivo live (`hurst_max` nei params). Il report orario su Telegram **monitora lo stop-rate fade
|
||||
> prima/dopo l'attivazione** e dà il verdetto automatico quando il campione è sufficiente.
|
||||
|
||||
### Esecuzione reale (shadow, Deribit testnet)
|
||||
|
||||
Sette sleeve single-leg — le **6 fade** (MR01/MR02/MR07 × BTC/ETH) e **DIP01** (dal 2026-06-04) —
|
||||
eseguono ordini **reali su Deribit testnet** accanto al fill simulato (*shadow*: il sim resta la
|
||||
verità che guida le decisioni; il reale misura la fattibilità). Punti chiave:
|
||||
|
||||
- **Strumenti lineari USDC** (`BTC_USDC`/`ETH_USDC-PERPETUAL`): payoff lineare = matematica del
|
||||
backtest; fee e PnL in USDC. Quantizzazione `Decimal` di amount (step) e prezzi (tick).
|
||||
- **Take-profit reale = limit reduce-only AL livello** (v1.0.7): piazzato all'apertura, copre la
|
||||
sola quota del worker (gli strumenti sono condivisi fra worker e nettati per conto); alla
|
||||
chiusura il worker cancella il resting, riconcilia i fill dal trade history per `order_id` e
|
||||
chiude a market solo il residuo. Fix della divergenza misurata: il market-on-poll usciva
|
||||
+235 bps oltre il livello TP. Fill da resting = fee maker (~0%).
|
||||
- **Stop-loss close-confirm** (v1.1.0): uscita al close che sfonda il livello → market
|
||||
reduce-only al poll (nessun ordine stop sul book, per scelta: i trigger Deribit generano un
|
||||
nuovo order_id allo scatto, non verificabile, e i wick non devono stoppare).
|
||||
- **Verifica sul trade** (order_id in `get_trade_history`), fee reali dai `trades[]`, ledger
|
||||
reale parallelo persistito (`real_capital`), eventi `REAL_OPEN`/`REAL_TP_RESTING`/`REAL_CLOSE`
|
||||
nel log + alert Telegram (`REAL_EXEC_LIVE`, `REAL_OPEN_FAIL`).
|
||||
- Config in `portfolios.yml` → `overrides.execution {enabled, sleeves, instruments}`.
|
||||
**Pairs/rotation/TSMOM/shape restano simulati**: i pairs richiedono un executor a 2 gambe
|
||||
(leg-risk), i multi-asset un rebalance-to-target; roadmap nel diario.
|
||||
|
||||
### Versione & deploy
|
||||
|
||||
Ogni deploy ha una **versione** (file `VERSION`, semver) che compare nei messaggi Telegram (notifiche
|
||||
trade + report orario), così correli ogni messaggio al codice che l'ha generato. Il sorgente è **cotto
|
||||
nell'immagine** → per aggiornare il live serve un **rebuild**, non un semplice restart:
|
||||
|
||||
```bash
|
||||
./scripts/deploy.sh # bump patch (1.0.0 → 1.0.1) + commit + rebuild + ricrea container
|
||||
./scripts/deploy.sh minor # 1.0.x → 1.1.0
|
||||
```
|
||||
|
||||
Il volume `data/` persiste tra i deploy → i worker fanno RESUME dello stato (capitale, posizioni aperte).
|
||||
|
||||
### Avvio del paper trader a portafoglio
|
||||
|
||||
```bash
|
||||
# Backtest del portafoglio di default (PORT06)
|
||||
uv run python scripts/portfolios/PORT06_master_shape.py
|
||||
|
||||
# Paper trading live a portafoglio
|
||||
uv run python -m src.portfolio.runner
|
||||
|
||||
# Report orario su Telegram (stato + stop-rate fade prima/dopo loss-guard) — via cron
|
||||
uv run python scripts/portfolios/hourly_report.py
|
||||
|
||||
# Smoke test del data layer Cerbero v2
|
||||
uv run python scripts/analysis/smoke_portfolio.py
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# Clona e installa
|
||||
git clone <repo-url> && cd PythagorasGoal
|
||||
uv sync
|
||||
|
||||
# Scarica dati storici (~70 MB)
|
||||
uv run python -m src.data.downloader
|
||||
|
||||
# Backtest strategia attiva
|
||||
uv run python scripts/strategies/MR01_bollinger_fade.py
|
||||
|
||||
# Paper trading live
|
||||
uv run python -m src.live.multi_runner
|
||||
```
|
||||
|
||||
### Requisiti
|
||||
|
||||
- Python ≥ 3.11
|
||||
- [uv](https://docs.astral.sh/uv/) come package manager
|
||||
- Accesso a Cerbero MCP (`cerbero-mcp.tielogic.xyz`) per dati Deribit live
|
||||
- Docker (opzionale, per deploy su VPS)
|
||||
|
||||
## Dati
|
||||
|
||||
| Asset | Timeframe | Copertura |
|
||||
|-------|-----------|-----------|
|
||||
| BTC, ETH | 5m / 15m / 1h | 2018-01 → oggi |
|
||||
| SOL, LTC, ADA, XRP, BNB, DOGE | 15m / 1h | 2019-2022 → oggi (variabile per asset) |
|
||||
|
||||
Fonte primaria: perpetual Deribit via Cerbero MCP. Fallback: Binance spot via ccxt.
|
||||
Formato: Apache Parquet (in `data/raw/`, gitignored).
|
||||
|
||||
> **Nota sul naming Deribit (per il feed live).** I major sono perpetui *inverse*
|
||||
> (`BTC-PERPETUAL`, `ETH-PERPETUAL`); gli altcoin sono perpetui *lineari USDC*
|
||||
> (`SOL_USDC-PERPETUAL`, `LTC_USDC-PERPETUAL`, …) con storia dal 2022. Attenzione:
|
||||
> `LTC-PERPETUAL`/`ADA-PERPETUAL` non esistono e `SOL-PERPETUAL` restituisce dati
|
||||
> errati — per gli altcoin usare sempre la forma `_USDC-PERPETUAL`.
|
||||
|
||||
### Discovery & validazione strumenti
|
||||
|
||||
`src/data/instruments.py` scopre e **valida** gli strumenti disponibili sugli
|
||||
exchange implementati — **Deribit** e **Hyperliquid** (esclusi Alpaca/stocks e
|
||||
**Bybit**, feed testnet inaffidabile). Ogni perpetuo viene testato sui dati
|
||||
storici realmente raccoglibili: esistenza, congruenza OHLC, contratto non-morto,
|
||||
liquidità e **congruenza prezzo cross-exchange** (mediana per base-coin, tolleranza
|
||||
5%) — così feed farlocchi e contratti sbagliati (es. `SOL-PERPETUAL`=9.6) vengono
|
||||
scartati. Il risultato è `data/instruments_registry.json` (strumenti validi +
|
||||
timeframe + data d'inizio).
|
||||
|
||||
**Solo gli strumenti validati possono essere scaricati**: il downloader ha un gate
|
||||
(`_download_cerbero_range`) che rifiuta quelli non nel registry. Rigenera con:
|
||||
|
||||
```bash
|
||||
uv run python -m src.data.instruments
|
||||
```
|
||||
|
||||
Simboli Deribit: BTC/ETH = `<COIN>-PERPETUAL` (inverse); altcoin =
|
||||
`<COIN>_USDC-PERPETUAL` (lineari USDC). Registry attuale (testnet): Deribit 18/106
|
||||
validi (major liquidi, BTC dal 2018), Hyperliquid 66/74.
|
||||
|
||||
## Riferimenti
|
||||
|
||||
- Serleto, L. & Malanga, C. — *Pythagoras Trading Prediction* (2024)
|
||||
- Serleto, L. & Malanga, C. — *Libro dei Frattali* (2024)
|
||||
|
||||
## Licenza
|
||||
|
||||
Uso privato. Non destinato alla distribuzione.
|
||||
**Onestà prima di tutto**: nessun numero va creduto finché non è netto fee, out-of-sample, robusto
|
||||
su griglia, e su dati certificati, liquidi ed eseguibili. Il resto di questo repo esiste per rendere
|
||||
quella frase verificabile invece che dichiarata.
|
||||
|
||||
+13
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"_nota": "Config esecuzione LIVE del BOOK DERIBIT (TP01+SKH01 nettati in software). execution_enabled=true + --execute -> ordini REALI. ARMATO 2026-06-23: esecutore scripts/live/book_execute.py via cron ORARIO scripts/cron_book.sh (SKH01 e' a 230m). disaster-SL on-book -30% sulla posizione netta. Tutto flat all'arming -> nessun ordine finche' un segnale non arma.",
|
||||
"_nota_cap": "Cap notional per-asset DINAMICO (frontiera 2026-07-03): con max_notional_per_asset_frac=0.5 il cap = equity/2, cosi' cresce col capitale e un deposito non resta strozzato. AGGIORNATO 2026-07-26: max_notional_per_asset_usd alzato 300 -> 3000 in previsione del versamento (EUR 5.000 + 500/mese -> equity ~$6.050, equity/2 ~$3.025). \u26a0\ufe0f Alzarlo NON e' pericoloso perche' dal 2026-07-26 il cap di FALLBACK (equity reale non leggibile) e' min(questo valore, ultima_equity_reale_osservata * frac) \u2014 vedi src/live/book._cap e il watermark data/live/equity_seen.json. Senza quel legame, un cap da $3.000 su un conto da $597 avrebbe permesso $2.000 di nozionale lordo = 3.35x di leva nel momento peggiore. Questo rende inutile l'azione manuale 'al deposito alzare il cap' (pre-registrata 2026-07-02).",
|
||||
"_nota_cap": "Cap notional per-asset DINAMICO (frontiera 2026-07-03): con max_notional_per_asset_frac=0.5 il cap = equity/2, cosi' cresce col capitale e un deposito non resta strozzato. AGGIORNATO 2026-07-26: max_notional_per_asset_usd alzato 300 -> 3000 in previsione del versamento (EUR 5.000 + 500/mese -> equity ~$6.050, equity/2 ~$3.025). ⚠️ Alzarlo NON e' pericoloso perche' dal 2026-07-26 il cap di FALLBACK (equity reale non leggibile) e' min(questo valore, ultima_equity_reale_osservata * frac) — vedi src/live/book._cap e il watermark data/live/equity_seen.json. Senza quel legame, un cap da $3.000 su un conto da $597 avrebbe permesso $2.000 di nozionale lordo = 3.35x di leva nel momento peggiore. Questo rende inutile l'azione manuale 'al deposito alzare il cap' (pre-registrata 2026-07-02).",
|
||||
"execution_enabled": true,
|
||||
"max_notional_per_asset_usd": 3000,
|
||||
"max_notional_per_asset_frac": 0.5,
|
||||
@@ -9,5 +9,16 @@
|
||||
"_nota_stale": "Staleness-gate (2026-07-25): se l'ultima barra del feed certificato e' piu' vecchia di max_data_age_days, book_execute NON invia ordini e allerta su Telegram. Il 2026-07-14 il book compro' ETH con il feed fermo da 6 giorni (conto online e posizione leggibile -> gli altri due gate non scattavano). Follow-up raccomandato nel diario 2026-07-15-feed-freeze, ora cablato.",
|
||||
"max_data_age_days": 2,
|
||||
"_nota_skh_feed": "Freschezza del feed 5m usato per il segnale SKH01 (2026-07-26). fresh_5m ricade sul feed certificato IN SILENZIO se il fetch pubblico Deribit fallisce, e il certificato si rigenera 1x/giorno: senza controllo la latenza d'uscita di SKH01 passa da ~1h a ~1 giorno senza segnalazione. Sopra soglia book_execute ALLERTA e NON blocca (bloccare fermerebbe anche TP01, nettato sullo stesso strumento, per un guasto di rete). Diario 2026-07-26-t1-esecuzione-skh-live.md.",
|
||||
"skh_feed_max_age_min": 30
|
||||
"skh_feed_max_age_min": 30,
|
||||
"_nota_usde": "Collaterale USDE a rendimento (test di eligibilita' 2026-08-26: 500 USDE, verdetto entro il 29/08 — diario 2026-08-26-usde-analisi). L'autorita' che LEGGE questa sezione e' src/live/usde.py; la usano shadow._collaterale_usde (equity oraria del book) e scripts/live/usde_watch.py (sorveglianza giornaliera 12:35 UTC). Criteri delle soglie, dichiarati (P6): depeg_warn 0.99 = fuori dalla banda operativa dello spot (~3 bps) e oltre il clamp +-0.5% per fonte dell'indice usde_usdc — a quel prezzo non e' rumore di book; depeg_crit 0.95 = meta' del buffer di haircut (10%) consumata. quota_target 0.70 = la quota DECISA dall'operatore il 2026-08-30 (la decisione di quota che il gate USDE-01 apriva; anticipata di un giorno sul rinvio al 31/08, su richiesta esplicita dell'operatore \"porta in usde tutto il capitale che non viene usato\"). Il 70% NON e' un argmax (M8): e' il massimo compatibile col CUSCINO DI REGOLAMENTO, cioe' il vincolo che r0830_usde_quota non aveva guardato — il P&L e il funding dei perp USDC-lineari si regolano in USDC, non nel collaterale, quindi a quota alta il saldo USDC va negativo alla prima perdita del libro e Deribit lo finanzia a interesse. Il cuscino richiesto e' il disaster-SL sulla massima esposizione lorda: n_asset x frac x disaster_sl_pct = 2 x 0.5 x 0.30 = 30% dell'equity, che lascia esattamente il 70%. quota_max_frac 0.50 = tetto di ALLERTA sulla quota USDE/equity totale (N4: la quota e' l'unica leva contro il rischio emittente, -100% = 25 anni di resa, non recuperabile). Alzato a 0.85 il 2026-08-30 in previsione della quota al 70% e RIMESSO A 0.50 lo stesso giorno perche' il 70% pareva irraggiungibile; RIMESSO A 0.85 il 2026-09-06, quando la quota e' arrivata al 68,7% su autorizzazione dell'operatore e la sonda non ha trovato tetto. 🚨 venue_cap_frac: il 06/09 il tetto NON C'ERA (null, vedi venue_cap_misurato) — quanto segue e' la storia del 30-31/08, tenuta perche' il tetto puo' tornare: venue_cap_frac 0.318 = TETTO DEL VENUE sull'USDE, misurato e NON documentato da Deribit (ne' 'Cross collateral specifications' ne' 'Yield-generating collateral' prevedono un limite sulle quantita' detenibili; il Cap ETHENA che esiste diluisce il TASSO a livello di exchange, non limita gli acquisti). Ogni acquisto oltre il tetto e' rifiutato con `not_enough_funds_in_currency` pur avendo $1.400 disponibili: messaggio FUORVIANTE. E' una FRAZIONE dell'equity, non un livello: provato il 31/08 lasciando scendere l'equity di $8, il tetto e' sceso con lei. ⚠️ DIPENDE DAL MODELLO DI MARGINE, ma pochissimo — misurato a saldo neutro sotto entrambi: SEGREGATO S:SM [643,18-644,18) con equity $2.055,56 = 31,29-31,34%; CROSS X:SM [654,18-655,18) con equity $2.054,90 = 31,84-31,88%. Il passaggio a cross ha comprato +0,55pp = **+11 USDE (~$11)**: il modello entra nel tetto, ma non lo spiega. ⇒ la quota resta ~31-32% sotto qualunque configurazione e il 70% di quota_target NON e' raggiungibile (manca un fattore ~2,2x). Il valore 0.318 e' il bordo BASSO del bracket CROSS, che e' il modello attivo: fa fallire il piano PRIMA dell'ordine. Se si torna a S:SM va rimesso a 0.312. haircut 0.05 — ✅ DIVERGENZA CHIUSA il 2026-08-31 (era 0.10, SBAGLIATO). La pagina margini del conto non espone l'haircut come numero: si RICAVA per differenza fra le due righe, perche' il modello CROSS conta l'USDE scontato e il SEGREGATO non lo conta affatto. Contributo dell'USDE al cross $610,81 su $643,05 di valore -> 5,0137%: l'ipotesi 5% torna a $0,09, la 10% sbaglia di $32,06. Riproducibile: `scripts/research/r0831_margini_conto.py` (N11). Il 26/08 il 10% fu registrato come 'verificato sul venue' senza lasciare traccia di come, e non lo era. 🚨 SCOPERTO NELLA STESSA LETTURA, e vale piu' dell'haircut: il modello di margine ATTIVO e' **Segregated: Standard Margin (S:SM)**, NON cross-collateral. Nella tabella del modello attivo l'USDE **non compare**: non fa margine per i perp USDC-settled del book. Percio' l'haircut oggi non si applica affatto — ed e' esattamente il motivo per cui la misura del 31/08 trovava $0,0000 accantonati. Passare a X:SM aggiungerebbe $610,87 di margine utilizzabile: e' una decisione dell'operatore, non un refactor, e porta con se' la meccanica cross (collateral fee 0,05%/giorno sul saldo negativo, ribilanciamento automatico).",
|
||||
"usde": {
|
||||
"index_name": "usde_usdc",
|
||||
"haircut": 0.05,
|
||||
"quota_target": 0.7,
|
||||
"venue_cap_frac": null,
|
||||
"venue_cap_misurato": "2026-09-06: NESSUN tetto fino al 68,7% (fermata dal cuscino di regolamento al 70%, non dal venue): +2.434 USDE in 39 ordini a saldo crescente, zero rifiuti (r0906_usde_tetto_sonda.py). Il 30-31/08 il tetto c'era: [643-644) e [654-655) USDE su ~$2.055 = 31,3-31,9%. Non spiegato: e' SPARITO, non e' scalato. null = nessun tetto noto; usde_convert si affida al chunk+backoff sui rifiuti",
|
||||
"quota_max_frac": 0.85,
|
||||
"depeg_warn": 0.99,
|
||||
"depeg_crit": 0.95
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# 2026-08-25 — Il versamento da $3.000: cosa compra, e cosa NON compra
|
||||
|
||||
**Domanda dell'operatore:** *"rianalizza e vediamo come procedere. potrei versare a breve altri 3k"*.
|
||||
**Script:** `scripts/research/r0825_versamento_3k.py` (lente L3, stessi semi e path del piano:
|
||||
tutto appaiato, M22). Assunzioni dichiarate: 3k = $3.000 USDC, lump oggi, start $2.065 (watermark
|
||||
18:47:13Z). Controllo M23: da $2.065 il 10a riproduce $114.929 contro i $114.934 pubblicati da
|
||||
$2.067 — la macchina riproduce il numero vecchio.
|
||||
|
||||
## Cosa compra (€500/mese, appaiato sugli stessi path)
|
||||
|
||||
| orizzonte | senza lump | con lump | Δ | €/g con lump | P(≥50€/g) |
|
||||
|---|---|---|---|---|---|
|
||||
| 5a | $44.921 | $49.730 | +10,7% | 7,94 | 0% |
|
||||
| 10a | $114.929 | $122.491 | +6,6% | 19,56 | 0% |
|
||||
| 15a | $228.770 | $241.490 | +5,6% | 38,56 | 12,3% → 17,3% |
|
||||
| 20a | $408.105 | $427.738 | +4,8% | 68,30 | 79,4% → 83,9% |
|
||||
|
||||
Il lump = **5,5 mesi di versamenti anticipati in un colpo** (N3: e' la prima leva, esercitata).
|
||||
Pietre miliari con €500/mese: $15k (C\* Hyperliquid) 1,7a → **1,3a**; $20k (si riapre la scelta
|
||||
del venue) 2,4a → **1,9a**.
|
||||
|
||||
## L'effetto collaterale che conta: il gate XSR01 del 23/10 si RIBALTA
|
||||
|
||||
P(capitale ≥$5k al 23/10): **0,0% → 100,0%** (mediana $5.664, p10 $5.417). Senza lump il gate
|
||||
moriva da solo sul criterio di capitale e i suoi tre difetti erano accademici. Col lump il
|
||||
criterio formale **passa**, e la decisione poggerebbe per intero su:
|
||||
(a) il monitor rotto che registra ~41 min/giorno (§5.1); (b) l'haircut col pavimento sbagliato
|
||||
di 5-7× (§5.3); (c) un C\* vero $15-20k contro i $1.266 che il 25% di $5.065 mette sul venue.
|
||||
**Il lump rende il gate LEGGIBILE senza rendere XSR01 comprabile** — il caso peggiore per un
|
||||
criterio scritto male. La scelta fra riscrivere adesso (dichiarandolo) e lasciare-e-citare va
|
||||
presa **prima** che il lump atterri, non il 23/10.
|
||||
|
||||
## Meccanica live a $5.065 — niente da toccare
|
||||
|
||||
cap per-asset $1.032 → $2.532 (equity×0,5, si adegua da solo); min_order $5 dal 0,48% al 0,20%
|
||||
del cap (C2); fallback min($3.000, wm×0,5) = $2.532; disaster-SL invariato (e il nome continua a
|
||||
ingannare). ⚠️ **Slip-audit da rifare** dopo i primi fill: misurato su $597-2.067, a $5k la taglia
|
||||
relativa raddoppia — lo script con normalizzazione per-fill c'e' gia'.
|
||||
@@ -0,0 +1,114 @@
|
||||
# 2026-08-25 — XSR01 sotto la lente della RENDITA: e' un conto deposito con un secondo venue
|
||||
|
||||
**Domanda dell'operatore:** *"rendita perpetua ma non ho ancora capito se serve XSR01"*.
|
||||
**Script:** `scripts/research/r0825_xsr_rendita.py` (N11: ogni numero qui sotto lo riproduce).
|
||||
|
||||
## Perche' era una misura nuova
|
||||
|
||||
XSR01 era stato giudicato tre volte — ammissione (25/07), gate di deploy pre-registrato (23/10),
|
||||
accumulo (oggi) — e **mai sotto la lente della rendita**. Sono criteri diversi: l'accumulo premia
|
||||
il **drift**, la rendita premia la **perpetua** (il prelievo piu' alto che sopravvive a 20 anni con
|
||||
P≥90%). Dichiarato l'obiettivo (N6), il criterio cambia, e nessuno aveva rifatto il conto.
|
||||
|
||||
## Cosa mi aspettavo prima di misurare (M12)
|
||||
|
||||
(a) a iso-nozionale il mix perde drift e **alza** il muro; (b) a iso-rischio (M6) il verdetto puo'
|
||||
**ribaltarsi**; (c) il vincolo che decide non sara' ne' (a) ne' (b) ma il **capitale**.
|
||||
**Tutte e tre vere** — ma (c) e' arrivata con una gamba che non avevo previsto: il null.
|
||||
|
||||
## Le serie, e la finestra in cui esistono entrambe
|
||||
|
||||
| serie | finestra | drift/a | vol/a | Sharpe | maxDD | SE drift |
|
||||
|---|---|---|---|---|---|---|
|
||||
| libro L3 — **tutta** la storia | 2019-03-14 → 2026-08-22 | 15,19% | 11,29% | 1,35 | −12,4% | 5,19% |
|
||||
| libro L3 — finestra comune | 2024-01-01 → 2026-08-22 | 10,96% | 10,14% | 1,08 | −8,6% | 7,97% |
|
||||
| XSR01 — finestra comune | idem (965 g, 2,64 anni) | **3,72%** | 2,34% | 1,59 | −2,5% | 1,46% |
|
||||
|
||||
corr libro↔XSR01 **+0,049**. Finestra comune = **35%** della storia del libro → **il muro calcolato
|
||||
qui NON e' il $313k pubblicato**: si confronta solo con se stesso (base $602.660).
|
||||
📌 Sharpe di XSR01 a oggi, **lente dei gate, sole barre chiuse: 1,56** — il "1,82" pubblicato e' una
|
||||
**terza** lente (XSR-REPRO, 22/08). Si cita sempre la coppia (lente, ultima barra chiusa).
|
||||
|
||||
## (2) iso-nozionale — XSR01 ALZA il muro
|
||||
|
||||
w 5→50%: muro da $604k a $668k (**+0,3% → +10,8%**). Il mix e' piu' liscio ma piu' povero, e per
|
||||
una rendita il drift comanda. **A iso-nozionale XSR01 peggiora l'obiettivo dichiarato.**
|
||||
|
||||
## (3) iso-rischio (M6) — si ribalta, e sale il sospetto
|
||||
|
||||
Ri-scalando ogni mix a `vol(libro)`: w 25% → k **1,32x**, muro **$478.729 (−20,6%)**.
|
||||
⚠️ **L'argmax e' al BORDO** (w=50%, k=1,93x): il muro scende **monotonamente** in w, quindi la
|
||||
griglia non indica un ottimo, dice *"il piu' possibile"* — e cio' che si compra in quella direzione
|
||||
e' il **k**, non XSR01 (M8). Sopra 1,40x il libro **non puo' eseguire** (decisione 23/08, e in
|
||||
`config/live.json` non esiste una chiave di scala). Peso portato avanti: **25%, k 1,32x**.
|
||||
|
||||
## (3b) IL NULL — ed e' il risultato della sessione
|
||||
|
||||
A iso-vol vale `drift = Sharpe × vol_libro`: la colonna "drift" della sezione (3) **e' la colonna
|
||||
"Sharpe" ri-etichettata**. Tutto il guadagno di muro e' guadagno di **Sharpe da diversificazione**.
|
||||
Quattro sostituti, stesso peso 25%, stessa ri-scalatura:
|
||||
|
||||
| sostituto | muro | Δ vs base |
|
||||
|---|---|---|
|
||||
| **XSR01 (vero)** | **$478.729** | **−20,6%** |
|
||||
| N1 mescolato — stessi rendimenti, ordine casuale (3 semi) | $475.933 / $483.846 / $484.421 | −21,0 / −19,7 / −19,6% |
|
||||
| N2 rumore gaussiano, media e vol di XSR01 (3 semi) | $457.751 / $499.263 / $531.158 | −24,0 / −17,2 / −11,9% |
|
||||
| N3 rumore, **drift ZERO** (3 semi) | $558.080 / $617.270 / $662.436 | −7,4 / **+2,4** / **+9,9%** |
|
||||
| **N4 conto remunerato 3,72%, vol 0** | **$477.607** | **−20,8%** |
|
||||
| N4 conto remunerato 4,0%, vol 0 | $469.353 | −22,1% |
|
||||
|
||||
📌 **Il MECCANISMO di XSR01 vale 0,8% di muro** — dentro la risoluzione Monte Carlo (spread fra semi
|
||||
0,151% di perpetua ≈ $17k di muro, M23). **Mescolare i suoi rendimenti non cambia niente.**
|
||||
📌 **N3 (drift zero) non paga**: mediana $617k **sopra** la base. La sola riduzione di varianza non
|
||||
compra rendita. Quindi **cio' che si compra e' il DRIFT scorrelato**, non la scorrelazione.
|
||||
🚨 **N4 e' la riga che risponde alla domanda.** Un **conto remunerato** allo stesso tasso (3,72%),
|
||||
**vol zero, corr zero, nessun secondo venue**, da' $477.607 contro i $478.729 di XSR01: **identici
|
||||
dentro la risoluzione**. Al 4,0% e' **meglio**. Ed e' un confronto **in svantaggio per il conto**:
|
||||
la lente L3 gli applica il **33%** di `c-sexies`, mentre un BOT paga 12,5% e un deposito 26%.
|
||||
|
||||
## (4)-(5) gate e risoluzione
|
||||
|
||||
`weights_tilt_null` (XSR01 25% vs 0%): Δ_is +0,061 · Δ_hold +0,162 · pctl 25,8 < 90,0 → **PASS**.
|
||||
⚠️ hold-out del gate = 2025-01-01 → la gamba in-sample e' **un solo anno**: gira, ma e' un indizio.
|
||||
Differenza appaiata di drift **+1,16%/anno, SE 0,48% → t +2,40**. Drift XSR01 **+3,72%, SE 1,46%,
|
||||
t +2,55**. Risolto, ma di poco, e su 2,64 anni.
|
||||
|
||||
## (6) il vincolo che non e' nelle tabelle
|
||||
|
||||
XSR01 gira su **Hyperliquid**: il suo peso e' capitale che **esce** da Deribit, non che si aggiunge.
|
||||
A $2.067 il 25% sono **$517**, contro un **C\* misurato di $15.000-20.000** (il gate 23/10 e' tarato
|
||||
su ~$3.000, §5.3). Per un 25% sopra C\* servono **$60.000 sul conto totale**.
|
||||
E l'haircut mangia **proprio la cosa che il null dice di star comprando**:
|
||||
|
||||
| haircut | netto/anno | vs conto 4,0% | vs conto 2,0% |
|
||||
|---|---|---|---|
|
||||
| 0% | 3,72% | SOTTO | sopra |
|
||||
| 20% | 2,97% | SOTTO | sopra |
|
||||
| 40% (soglia del gate) | 2,23% | SOTTO | sopra |
|
||||
| 60% | 1,49% | SOTTO | **SOTTO** |
|
||||
|
||||
**Pareggio con un conto al 2%: haircut 46%. Con un conto al 4%: non pareggia nemmeno a haircut ZERO.**
|
||||
E l'haircut pubblicato ($14,41 di ticket) e' **~2,4x ottimista e senza script che lo riproduca**.
|
||||
|
||||
## VERDETTO
|
||||
|
||||
**4/6 condizioni** — ma il conteggio non e' la lettura. La lettura e':
|
||||
|
||||
> Sotto la lente **rendita perpetua**, XSR01 fa una cosa reale — porta un **drift scorrelato a bassa
|
||||
> vol** — e la fa in modo **non proprietario**: mescolarlo non cambia il risultato, e un conto
|
||||
> remunerato allo stesso tasso lo **eguaglia a vol zero e senza un secondo venue**.
|
||||
> Il vincolo binding non e' il rendimento: e' il **capitale** ($60.000 per un 25% sopra C\*), e sotto
|
||||
> quella soglia la domanda *"serve XSR01?"* **non ha una risposta comprabile**.
|
||||
|
||||
**Cosa NON dice.** Non dice che XSR01 e' morto: lo Sharpe standalone 1,56 e il DSR 0,983 reggono, e
|
||||
sotto la lente **accumulo** il conto va rifatto (li' il drift comanda in modo diverso). Dice che
|
||||
**per la rendita non e' lo strumento**, e che il gate del 23/10 sta per decidere su una gamba che a
|
||||
questo capitale non e' comprabile.
|
||||
|
||||
## Cosa e' cambiato in me mentre misuravo
|
||||
|
||||
Ho copiato in questo script **lo stesso difetto che avevo riparato stamattina** in
|
||||
`r0825_capitale_fermo.py`: la maschera dei giorni flat presa dalla serie **de-luckata**, dove gli
|
||||
zeri esatti non esistono piu' perche' `deluck` sottrae una costante. Stampava `0.0%` e non si sarebbe
|
||||
notato senza la riga che conta i giorni. Riparata con l'`assert` che manca(va) a monte: la maschera
|
||||
si prende dalla **serie grezza**. *Riparare un difetto in un file non lo ripara nella testa.*
|
||||
@@ -0,0 +1,95 @@
|
||||
# 2026-08-26 — `advance()` riparato e rigenerato, quattro debiti chiusi in un giorno
|
||||
|
||||
Quattro debiti di CLAUDE.md §5 chiusi: **§5.1** (advance + rigenerazione + guardia),
|
||||
**§5.12** (isolamento test esteso), **§5.5** (test di leva cancellato con nota),
|
||||
**§5.9** (test rosso indagato e ancorato). Suite: **751 passati, 0 falliti** — tutta verde
|
||||
per la prima volta dal ~24/08.
|
||||
|
||||
## §5.1 — advance() consuma solo barre CHIUSE, e le serie sono RIGENERATE
|
||||
|
||||
**Il fix** (`src/live/paper_guard.py` + 6 monitor): la riga
|
||||
`new = [i for i in range(len(ts)) if ts[i] > last_ts]` diventa `PG.nuove_chiuse(ts, last_ts,
|
||||
cadenza)` — una barra open-labeled e' chiusa quando `ts + cadenza <= adesso`. Filtro condiviso,
|
||||
importato da tutti e sei i monitor (anche `paper_combo`, che era sano *per caso*: ora e' sano
|
||||
*per costruzione*).
|
||||
|
||||
⚠️ **D6 pagata DI NUOVO scrivendo il fix:** la prima stesura di `indice_chiuso` usava `asi8`
|
||||
assumendo nanosecondi — pandas 3 usa `us` di default, e l'intero nella scala sbagliata dava
|
||||
"chiusa" la barra del giorno in corso **senza eccezioni**. Beccata dal test di fumo, riparata
|
||||
con la sottrazione dall'epoca (la forma di `resample_tf`), e blindata da un test che prova
|
||||
`ns/us/s`. *La regola che stavo cablando e' quella che stavo violando.*
|
||||
|
||||
**La guardia** (era "raccomandata e non cablata"): `monitor_health` ha ora lo stato
|
||||
**PREMATURO** — ultima barra che chiude DOPO l'mtime del file, grazia 5 min. Derivata dalle
|
||||
spec esistenti (P1), con `open_labeled=False` per `collect_chain` che timbra i giri, non barre
|
||||
(P14: senza il flag avrebbe allertato sempre). Controllo positivo sul dato vivo: **ha segnalato
|
||||
esattamente i 5 rotti e taciuto sui 2 sani** prima della rigenerazione; dopo, 7/7 OK.
|
||||
|
||||
**La rigenerazione** (`scripts/live/paper_regen.py`): stesso `start_ts` pre-registrato, config
|
||||
congelata, replay del codice di produzione riparato — il metodo validato due volte dall'audit
|
||||
del 22/08 (0 barre chiuse cambiate su 1.427/1.488 + 6/6 al bit). Vecchi file archiviati come
|
||||
`*.pre_regen_20260826.*` (evidenza del difetto, nel perimetro di backup). **Nessuna data di
|
||||
gate si sposta.**
|
||||
|
||||
| monitor | barre | Sharpe rotto → vero |
|
||||
|---|---|---|
|
||||
| `paper_statarb` (gate **27/09**) | 58 → 57 | **+1,95 → −1,61** — il ribaltamento previsto dall'audit |
|
||||
| `paper_dvolspread` (kill 24/10) | 32 → 31 | −14,73 → −4,41 |
|
||||
| `paper_xsr` (gate 23/10) | 32 → 31 | −4,98 → −2,72 |
|
||||
| `paper_prevday` | 1584 → 1584 | +0,39 → +0,40 |
|
||||
|
||||
`paper_portfolio` **non** rigenerato (gamba GTAA su ADJUSTED_LAST ri-aggiustato: il replay non
|
||||
sarebbe la serie registrata — P12); tolta la sola coda non chiusa, cosi' la guardia non resta
|
||||
accesa per sempre su un difetto gia' riparato (P14). Storia pre-fix dichiarata corrotta.
|
||||
|
||||
## §5.12 — un test non puo' piu' scrivere nel libro di bordo vivo
|
||||
|
||||
`tests/conftest.py`: oltre al watermark, ora deviati **`trades.db`** (wrapper su `connect` —
|
||||
il default e' catturato alla definizione, patchare la costante non serviva a niente) e
|
||||
**`docs/journal/`** (pagine tracciate da git). Per `book_executions.jsonl`, che i test caricano
|
||||
ad-hoc con importlib e conftest non puo' raggiungere prima: **impronta a inizio suite, verifica
|
||||
a fine suite**, col falso positivo possibile (fill reale del cron :47 durante la suite)
|
||||
dichiarato nel messaggio.
|
||||
|
||||
## §5.5 — il test di leva e' CANCELLATO, non aggiustato
|
||||
|
||||
`test_leva_massima_da_config_resta_sotto_o_uguale_a_1x` misurava `frac x n_asset`: con una
|
||||
chiave di scala la grandezza vera diventerebbe `frac x n_asset x scala` e il test avrebbe
|
||||
continuato a passare smettendo di controllare. Al suo posto una nota che rimanda al tetto sul
|
||||
PRODOTTO di GATE SCALA-01, da cablare nel codice che leggera' la chiave, quando esistera'.
|
||||
|
||||
## §5.9 — il test rosso non segnalava un guasto: segnalava una banda scritta male
|
||||
|
||||
`r0826_skh_band_drift.py` — feed tagliato a date crescenti, stesso codice di produzione:
|
||||
|
||||
| taglio | Sharpe hold-out |
|
||||
|---|---|
|
||||
| 02/07 (audit) | **1,6376** — riproduce l'audit |
|
||||
| 25/07 (banda cablata) | 1,5751 |
|
||||
| 01→15/08 | 1,566 → 1,547 (scivola piano) |
|
||||
| **22/08** | **1,8966** |
|
||||
| oggi | 1,9306 — riproduce il rosso |
|
||||
|
||||
**Il dato regge** (in-sample 1,424 **identico su ogni taglio**; il taglio all'epoca riproduce
|
||||
l'epoca). Il codice non c'entra. 📌 **La scoperta vera: la SOLA settimana 15-22/08 (rally ETH
|
||||
con SKH01 long) vale +0,35 di Sharpe hold-out** — il numero e' cosi' fragile, e chi cita
|
||||
"hold-out 1,9" oggi citerebbe 1,55 di una settimana fa. Coerente con §2: l'hold-out canonico
|
||||
di SKH01 era gia' il numero da non citare.
|
||||
|
||||
**La riparazione:** il test ora **taglia il feed al 02/07** e verifica la riproduzione stretta
|
||||
(±0,02) di full/in-sample/hold-out/maxDD. Si rompe se cambiano codice o dato sulla finestra
|
||||
congelata — e per nessun altro motivo. La deriva del vivo non e' affare suo: per quella ci
|
||||
sono i monitor (e da oggi la guardia PREMATURO).
|
||||
|
||||
Nel farlo, un altro inciampo istruttivo: la prima esecuzione del drift stampava **sette righe
|
||||
identiche** — `run_asset` memoizza in un dict di modulo (`_CACHE`), non in `lru_cache`, e lo
|
||||
svuotamento non lo toccava. Anche il test riparato ora pulisce quel dict, prima E dopo, per
|
||||
non avvelenare gli altri test.
|
||||
|
||||
## Conseguenze sui gate (nessuna data si muove)
|
||||
|
||||
- **STATARB 27/09**: il monitor ora misura la serie vera (**−1,61** a oggi). Coi criteri
|
||||
pre-registrati invariati, a oggi punta al RITIRO — si legge quel giorno, non prima.
|
||||
- **XSR01 23/10 / DVOLSPREAD 24/10**: metrica primaria finalmente su serie reali.
|
||||
Restano i difetti dichiarati (§5.3 pavimento del gate XSR01; veto DVOLSPREAD ora sorvegliato
|
||||
dalla guardia).
|
||||
@@ -0,0 +1,58 @@
|
||||
# 2026-08-26 — USDC Rewards su Deribit: il prodotto esiste, il conto NON li riceve
|
||||
|
||||
**Domanda dell'operatore:** *"con 5k non si possono far partire altre strategie/investimenti?"*
|
||||
→ unica pista compatibile con "100% Deribit fino a $20k": rendimento sull'USDC fermo (il libro
|
||||
e' flat il 75% dei giorni del 2026, e la misura del 25/08 dice che un 3,7-4% su quel capitale
|
||||
vale quanto tutto XSR01).
|
||||
|
||||
## Il prodotto (verificato SUL VENUE prima che sulle fonti, N10)
|
||||
|
||||
- API pubblica `public/get_currencies`, 2026-08-26: **USDC `apr: 3.4`** · USDE `apr: 4.0` ·
|
||||
USYC nel pool cross-collateral · haircut cross-collateral USDC **0%** → il saldo matura
|
||||
MENTRE fa da margine, nessun lock.
|
||||
- Fonti (Deribit Insights): programma "USDC Rewards" dal 15/07/2025 — Deribit custodisce
|
||||
presso Coinbase, Coinbase paga, Deribit gira agli **utenti di giurisdizioni autorizzate**
|
||||
(lista non pubblicata). Maturazione sul **minimo di equity USDC delle 24h** (00:00 UTC),
|
||||
pagamento mensile in unica soluzione nei primi ~14 giorni del mese successivo. Tasso
|
||||
variabile (4% a lug 2025, 3,4% oggi).
|
||||
|
||||
## La verifica empirica: il conto NON li riceve
|
||||
|
||||
L'operatore era da cellulare → verifica indiretta sulla NOSTRA serie equity oraria
|
||||
(`data/live/trades.db`), che su libro flat e' piatta salvo accrediti:
|
||||
|
||||
| finestra | letture | equity | salti orari ≥$0,40 | reward atteso |
|
||||
|---|---|---|---|---|
|
||||
| 01-14 lug (paga giugno) | 326 | $598,06 → $598,49 | 1 (= il fill ETH del 14/07) | ~$0,65 — **assente** |
|
||||
| 01-14 ago (paga luglio) | 334 | $596,92 → $597,12 | **0** | **~$1,70-2,00 — assente** |
|
||||
|
||||
Un accredito da $2 su una serie che si muove di $0,20 in due settimane sarebbe stato
|
||||
inequivocabile. **Due mesi indipendenti, stesso esito: nessun reward.**
|
||||
|
||||
## VERDETTO (stesso giorno, poche ore dopo): l'Italia e' ESCLUSA PER LEGGE — pista chiusa
|
||||
|
||||
La causa non era il conto ma **MiCA**: il regolamento UE vieta la remunerazione dei token di
|
||||
moneta elettronica, e **Coinbase — che e' chi paga i reward del programma Deribit — ha cessato
|
||||
i reward USDC in tutta l'EEA dal 2024-12-01** proprio per questo. Conferme indipendenti:
|
||||
(a) l'espansione Deribit del 2026-08-01 aggiunge **80 paesi, nessuno EEA**; (b) il transaction
|
||||
log del conto non ha MAI avuto una voce reward (misurato stamattina su due mesi). **Nessun
|
||||
ticket necessario: la risposta e' normativa, non di supporto.** Cade anche la domanda fiscale.
|
||||
|
||||
⚠️ **Episodio di sicurezza, registrato perche' non si ripeta.** L'operatore ha chiesto dei
|
||||
reward in una chat dove due sedicenti agenti ("Matt Andreas [MOD]", "Tom Mavencourt") hanno
|
||||
risposto a copione — stessa domanda fuori tema due volte, scuse per un "guasto" mai segnalato,
|
||||
l'avviso "beware of impersonators" usato per comprarsi fiducia — e l'affermazione
|
||||
**"Yes, Italy is supported for USDC rewards": FALSA** (un agente Deribit vero sa che l'EEA e'
|
||||
esclusa). Pattern coerente con lo script dei wallet-drainer: trattenere in chat, poi chiedere
|
||||
"verifica" (link/2FA/chiavi API/schermo). Regola permanente: il supporto passa SOLO
|
||||
dall'app ufficiale o da support.deribit.com; nessun codice, chiave o link, mai; una
|
||||
risoluzione vera non richiede azioni dell'utente.
|
||||
|
||||
## Cosa resta della pista "rendimento sul capitale fermo"
|
||||
|
||||
- **USDC rewards: morta** per residenti EEA finche' MiCA resta cosi'.
|
||||
- **USDE (Ethena, apr 4.0 dall'API)** come collaterale: non e' un EMT, MiCA non lo vieta allo
|
||||
stesso modo — ma ha rischio proprio (depeg, basis-trade, Ethena GmbH chiusa dopo BaFin).
|
||||
Se si apre, va da candidato: misura + gate + verifica C6 sul NOSTRO conto.
|
||||
- **USYC**: probabile riservato a istituzionali — da verificare prima di parlarne.
|
||||
- Altrimenti vale N3: a questo capitale la leva vera restano i versamenti.
|
||||
@@ -0,0 +1,105 @@
|
||||
# 2026-08-26 — USDE come collaterale a rendimento: analisi da candidato
|
||||
|
||||
**Domanda dell'operatore:** *"analizziamo usde"* — dopo la chiusura della pista USDC rewards
|
||||
(Italia esclusa da MiCA). **Script:** `scripts/research/r0826_usde_scenari.py` (N11).
|
||||
|
||||
## Verificato SUL VENUE (API pubblica, oggi — N10)
|
||||
|
||||
- `apr: 4.0` per USDE (era "fino a 9%" al lancio 03/2025: tasso = APR settimanale Ethena − 5%
|
||||
di fee Deribit; comprime coi funding) · reward **giornalieri** ~12:00 UTC sul minimo di
|
||||
equity USDE, voce nel Transaction Log → **falsificazione in 24-72h**
|
||||
- spot `USDE_USDC` attivo: spread osservato **~3 bps**, fee spot **zero**
|
||||
- **haircut cross-collateral 10%** (USDC 0%): il 90% fa margine — al nostro profilo di leva
|
||||
(max realizzata 0,52x su tetto 1,0x) **non morde**
|
||||
- **indice `usde_usd` multi-exchange con mediana e clamp ±0,5%** — NON il book interno:
|
||||
è la differenza strutturale dal caso Binance del 10/10/2025 ($0,65 dall'oracle sul proprio
|
||||
book da $8M mentre USDe quotava ~$0,99 altrove, riscatti regolari, peg tornato in 8h)
|
||||
- eligibilità **per residenza, lista non pubblica** → si verifica sul CONTO, non sui documenti
|
||||
(la lezione USDC di stamattina)
|
||||
|
||||
## Rischi dichiarati (p non stimabile → si controlla con la QUOTA, N4)
|
||||
|
||||
R1 **emittente**: basis trade (staking + short perp); fallimento = fino a −100% della quota —
|
||||
si SOMMA al rischio venue accettato il 26/07, non lo sostituisce. R2 **marcatura in crash**
|
||||
(P10: l'accoppiamento — il mark scende proprio quando il libro è lungo). R3 **tasso**: 9→4%
|
||||
in 17 mesi; funding negativi prolungati → ~0. R4 **regolatorio EU** (Ethena GmbH liquidata
|
||||
dopo BaFin; USDe non è EMT MiCA — per questo PUÒ pagare dove USDC non può).
|
||||
📌 Nota a favore da misurare se si procede: il rendimento USDe sale coi **funding positivi**,
|
||||
cioè quando il nostro libro long li PAGA (−2,16%/anno): sconto parziale correlato sulla tassa
|
||||
di funding.
|
||||
|
||||
## L'aritmetica (dallo script)
|
||||
|
||||
| equity | quota 50% rende | −100% costa | note |
|
||||
|---|---|---|---|
|
||||
| $2.065 | $41/anno | $1.032 | conversione ripagata in ~3 giorni |
|
||||
| $5.065 | $101/anno | $2.532 | scenario post-versamento |
|
||||
| $20.000 | $400/anno | $10.000 | dove la posta diventa seria |
|
||||
|
||||
Mark −1% = 3 mesi di resa · −3,5% = 11 mesi · **−100% = 25 anni: non si recupera mai**
|
||||
→ la leva di controllo è la **quota**, non il tasso.
|
||||
|
||||
## PROPOSTA (pre-registrata qui, esito da scrivere sotto)
|
||||
|
||||
**Test di eligibilità sul conto**: convertire **~$500 USDC→USDE** (costo <$0,20 totale),
|
||||
tenere **3 giorni**, guardare il Transaction Log alle ~12:00 UTC (reward atteso ~$0,05/g).
|
||||
Regola dichiarata PRIMA di vedere l'esito: **≥1 voce reward in 3 giorni → idoneo**, e si apre
|
||||
la decisione di quota (con un gate: quota massima, chi la decide, cosa la riapre);
|
||||
**0 voci → non idoneo**, si riconverte e la pista si chiude come l'USDC.
|
||||
L'esecuzione è dell'operatore (app: Spot → USDE/USDC) o autorizzata esplicitamente via gateway.
|
||||
Il libro non c'entra: nessun codice del percorso soldi viene toccato dal test.
|
||||
|
||||
## ESECUZIONE DEL TEST (stesso giorno, 13:04 UTC — autorizzata dall'operatore: "fai test")
|
||||
|
||||
Ordine via gateway, limit marcabile con banda di sicurezza sul prezzo (0,995-1,005):
|
||||
**500 USDE @ 1,0003, filled, fee 0** (`USDE_USDC-8977542793`). Costo totale ~$0,15.
|
||||
Prima finestra utile di reward: ~**28/08 12:00 UTC** (il calcolo usa il minimo di equity
|
||||
USDE della finestra); verifica finale il **29/08**: ≥1 voce reward nel Transaction Log →
|
||||
idoneo; 0 voci → si riconverte e la pista si chiude.
|
||||
|
||||
🚨 **DIFETTO TROVATO E RIPARATO DURANTE IL TEST — il test valeva gia' per questo.**
|
||||
`shadow._equity` leggeva SOLO il conto USDC: dopo la conversione il book avrebbe visto
|
||||
$1.556 (−24,3%) al giro delle 13:47 → falso 💰 "USCITA DI FONDI" **e vendita indesiderata
|
||||
di ~$140 di posizioni** (i target scalano con l'equity). Riparato in 35 minuti, prima del
|
||||
giro: `_collaterale_usde()` valuta l'USDE all'**indice pubblico** `usde_usdc` (mediana
|
||||
multi-exchange — la lezione Binance 10/10), con tre proprieta' blindate da 6 test nuovi
|
||||
(`tests/test_shadow_usde.py`): un **depeg PASSA nel sizing** (mai nascosto), sopra la pari
|
||||
si **clampa a 1,0**, indice illeggibile → **1,0 dichiarato, mai 0** (contare 0 ricreerebbe
|
||||
il falso −24%: il fallback si sceglie sul danno, P5). Verificato live: equity vista dal
|
||||
book $2.055,88 ("mainnet USDC + USDE 500 @ 0.9999").
|
||||
*Un test da $500 progettato per misurare l'eligibilita' ha scoperto che il book era cieco
|
||||
al cross-collateral: se la prima conversione fosse stata fatta a quota vera, l'errore
|
||||
sarebbe costato caro. I test piccoli esistono per questo.*
|
||||
|
||||
*Esito eligibilita': (da scrivere entro il 29/08)*
|
||||
|
||||
## STRUTTURA (stesso giorno, ~14:20 UTC — "struttura il progetto per gestire subito usde")
|
||||
|
||||
La patch d'emergenza delle 13:07 e' diventata struttura di prima classe:
|
||||
|
||||
- **`config/live.json` sezione `usde`** — l'unica autorita' sui parametri (P1): `index_name`,
|
||||
`haircut` 0.10, `quota_max_frac` 0.50 (tetto di ALLERTA — la quota effettiva la decide
|
||||
l'operatore), `depeg_warn` 0.99 / `depeg_crit` 0.95 coi **criteri dichiarati** in `_nota_usde`
|
||||
(P6: warn = fuori dalla banda spot ~3 bps e oltre il clamp ±0,5% per fonte dell'indice;
|
||||
crit = meta' del buffer di haircut consumata).
|
||||
- **`src/live/usde.py`** — modulo unico per config, catena di prezzo (indice pubblico → ticker
|
||||
spot → 1,0 dichiarato) e valutazione `valuta()` PURA. `shadow._collaterale_usde` ora DERIVA
|
||||
da qui (niente ridichiarazioni); i 6 test di shadow passano invariati.
|
||||
- **`scripts/live/usde_watch.py`** + `cron_usde.sh` alle **12:35 UTC** (dopo la finestra reward
|
||||
~12:00, fuori da :00/:25/:47) — sola lettura: (1) **reward per DELTA di equity al netto dei
|
||||
trade spot** (il gateway non espone il Transaction Log; un delta con trade nel mezzo si
|
||||
dichiara, trade illeggibili → delta NON attribuito, P12); (2) **depeg**: 🚨 solo sotto crit
|
||||
e ripetuto, ⚠️ warn/quota/BLIND solo alla transizione (P9); (3) **quota sul totale**, che
|
||||
allerta anche per deriva PASSIVA (il libro perde → la quota sale da sola, N4). Applica il
|
||||
**verdetto pre-registrato** (≥1 reward entro il 29/08 → IDONEO; zero → riconvertire) invece
|
||||
di lasciarlo a un promemoria. Limite dichiarato: cadenza giornaliera, un depeg intraday puo'
|
||||
sfuggire all'ALLERTA — ma non al SIZING, che legge l'indice a ogni giro orario del book.
|
||||
- **Serie `data/live/usde_watch.jsonl`** — dentro il perimetro di backup, `ts` in ms epoch →
|
||||
sorvegliata da **`monitor_health`** (max_age 30h, `open_labeled=False`): un watch fermo
|
||||
spegnerebbe gli allarmi depeg e il silenzio si leggerebbe come "va tutto bene" (P5).
|
||||
- **Baseline registrata alle 14:17:52Z**: 500 USDE @ 1,0000 (indice) = $500,00 · quota 24,3%
|
||||
(tetto 50%) · margine utilizzabile ~$2.008 · verdetto IN_ATTESA. E' la lettura che rende
|
||||
misurabile il delta del 28/08.
|
||||
- **Test: 775 passati** (+17 nuovi in `tests/test_usde_watch.py`: valutazione condivisa,
|
||||
rilevatore reward, verdetto — incluso che NON anticipa la scadenza del 29/08 —, condizioni
|
||||
di allerta, `px=None` che non finge un depeg).
|
||||
@@ -0,0 +1,44 @@
|
||||
# 2026-08-26 — Gate XSR01 (23/10): riscrittura DICHIARATA, decisa dall'operatore
|
||||
|
||||
**Decisione dell'operatore** (oggi, discussione in sessione): opzione **A** fra le tre proposte —
|
||||
riscrivere ora il gate, dichiarandolo, invece di lasciarlo e citare i difetti (B) o ritirare
|
||||
XSR01 in anticipo (C, che avrebbe violato la pre-registrazione).
|
||||
|
||||
Il docstring del gate prescrive: *"ogni modifica va motivata nel diario come violazione"*.
|
||||
Questa e' quella motivazione. **Scritta a 58 giorni dalla decisione, prima di vederne l'esito,
|
||||
e in direzione che STRINGE i criteri** — le tre proprieta' che rendono una riscrittura
|
||||
difendibile invece che selection-on-forward.
|
||||
|
||||
## Le tre modifiche
|
||||
|
||||
1. **Capitale per il deploy: $5.000 → $20.000.** Non e' tuning: il gate fu scritto il 25/07,
|
||||
e il 26/07 l'operatore ha preso la decisione vincolante *"100% Deribit fino a $20k"* —
|
||||
posteriore, quindi governa (M28: quando due impegni si contraddicono, la contraddizione e'
|
||||
informazione). Il criterio a $5k autorizzava capitale su Hyperliquid che un'altra decisione
|
||||
gia' vietava. Ora il deploy di XSR01 e la riapertura della scelta del venue coincidono a
|
||||
$20k: **una decisione sola, un punto solo**.
|
||||
2. **Gamba haircut: il numero decisivo viene da uno script committato** —
|
||||
`r0826_xsr_haircut.py` (N11: il "$14,41" dell'ammissione non aveva script ed era ~2,4x
|
||||
ottimista), **a pavimento $10** (il pavimento vero misurato da HL-EXEC; il libro REAL-$5000
|
||||
del monitor gira a $5 ed e' ottimista per costruzione), **citato accanto alla frazione di
|
||||
ordini eseguiti** (P7). Misurato oggi, ed e' la conferma empirica del difetto dello
|
||||
strumento: sul FULL a pavimento $10 l'haircut e' **−1,1%** con **il 22% di ordini
|
||||
eseguiti** — la guardia non puo' quasi scattare per costruzione, perche' un libro che non
|
||||
si muove ha haircut piccolo. **La soglia resta il 40% pre-registrato: nessuna soglia nuova
|
||||
viene scritta oggi coi dati in mano.**
|
||||
3. **Contesto corretto:** il riferimento `IS_SHARPE_NET = 1.82` era una terza lente
|
||||
(XSR-REPRO): ora cita **1,79**, lente dei gate alla scoperta. Il criterio non cambia.
|
||||
|
||||
## I numeri di oggi (58 giorni alla decisione — NON decidono niente)
|
||||
|
||||
- forward rigenerato (serie vera dal 26/08): **31 barre, Sharpe −2,72**, maxDD 0,8%
|
||||
- ticket per gamba a $5.000: mediano **$3,34**, medio $5,99, 63% sotto $5, **84% sotto $10**
|
||||
(46.200 ordini replay) — riproduce XSR-REPRO e sostituisce il $14,41
|
||||
- haircut forward a pavimento $10: −10,7% (dentro la guardia), **ordini eseguiti 38%**
|
||||
- FULL 968 barre: MODELED Sharpe 2,12; floor $10 → haircut −1,1%, eseguiti **22%**
|
||||
|
||||
## Cosa riaprirebbe XSR01 (registrato qui perche' non vada riscoperto)
|
||||
|
||||
Capitale ≥$60k (per un 25% sopra C\*) · un venue con C\* più basso di $15-20k · una misura che
|
||||
mostri nel meccanismo qualcosa oltre la coppia (media, vol) — il null del 25/08
|
||||
(`r0825_xsr_rendita.py`) ha detto che oggi non c'e'.
|
||||
@@ -0,0 +1,158 @@
|
||||
# 2026-08-28 — La fase che ruota, gli allarmi che si perdevano, e una risposta fiscale
|
||||
|
||||
*Scritto il 2026-08-28. Numeri riprodotti dagli script citati; le letture in prosa sono firmate
|
||||
come tali (P13).*
|
||||
|
||||
## In una riga
|
||||
|
||||
Un test rosso su GTAA01 non parlava di GTAA01: parlava del fatto che **la finestra "congelata"
|
||||
pre-2015 rotola da sola ogni notte**. Nel frattempo si e' chiuso il debito del trasporto degli
|
||||
allarmi, e la prima delle due domande al commercialista ha trovato una risposta alla fonte.
|
||||
|
||||
---
|
||||
|
||||
## 1. Il gate (A) della banda GTAA01 e' una moneta — la finestra rotola
|
||||
|
||||
`r0828_gtaa_band_phase.py`. Replica bit-exact della produzione verificata (max|Δ| = 0.00e+00 su
|
||||
7.544 barre). Codice fermo dal 07/08: nessun commit su `src/portfolio/gtaa.py` ne' su
|
||||
`r0727_gtaa_band_gate.py`. Si e' mosso il DATO.
|
||||
|
||||
**(0) La finestra in-sample non e' ferma.** IB serve una finestra **rotolante di 30 anni**. SPY e'
|
||||
l'unica gamba al muro (30,0 anni) e nel `logs/cron_daily.log` la sua data d'inizio avanza di un
|
||||
giorno di borsa ogni notte: `1996-07-02` il 24/06 → `1996-09-04` oggi. QQQ e IWM arriveranno allo
|
||||
stesso muro fra **2,5 e 3,7 anni**.
|
||||
|
||||
**(1) L'amplificatore e' la fase.** `_gated_returns` ribilancia su `i % every == 0`, dove `i` e' la
|
||||
**posizione nell'array**. Se la prima barra scivola, si ri-fasa ogni decisione di ribilanciamento
|
||||
di trent'anni di storia.
|
||||
|
||||
| misura | valore |
|
||||
|---|---|
|
||||
| spostamento max dello Sharpe in-sample dal 07/08 (finestra CHIUSA, codice fermo) | **0,076** |
|
||||
| banda scelta al buio in 10 notti consecutive | 25% · 40% · 60% |
|
||||
| notti col margine sotto il pavimento dichiarato (0,01) | **4/10** (minimo 0,0026) |
|
||||
| a dato FISSO, muovendo solo la fase | 25% su 3 fasi, 60% su 2 |
|
||||
|
||||
Il criterio decideva su ~0,007-0,015 di Sharpe mentre il rumore che lo scuote vale fino a 0,076.
|
||||
E' lo stesso modo di fallire del criterio a ranghi ritirato il 07/08 (che decideva su 0,00116), in
|
||||
un costume nuovo.
|
||||
|
||||
## 2. Criterio (C): la mediana delle 5 fasi
|
||||
|
||||
`r0828_gtaa_band_median_phase.py`. ⚠️ **Scelto DOPO aver visto la tabella delle fasi**: misura lo
|
||||
strumento, non valida la banda. Pavimento tenuto a 0,01, invariato.
|
||||
|
||||
- (i) scelta stabile su 10 notti: **SI** — `60%` in tutte e dieci
|
||||
- (ii) margine sempre ≥ 0,01: **SI** — minimo 0,0197
|
||||
- (iii) potenza (al buio ≠ hold-out): **SI** — 60% contro 40%
|
||||
|
||||
Escursione dello Sharpe in-sample su 10 notti: **−74/−99%** per ogni banda. Lo strumento regge.
|
||||
**Ma la cella che sceglie non e' la proposta:** al buio esce il 60%, il 25% vince **0 notti su 10**.
|
||||
Da tenere accanto: sull'hold-out il 60% e' **penultimo** (0,7430 contro 0,9315 del 40%), coerente
|
||||
con lo Spearman IS/OOS ~0 misurato il 07/08 — la scelta in-sample **non predice**.
|
||||
|
||||
**Contorno che vale piu' del verdetto:** le 5 serie di fase correlano **0,988** → **N_eff 1,01**.
|
||||
La mediana di 5 fasi e' UNA osservazione: toglie l'artefatto, non compra precisione.
|
||||
|
||||
### Le due domande che erano state confuse
|
||||
|
||||
Il gate (A) puo' chiedere due cose diverse, e il 07/08 coincidevano **per fortuna di fase**:
|
||||
|
||||
| forma | difetto | quanto e' grave |
|
||||
|---|---|---|
|
||||
| «la banda e' la scelta che si fa al buio?» | **non ha risoluzione** | cambia verdetto ogni notte |
|
||||
| «la banda non e' stata selezionata sull'hold-out?» | **non ha potenza** | 1 cella su 30 e' l'argmax dell'hold-out → **29/30 lo passano per costruzione** |
|
||||
|
||||
📌 *Lettura (agente, fallibile).* Il 25% non viene dalla griglia affatto: viene dall'argomento
|
||||
**strutturale** del 27/07 — una banda in dollari assoluti degenera al variare del capitale, una
|
||||
frazione no. Sotto la mediana delle fasi il 25% e' **2° in-sample e 3° sull'hold-out**: decente
|
||||
ovunque, primo da nessuna parte, che e' la firma di un parametro NON selezionato sui dati. A un
|
||||
parametro scelto per ragioni strutturali non si applica un gate di *selezione* ma uno di
|
||||
*robustezza* — «fa danno da qualche parte?». Risposta: no.
|
||||
|
||||
**Esito:** gate (A) **ritirato** come pass/fail, congelando il MOTIVO (come il 07/08), piu' una
|
||||
guardia sulla CAUSA che si rompe il giorno che la fase venisse ancorata al calendario.
|
||||
|
||||
## 3. Cosa cambia togliendo GTAA01 — e la decisione dell'operatore
|
||||
|
||||
`r0828_senza_gtaa.py`. Portafoglio di **ricerca** a 5 sleeve; NON il book live.
|
||||
|
||||
| | Sharpe | CAGR | maxDD | vol |
|
||||
|---|---|---|---|---|
|
||||
| CON GTAA01 | **2,28** | +19,2% | 6,1% | 7,8% |
|
||||
| SENZA, iso-nozionale | 2,16 | **+23,8%** | 7,8% | 10,1% |
|
||||
| SENZA, **iso-rischio** (×0,77) | 2,16 | +18,0% | 6,0% | 7,8% |
|
||||
|
||||
A iso-nozionale togliendolo si guadagna **drift** e si compra **volatilita'**. A iso-rischio — la
|
||||
sola lente onesta per un diversificatore a basso CAGR (M6) — **Δ Sharpe +0,121**, maxDD invariato.
|
||||
Hold-out 2025+: **Δ +0,247**. Aiuta in **6 anni su 8**. Correlazione col resto **+0,087**: e'
|
||||
davvero altro. Standalone Sharpe 1,06, CAGR +5,8%.
|
||||
|
||||
⚠️ **Con la sua fascia di fase:** il Δ sulle 5 fasi va da **+0,095 a +0,124** e supera la soglia
|
||||
dichiarata (0,12) in **2 fasi su 5**. Il **segno e' stabile**, il verdetto **binario** no.
|
||||
|
||||
### La decisione: rinviata a $15k di book
|
||||
|
||||
Discussa con l'operatore. L'argomento che ha deciso non e' «GTAA01 non contribuisce» — la misura
|
||||
dice il contrario — ma che **a questo capitale lo sleeve non e' accendibile**: `GTAA_MIN_CAPITAL`
|
||||
e' **$3.000 allocati**, che al peso 20% significa **$15.000 di book** contro i **$2.068** attuali.
|
||||
Sotto quella soglia e' manutenzione senza beneficio incassabile.
|
||||
|
||||
Due ragioni registrate **contro** il blocco immediato, entrambe regole del progetto:
|
||||
- **N7** — uno sleeve difensivo si giudica **sul sinistro, non sul premio**, e la finestra
|
||||
2019-2026 non contiene il sinistro per cui esiste un GTAA a sei gambe;
|
||||
- **N4** — il rischio di venue si compra con un **conto**, non con uno sleeve: togliere GTAA01
|
||||
renderebbe il portafoglio di ricerca **100% cripto su un venue solo**.
|
||||
|
||||
E un costo che sarebbe andato pagato, non saltato: GTAA01 e' uno dei **quattro** nomi di
|
||||
`r0726_wall_fixedpoint.W_DEPLOY`, da cui escono i **$313k** e tutta la pianificazione da €500/mese.
|
||||
|
||||
**Perche' la decisione non si dimentichi** (N9), la soglia e' sorvegliata dal giornale:
|
||||
`journal.SOGLIE_CAPITALE` legge l'equity ogni giorno e il giorno che supera $15k la voce lo dice,
|
||||
con la domanda da rispondere accanto. Stessa tabella, seconda riga: **$20k**, dove si riapre
|
||||
«100% Deribit fino a $20k».
|
||||
|
||||
## 4. Allarmi: il marcatore «gia' detto» si scrive DOPO l'invio
|
||||
|
||||
Debito §5.2 chiuso su decisione dell'operatore. `venue_watch.run_once` salvava lo stato coi
|
||||
marcatori `alerted` gia' a True e l'invio lo faceva il chiamante **dopo**: col 6,9% di invii
|
||||
falliti misurato (2/29), un 🚨 perso restava perso per l'**episodio intero** (200-2.324 ore).
|
||||
|
||||
Ora il sender e' **iniettato** (la funzione resta testabile senza rete d'uscita, che era la ragione
|
||||
del disegno precedente) e su fallimento si disfano i **soli marcatori**, non le misure:
|
||||
asset in ALERT → ri-allerta l'ora dopo; lock MAINT/ALERT → flag azzerati **ma le ore continuano a
|
||||
correre**, cosi' una manutenzione che sfora la grazia sale ad ALERT anche col trasporto giu';
|
||||
lock RIENTRATO → stato ripristinato, perche' il rientro si annuncia una volta sola.
|
||||
`notify()` accetta `tentativi` (venue_watch: 3) e l'esito finisce nel log del cron.
|
||||
|
||||
⚠️ Resta scoperto **ogni altro chiamante di `notify()`**: la disciplina e' oggi solo in venue_watch.
|
||||
|
||||
## 5. L'analista del 27/08, e la riga di diagnosi che mancava
|
||||
|
||||
La voce del 27/08 era senza analisi. Causa, dal transcript della sessione headless:
|
||||
`Failed to authenticate: OAuth session expired and could not be refreshed`, exit 1 alle
|
||||
`00:37:00.98Z`. La meccanica aveva retto (nessuna analisi vecchia spacciata per nuova, esito nel DB,
|
||||
notifica partita) ma il motivo registrato era `uscita 1: ` — vuoto: `interroga()` leggeva il solo
|
||||
**stderr**, e la CLI aveva scritto su **stdout**. Riparato (`motivo_uscita`), analisi recuperata.
|
||||
Guasto **transitorio**: il refresh token era valido, unico caso su 24 sessioni.
|
||||
|
||||
## 6. Fisco: la domanda (a) ha una risposta alla fonte
|
||||
|
||||
Circolare AdE **30/E del 27/10/2023**, letta direttamente dal PDF ufficiale. **I derivati con
|
||||
sottostante cripto stanno in `c-quater` → 26%**, non in `c-sexies` → 33%, e la circolare dice
|
||||
esplicitamente che la riforma 2023 non sposta nulla. Il **collaterale** sconta il **2‰** al 31/12
|
||||
(intermediario non residente → imposta sul valore delle cripto-attivita', ~$4/anno a questo
|
||||
capitale). E la conversione **USDC→USDE non e' fattispecie realizzativa**. Dettaglio e citazioni
|
||||
verbatim in CLAUDE.md §5.4. La (b), i payout di una prop firm, **resta aperta**.
|
||||
|
||||
## Cosa cambia
|
||||
|
||||
- `CLAUDE.md`: §3 nuova riga (GTAA01 rinviato a $15k) · §5.2 risolto · §5.4 riscritto · §5.13 nuovo
|
||||
debito (la fase che ruota, quantificata e innocua oggi).
|
||||
- `src/live/journal.py`: `SOGLIE_CAPITALE` + regola `soglia_capitale`.
|
||||
- `src/live/venue_watch.py`, `src/live/notifier.py`, `scripts/live/venue_watch.py`: commit dopo l'invio.
|
||||
- `src/live/analista.py`: `motivo_uscita` da stdout E stderr.
|
||||
- `tests/test_gtaa_band_gate.py`: gate (A) ritirato, motivo e causa congelati.
|
||||
- Script nuovi: `r0828_gtaa_band_phase.py`, `r0828_gtaa_band_median_phase.py`, `r0828_senza_gtaa.py`.
|
||||
|
||||
**Suite: 789 passati, 0 falliti.**
|
||||
@@ -0,0 +1,67 @@
|
||||
# 2026-08-29 — USDE idoneo, la quota rinviata a lunedì, e il fix IB confermato
|
||||
|
||||
*Scritto il 2026-08-29. Numeri letti dalle serie citate.*
|
||||
|
||||
## 1. GATE USDE-01: CHIUSO **IDONEO**
|
||||
|
||||
Il 2026-08-28 alle 12:35Z `usde_watch` ha rilevato il primo reward:
|
||||
|
||||
```
|
||||
equity USDE : 500.0521 @ 1.0001 (indice) -> $500.05
|
||||
delta : +0.052055 USDE dall'ultima lettura
|
||||
verdetto : IDONEO — reward rilevato il 2026-08-28T12:35:01Z
|
||||
```
|
||||
|
||||
La regola era pre-registrata il 26/08, **prima** dell'esito: ≥1 reward entro il 29/08 → IDONEO,
|
||||
e si apre la decisione di QUOTA, che è dell'operatore. Il gate ha fatto quello che doveva.
|
||||
|
||||
## 2. Il tasso, e perché la quota è stata rinviata
|
||||
|
||||
| lettura | tasso implicito |
|
||||
|---|---|
|
||||
| il solo pagamento (0,052055 su 500) | **3,80% annuo** |
|
||||
| su **due** finestre — il 27/08 ha pagato **zero** | **1,90% annuo** |
|
||||
| annunciato da Deribit (ricerca 26/08) | 9% |
|
||||
|
||||
**Decisione dell'operatore (29/08): la quota si decide lunedì 31/08**, su tre-quattro finestre
|
||||
invece che su una. Il motivo è nella tabella: fra 3,80% e 1,90% c'è tutta la decisione, e il
|
||||
campione è **un pagamento**. Il costo dell'attesa è zero — i 500 USDE rendono comunque.
|
||||
|
||||
📌 Perché il numero non venga letto male: `usde_watch.rendimento()` calcola l'APR sulle finestre
|
||||
**osservate**, col denominatore sul **tempo vero** e non sul numero di finestre pagate — così una
|
||||
finestra che non paga **abbassa** la stima invece di sparire (P5: quel silenzio è uno zero). Le
|
||||
letture con trade nel mezzo si escludono (P12). L'APR si stampa sempre **col suo `n`**, e sotto
|
||||
4 finestre la riga dice *«un pagamento non è un tasso»*. Sulle letture vere di oggi:
|
||||
**1,97% annuo su 1,9 giorni, 1/2 finestre pagate**.
|
||||
|
||||
📌 Perché la decisione non si dimentichi (N9): `quota_da_decidere()` è vera **solo** se il conto è
|
||||
IDONEO **e** il rinvio è scaduto. Da lunedì il watch manda il 📌 **ogni giorno** — con quota
|
||||
attuale, tetto di allerta e APR osservato accanto — finché non si decide. Si smette togliendo
|
||||
`DECISIONE_QUOTA_DAL`. È lo stesso schema della soglia $15k di GTAA01, con la data al posto
|
||||
del capitale.
|
||||
|
||||
⚠️ Nota di contorno che vale una lezione: il 📌 dell'IDONEO era partito su Telegram il 28/08 alle
|
||||
12:35Z, **in mezzo ai messaggi-spazzatura dei test** delle 14:47 e 14:52 (difetto riparato lo
|
||||
stesso giorno). L'unico messaggio che contava è arrivato nel giorno in cui il canale era sporco.
|
||||
È P9 in forma concreta.
|
||||
|
||||
## 3. Il fix IB è confermato
|
||||
|
||||
Il giro del cron delle 00:30 di stanotte: **zero** righe `open orders request timed out` (erano
|
||||
2 per giro, 63 giri su 63) e tutti e sei gli ETF scaricati — `SPY n=7541`, `QQQ n=6912`,
|
||||
`IWM n=6602`, `TLT n=2658`, `GLD n=5476`, `HYG n=4877`, aggiornati al 28/08. Il `readonly=True`
|
||||
resta.
|
||||
|
||||
📌 E una conferma involontaria del debito §5.13: **SPY è passato da `1996-09-04` a `1996-09-05`**.
|
||||
La finestra rotolante ha perso un altro giorno di borsa in una notte, esattamente come misurato
|
||||
il 28/08 — il difetto si legge a occhio nudo nel log del cron.
|
||||
|
||||
## Cosa cambia
|
||||
|
||||
- `scripts/live/usde_watch.py`: `rendimento()` (APR sulle finestre osservate, col suo n) ·
|
||||
`quota_da_decidere()` · `DECISIONE_QUOTA_DAL = "2026-08-31"` · il 📌 giornaliero da lunedì.
|
||||
- `tests/test_usde_watch.py`: 6 test nuovi, incluso il controllo positivo (*la finestra sola
|
||||
darebbe il doppio*) che è la ragione per cui l'APR si stampa col suo `n`.
|
||||
- `CLAUDE.md`: §1 e §4 aggiornati — gate CHIUSO IDONEO, quota rinviata a lunedì.
|
||||
|
||||
**Suite: 795 passati, 0 falliti.**
|
||||
@@ -0,0 +1,318 @@
|
||||
# 2026-08-30 — "porta in USDE tutto il capitale che non viene usato": il venue dice 31%
|
||||
|
||||
**Richiesta dell'operatore**, verbatim: *"porta in usde tutto il capitale che non viene usato"*.
|
||||
Arriva il 30/08, cioè **un giorno prima** della data a cui l'operatore stesso aveva rinviato la
|
||||
decisione di quota (gate USDE-01, rinvio al 31/08). L'anticipo è esplicito e vale come decisione.
|
||||
|
||||
## 1. "Il capitale che non viene usato" non è una quantità piccola: è quasi tutto
|
||||
|
||||
Stato letto dal gateway alle 19:50Z: USDC **$1.563,09** (di cui **$11,36** impegnati a margine),
|
||||
USDE 500,1757 → **$500,18**, totale **$2.063,26**, quota **24,2%**. Posizioni BTC $354,72 + ETH
|
||||
$213,36 = **$568,08** lordi (0,275x).
|
||||
|
||||
Il margine consuma **$11 su $2.063**, e l'USDE è cross-collateral: convertirlo non lo toglie
|
||||
dall'uso. Anche a piena esposizione del libro (lordo 1,0x) il margine sarebbe ~$41. Presa alla
|
||||
lettera la richiesta è una quota **~99%** — il 100% che il gate esclude per nome ("mai 100%: R1
|
||||
emittente non recuperabile"). Le due letture plausibili ("non a margine" e "non esposto dal libro")
|
||||
convergono entrambe su ~97-99%: non è un'ambiguità che si risolve scegliendo, è una che si porta
|
||||
all'operatore.
|
||||
|
||||
## 2. Il vincolo che `r0830_usde_quota` non aveva guardato: il cuscino di REGOLAMENTO
|
||||
|
||||
L'analisi del 30/08 aveva misurato il **margine** e concluso — correttamente — che l'haircut non
|
||||
morde a nessuna quota fino al 90%, nemmeno a leva 1,50x con USDE a 0,95. Vero, e irrilevante:
|
||||
il P&L e il funding dei perp **USDC-lineari si regolano in USDC**, non nel collaterale. Lo si vede
|
||||
nel conto: `equity − balance = $7,24` di floating, tutto nel secchio USDC.
|
||||
|
||||
A quota ~99% resterebbero **$41 di USDC** contro un libro che, col disaster-SL a −30% sulla massima
|
||||
esposizione, può perderne **~$619**: il saldo va negativo e Deribit lo finanzia a interesse, che si
|
||||
mangia la resa che si stava comprando. **Il vincolo non è il margine, ma non è nemmeno assente.**
|
||||
|
||||
> 📌 **Corretto e QUANTIFICATO il 31/08** (KB *Cross collateral specifications*): «*a collateral fee
|
||||
> will be charged […] **default = 0.05% per day***», al secondo, sulla valuta negativa — cioè
|
||||
> **18,25%/anno, 4,3× la resa USDE** che si starebbe comprando. Il ribilanciamento automatico non
|
||||
> salva: scatta a **$1M** assoluti o al **100% della cross equity**, soglie irraggiungibili a $2k.
|
||||
> Qui il 30/08 avevo scritto «lo finanzia a interesse» **senza il numero**: il numero rende il
|
||||
> criterio del cuscino più giustificato, non meno.
|
||||
|
||||
Criterio dichiarato: cuscino USDC ≥ disaster-SL sulla massima esposizione lorda del libro =
|
||||
`n_asset × frac × disaster_sl_pct` = 2 × 0,5 × 0,30 = **30% dell'equity** — tutto derivato da
|
||||
`config/live.json` e `src/live/book.py`, mai ridichiarato (P1). Che lascia esattamente **70%**.
|
||||
Non è un argmax (M8): la quota massima e il criterio coincidono per costruzione, e infatti il 70%
|
||||
cade **esattamente sul limite, con zero slack**.
|
||||
|
||||
Tabella portata all'operatore (APR allora creduta 3,26% [1,13-4,51], n=4 — vedi §5, è sbagliata):
|
||||
|
||||
| quota | converti | USDC che resta | resa/anno | evento emittente vs maxDD libro |
|
||||
|---|---|---|---|---|
|
||||
| 24,2% (allora) | — | $1.563 | $16 | 3,1× |
|
||||
| 50% | $531 | $1.032 | $34 | 6,3× |
|
||||
| **70%** | **$944** | **$619** | **$47** | **8,8×** |
|
||||
| ~99% | $1.522 | $41 | $66 | 12,3× |
|
||||
|
||||
**Scelta dell'operatore: 70%**, il massimo compatibile col cuscino.
|
||||
|
||||
## 3. Esecuzione: da 500 a 644 USDE, poi il muro
|
||||
|
||||
Nuovo attrezzo **`scripts/live/usde_convert.py`** (dry-run di default). Non passa da
|
||||
`DeribitTrader`: `execution.ALLOWED` ammette solo i due perp del libro ed è il guardrail
|
||||
anti-fat-finger del percorso soldi — **non si allarga** per farci entrare uno spot che col libro
|
||||
non c'entra. Lo script porta le proprie guardie: banda prezzo [0,995 – 1,005], tetto HARD di quota
|
||||
0,95, il cuscino di regolamento, dry-run.
|
||||
|
||||
Convertiti **+144 USDE** (500,1757 → **644,175691**), quota **24,24% → 31,21%**, tutti i fill a
|
||||
1,0002-1,0003, **fee 0**. Poi ogni acquisto ha smesso di passare.
|
||||
|
||||
## 4. `not_enough_funds_in_currency` è un messaggio FUORVIANTE — la cronaca delle ipotesi sbagliate
|
||||
|
||||
Si tiene apposta, perché chi rilegge non deve rifare questo giro. Tre ipotesi, tutte plausibili,
|
||||
tutte sbagliate:
|
||||
|
||||
1. **"È la taglia."** 943,98 rifiutato per `Invalid params` → vero: `min_trade_amount: 1` e
|
||||
`contract_size: 1`, il passo è **intero** (confermato da `public/get_instrument`; il gateway
|
||||
non espone quel tool, la risposta è arrivata dall'API pubblica). Quantizzato a intero: 933
|
||||
rifiutato per **fondi**, con **$1.541 disponibili**. Falso indizio.
|
||||
2. **"È il rate-limit."** 100 passava e 300 no; poi anche **1** rifiutato otto volte di fila.
|
||||
Aggiunto backoff sul tempo fino a 90s: rifiutato lo stesso. Falso.
|
||||
3. **"È il prezzo."** Diagnosi a tre ordini da 1 USDE: SELL @1,0000 OK, BUY **@1,0003 OK**,
|
||||
BUY @1,0002 e BUY market rifiutati. Vero **in parte**: il book REST pubblico è in **ritardo**
|
||||
sul matching engine, e un limite a `ask+1 tick` a volte non incrocia davvero. Difetto mio, e
|
||||
istruttivo: prezzavo l'ordine sull'**indice** invece che sul **book**. Sono due prezzi con due
|
||||
mestieri — l'indice **marca** il collaterale (`usde.valuta`, mediana multi-exchange: la lezione
|
||||
Binance 10/10), il book **prezza** lo scambio. Corretto con 5 tick di margine e tetto duro
|
||||
1,0010: il fill avviene al prezzo del libro, quindi il margine non si paga.
|
||||
|
||||
Ma corretto il prezzo, **200 e 800 continuavano a fallire con 4.552 di profondità all'ask**.
|
||||
|
||||
## 5. 🚨 Il fatto vero: un TETTO DEL VENUE sull'USDE, a 644,175691 (31,21%)
|
||||
|
||||
Esperimento **a saldo neutro**, quattro ordini di fila (20:14Z):
|
||||
|
||||
```
|
||||
BUY 20 @1.0007 -> not_enough_funds_in_currency USDE 644.1757
|
||||
SELL 20 @0.9996 -> OK USDE 624.1757
|
||||
BUY 20 @1.0007 -> OK USDE 644.1757
|
||||
BUY 5 @1.0007 -> not_enough_funds_in_currency USDE 644.1757
|
||||
```
|
||||
|
||||
È un **tetto sul LIVELLO**: non taglia, non prezzo, non liquidità, non cadenza, non fondi
|
||||
($1.408 disponibili). Lo stesso ordine che viene rifiutato passa subito dopo una vendita di pari
|
||||
taglia. **La quota del 70% non è raggiungibile**, e nemmeno il 50%.
|
||||
|
||||
La formula del tetto **non è leggibile da qui**: `public/get_currencies` non espone un cap
|
||||
(`in_cross_collateral_pool: true`, nient'altro), e il gateway non espone né `get_order_book` né
|
||||
`available_withdrawal_funds` — è il debito #11, il gateway è l'unico pezzo della catena che
|
||||
possediamo e si ha solo quel che espone. Registrato come fatto misurato, non spiegato (D5: un buco
|
||||
quantificato è un risultato, uno taciuto è un debito).
|
||||
|
||||
## 6. 🚨 E la resa non è quella che credevamo: **l'USDC paga 3,40%**
|
||||
|
||||
`public/get_currencies`, letta oggi:
|
||||
|
||||
| valuta | APR pubblicata |
|
||||
|---|---|
|
||||
| USDE | **4,1071%** |
|
||||
| USDC | **3,4000%** |
|
||||
|
||||
I nostri reward USDE misurati (+0,052055 · +0,061815 · +0,061821 al giorno su 500 → **~4,5%/anno**)
|
||||
**confermano** che la APR pubblicata di USDE è reale. Il che rende credibile anche l'altra riga.
|
||||
|
||||
Se l'USDC frutta già 3,40%, il guadagno del passaggio a USDE **non è il tasso: è lo spread**,
|
||||
**0,71 punti**. Sui $644 che teniamo vale **~$4,6/anno**, non $21; sui $144 convertiti oggi,
|
||||
**~$1,0/anno**. Il gate USDE-01 ha misurato con cura il reward dell'USDE e **non ha mai chiesto
|
||||
cosa facesse l'USDC fermo**: è il controfattuale mancante — M1 in un'altra veste, si giudica il
|
||||
**marginale**, non il livello. E cambia il verso della decisione: si prende un rischio emittente
|
||||
**non recuperabile** (a 31,2% vale 3,9× il maxDD dell'intero libro) per **0,71 punti**.
|
||||
|
||||
⚠️ **Quello che NON è dimostrato:** che la APR USDC sia effettivamente accreditata *sul nostro
|
||||
conto*. Sull'USDE l'abbiamo vista arrivare per delta; sull'USDC il P&L di trading copre $0,13/giorno
|
||||
di interesse e il gateway non espone il Transaction Log. **Va verificato su un giorno senza trade
|
||||
prima di trattarlo come misura** — qui è una APR pubblicata dal venue più una conferma indiretta,
|
||||
non un reward osservato.
|
||||
|
||||
## 7. Cosa resta a terra
|
||||
|
||||
- **Conto**: USDC $1.419,61 · USDE 644,175691 ($644,18) · totale **$2.063,79** · quota **31,21%**.
|
||||
Nessun ordine spot appeso (restano i due `tp01-disaster` reduce_only). Posizioni invariate.
|
||||
Equity vista dal book **$2.063,86** ("mainnet USDC + USDE 644 @ 1.0000"): sizing corretto,
|
||||
nessun falso "uscita di fondi".
|
||||
- **`config/live.json`**: aggiunto `quota_target: 0.70` (la decisione dell'operatore, oggi bloccata
|
||||
dal venue). `quota_max_frac` alzato a 0,85 e **rimesso a 0,50** nella stessa sessione: la soglia
|
||||
larga presupponeva un 70% che non esiste, e lasciarla avrebbe **disarmato la guardia per uno
|
||||
scenario che non si è verificato. Con la quota reale al 31%, il 50% morde di nuovo.**
|
||||
- **Non fatto**: portare la quota al 70%. Il venue non lo consente. Non è una rinuncia
|
||||
discrezionale, è un rifiuto misurato e riproducibile.
|
||||
|
||||
---
|
||||
|
||||
# APPENDICE (stessa sera, 23:40-23:55Z) — **l'USDC NON frutta sul nostro conto: §6 era sbagliata**
|
||||
|
||||
Richiesta dell'operatore: *"verifica se l'USDC frutta davvero sul nostro conto"*. Verificato.
|
||||
**La risposta ribalta la §6 di questo stesso diario**, che va letta con questa appendice accanto.
|
||||
|
||||
## Perché la domanda sembrava dover aspettare, e invece no
|
||||
|
||||
Il gateway non espone il Transaction Log (provati `get_transaction_log`, `get_settlement_history`,
|
||||
`get_deposits`, `get_transfers`, `get_interest_history`: **tutti 404** — debito #11). L'unica serie
|
||||
storica del conto è `trades.db.equity`: **oraria ma arrotondata a 2 decimali**, e contiene il P&L
|
||||
non realizzato, che a posizioni aperte oscilla di ±$2-8/ora. $0,13/giorno di interesse ci sparisce.
|
||||
|
||||
Poi la fonte ufficiale Deribit ha spostato il problema dal *rumore* al *calendario*:
|
||||
|
||||
> «Every day at 00:00 UTC, Deribit calculates the **minimum equity** of USDC that a user has been
|
||||
> holding over the previous 24 hours. […] After the month is over, the rewards from each day are
|
||||
> summed together and **paid out as a single monthly payment early in the following month**.»
|
||||
> — `insights.deribit.com/education/usdc-rewards-now-paid-on-deribit/`
|
||||
|
||||
**I reward USDC si pagano UNA VOLTA AL MESE**, non ogni giorno come quelli USDE. Ecco perché non li
|
||||
avevamo mai visti: guardavamo a cadenza giornaliera, dove l'USDE si vede e l'USDC per costruzione no.
|
||||
E un accredito mensile da qualche dollaro **si vede benissimo anche a 2 decimali** — purché il libro
|
||||
sia FLAT, perché allora `equity == balance == USDC` e ogni scalino è un accredito.
|
||||
|
||||
## La misura: due confini di mese, entrambi a libro flat
|
||||
|
||||
| finestra | punti orari | equity min | equity max | ore senza alcun movimento | scalini >1 cent | reward atteso se idoneo |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **29/06 → 08/07** | 238 | **598,06** | **598,06** | **237 / 237** | **0** | $0,45 (8 gg dal 23/06) |
|
||||
| **31/07 → 03/08** | 96 | **596,92** | **596,92** | **tutte** | **0** | **$1,72** (luglio intero) |
|
||||
|
||||
Il secondo è il caso decisivo: **$1,72 attesi contro una risoluzione di $0,01 — 172×** — e l'equity
|
||||
non si muove di un centesimo per quattro giorni pieni, coprendo tutta la finestra "early in the
|
||||
following month". Non è un'assenza sotto la soglia di rilevabilità: è uno zero misurato.
|
||||
|
||||
⇒ **Il conto NON riceve i reward USDC.** N=2 confini indipendenti, entrambi puliti per costruzione.
|
||||
|
||||
## Perché, e perché è coerente col fatto che l'USDE invece paga
|
||||
|
||||
Deribit: *«A user's eligibility to receive USDC is based on their location»*. L'ipotesi che spiega
|
||||
entrambe le osservazioni è **MiCA**: USDC è un *e-money token* regolamentato, e a un residente UE
|
||||
non se ne può corrispondere rendimento; **USDe non è un EMT**, e infatti i suoi reward arrivano
|
||||
(li abbiamo visti per delta: +0,052055 · +0,061815 · +0,061821). ⚠️ *Questa è la spiegazione
|
||||
plausibile, non una fonte normativa verificata* (P13/M27: una norma citata si verifica come un
|
||||
numero). **Il fatto misurato è lo zero, non il suo motivo.**
|
||||
|
||||
## Cosa cambia (e cosa la §6 aveva sbagliato)
|
||||
|
||||
- **La premessa originale del gate USDE-01 è RESTAURATA.** Il guadagno di tenere USDE **non è lo
|
||||
spread 0,71 punti: è il tasso pieno ~4,1%**, perché l'alternativa sul nostro conto rende **0**.
|
||||
Sui $644 che teniamo: **~$26/anno** (e ~$29 al ritmo misurato di ~4,5%), non i ~$4,6 di §6.
|
||||
- **Cosa avevo sbagliato, e la lezione.** Ho letto `apr: 3.4` in `public/get_currencies` e l'ho
|
||||
trattato come una proprietà **del nostro conto**. È una proprietà **del venue**: un listino, non
|
||||
un accredito. La conferma indiretta che invocavo ("i reward USDE misurati coincidono con la loro
|
||||
APR pubblicata") provava che il listino è reale **per l'USDE**, e non diceva nulla sull'idoneità
|
||||
dell'USDC. 📌 **Un tasso pubblicato non è un tasso incassato: si verifica sul CONTO, e la verifica
|
||||
costava una query sulla serie che avevamo già.** È N10 in una veste nuova — la verifica a €0 va
|
||||
fatta *prima*, e si fa **sul venue, non sul sito**; qui perfino il venue non bastava, serviva il
|
||||
conto.
|
||||
- Resta vero e non toccato: il **tetto del venue al 31,21%**, il **cuscino di regolamento** (30%
|
||||
dell'equity, che lascia il 70%), e che a 31,2% l'evento emittente vale ~3,9× il maxDD del libro.
|
||||
La decisione "tenere o no i $644" cambia però di segno rispetto a come l'avevo chiusa: si compra
|
||||
**$26/anno**, non $4,6.
|
||||
|
||||
## Strumento lasciato in piedi
|
||||
|
||||
**`scripts/live/balance_watch.py`** + **`scripts/cron_balance.sh`** (orario al minuto **:42**, libero
|
||||
fra :25/:35/:47; sola lettura). Registra il **balance a 8 decimali** per valuta — la serie che
|
||||
mancava — con il conteggio dei fill dall'ultimo campione e il nozionale lordo, così una finestra
|
||||
sporca si riconosce invece di essere mediata dentro. Etichetta PULITA le finestre a 0 fill e libro
|
||||
flat, dove il Δ USDC **è** l'interesse, e ne stampa l'APR implicita accanto a quella attesa.
|
||||
Serve a sorvegliare che lo zero resti zero (o che cambi, se l'idoneità cambia): l'inizio di
|
||||
settembre è il prossimo confine di mese, ed è già strumentato.
|
||||
|
||||
---
|
||||
|
||||
# APPENDICE 2 (2026-08-31) — la fonte ufficiale: **due correzioni alle mie conclusioni**
|
||||
|
||||
L'operatore ha passato l'articolo `support.deribit.com/.../Yield-reward-bearing-coins`. WebFetch lo
|
||||
prende 403 (Cloudflare); si legge dall'**API Help Center** in JSON:
|
||||
`support.deribit.com/api/v2/help_center/en-us/articles/31424939199261.json` → HTTP 200.
|
||||
*Aggiornato dal venue il 2026-08-20.* Contiene tre cose che il progetto non sapeva.
|
||||
|
||||
## 1. L'ITALIA è nella lista degli esclusi dai reward USDC — e non è MiCA
|
||||
|
||||
> «The following jurisdictions are **not eligible** to receive USDC rewards: Austria, Belarus,
|
||||
> Belgium, Bulgaria, Canada, […] **Italy**, Japan, […]»
|
||||
|
||||
Lo zero misurato è **confermato dalla fonte**. Ma l'ipotesi che avevo scritto — MiCA, USDC è un
|
||||
e-money token e USDe no — **è sbagliata**: la lista contiene Canada e Giappone, non è il perimetro
|
||||
MiCA. È policy di giurisdizione di Deribit. *M27: una fonte normativa citata si verifica come un
|
||||
numero — e io l'avevo dedotta invece di leggerla.*
|
||||
|
||||
## 2. La finestra di pagamento è di DUE SETTIMANE, non di tre giorni — il «172×» era sovra-affermato
|
||||
|
||||
> «The monthly USDC rewards payment is then made **within the first two weeks** of the following
|
||||
> month.»
|
||||
|
||||
Avevo verificato su 29/06→08/07 e 31/07→03/08: **8 giorni su 14 e 3 su 14**. Rifatto sulle finestre
|
||||
vere:
|
||||
|
||||
| accredito | finestra vera | letture | escursione TOTALE equity | scalini ≥70% dell'atteso | esito |
|
||||
|---|---|---|---|---|---|
|
||||
| giugno ($0,45) | 01→16/07 | 374 | **$2,74** | 7 | ❌ **NON conclusivo** |
|
||||
| luglio ($1,72) | 01→16/08 | 382 | **$0,48** | **0** | ✅ **conclusivo** |
|
||||
|
||||
Luglio regge da solo: il credito atteso è **3,6× l'intera escursione dell'equity** su due settimane.
|
||||
Giugno no — il libro ha iniziato a operare a metà mese e il rumore ha la taglia del segnale.
|
||||
⇒ La conclusione **non cambia** (una finestra conclusiva + la lista ufficiale), ma l'evidenza è
|
||||
**una** finestra, non due. *Avevo citato la sotto-finestra piatta e chiamato «zero misurato» ciò che
|
||||
la finestra completa non copriva: scegliere la sotto-finestra che fa vedere lo zero è la stessa
|
||||
mossa che il progetto vieta a un backtest.*
|
||||
|
||||
## 3. Il tetto NON è un livello: è una FRAZIONE dell'equity, e scala col conto
|
||||
|
||||
L'articolo documenta un **Cap ETHENA** che diluisce il **tasso** a livello di exchange —
|
||||
`User APR = min(min_exchange_USDe_balance, Ethena Cap) / min_exchange_USDe_balance × Ethena APR` —
|
||||
e **nessun limite sulle quantità detenibili da un conto**. Quindi il muro non aveva base
|
||||
documentale, e andava ri-sondato. Fatto (31/08, giorno UTC nuovo → non è un limite giornaliero):
|
||||
|
||||
```
|
||||
BUY 5 -> not_enough_funds_in_currency USDE 644.175691 (muro ancora li')
|
||||
SELL 10 -> OK USDE 634.175691
|
||||
BUY 10 -> not_enough_funds_in_currency USDE 634.175691 <-- ieri 644,18 passava!
|
||||
BUY 5 -> OK USDE 639.175691
|
||||
BUY 3 -> OK USDE 642.175691
|
||||
BUY 1 -> OK USDE 643.175691
|
||||
BUY 1 -> not_enough_funds_in_currency USDE 643.175691
|
||||
```
|
||||
|
||||
**Ieri si tornava a 644,18 e oggi no**, con l'equity scesa di $8. Il tetto si è mosso con lei:
|
||||
|
||||
| | tetto | equity | frazione |
|
||||
|---|---|---|---|
|
||||
| 30/08 | [644,18 · 645,18) | $2.063,79 | 31,21% – 31,26% |
|
||||
| 31/08 | [643,18 · 644,18) | $2.055,56 | 31,29% – 31,34% |
|
||||
|
||||
I bracket distano **0,05pp**, dentro il rumore dell'equity (±$2-8/ora). Candidato pulito **5/16 =
|
||||
31,25%**, ma con questa risoluzione non si distingue da una regola sul collaterale scontato
|
||||
dell'haircut (~29%): **non si sceglie quella che conviene, si cita la banda** (M25).
|
||||
|
||||
⇒ **Il tetto SCALA col conto.** La quota resta ~31% per sempre, il valore in dollari cresce col
|
||||
capitale, e il 70% non è raggiungibile né ora né mai. Conseguenza che ieri non avevo visto: essendo
|
||||
pinnati al tetto, **il rischio emittente resta una frazione COSTANTE del conto** — non si diluisce
|
||||
crescendo, a meno di vendere apposta.
|
||||
|
||||
## 4. E l'USDe ha un fee del 5% che non avevamo mai nominato
|
||||
|
||||
> «Deribit Fee: A percentage deducted from the reward before distribution (**currently 5%**)»
|
||||
> · reward su **minima equity 00:00→24:00 UTC**, distribuiti **~12:00 UTC**, «visible inside
|
||||
> Transaction Log» (che il gateway non espone: da lì il metodo per delta).
|
||||
|
||||
Il nostro **misurato** (~4,5%/anno) è già netto del fee: è quello che arriva sul conto, ed è
|
||||
l'unico numero da citare. La `apr` di `get_currencies` (4,1071%) è un listino, non un incasso —
|
||||
la stessa confusione che mi era costata la §6.
|
||||
|
||||
## 5. Cablato, così il tetto smette di vivere in prosa
|
||||
|
||||
Alla domanda dell'operatore «dove è scritto il valore del tetto» la risposta era: **in tre note di
|
||||
testo e in nessun posto che il codice legga**, tanto che `usde_convert --quota 0.70` dichiarava
|
||||
«piano valido» per un ordine che il venue avrebbe rifiutato. Ora:
|
||||
|
||||
- `config/live.json` → `usde.venue_cap_frac` **0.312** (bordo BASSO dei bracket: fa fallire il
|
||||
piano *prima* dell'ordine invece che dopo il rifiuto) + `venue_cap_misurato` con la data;
|
||||
- `src/live/usde.py` lo porta nei default — unica autorità, come tutto il resto della sezione (P1);
|
||||
- `usde_convert.piano()` rifiuta il bersaglio sopra il tetto e stampa il massimo raggiungibile.
|
||||
Verificato: `--quota 0.70` → *«TETTO DEL VENUE: bersaglio $1.438,90 sopra $641,34 (31,2%
|
||||
dell'equity, misurato 2026-08-31)»*, nessun ordine inviato.
|
||||
|
||||
**Stato finale**: USDE **643,175691** (quota **31,29%**, cioè al tetto), USDC $1.412,40,
|
||||
totale $2.055,57.
|
||||
@@ -0,0 +1,128 @@
|
||||
# 2026-08-30 — La quota USDE: l'EV non puo' sceglierla, e aspettare costa $1,33
|
||||
|
||||
*Scritto 2026-08-30T17:32:57Z. Script: `scripts/research/r0830_usde_quota.py` (verdetto a runtime, N11).
|
||||
Stato conto congelato nello script alla lettura del 2026-08-30T17:05Z.*
|
||||
|
||||
## La domanda
|
||||
|
||||
Il gate USDE-01 e' **chiuso IDONEO** dal 28/08. Resta la decisione di **quota**, rinviata
|
||||
dall'operatore a **lunedi' 31/08**: quanto del conto tenere in USDE.
|
||||
|
||||
## Il risultato strutturale — dichiarato prima di guardare i numeri
|
||||
|
||||
Il valore atteso e' **lineare in q**. Un massimo lineare sta sempre in un **angolo** (0% o
|
||||
100%). ⇒ **l'EV non puo' scegliere una quota interna**: puo' solo dirne il segno. Ogni quota
|
||||
intermedia nasce da un criterio sulla **coda**, che va **dichiarato**, non ottimizzato (M8).
|
||||
|
||||
Per questo lo script non sceglie: mette il **prezzo accanto a ogni criterio**.
|
||||
|
||||
## (A) Il rendimento, col suo n
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| finestre misurabili | **4** (pagate 3) |
|
||||
| per finestra | 0,00% · 3,80% · 4,51% · 4,51% |
|
||||
| APR di `usde_watch` | **3,26%** annuo (tempo vero, 3,93 giorni) — *il numero che gira* |
|
||||
| APR per-finestra | 3,21% (n finestre, 4,00 giorni) |
|
||||
| banda bootstrap 95% | **[1,13% – 4,51%]** (200.000 ricampionamenti) |
|
||||
|
||||
📌 La banda e' larga **3,4 punti** su un livello di 3,2%: a n=4 il tasso **non e' misurato, e'
|
||||
abbozzato**. La divergenza fra i due denominatori (0,06 punti) e' **dichiarata, non appianata**
|
||||
(P12), e il punto stimato si **deriva** da `usde_watch.rendimento()` invece di essere
|
||||
ridichiarato (P1).
|
||||
|
||||
## (B) L'hazard di pareggio — l'unico criterio che non richiede un criterio
|
||||
|
||||
Perdita totale **non recuperabile**, resa 3,26%/anno ⇒ **hazard annuo di pareggio = APR**.
|
||||
|
||||
- **3,26%/anno** (banda 1,13% – 4,51%)
|
||||
- recupero di una perdita totale con la sola resa: **30,6 anni**, *indipendente dalla quota*
|
||||
- ⇒ la quota e' EV-positiva **se e solo se** si crede che Ethena abbia meno del **3,3% annuo**
|
||||
di probabilita' di evento catastrofico. Quel numero e' **assunto, non stimato** — esattamente
|
||||
come il `p` di Deribit della decisione 26/07.
|
||||
|
||||
## (C) Il rischio marginale, dato il 100%-Deribit gia' accettato
|
||||
|
||||
Framework **ereditato** dalla decisione 26/07, non re-inventato: `P = 1-(1-p)^20`. Lo script lo
|
||||
**riproduce** (M23: far riprodurre alla macchina il numero vecchio prima di pubblicarne uno nuovo):
|
||||
10/18/33/64% contro i 10/18/34/64% registrati — **OK** a 1,5 punti.
|
||||
|
||||
Due letture che **non si annullano**, e la contraddizione e' informazione (M28):
|
||||
|
||||
- ✅ L'USDE fa perdere soldi **solo** nel mondo in cui Ethena salta **e Deribit sopravvive**. Nel
|
||||
mondo in cui salta Deribit, USDC e USDE si perdono insieme e la quota e' irrilevante. Il rischio
|
||||
**aggiunto** e' quindi di **secondo ordine** rispetto a quello gia' accettato.
|
||||
- ⚠️ Ma e' un **terzo strato sullo stesso conto**, e **N4** dice che un rischio di venue si compra
|
||||
con un **secondo CONTO**, non aumentando l'esposizione al primo.
|
||||
|
||||
La prima dice che la quota costa poco; la seconda che **non e' li' che si compra sicurezza**.
|
||||
|
||||
## (D) I vincoli operativi: nessuno morde
|
||||
|
||||
Il libro gira al **2,00% di margine** ($11,37 su $568,64 di nozionale lordo). Al tetto di leva
|
||||
1,00x servirebbero ~$41 su un collaterale di ~$2.000.
|
||||
|
||||
| quota | margine utilizzabile | serve al tetto | morde? |
|
||||
|---|---|---|---|
|
||||
| 24,2% | $2.014 | $41 | no |
|
||||
| 50,0% | $1.961 | $41 | no |
|
||||
| 90,0% | $1.878 | $41 | no |
|
||||
|
||||
⇒ **l'haircut 10% non morde a nessuna quota testata**, tre ordini di grandezza di margine. D5:
|
||||
buco quantificato e innocuo. **Il vincolo non e' il margine.**
|
||||
|
||||
Churn da depeg: a soglia critica 0,95 l'equity cala di 5%·q ⇒ a quota 50% sono **2,50%**, cioe'
|
||||
~$52 di nozionale da girare. Il libro **de-risca mentre l'USDE e' a sconto e ri-carica al ritorno
|
||||
del peg** — compra alto e vende basso — ma per importi di **secondo ordine**. Non e' il vincolo.
|
||||
|
||||
## (E) Il menu dei criteri, ciascuno col suo prezzo
|
||||
|
||||
| criterio | quota | USDE | resa $/a | drift libro | == €/mese | mesi di bonifico a rischio |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **C0** status quo | 24,2% | $500 | $16 | 0,79% | 19 | **2,0** |
|
||||
| **C1** ≤ maxDD del libro (7,94%) | 7,9% | $164 | $5 | 0,26% | 6 | 0,7 |
|
||||
| **C2** ≤ 6 mesi di versamento | 72,7% | $1.500 | $49 | 2,37% | 58 | 6,0 |
|
||||
| **C3** ≤ 12 mesi di versamento | 100% | $2.064 | $67 | 3,26% | 80 | 8,3 |
|
||||
| **C4** tetto di ALLERTA di config | 50,0% | $1.032 | $34 | 1,63% | 40 | 4,1 |
|
||||
|
||||
⚠️ **Due monete, non si sommano** (N8): *resa $/a* e' **cassa** vera; *== €/mese* e' l'equivalente
|
||||
in **versamento** via l'equivalenza registrata (100 €/mese == +4,07%/anno di drift), che e' una
|
||||
capitalizzazione a 10 anni, **non un flusso**. Semplificazione dichiarata: EUR e USD 1:1 (a ~1,08
|
||||
sposta i mesi di ~8%, non muove nessuna soglia).
|
||||
|
||||
## (F) Cosa costa aspettare — il numero decisivo
|
||||
|
||||
La conversione e' su spot `USDE_USDC`: **fee 0, spread ~3 bps** ⇒ andata+ritorno **~6 bps**. La
|
||||
decisione e' **reversibile a costo quasi nullo** — non e' una porta a senso unico e va decisa come
|
||||
tale.
|
||||
|
||||
| attendere | finestre | andata/ritorno | resa persa | costo totale |
|
||||
|---|---|---|---|---|
|
||||
| 0 sett | 4 | $0,32 | $0,00 | $0,32 |
|
||||
| 2 sett | 18 | $0,32 | $0,67 | $0,98 |
|
||||
| **4 sett** | **32** | $0,32 | $1,33 | **$1,65** |
|
||||
| 8 sett | 60 | $0,32 | $2,66 | $2,98 |
|
||||
|
||||
📌 **Portare la quota a 50% fra quattro settimane invece che domani costa $1,33 di resa non
|
||||
incassata e compra 8× le finestre** (n da 4 a 32), stringendo la banda di ~2,8× — da ~3,4 punti a
|
||||
~1,2. **$1,33 per sapere se il tasso e' 1,1% o 4,5%.** E' l'applicazione diretta di N10: una data
|
||||
si giustifica col **costo della misura**, non con la sua difficolta' percepita.
|
||||
|
||||
## Verdetto (calcolato a runtime)
|
||||
|
||||
- **RISOLUZIONE INSUFFICIENTE PER ALZARE** — n=4, banda larga 3,4 punti. Alzare ora e' comprare un
|
||||
tasso che non e' ancora misurato.
|
||||
- **NESSUN VINCOLO OPERATIVO** — l'haircut non morde a nessuna quota; il vincolo non e' il margine.
|
||||
- **SCALA** — la resa alla quota di oggi vale **$16/anno**, meno di **un mese** di versamento. La
|
||||
leva binding resta il **bonifico** (N1/N3), non la quota.
|
||||
- **ASIMMETRIA** — gia' a 24,2% l'evento emittente vale **3,1× il maxDD dell'intero libro** (7,94%),
|
||||
che e' il rischio che il progetto ha passato due anni a limare.
|
||||
- **REVERSIBILE E QUASI GRATIS DA RIMANDARE** — $1,33 + 6 bps per 8× il campione.
|
||||
|
||||
## Cosa NON dice questa analisi
|
||||
|
||||
- Non stima `p` per Ethena. Nessuna fonte qui lo misura: e' **assunto**, e lo script lo tratta come
|
||||
parametro, non come risultato.
|
||||
- Non sceglie la quota. **La scelta e' dell'operatore** (N4).
|
||||
- Il tasso osservato viene da un metodo **indiretto** (delta di equity al netto dei trade): il
|
||||
gateway non espone il Transaction Log. Vale col suo limite gia' dichiarato in `usde_watch`.
|
||||
@@ -0,0 +1,181 @@
|
||||
# 2026-08-31 — GATE BUIDL-01: test di eligibilità sul collaterale BlackRock
|
||||
|
||||
⚠️ **Questo documento è scritto PRIMA di eseguire e PRIMA di conoscere l'esito, e committato
|
||||
separatamente dall'esecuzione**: l'ordine dei fatti è verificabile in git, non sulla mia parola.
|
||||
È la stessa disciplina che ha reso valido il test USDE del 26/08 (M13, N11).
|
||||
|
||||
## Da dove nasce
|
||||
|
||||
L'operatore chiedeva se convenisse aggiungere **stETH** e i custodi Fireblocks/Sygnum/Komainu.
|
||||
Risposta: no a entrambi — stETH rende **2,2254%** (metà dell'USDE) e non è un dollaro ma **ETH**,
|
||||
quindi è esposizione direzionale che `book.py` non netta e che nessun gate autorizza; coprirla con
|
||||
uno short perp è **CC01**, già misurato il 26/06 e parcheggiato come *"LEAD a scala ~$20k+"*.
|
||||
I custodi sono istituzionali, fuori portata a $2k e coperti dalla decisione *"100% Deribit fino a
|
||||
$20k"* — e comunque **nessuna via custodiale restituisce i reward USDC**, perché l'esclusione
|
||||
italiana è per giurisdizione.
|
||||
|
||||
Ma la ricerca ha trovato la voce che conta. Le valute a rendimento su Deribit sono quattro:
|
||||
|
||||
| valuta | APR pubblicata | emittente / natura | note |
|
||||
|---|---|---|---|
|
||||
| USDE | 4,2143% | Ethena, sintetico delta-hedged | **al tetto** (~31,2% dell'equity) |
|
||||
| USDC | 3,4000% | Circle / custodia Coinbase | **Italia esclusa → 0 sul nostro conto** |
|
||||
| **BUIDL** | **3,2018%** | **BlackRock, Treasury USA tokenizzati** | `BUIDL_USDC` liquido |
|
||||
| STETH | 2,2254% | Lido | è ETH, non un dollaro |
|
||||
|
||||
**Il punto non è il tasso, è l'emittente.** Oggi il conto ha **$1.412,71 di USDC che rendono
|
||||
esattamente zero** e un rischio emittente concentrato al 100% su Ethena. BUIDL è un R1
|
||||
**completamente diverso** (Treasury USA): non aggiunge un grammo di esposizione Ethena e va
|
||||
esattamente nella direzione che N4 indica — *la quota è l'unica leva contro il rischio emittente*.
|
||||
|
||||
## Cosa si compra e cosa resta
|
||||
|
||||
- USDC $1.412,71 · USDE 643,175691 ($643,18) · **totale $2.055,88**
|
||||
- cuscino di regolamento richiesto (`n_asset × frac × disaster_sl_pct` = 30% dell'equity): **$616,76**
|
||||
- **USDC libero sopra il cuscino: $795,94**
|
||||
|
||||
**Taglia del test: 500 BUIDL (~$500)** — la stessa del test USDE, per confrontabilità. Lascia USDC
|
||||
a ~$912, cioè **$295 di slack sopra il cuscino**: il vincolo di regolamento resta rispettato con
|
||||
margine. Costo: spread 4 bps ≈ **$0,20**, reversibile.
|
||||
|
||||
## Meccanica dichiarata (dall'articolo ufficiale, non dedotta)
|
||||
|
||||
> «BUIDL rewards are paid based on the **minimum equity** of BUIDL held in an account over the
|
||||
> preceding day. The minimum equity held over the preceding day must be **at least 1 BUIDL**.
|
||||
> The minimum equity calculation period is the **24 hours up to 20:00 UTC** each day. […] BUIDL
|
||||
> rewards are **only paid on weekdays** […] between **20:00 UTC and 23:00 UTC**. […] Deribit
|
||||
> charges an administrative fee of **5%**.»
|
||||
|
||||
Comprando **lunedì 31/08 mattina**, la finestra 20:00 dom → 20:00 lun ha minimo **0** (prima
|
||||
dell'acquisto non tenevamo BUIDL): **nessun reward atteso lunedì sera**. La prima finestra piena è
|
||||
20:00 lun → 20:00 mar, pagata **martedì 01/09 fra le 20:00 e le 23:00 UTC**.
|
||||
|
||||
Reward atteso su 500 BUIDL: `500 × 3,2018%/365` = **0,04386/giorno** lordo, ~**0,0417 netto** del
|
||||
fee 5%. BUIDL ha **6 decimali**: il segnale è ~40× la soglia di rumore che usiamo per l'USDE (1e-3).
|
||||
⚠️ *La APR pubblicata è un listino, non un incasso* — lezione pagata il 30/08 sull'USDC. Il numero
|
||||
da citare sarà quello **misurato**, con il suo `n`.
|
||||
|
||||
## 🔒 CRITERI PRE-REGISTRATI — dichiarati adesso, prima dell'esito
|
||||
|
||||
**GATE BUIDL-01, si legge il 2026-09-04.**
|
||||
|
||||
1. **ELIGIBILITÀ** *(la domanda primaria: la giurisdizione italiana esclude l'USDC, e per BUIDL
|
||||
l'articolo non pubblica alcuna lista — quindi è ignoto, non noto)*
|
||||
**≥1 reward BUIDL rilevato entro le 23:00 UTC del 2026-09-03 → IDONEO**; si apre la decisione
|
||||
di quota, che è dell'**operatore**. **Zero reward → NON IDONEO**: si riconverte in USDC e la
|
||||
pista si chiude, come si sarebbe chiusa quella USDE.
|
||||
Finestra: tre giorni feriali pieni (mar 01, mer 02, gio 03), robusta a una finestra mancata.
|
||||
Rilevazione **per delta** sull'equity BUIDL al netto dei trade — il gateway non espone il
|
||||
Transaction Log (debito #11), stesso metodo dichiarato dell'USDE, coi suoi limiti.
|
||||
Soglia di rilevazione **1e-3 BUIDL** (il reward atteso è 40×).
|
||||
|
||||
2. **TETTO DEL VENUE** — si sonda subito, col metodo **a saldo neutro** già validato sull'USDE
|
||||
(BUY rifiutato → SELL → BUY riaccettato → BUY rifiutato). Si registra il bracket **con l'equity
|
||||
del momento**, perché sull'USDE il tetto si è rivelato una **frazione** e non un livello: senza
|
||||
l'equity accanto il numero non è interpretabile (P7).
|
||||
|
||||
3. **HAIRCUT**: **dichiarato NON misurabile a questa taglia** e non vincolante — sull'USDE è stato
|
||||
misurato che non morde fino al 90% di quota, e qui il margine impegnato è ~$11 su $2.056.
|
||||
Non lo si stima al buio: si scrive che non lo sappiamo (D5).
|
||||
|
||||
**Cosa NON decide questo test**: la quota. Decide solo se la pista esiste. La quota è
|
||||
dell'operatore, e sull'USDE si è vista arrivare comunque dal venue.
|
||||
|
||||
## Sorveglianza
|
||||
|
||||
`balance_watch.py` (orario, minuto :42) viene esteso a **BUIDL**: registra il balance a 6-8
|
||||
decimali accanto a USDC e USDE, col conteggio dei fill dall'ultimo campione. È già dentro il
|
||||
perimetro di backup. La finestra dei reward BUIDL (20:00-23:00 UTC) è coperta da 4 campioni orari.
|
||||
|
||||
*Esito: da scrivere dopo il 2026-09-03.*
|
||||
|
||||
---
|
||||
|
||||
## ESITO (stesso giorno, 07:35-07:45Z) — **GATE BUIDL-01 CHIUSO: NON ENTRABILE**
|
||||
|
||||
Il gate si chiude in dieci minuti, e con un esito che **non era fra i due previsti**. I criteri
|
||||
pre-registrati contemplavano *"≥1 reward → IDONEO"* e *"zero reward → NON IDONEO"*. La realtà è una
|
||||
terza cosa: **non si riesce a comprare BUIDL affatto**, quindi la domanda sull'eligibilità ai reward
|
||||
è priva di oggetto. *Un gate può fallire sulla sua precondizione invece che sul suo criterio, e va
|
||||
scritto così invece di essere forzato in una delle caselle previste.*
|
||||
|
||||
### La misura
|
||||
|
||||
Undici ordini rifiutati, tutti con `not_enough_funds_in_currency`:
|
||||
|
||||
```
|
||||
BUY 500 · 250 · 125 · 62 · 31 · 15 · 7 · 3 @1.0009 -> rifiutati (scala per dimezzamento)
|
||||
BUY 1 @1.0006 (ask 1.0004 x 10.730) -> rifiutato
|
||||
BUY 1 @1.0024 (ask 1.0004 x 10.730) -> rifiutato
|
||||
BUY 1 @1.0100 (ask 1.0004 x 10.730) -> rifiutato
|
||||
```
|
||||
|
||||
Con **BUIDL 0,000000 in mano** e **USDC $1.412,32 disponibili**, per comprare **$1**. Esclusi uno
|
||||
per uno tutti i sospetti che sull'USDE avevano portato fuori strada:
|
||||
|
||||
| sospetto | escluso perché |
|
||||
|---|---|
|
||||
| prezzo non marcabile | limite **1,0100** contro ask **1,0004** — incrocia di 96 bps |
|
||||
| liquidità | **10.730** unità al miglior ask, 23.416 tre livelli sotto |
|
||||
| taglia | rifiutata **1 unità**, il minimo dello strumento |
|
||||
| fondi | **$1.412** disponibili per un ordine da **$1** |
|
||||
| tetto sul livello | **teniamo zero**: non c'è nulla da superare |
|
||||
| wallet inesistente | `account_summary("BUIDL")` risponde, equity 0.0 |
|
||||
|
||||
⇒ **Il conto non è abilitato ad acquisire BUIDL.** Costo del test: **$0** (nessun fill).
|
||||
|
||||
### 🚨 E la frase pubblicata è FALSA per il nostro conto
|
||||
|
||||
> «**All Deribit users are permitted to buy and sell BUIDL tokens in the spot markets on Deribit,
|
||||
> with no extra requirements.**» — `insights.deribit.com/education/buidl-launches-on-deribit/`
|
||||
|
||||
Non per noi. **Il fatto misurato è il rifiuto, non il suo motivo** (stessa disciplina dello zero
|
||||
USDC). Le spiegazioni candidate sono due, e da fuori **non sono distinguibili**:
|
||||
|
||||
- **(a) giurisdizione** — BUIDL è un **titolo** (fondo BlackRock emesso via Securitize), la cui
|
||||
distribuzione è ristretta a monte dell'exchange. L'articolo di marketing non lo dice.
|
||||
- **(b) limiti di spot per TIER DI CONTO** — la documentazione BUIDL, sezione *Limits and
|
||||
Requirements*: «*Default non-margin spot order limits apply across assets. Check your account
|
||||
tiers […] for specific maximum open order size configurations.*» Un tier che per questo asset
|
||||
vale **zero** produrrebbe esattamente ciò che vediamo.
|
||||
|
||||
⚠️ Ma i nostri dati **restringono**: lo **spot USDE funziona** — ne abbiamo comprati 644 lo stesso
|
||||
giorno, sullo stesso conto, con lo stesso codice. Quindi **non è un blocco generale sullo spot né
|
||||
un limite di tier trasversale: è SPECIFICO DELL'ASSET.** Il che è compatibile con entrambe le
|
||||
ipotesi (una giurisdizione per-asset, o un tier per-asset a zero) e non ne elegge nessuna.
|
||||
*Restringere le ipotesi con l'evidenza che si ha vale più che sceglierne una con l'evidenza che
|
||||
non si ha.*
|
||||
|
||||
Irrilevante per noi ma registrato: «*Deribit only supports the ERC-20 version of BUIDL on the
|
||||
Ethereum blockchain*» — riguarda i trasferimenti on-chain, e la nostra uscita sarebbe comunque
|
||||
una vendita sullo spot, non un prelievo.
|
||||
|
||||
📌 **Due affermazioni pubblicate di Deribit smentite dal conto in due giorni**: la APR USDC al
|
||||
3,40% (30/08) e ora «all users can buy BUIDL». Non è sfortuna, è una **regola**:
|
||||
|
||||
> **Una capacità pubblicata dal venue non è una capacità del conto. Si verifica sul CONTO, prima
|
||||
> che entri in un piano — e costa un ordine da $1.**
|
||||
|
||||
È N10 portata un passo più in là: la verifica a €0 va fatta prima, si fa **sul venue e non sul
|
||||
sito** — e adesso si sa che nemmeno il venue basta: serve il **conto**.
|
||||
|
||||
### Il sottoprodotto che vale più del test
|
||||
|
||||
`not_enough_funds_in_currency` è il **messaggio generico di Deribit per «non puoi acquisire altra
|
||||
di questa valuta»**, qualunque sia il motivo — permesso, giurisdizione, o tetto. Lo si è visto ora
|
||||
su BUIDL (permesso assente, zero in mano) e il 30-31/08 su USDE (tetto a ~31,2% dell'equity). Il
|
||||
messaggio parla di **fondi** e i fondi non c'entrano mai. Chi lo incontra in futuro deve saltare
|
||||
direttamente alla domanda giusta — *questa valuta la posso acquisire, e fino a quanto?* — invece di
|
||||
inseguire taglia, cadenza e prezzo come è successo il 30/08.
|
||||
|
||||
### Cosa resta
|
||||
|
||||
- **Pista BUIDL chiusa.** Non riapribile da noi: dipende da un permesso del venue, non da una
|
||||
nostra scelta. La riapre solo un cambio lato Deribit.
|
||||
- **`balance_watch` tiene BUIDL nella lista** (costo: una lettura oraria). Se l'accesso si aprisse,
|
||||
un balance diverso da zero lo direbbe da solo — la stessa logica per cui l'USDC resta sorvegliato
|
||||
malgrado lo zero misurato.
|
||||
- **L'USDE resta l'unico collaterale a rendimento che il conto può usare**, al suo tetto di ~31,2%,
|
||||
e i **$1.412 di USDC continuano a rendere zero**. Non per mancanza di alternative: perché le tre
|
||||
alternative sono chiuse una per giurisdizione (USDC), una per permesso (BUIDL) e una perché non è
|
||||
un dollaro (stETH).
|
||||
@@ -0,0 +1,426 @@
|
||||
# 2026-08-31 — Fine della Proof of Reserves, e il guardiano che mancava
|
||||
|
||||
L'operatore ha passato l'annuncio *Deribit to Discontinue Daily Proof of Reserves Publication*.
|
||||
La notizia in sé vale poco per noi. Quello che è emerso cercandola vale molto di più.
|
||||
|
||||
## 1. La notizia: frequenza giù, sostanza su, per noi nulla cambia
|
||||
|
||||
Dal **1 settembre 2026** la pagina Proof of Reserves sparisce e gli aggiornamenti quotidiani
|
||||
cessano. Al loro posto, sotto il regime **VARA** di Dubai: audit **annuale** indipendente delle
|
||||
riserve, audit annuale del bilancio, obbligo di **copertura 100%** e **segregazione** degli attivi
|
||||
dei clienti. E: «*approximately 90% of client assets have been migrated to Coinbase, which acts as
|
||||
a custodian for Deribit*».
|
||||
|
||||
**Non è un peggioramento netto, ed è importante non raccontarlo come tale.** Si perde un segnale
|
||||
**ad alta frequenza e debole** (una PoR auto-pubblicata prova che gli attivi esistono in un istante,
|
||||
non che coprano le passività, ed è aggirabile attorno allo snapshot) e si guadagna un segnale **a
|
||||
bassa frequenza e più forte** (audit indipendente obbligatorio invece di pubblicazione volontaria),
|
||||
più un miglioramento **sostanziale** di custodia: 90% degli attivi presso un custode terzo regolato
|
||||
invece che nei wallet dell'exchange.
|
||||
|
||||
**Per noi non cambia nulla di operativo**, e per una ragione che va detta: *la PoR non l'abbiamo
|
||||
mai sorvegliata*. Zero occorrenze in tutto il codice. `venue_probe` legge se il venue **risponde**,
|
||||
non cosa **dichiara**. Si perde un segnale che non stavamo usando.
|
||||
|
||||
Ho provato a catturare l'ultimo dato prima che la pagina sparisse — è la mossa giusta per un dato
|
||||
non ricostruibile con una scadenza. **Rinunciato di proposito**: la pagina è una SPA e
|
||||
`get_proof_of_reserves` non esiste come metodo API («Method not found»), ma soprattutto uno
|
||||
snapshot singolo e auto-riportato non regge lo standard di prova di questo progetto e **non
|
||||
permetterebbe comunque di stimare `p`**. Un dato che non cambierebbe una decisione non vale il
|
||||
lavoro per prenderlo.
|
||||
|
||||
## 2. 📌 La conseguenza vera: il riapritore della decisione più grande si è ristretto
|
||||
|
||||
La decisione vincolante **«100% Deribit fino a $20k»** (26/07) ha come riapritori dichiarati:
|
||||
*«**$20k**, o un cambio di piano, o `p` che diventa **stimabile** invece che assunto»*.
|
||||
|
||||
Dal 1 settembre il segnale pubblico di solvibilità passa da **quotidiano ad annuale**. Il terzo
|
||||
riapritore non è formalmente chiuso — un audit indipendente è semmai evidenza più forte — ma
|
||||
diventa **praticamente irraggiungibile**: da una serie giornaliera si può costruire una stima, da
|
||||
un punto all'anno no. ⇒ **In pratica quella decisione è ora gated SOLO dal capitale**: si riapre a
|
||||
$20k, o non si riapre. Non cambia la decisione di oggi (fu presa con `p` esplicitamente **assunto**,
|
||||
non osservato), ma chiude l'unica uscita non-capitale che aveva.
|
||||
|
||||
## 3. 🚨 Quello che ho trovato cercando: quattro annunci materiali che nessuno leggeva
|
||||
|
||||
Il debito #8 dice, testuale: *«Resta scoperto il Rulebook vero e proprio: la sonda legge se il
|
||||
venue RISPONDE, non cosa il venue ANNUNCIA»*. Esiste un feed RSS pubblico
|
||||
(`insights.deribit.com/exchange-updates/feed/`) con 10 voci. Alla prima lettura:
|
||||
|
||||
| data | annuncio | perché ci tocca |
|
||||
|---|---|---|
|
||||
| 27/08 | Discontinue Daily Proof of Reserves | §1 e §2 di questo diario |
|
||||
| **14/08** | **Contract Specifications change for Linear USDC Perpetuals** | **i nostri due strumenti** |
|
||||
| 05/08 | New SM Margin Model On Deribit | leva per taglia, tiered |
|
||||
| **31/07** | **USDC Rewards Available In More Countries** | tocca una conclusione viva |
|
||||
| 29/06 | New Fee Schedule On Deribit | `fee_watch` esiste per questo |
|
||||
|
||||
**Il caso che decide la questione è il 14/08.** Le specifiche dei perpetual USDC sono cambiate il
|
||||
**18/08**; l'annuncio è del **14/08**. Il codice lo racconta già con onestà: la tabella «*non se
|
||||
n'era accorta […] è andata bene per la direzione del cambiamento, non perché ce ne fossimo
|
||||
accorti*» — i cambi erano **riduzioni** (tick BTC 0,5→0,1, ETH 0,05→0,01, min/step ETH
|
||||
0,001→0,0001) e un valore più grosso resta conforme. `check_specs()` è **nato da quella svista** e
|
||||
oggi gira ogni ora. Ma rileva la deriva **dopo**; il feed l'avrebbe detta **quattro giorni prima**.
|
||||
*Il giorno che Deribit ALZA un minimo, «dopo» significa ordini rifiutati.*
|
||||
|
||||
Il 31/07 chiude un cerchio aperto ieri: l'espansione delle giurisdizioni idonee ai reward USDC
|
||||
**non contiene l'Italia**, né alcun paese UE — mentre **San Marino e Città del Vaticano** (i due
|
||||
microstati europei fuori dall'UE) **sono idonei**. La lista che avevo letto (aggiornata 20/08) è
|
||||
posteriore all'espansione: **la conclusione dello zero USDC è confermata due volte**. Il pattern
|
||||
UE-fuori/microstati-dentro suggerisce un driver regolatorio, ma la lista contiene anche Canada e
|
||||
Giappone: **non asserisco una causa**, il fatto è l'esclusione.
|
||||
|
||||
## 4. Costruito: `venue_news.py`
|
||||
|
||||
Sorveglianza giornaliera del feed, dentro `cron_daily.sh` accanto a `fee_watch` (suo parente
|
||||
stretto). Scelte che contano:
|
||||
|
||||
- **NON interpreta.** Dice *«è uscito questo, guardalo»*. Nessun automatismo su un testo di
|
||||
marketing: P13 — una guardia sui NUMERI non copre il RAGIONAMENTO, e la prosa si legge come
|
||||
opinione di un lettore fallibile. L'unica cosa che classifica è l'**urgenza**.
|
||||
- **Le parole-chiave sono DERIVATE, non ridichiarate** (P1, il difetto più ricorrente del
|
||||
progetto): gli strumenti vengono da `deribit._CONTRACT`, la valuta di collaterale da
|
||||
`config/live.json`. *Chi aggiunge un asset al book allarga la sorveglianza senza toccare questo
|
||||
file* — ed è esattamente il test che lo blinda.
|
||||
- **Primo giro semina senza allertare** (P9: l'allarme massimo non si spende per un arretrato di
|
||||
10 voci, o non verrà letto il giorno che è vero).
|
||||
- **Feed illeggibile → codice 2 e nessun silenzio implicito** (P5: «non vedo» non è «niente di
|
||||
nuovo»).
|
||||
|
||||
5 test sulle funzioni pure. La rete no: `scarica()` ritorna `None` su qualunque errore, ed è quello
|
||||
il contratto.
|
||||
|
||||
**Cosa NON copre**, e va detto: il **Rulebook** vero e proprio (ADL, perdita socializzata,
|
||||
*emergency powers*, conti dormienti) non ha un feed. Il debito #8 si restringe, non si chiude.
|
||||
|
||||
---
|
||||
|
||||
## 5. La Knowledge Base risponde a due domande aperte — e corregge due cose mie
|
||||
|
||||
L'operatore ha passato anche *yield-generating-collateral-usde-buidl-and-more*, che **non** documenta
|
||||
tetti né haircut ma rimanda alla Knowledge Base. Interrogata via API Zendesk
|
||||
(`support.deribit.com/api/v2/help_center/articles/search.json`), l'articolo giusto è
|
||||
**«Cross collateral specifications»** (aggiornato 2026-02-20).
|
||||
|
||||
### (a) 🚨 Il costo del saldo negativo: **0,05% al GIORNO**
|
||||
|
||||
> «*While the equity of a currency in an account remains negative, a **collateral fee** will be
|
||||
> charged to that account. This fee is charged daily in the same currency as the negative balance
|
||||
> (**default = 0.05% per day**). The fee is charged based on the amount of time the negative equity
|
||||
> is held, down to a granularity of seconds.*»
|
||||
|
||||
**18,25% annuo**, cioè **4,3× la resa USDE** che si starebbe comprando tenendo meno USDC. Il 30/08
|
||||
avevo scritto «Deribit lo finanzia a interesse» **senza il numero** — l'affermazione era giusta e
|
||||
vuota. Con il numero il criterio del **cuscino di regolamento** smette di essere prudenza e diventa
|
||||
aritmetica: scendere sotto il cuscino per tenere più USDE è **−14 punti**.
|
||||
|
||||
E il ribilanciamento automatico **non salva**: scatta solo oltre **$1.000.000** assoluti o il
|
||||
**100% della cross equity** (default tabulati), soglie che a $2k non si toccano mai. Non veniamo
|
||||
ribilanciati: **sanguiniamo la fee**. *La cosa che sembrava un paracadute è, alla nostra taglia,
|
||||
esattamente l'assenza di un paracadute.*
|
||||
|
||||
### (b) 🚨 Haircut USDe: la fonte dice **5%**, noi abbiamo registrato **10%**
|
||||
|
||||
| valuta | haircut X:PM | X:SM |
|
||||
|---|---|---|
|
||||
| BTC · ETH · **USDC** | — | — |
|
||||
| USDT · USYC · **BUIDL** | 2% | 2% |
|
||||
| PAXG | 2,5% | 5% |
|
||||
| **USDe** | **5%** | **5%** |
|
||||
| stETH | 7,5% | 7,5% |
|
||||
| SOL | — | 15% |
|
||||
|
||||
`config/live.json` dice `haircut: 0.10`, e il diario del 26/08 lo dà per «verificato sul venue»
|
||||
senza lasciare traccia di **come**. L'articolo è **anteriore** a quella data, quindi non si sa
|
||||
quale delle due sia stale.
|
||||
|
||||
**Non l'ho riparato** (P12: fra due fonti che non concordano, una riparazione silenziosa è
|
||||
un'invenzione; M28: la contraddizione è informazione). Si tiene **0,10** per tre ragioni dichiarate:
|
||||
è il lato **conservativo** (sottostima il margine utilizzabile), **non è sul percorso soldi** (non
|
||||
entra nel sizing — lo usa solo il report di `usde_watch`), ed è già misurato che **non morde a
|
||||
nessuna quota fino al 90%**. Si chiude leggendo la pagina margini del conto, che è dove Deribit
|
||||
stesso dice di guardare.
|
||||
|
||||
### (c) Un dato di lato che vale per il futuro
|
||||
|
||||
**BUIDL ha haircut 2%** — sarebbe stato il *miglior* collaterale a rendimento del listino (contro
|
||||
il 5% dell'USDe), se solo lo si potesse comprare. E **stETH 7,5%**, il peggiore: terza ragione
|
||||
indipendente per lasciarlo stare, dopo il tasso dimezzato e l'esposizione ETH.
|
||||
|
||||
📌 **Nessuna delle due fonti documenta un tetto sulle quantità detenibili.** Il ~31,2% sull'USDE
|
||||
resta **misurato e non spiegato** — e ora si sa che non è una svista di lettura: non è scritto da
|
||||
nessuna parte.
|
||||
|
||||
### (d) Tentata la chiusura della divergenza sull'haircut: **non è raggiungibile da qui**
|
||||
|
||||
Deribit dice: «*Haircut rates can be seen on the margin page in your account*». Quella pagina è web
|
||||
UI, e le credenziali vivono solo dentro il gateway (debito #11). Provato comunque:
|
||||
|
||||
- `account_summary` per la valuta **USD** (la "riga USD" che il doc menziona) → `Invalid currency`;
|
||||
- parametro **`extended`** (che su Deribit apre i dettagli di margine) → **ignorato**;
|
||||
- il gateway filtra a **8 campi**: niente `initial_margin`, niente `maintenance_margin`.
|
||||
|
||||
E non è che si sia guardato nel secchio sbagliato — **la contabilità torna esatta senza haircut**:
|
||||
|
||||
```
|
||||
USDC equity 1412.281074 available 1400.728093 riservato 11.5530
|
||||
USDE equity 643.175691 available 643.175691 riservato 0.0000
|
||||
posizioni lorde $577.65
|
||||
|
||||
riservato in USDC ........... $11.5530
|
||||
IM di posizione al 2% ....... $11.5530
|
||||
RESIDUO ..................... $-0.0000
|
||||
```
|
||||
|
||||
Un haircut al **5%** chiederebbe **$32,16** accantonati, al **10%** ne chiederebbe **$64,32**: non
|
||||
ci sono, e il secchio USDE riserva `0.000000`. ⇒ **L'haircut non è osservabile in alcun campo
|
||||
esposto.** O è applicato solo nella vista cross/USD che il gateway filtra via, o non è applicato al
|
||||
nostro conto: da qui **le due cose non si distinguono**, e non le si sceglie tirando a indovinare.
|
||||
|
||||
**La divergenza resta APERTA**, ma ora con la ragione misurata invece che supposta. La chiudono due
|
||||
cose, entrambe dell'operatore: **30 secondi sulla pagina margini** della web UI, oppure delle
|
||||
**chiavi API Deribit** — che è la decisione già dichiarata nel debito #11, non un refactor.
|
||||
Nel frattempo **non morde nulla di osservabile**: l'IM è il 2% del nozionale e il valore pieno
|
||||
dell'USDE resta disponibile, quindi 5% o 10% non cambia una singola cifra operativa.
|
||||
|
||||
📌 **Sottoprodotto non cercato**: $11,5530 / $577,65 = **2,0000% = esattamente 1/50**. È il
|
||||
`C1 = 50` (start leverage, tier 1) del **nuovo modello di margine SM** annunciato il 05/08 — quello
|
||||
che `venue_news` ha appena tirato fuori dal feed. **Confermato sul nostro conto senza averlo
|
||||
cercato**, ed è la prima volta che un annuncio del venue viene verificato contro il conto invece
|
||||
che creduto.
|
||||
|
||||
### (e) L'operatore dichiara: il conto è **Standard Margin** — due conferme incrociate
|
||||
|
||||
Lo screenshot della pagina margini non è arrivato fin qui (sta sul desktop dell'operatore, non
|
||||
sulla VPS: il percorso non esiste su questa macchina). Ma il fatto dichiarato — **«è attivo
|
||||
Standard margin»** — aggancia due cose che erano sospese:
|
||||
|
||||
1. **Quale colonna leggere.** La tabella KB ha `Haircut (X:PM)` e `Haircut (X:SM)`. Il conto è
|
||||
**X:SM**, quindi vale la seconda — che per USDe dice **5%**, come la PM. La divergenza col
|
||||
nostro `0.10` resta quindi intatta, ma ora si sa con certezza *quale* numero ufficiale la
|
||||
contraddice, invece di doverne scegliere uno fra due colonne.
|
||||
2. **Perché l'IM misurata era esattamente 1/50.** Il nuovo modello di margine del 05/08 si applica,
|
||||
testuale, ai *«standard margin accounts»*. Il conto è standard margin, e noi abbiamo misurato
|
||||
IM = **2,0000%** del nozionale = **1/50** = il `C1 = 50` di tier 1. **Le due cose si confermano
|
||||
a vicenda**: un fatto dichiarato dall'operatore e una misura fatta sul conto senza conoscerlo.
|
||||
|
||||
*Vale la pena notarlo perché è raro: in tutta questa settimana ogni affermazione pubblicata dal
|
||||
venue si è rivelata falsa per il nostro conto (APR USDC, «all users can buy BUIDL»). Questa è la
|
||||
prima che il conto conferma.*
|
||||
|
||||
**Cosa manca ancora**, e resta l'unica cosa che chiude la divergenza: il numero di haircut per USDe
|
||||
**mostrato sulla pagina Standard Margin del conto**. Se dice 5%, la nostra config è stale e il 26/08
|
||||
registrò male; se dice 10%, la KB è stale e la config ha ragione. Finché non c'è, si tiene 0.10 —
|
||||
lato conservativo, fuori dal percorso soldi, e **senza un solo effetto operativo misurabile**.
|
||||
|
||||
---
|
||||
|
||||
## 6. La pagina margini arriva davvero — e la seconda cosa che dice vale più della prima
|
||||
|
||||
Lo screenshot era su **Wasabi** (`rclone` remote `wasabi:`, bucket `adp-work`, cartella `_scambio`),
|
||||
non sulla VPS: per questo il percorso non esisteva. Scaricato e letto.
|
||||
Riconciliazione riproducibile in **`scripts/research/r0831_margini_conto.py`** (N11).
|
||||
|
||||
### (a) ✅ Haircut = **5%**. La nostra config aveva torto.
|
||||
|
||||
La pagina **non espone l'haircut come numero**: si ricava per differenza, perché il modello CROSS
|
||||
conta l'USDE scontato e il SEGREGATO non lo conta affatto.
|
||||
|
||||
```
|
||||
S:SM (attivo) USDC Available 1,400.86 · USDC equity 1,412.28 − IM 11.55 = 1,400.73 (scarto $0.13)
|
||||
X:SM CROSS Available 2,011.73
|
||||
contributo USDE al cross = 2,011.73 − (1,412.28 + 0.20 − 11.55) = $610.81 su $643.05 di valore
|
||||
→ haircut implicito 5,0137%
|
||||
ipotesi 5% (KB) → atteso $610.89 scarto $ 0.09 ✅
|
||||
ipotesi 10% (nostra) → atteso $578.74 scarto $32.06 ❌
|
||||
```
|
||||
|
||||
`config/live.json` passa da `0.10` a **`0.05`**. Il 26/08 il 10% fu registrato come «verificato sul
|
||||
venue» **senza lasciare traccia di come**, e non lo era. *Una nota di provenienza che dice «verificato»
|
||||
senza dire con quale lettura non è provenienza: è la stessa parola usata come garanzia.*
|
||||
|
||||
### (b) 🚨 Il conto **non è cross-collateral**. È `Segregated: Standard Margin`.
|
||||
|
||||
Lo dice la schermata in cima — *Current status: **Segregated: Standard Margin*** — e la tabella del
|
||||
modello attivo elenca **BTC, ETH, USDC. L'USDE non c'è.**
|
||||
|
||||
⇒ **L'USDE non fa margine per i perp USDC-settled del book**, e l'haircut **oggi non si applica
|
||||
affatto**. Il che spiega, a posteriori e in modo pulito, perché la misura di stamattina trovava
|
||||
**$0,0000** accantonati: non stavo guardando nel secchio sbagliato, *stavo cercando un parametro
|
||||
che sul nostro conto non è in vigore*.
|
||||
|
||||
**NON MORDE**, e va detto subito per non allarmare: al massimo lordo del libro (1,0x ≈ $2.056 di
|
||||
nozionale) l'IM sarebbe ~$41 contro **$1.400 di USDC disponibile** — 34× di copertura. Nessuna
|
||||
decisione operativa cambia oggi.
|
||||
|
||||
**Ma la premessa scritta in CLAUDE.md era falsa**, e il modo in cui era falsa è istruttivo. Diceva:
|
||||
*«l'equity del book è il TOTALE cross-collateral»*. Sono due cose diverse messe sotto un nome solo:
|
||||
|
||||
- come **ricchezza**, sommare USDC+USDE è **giusto** — ed era il punto della riparazione del 26/08,
|
||||
che evitò il falso «USCITA DI FONDI −24%» e una vendita indesiderata;
|
||||
- come **capacità di margine**, è **sbagliato**: nel modello attivo l'USDE vale zero.
|
||||
|
||||
Finché il margine non morde le due coincidono nell'uso, e infatti non è mai emerso. *Un errore che
|
||||
non ha conseguenze finché una terza cosa resta vera è un debito, non un'assoluzione.* Corretto in
|
||||
CLAUDE.md, e corretta anche la riga di `usde_watch` che stampava «margine utilizzabile ~$2.013
|
||||
(haircut 10%)»: era falsa due volte insieme.
|
||||
|
||||
### (c) Come cambia il modo di pensare alla quota USDE
|
||||
|
||||
Non è «collaterale diversificato»: è **cassa messa da parte a rendimento, fuori dal sistema di
|
||||
margine**. Il che rende il tetto del venue al ~31,2% meno una limitazione e più un caso fortunato —
|
||||
tiene automaticamente dentro al 31% la parte di conto che non lavora come margine.
|
||||
|
||||
E apre una **decisione dell'operatore**, non mia: passare a **X:SM** aggiungerebbe **$610,87** di
|
||||
margine utilizzabile, ma porta con sé la meccanica cross — *collateral fee 0,05%/giorno* sul saldo
|
||||
negativo e ribilanciamento automatico. Oggi non serve (34× di copertura); servirebbe solo a leve
|
||||
che non sono autorizzate.
|
||||
|
||||
📌 **Ipotesi nuova sul tetto del ~31,2%**, non verificata: potrebbe dipendere proprio dal modello
|
||||
segregato. Si saprebbe passando a X:SM e ri-sondando — un esperimento che ora ha un senso, dove
|
||||
prima non si sapeva nemmeno cosa variare.
|
||||
|
||||
---
|
||||
|
||||
## 7. Passaggio a X:SM e ri-sondaggio del tetto — l'ipotesi regge tecnicamente e cade in pratica
|
||||
|
||||
L'operatore è passato a **Cross: Standard Margin** e ha chiesto di ri-sondare, per verificare
|
||||
l'ipotesi lasciata aperta in §6(c): *il tetto del ~31,2% dipende dal modello segregato?*
|
||||
|
||||
### Il passaggio è verificabile dal gateway, senza fidarsi di una dichiarazione
|
||||
|
||||
`available_funds` USDC è passato da **~$1.400** a **$2.011,09**, contro **$2.010,90** attesi
|
||||
sommando l'USDE scontato al 5% — **scarto $0,19**. È anche la **seconda conferma indipendente
|
||||
dell'haircut al 5%**, da una lettura completamente diversa dallo screenshot: due strade, stesso
|
||||
numero.
|
||||
|
||||
### Il risultato
|
||||
|
||||
| modello | tetto misurato | equity | frazione |
|
||||
|---|---|---|---|
|
||||
| Segregato **S:SM** (31/08) | [643,18 – 644,18) | $2.055,56 | 31,29% – 31,34% |
|
||||
| Cross **X:SM** (31/08, dopo) | [654,18 – 655,18) | $2.054,90 | **31,84% – 31,88%** |
|
||||
|
||||
**+0,55pp = +11 USDE ≈ $11.** ⇒ **L'ipotesi è vera e inutile**: il modello di margine *entra* nel
|
||||
tetto, ma non lo spiega. Per arrivare al 70% mancano ancora ~38 punti, un fattore **2,2×**.
|
||||
*Un'ipotesi confermata al terzo decimale e falsa all'ordine di grandezza va archiviata come falsa:
|
||||
il tetto resta misurato e non spiegato.*
|
||||
|
||||
### 🚨 La lezione di metodo, che vale più del risultato
|
||||
|
||||
Il **primo probe fu un BUY 20, rifiutato**. Da solo avrebbe chiuso la questione con *"tetto
|
||||
invariato, l'ipotesi è refutata"* — e sarebbe stato **falso**. Il tetto si era mosso di 11, cioè
|
||||
meno della taglia del probe. È stato il test a **saldo neutro con passi piccoli** (BUY 1 → SELL 10
|
||||
→ BUY 10 → BUY 1, tutti accettati) a rivelarlo.
|
||||
|
||||
📌 **Un probe unico di taglia sbagliata produce un falso negativo che si legge come risultato.**
|
||||
La taglia del sondaggio va scelta **sulla risoluzione dell'effetto che si cerca**, non su ciò che
|
||||
è comodo — ed è M13 in una veste nuova: *un criterio si misura sulla sua risoluzione prima che sul
|
||||
suo esito*.
|
||||
|
||||
### Cosa ha comprato il passaggio, e cosa è costato
|
||||
|
||||
- **Comprato**: **$611** di margine utilizzabile — inutile a 0,28x di leva, serve solo sopra ~1,4x
|
||||
che non è autorizzata — più **$11** di capienza sul tetto.
|
||||
- **Costato**: sotto segregato l'USDE era **ring-fenced** dalle perdite del libro (solo il silo
|
||||
USDC rispondeva delle posizioni); sotto cross **risponde l'intero conto**. Non morde oggi (perdita
|
||||
massima plausibile ~$617 col disaster-SL, dentro il solo USDC), ma **il rischio strutturale ha
|
||||
cambiato verso**. Se un giorno il cross non servisse più, tornare a S:SM ri-recinta l'USDE.
|
||||
|
||||
`config`: `venue_cap_frac` 0.312 → **0.318** (bordo basso del bracket CROSS, che è il modello
|
||||
attivo). Se si torna a S:SM va rimesso a 0.312.
|
||||
|
||||
## 8. **X:PM valutato e SCARTATO** — la risposta era già nello screenshot
|
||||
|
||||
L'operatore ha chiesto di provare **Cross: Portfolio Margin**. Non serve provarlo: il confronto è
|
||||
nella schermata di §6.
|
||||
|
||||
| modello | Available Balance | IM | MM |
|
||||
|---|---|---|---|
|
||||
| **X:SM** (attivo) | **$2.011,73** | 2,13% | **0,37%** |
|
||||
| **X:PM** | **$1.935,14** | 5,85% | **3,43%** |
|
||||
|
||||
Sulla colonna il cui significato è inequivocabile — stessa riga, stesse unità — **X:PM dà $76,59
|
||||
di collaterale utilizzabile in MENO**, e chiede più margine. ⚠️ *La frase «su entrambe le misure»
|
||||
è stata corretta in §9: l'«IM %» stampata dalla pagina non è il margine delle posizioni.
|
||||
La conclusione non cambia — migliora.*
|
||||
|
||||
**Perché**, ed è strutturale e non contingente: il Portfolio Margin è **basato su scenari di
|
||||
rischio** e premia i portafogli in cui il rischio si **compensa**, tipicamente i book di opzioni.
|
||||
Il nostro è **due long direzionali nudi senza nulla che compensi** — il caso peggiore per PM, che
|
||||
addebita lo scenario di stress invece di un'aliquota piatta.
|
||||
|
||||
**La riga che decide è la MM: 3,43% contro 0,37%, ~9×.** Il maintenance margin è ciò che innesca la
|
||||
liquidazione: non morde a 0,28x, ma sposta il punto di liquidazione molto più vicino su un libro
|
||||
con soldi veri. E il beneficio atteso sul tetto è noto per analogia: il salto S:SM→X:SM ne ha
|
||||
comprato **$11**.
|
||||
|
||||
⇒ **SCARTATO.** ⚠️ *Non "PM è peggio", ma "PM è peggio PER QUESTO portafoglio"*:
|
||||
**cosa lo riapre** — il giorno che VRP01 o un altro sleeve di opzioni entra in deploy, il rischio
|
||||
inizia a compensarsi e quel confronto **cambia di segno**. Va rifatto allora, non prima.
|
||||
|
||||
---
|
||||
|
||||
## 9. X:SM attivo: la pagina si ricostruisce dal gateway — e una mia lettura era sbagliata
|
||||
|
||||
L'operatore ha incollato la riga CROSS col modello **attivo** invece che proiettato:
|
||||
`Available $2.010,81 · IM 2,15% · MM 0,37%`. Il gateway, letto **allo stesso minuto** (08:34Z), dice
|
||||
`available_funds` = **2010,82299383**. **Scarto $0,01.**
|
||||
|
||||
### (a) Lo screenshot non serve più
|
||||
|
||||
⇒ **La riga CROSS della pagina È `available_funds`**, che leggiamo da soli a ogni giro. Ieri quella
|
||||
schermata era l'**unica** fonte per l'haircut e per il modello attivo, e per averla è servito un
|
||||
passaggio dall'operatore e da Wasabi. Oggi non serve a niente: `r0831_margini_conto.live()` la
|
||||
ricostruisce.
|
||||
|
||||
### (b) Terza conferma del 5%, e la prima ESATTA
|
||||
|
||||
1.400,71 + 0,95 × 654,176 + 0,20 dust − 11,553 IM = $2.010,82 pagina $2.010,81
|
||||
|
||||
**Scarto $0,007.** E il 10% della vecchia config non è *improbabile*: invertendo la stessa riga dà
|
||||
**IM = −$21,16**, cioè **aritmeticamente impossibile**. Le tre strade — differenza fra le righe
|
||||
della pagina (31/08 07:50), salto di `available_funds` al passaggio S:SM→X:SM ($0,19), e ora questa
|
||||
ricostruzione al centesimo — sono indipendenti e danno lo stesso numero.
|
||||
|
||||
### (c) 🚨 L'«IM %» della pagina NON è il margine delle posizioni — mia lettura sbagliata
|
||||
|
||||
`(margin_balance − available)/margin_balance = 2,1532%`, ed è **esattamente** ciò che la pagina
|
||||
stampa come «IM 2,15%». Ma si scompone così:
|
||||
|
||||
| voce | USD | % di margin_balance |
|
||||
|---|---|---|
|
||||
| haircut sull'USDE (5% × 654,18) | $32,71 | **1,592%** |
|
||||
| IM vera delle due posizioni | $11,55 | 0,562% |
|
||||
| **totale riservato** | **$44,26** | **2,153%** |
|
||||
|
||||
⇒ **il 74% di quella percentuale è haircut, non rischio.** È salita da 2,13% a 2,15% **perché
|
||||
abbiamo comprato collaterale a rendimento** — cioè per un motivo che con l'esposizione del libro
|
||||
non c'entra nulla. *Letta come misura di rischio direbbe che ieri sera abbiamo alzato la leva
|
||||
comprando USDE: l'opposto di quello che è successo.*
|
||||
|
||||
La **MM invece è pulita**: 0,37% = $7,60, e non può contenere l'haircut ($32,71 da solo la
|
||||
renderebbe negativa). **Le due colonne hanno basi diverse**, e la pagina non lo dice.
|
||||
|
||||
📌 Regola: *una percentuale letta da una schermata va **invertita nella sua definizione** prima di
|
||||
essere confrontata.* Due colonne affiancate, con la stessa unità e lo stesso aspetto, possono avere
|
||||
denominatori — e qui numeratori — diversi. È P7 su una superficie nuova: un numero si etichetta
|
||||
con la sua configurazione, e una pagina del venue non è obbligata a farlo per noi.
|
||||
|
||||
### (d) La decisione su X:PM esce RAFFORZATA
|
||||
|
||||
La KB dà **USDe al 5% sotto entrambi i modelli** (§6b): nel confronto l'haircut **si cancella**, e
|
||||
l'inversa restituisce il margine di posizione puro sulle stesse due posizioni, stesso istante:
|
||||
|
||||
| modello | available | **IM di posizione** |
|
||||
|---|---|---|
|
||||
| X:SM | $2.011,73 | **$11,64** |
|
||||
| X:PM | $1.935,14 | **$88,23** |
|
||||
|
||||
**PM chiede 7,6× il margine iniziale e 9,3× la MM.** Due misure ora pulite, stesso verso: la
|
||||
bocciatura di §8 **regge, meglio fondata di quando l'ho scritta**.
|
||||
|
||||
E c'è una conferma che ieri mancava: la riga X:SM era una **proiezione** di un modello **inattivo**,
|
||||
e oggi che è attivo la stessa aritmetica la riproduce al centesimo. ⇒ **anche la riga X:PM, che
|
||||
resta una proiezione, è affidabile**: la decisione presa senza provare il modello era presa su un
|
||||
numero buono. *Non provare X:PM è stato corretto, e ora si sa perché e non solo che.*
|
||||
@@ -0,0 +1,238 @@
|
||||
# 2026-09-01 — COLLAR01: il collar riduce il DD, il tetto lo paga troppo, e ciò che vince è VRP01 travestito
|
||||
|
||||
> ⚠️ **Titolo corretto lo stesso giorno.** Diceva *«il pavimento funziona»*: la riduzione di DD è del
|
||||
> **collar**, non del pavimento — la put da sola lo peggiora in 16/16 celle (§72, sezione A1 sotto).
|
||||
|
||||
*Scritto il 2026-09-01. Ogni numero è riprodotto da `scripts/research/r0901_btc_collar.py`.
|
||||
**Libro, pesi, cron, config INVARIATI. Nessun ordine.***
|
||||
|
||||
## 0. La domanda, come è arrivata
|
||||
|
||||
> *«crea una strategia di hold BTC in long o short con copertura con options (max 15gg)»*
|
||||
> *«opzione deve essere al max di 15gg»*
|
||||
> *«voglio ridurre la vincita, ma bloccare la perdita»*
|
||||
> *«ovviamente l'entrata deve essere gestita da una strategia confortata da indicatori (es. è in forte bull)»*
|
||||
> *«dalle opzioni dobbiamo uscire prima del termine (tra 50% e 75% del tempo)»*
|
||||
|
||||
Le tre precisazioni cambiano l'oggetto, e vanno lette insieme: **non è una put protettiva, è un
|
||||
COLLAR** (pavimento comprato, tetto venduto per finanziarlo), su un **hold gated da indicatori**,
|
||||
con **scadenza ≤15 giorni**.
|
||||
|
||||
## 1. Perché questo filone si poteva riaprire
|
||||
|
||||
§46 (TAIL-HEDGE, 2026-08-23) è **REFUTATO** e la memoria dice di battere il motivo, non di
|
||||
ripetere la misura. Il motivo di §46, verbatim:
|
||||
|
||||
> *il maxDD **SALE in 162/162 celle**, a ogni lente di f, perché il **beta del libro al sottostante
|
||||
> è +0,076**: non si assicura un libro che nei crash è già quasi piatto.*
|
||||
|
||||
**Quel motivo non si applica qui.** §46 assicurava il *libro* (TP01+SKH01, piatto il 28% dei
|
||||
giorni); qui il sottostante è un **hold di BTC, beta 1,0 per costruzione**. E §46 dichiara di non
|
||||
aver provato proprio questo: *«Nessuna copertura dinamica (gated su regime) è stata provata: la
|
||||
domanda era la statica»*.
|
||||
|
||||
Riusati e non rimisurati: metriche e `k_for_same_dd` da `r0823_tail_hedge`, il listino fee opzioni
|
||||
Deribit, la catena via `cblib.load_chain()`.
|
||||
|
||||
## 2. L'entrata, presa dal progetto invece che inventata
|
||||
|
||||
`trend_portfolio.tsmom_blend` media tre `np.sign()` sugli orizzonti (30, 90, 180), quindi assume
|
||||
**solo** i valori {−1, −1/3, +1/3, +1} (il bucket 2/3 non esiste — errore già corretto in §57).
|
||||
Questo dà a **"forte bull" una definizione che non aggiunge nemmeno un parametro nuovo**:
|
||||
|
||||
- **gate FORTE** = `|blend| == 1`, tutti e tre gli orizzonti concordi → a mercato **52,5%** dei giorni
|
||||
- **gate LARGO** = `|blend| ≥ 1/3`, confronto dichiarato → a mercato **97,1%** dei giorni
|
||||
|
||||
Il segno dà la direzione, quindi **long e short** sono entrambi coperti come chiesto. Si entra il
|
||||
giorno **dopo** il segnale (eseguibile, §8.1).
|
||||
|
||||
## 3. La calibrazione: quello che il modello non poteva assumere
|
||||
|
||||
161.964 quote a due lati su 95 giorni (2026-05-07 → 2026-09-01), scadenze 1-15 giorni.
|
||||
|
||||
**Lo skew, ed è il fatto strutturale del filone:**
|
||||
|
||||
| \|δ\| | IV put / ATM | IV call / ATM | la put costa |
|
||||
|---|---|---|---|
|
||||
| 0,10 | 1,249 | 0,959 | **+30%** della call |
|
||||
| 0,20 | 1,132 | 0,950 | **+19%** |
|
||||
| 0,30 | 1,072 | 0,960 | **+12%** |
|
||||
|
||||
**Un collar delta-simmetrico è un debito netto**: compro l'ala cara e vendo quella a buon mercato.
|
||||
Non è un dettaglio di prezzo, è la ragione per cui "gratis" non esiste a delta simmetrici.
|
||||
Spread (mezza forchetta / mid): put 1,9-8,3%, call 2,1-10,0% secondo il delta. Struttura a termine
|
||||
IV/DVOL30 ≈ 0,92-0,94 a 2-14 giorni.
|
||||
|
||||
📌 **A5 confermata, e corregge un muro di §46:** il tick da **5 USDC** che lì era «il secondo muro,
|
||||
strutturale» vale per la famiglia **USDC**. La catena che raccogliamo è **100% inverse**
|
||||
(`BTC-31JUL26-45000-P`), col tick in BTC. Il muro di §46 **non si applica a questo filone**.
|
||||
|
||||
## 4. Il risultato, in ordine di come uccide
|
||||
|
||||
Lente lunga 2021-03-24 → 2026-09-01 (1.988 giorni, 5,44 anni). BTC buy&hold nudo: Sharpe 0,413 ·
|
||||
maxDD 76,73% · drift +7,82%/a. Griglia dichiarata prima: 3 δput × 3 δcall × 2 tenor × 2 gate = **48
|
||||
celle**, più 12 varianti zero-cost dichiarate a parte.
|
||||
|
||||
### ✅ A1 CONFERMATA — il COLLAR riduce il maxDD (36/48) — ⚠️ ma NON è il pavimento a farlo
|
||||
|
||||
**maxDD scende in 36/48 celle.** Con beta 1,0 la put para davvero: è il risultato che §46 non
|
||||
poteva ottenere.
|
||||
|
||||
🚨 **CORREZIONE (stesso giorno, `r0901c_pavimento_leva.py`, §72).** Questa riga diceva *«il
|
||||
pavimento funziona davvero»* e attribuiva la riduzione al pavimento. **È il COLLAR a ridurre il DD,
|
||||
non il pavimento**: la put **da sola**, a premio reale, **peggiora il maxDD in 16/16 celle**
|
||||
(FORTE 51,83% → 55,4-75,8%, LARGO 71,94% → 74,8-81,7%). Nel collar la riduzione viene dal **tetto**
|
||||
— il suo premio compensa il bleed della put, e cappare l'upside **abbassa il picco** da cui il DD si
|
||||
misura. Il pavimento pareggerebbe il DD nudo solo se le put costassero il **36-79% del reale**.
|
||||
Quindi: *il motivo di §46 (beta) non si applica, ma il suo verdetto — il maxDD SALE — si riproduce
|
||||
a beta 1,0 per un motivo diverso:* **i drawdown di BTC sono grind di 290-818 giorni, e una put a
|
||||
≤15 giorni copre una finestra.** Dettaglio in `2026-09-01c-pavimento-leva.md`.
|
||||
|
||||
### ❌ A3 CONFERMATA — ma il de-levering lo fa meglio in 45/48 celle
|
||||
|
||||
| gate | base gated senza opzioni | |
|
||||
|---|---|---|
|
||||
| FORTE | Sharpe 0,442 · maxDD **51,83%** · drift **+10,04%**/a | ← il null da battere |
|
||||
| LARGO | Sharpe 0,578 · maxDD **71,94%** · drift **+17,37%**/a | |
|
||||
|
||||
Il null è **lo stesso hold gated, senza opzioni, scalato a iso-maxDD** — generato dallo stesso
|
||||
motore, non ridichiarato (P1). Il collar lo batte in **3 celle su 48**.
|
||||
|
||||
### ❌ A2 CONFERMATA — ogni punto di DD risparmiato costa 2-8 punti di drift
|
||||
|
||||
Δdrift/ΔmaxDD mediano: **7,90** col gate FORTE, **1,98** col LARGO. Il tetto costa più di quanto
|
||||
il pavimento renda, e non di poco.
|
||||
|
||||
### 🚨 C9 — non è protezione, è troncatura
|
||||
|
||||
Cella migliore, 196 cicli: **il tetto taglia nel 46,2% dei cicli vincenti, il pavimento para nel
|
||||
6,7% dei cicli perdenti.** Scatta **7 volte più spesso sui vincenti che sui perdenti** — la
|
||||
definizione letterale di C9. *La regola dell'operatore («ridurre la vincita, bloccare la perdita»)
|
||||
è implementata fedelmente: il problema è che su BTC quel baratto è pagato male.*
|
||||
|
||||
### ❌ M1 — dentro il libro non aggiunge nulla
|
||||
|
||||
Collar Sharpe 0,508 contro TP01 **0,852**, corr +0,486. TP01 + 10% di collar: Sharpe **+0,000** e
|
||||
maxDD **+2,32pp**. A 25%: **−0,065** di Sharpe e **+6,57pp** di maxDD. *Peggiora proprio la cosa
|
||||
che dovrebbe proteggere.*
|
||||
|
||||
## 5. 🚨 Il fatto che vale più del verdetto
|
||||
|
||||
**Tutte e 3 le celle vincenti stanno sul BORDO** della griglia (δput al minimo, δcall al massimo).
|
||||
M8: un argmax sul bordo non è una decisione. Ho esteso la famiglia (M4) con 36 trial dichiarati
|
||||
verso l'angolo — e lì **vince in 35/36 celle**, con il massimo *nell'angolo*:
|
||||
|
||||
> gate FORTE, 7 giorni, **δput 0,02 · δcall 0,50** → Sharpe **1,471** · maxDD **18,12%** · drift **+29,99%**/a
|
||||
|
||||
Il limite di quell'angolo è *nessun pavimento, tetto ATM*: **una covered call**. Cioè **la pendenza
|
||||
porta fuori da ciò che l'operatore ha chiesto e dentro lo short-vol.**
|
||||
|
||||
E quel Sharpe 1,471 non è una scoperta — **è il mio prezzatore che si paga da solo**:
|
||||
|
||||
| | DVOL / RV-forward | DVOL sta sopra |
|
||||
|---|---|---|
|
||||
| a 7 giorni | **1,320** | **76,9%** dei giorni |
|
||||
| a 14 giorni | **1,253** | **74,8%** dei giorni |
|
||||
|
||||
Riprezzando le opzioni alla **volatilità effettivamente realizzata** (diagnostica con look-ahead
|
||||
dichiarato — è il valore equo ex-post, non una strategia):
|
||||
|
||||
| cella | a DVOL | a vol realizzata | il VRP valeva |
|
||||
|---|---|---|---|
|
||||
| FORTE 7g δ0,02/0,50 | Sh **1,471** · +29,99% | Sh **0,511** · +8,45% | **+21,54 pp (72%)** |
|
||||
| LARGO 7g δ0,02/0,50 | Sh **1,141** · +32,17% | Sh **0,114** · **−1,10%** | **+33,26 pp (tutto)** |
|
||||
| FORTE 7g δ0,10/0,30 | VINCE | **perde** | +6,75 pp |
|
||||
| LARGO 14g δ0,10/0,30 | perde | perde | +8,02 pp |
|
||||
|
||||
**Il 72-100% dell'edge dell'angolo è il premio di varianza**, non la struttura. E ciò che
|
||||
sopravvive alla riprezzatura — **Sharpe 0,511** — è, entro il rumore, **il numero che il progetto
|
||||
ha già**: VRP01 a f=0,73 vale **Sharpe 0,47**. *Non ho trovato una strategia nuova: ho ri-scoperto
|
||||
VRP01 per una strada più lunga.* E §3 lo blocca comunque: **«niente short-vol da modello in
|
||||
deploy»** — questo è esattamente short-vol da modello.
|
||||
|
||||
## 5-bis. L'uscita anticipata: chiesta, implementata, e costa
|
||||
|
||||
L'operatore ha chiesto di **uscire dalle opzioni fra il 50% e il 75% del tempo**. Implementato come
|
||||
`exit_frac`, su tenor 14 giorni (⇒ uscita a 7 / 9 / 10 giorni). **La mia ipotesi a priori era che
|
||||
migliorasse**: uscire presto recupera valore temporale sulla put e rinuncia al theta più veloce
|
||||
sulla call, cioè proprio alla parte che stampava il premio di varianza. **Metà giusta, metà no.**
|
||||
|
||||
| cella `δ0,10/0,30`, gate FORTE | uscita | Sharpe | drift | null | esito | VRP residuo |
|
||||
|---|---|---|---|---|---|---|
|
||||
| a scadenza | 1,000 | 0,488 | **+9,48%** | +8,07% | VINCE | +3,38pp |
|
||||
| 75% | 0,750 | 0,457 | +8,59% | +7,72% | VINCE | +4,93pp |
|
||||
| 62,5% | 0,625 | 0,366 | +6,11% | +7,98% | **perde** | +2,51pp |
|
||||
| 50% | 0,500 | 0,278 | **+3,84%** | +8,04% | **perde** | **+1,27pp** |
|
||||
|
||||
**Il meccanismo previsto c'è** — il VRP residuo scende da +3,38 a +1,27pp, quindi uscire presto
|
||||
*davvero* rinuncia allo short-vol — **ma lo spread lo travolge**: −5,6 punti di drift sul gate
|
||||
FORTE, −10,2 sul LARGO (12,06% → 1,88%), e su `δ0,20/0,20` da −4,37% a −8,58%.
|
||||
|
||||
🚨 **E qui §46 va corretto nella sua applicazione.** §46 misurò: *«f si paga solo sulla parte di
|
||||
valore che converge a intrinseco, quindi un roll anticipato non lo paga»*. **Vero per una copertura
|
||||
SOLO LONG.** In un collar c'è una **gamba venduta da ricomprare**: uscire prima paga f *esattamente*
|
||||
sulla parte che a scadenza si sarebbe regolata gratis, e si fa ~2× più spesso per unità di tempo.
|
||||
**L'asimmetria di §46 si inverte quando la struttura ha una gamba corta.** È il risultato
|
||||
trasferibile di questa richiesta.
|
||||
|
||||
**Nella banda chiesta (50-75%) il verdetto si spacca:** a **0,75** la cella onesta passa ancora il
|
||||
null (di poco); a **0,625 e sotto** non lo passa più. Nessun `exit_frac` ribalta il verdetto del
|
||||
filone — lo peggiora.
|
||||
|
||||
## 6. I controlli dell'apparato (M15) — 3/3
|
||||
|
||||
§46 insegna che *«un controllo positivo rotto dichiara guasto l'apparato»*. Prima di credere a un
|
||||
verdetto negativo ho verificato che l'apparato sappia riconoscere un successo:
|
||||
|
||||
| controllo | atteso | misurato | |
|
||||
|---|---|---|---|
|
||||
| pavimento a **premio zero** (pranzo gratis) | deve VINCERE | maxDD 51,83%→**36,76%**, drift +10,04%→**+35,69%** | ✅ |
|
||||
| premio **×10** | deve PERDERE | drift **−33,96%**/a | ✅ |
|
||||
| zero-cost costruibile e finito | non NaN | drift −3,50%/a | ✅ |
|
||||
|
||||
## 7. Tre difetti miei, catturati dai controlli e non a occhio
|
||||
|
||||
1. **Bisezione dello zero-cost invertita** (`hi = md` dove serve `lo = md`): il premio cresce col
|
||||
delta, quindi se incasso troppo poco il tetto va *avvicinato*. Dava Sharpe −3,0/−3,9 e drift
|
||||
−53%/a — spazzatura che al primo giro avevo quasi pubblicato.
|
||||
2. **`dcall=NaN`** nella variante zero-cost propagava NaN nella cassa allo smontaggio anticipato.
|
||||
3. **C9 non consapevole della direzione:** usavo `S1 > S0` come proxy di "ciclo vincente", ma per
|
||||
un ciclo **short** un prezzo che sale è una **perdita**. Era la causa del risultato impossibile
|
||||
*«il pavimento para nello 0,0% dei cicli perdenti»* con una put a 10 delta.
|
||||
|
||||
E due difetti di contabilità trovati prima di misurare, che avrebbero **adulato il collar**:
|
||||
la base senza opzioni rollava lo spot ogni `tenor` giorni pagando ~3,6%/anno di fee inesistenti;
|
||||
e al roll delle opzioni chiudevo e riaprivo anche lo spot, che non ha motivo di muoversi.
|
||||
|
||||
## 8. Cosa NON ho misurato, dichiarato
|
||||
|
||||
- **La lente reale (quote vere, 2026-05→09) non è stata girata come backtest.** 123 giorni = ~8
|
||||
cicli non sovrapposti: sotto-potenziata per costruzione, e su una finestra in cui BTC è salito da
|
||||
~64,7k a ~77,5k — **avversa a un collar per costruzione**. La catena è servita a **calibrare**
|
||||
(skew, termine, spread), che è l'uso in cui 161.964 quote hanno potenza.
|
||||
- **Nessun DSR, nessun `study_family_honest`.** Non servono: il filone cade al **primo** gate (M5),
|
||||
e M2 si spende su ciò che il primo gate lascia in piedi.
|
||||
- **A6 e A7 non verificate** (lente reale non girata; il verso short è dentro i gate ma non
|
||||
separato). Restano previsioni non misurate, non risultati.
|
||||
- **Il funding dei perp non è nel motore**, come in ogni backtest del progetto: −2,16%/a di drift.
|
||||
Colpisce base e collar quasi allo stesso modo (stessa esposizione spot), quindi **non cambia il
|
||||
segno del confronto**, ma abbassa entrambi.
|
||||
|
||||
## 9. Verdetto
|
||||
|
||||
> **`IL PAVIMENTO FUNZIONA — E' IL TETTO CHE NON SI PUO' PAGARE. E CIO' CHE VINCE SUL BORDO NON E'
|
||||
> LA PROTEZIONE: E' IL PREMIO DI VARIANZA, CIOE' VRP01`** — **REFUTATO come chiesto.**
|
||||
|
||||
Con una postilla che è il vero risultato trasferibile: **la domanda «bloccare la perdita» ha
|
||||
risposta positiva sul maxDD (36/48) e negativa sul prezzo (45/48).** Su BTC il pavimento si compra
|
||||
meglio **tenendo meno BTC** che comprando una put e vendendo una call — e il de-levering, a
|
||||
differenza del collar, non tocca il rendimento nella coda destra dove BTC vive.
|
||||
|
||||
## 10. Cosa lo riaprirebbe
|
||||
|
||||
- Un **f di stress misurato su un crash catturato** — la stessa condizione che §3 pone allo
|
||||
short-vol. Con quello, l'angolo covered-call diventa discutibile invece che escluso.
|
||||
- Un sottostante **senza coda destra grassa**: il tetto costa perché BTC vive lì. Su un asset a
|
||||
distribuzione più simmetrica il baratto cambia di segno, e il conto va rifatto.
|
||||
- **Non** lo riapre un tenor diverso, un delta diverso o una griglia più fine: la pendenza è
|
||||
monotona verso l'angolo short-vol, e l'angolo è già misurato.
|
||||
@@ -0,0 +1,92 @@
|
||||
# 2026-09-01 — Stato trades, e la riparazione che non si è propagata
|
||||
|
||||
*Scritto il 2026-09-01, misure lette fra le 09:35Z e le 16:47Z. Numeri dalle serie citate.*
|
||||
|
||||
## 1. Lo stato, in breve
|
||||
|
||||
| | valore | fonte |
|
||||
|---|---|---|
|
||||
| fill registrati | **45**, dal 2026-07-14T14:00 al **2026-08-31T15:47** | `trades.db` |
|
||||
| round-trip chiusi | **29** (21 in utile) — lordo +41,43 · fee allocate 0,66 · **netto +40,77** | `trades.db` |
|
||||
| fee totali pagate | **$0,8617** | `trades.db` |
|
||||
| equity (lettura 16:47Z) | **$2.051,84** · picco $2.073,59 | serie `equity`, 1.657 letture |
|
||||
| posizione BTC | 0,0045 @ medio $79.208,11, dal 25/08 | libro di bordo |
|
||||
| posizione ETH | 0,0872 @ medio $2.473,04, dal 25/08 | libro di bordo |
|
||||
| non realizzato (mark 16:38Z) | **−$11,72** — BTC −7,91 (−2,22%) · ETH −3,80 (−1,76%) | gateway |
|
||||
| gross nozionale | $560,36 → **leva 0,27x** su un tetto di 1,0x | gateway |
|
||||
|
||||
**Nessun fill da 25 ore, e non è un blocco.** Il log del cron lo dice a ogni giro fino alle 15:47:
|
||||
`=> Nessuna azione: conto gia' al target netto del book`. TP01 fermo a +0,461 (BTC) e +0,278 (ETH),
|
||||
SKH01 flat su entrambi, scarto posizione-target sotto il `min_order_usd` di $5 ($356 contro $351,
|
||||
$214 contro $213). Feed SKH fresco (0 min), disaster-SL armati a $55.428,0 e $1.708,9.
|
||||
|
||||
`monitor_health`: **8/8 OK**, coperture 98-129%. Riconcilio a 3 fonti: **44/45 concordano, 0 prezzi
|
||||
divergenti**; l'unica coppia scoperta è il difetto già noto — stesso fill (ETH buy 0,04 @ 1869,74)
|
||||
timbrato `2026-07-14T14:00` nel log del cron e `2026-07-08` nel jsonl, i sei giorni di scarto della
|
||||
lezione già registrata. Non è un fill perso.
|
||||
|
||||
## 2. Il difetto: `--report` stampa un rendimento che non è un rendimento
|
||||
|
||||
`scripts/live/trades_db.py --report` stampa questa riga:
|
||||
|
||||
```
|
||||
equity : $598.06 -> $2,051.84 (+1453.78, +243.08%) | picco $2,073.59 | 1657 letture
|
||||
```
|
||||
|
||||
Accanto a una riga di P&L che dice `netto +40,77`. **Il +243% non è rendimento: è quasi tutto un
|
||||
versamento.** La serie di 1.657 letture orarie contiene **un solo salto**, il 2026-08-25 alle 11:47Z:
|
||||
$667,49 → $2.066,88, **+$1.399,39**.
|
||||
|
||||
Non è una mia classificazione a occhio: è quella del progetto. `journal.movimenti_capitale()`, che
|
||||
importa la soglia viva da `book.EQUITY_JUMP_ALERT` (0,10) e confronta il salto col massimo che il
|
||||
**mercato misurato** avrebbe potuto produrre in quell'ora a tetto di leva, restituisce
|
||||
`certi 1399.39 · ambigui 0.0`, un solo evento, classe `movimento`.
|
||||
|
||||
Al netto:
|
||||
|
||||
| lettura | valore |
|
||||
|---|---|
|
||||
| periodo 1 — 23/06 22:00Z → 25/08 10:47Z ($598,06 → $667,49) | **+11,61%** |
|
||||
| periodo 2 — 25/08 11:47Z → 01/09 16:47Z ($2.066,88 → $2.051,84) | **−0,73%** |
|
||||
| **TWR spezzato sul versamento** | **+10,80%** |
|
||||
| crescita di equity al netto del versamento, 69 giorni | **+$54,39** |
|
||||
|
||||
**Il numero regge al controllo incrociato.** Il giornale del 31/08, che lo scorporo lo fa già,
|
||||
scrive: *«cumulato dall'arming: $+1.461,39 di equity — di cui $+1.399,39 versati/prelevati ->
|
||||
trading $+62,00»*. Stessa base ($598,06), stesso versamento; la differenza fra i suoi +62,00 e i
|
||||
miei +54,39 è **−7,61 di marcatura** fra l'ultima lettura del 31/08 e quella di oggi. Le due misure
|
||||
concordano.
|
||||
|
||||
### Perché è un difetto e non un dettaglio di stampa
|
||||
|
||||
La riparazione **esiste già, e in questo stesso repo**. `src/live/journal.py:121` la porta scritta
|
||||
nel docstring, col caso d'origine:
|
||||
|
||||
> *«Il P&L di giornale e' un delta di equity, quindi un versamento ci finisce dentro come se fosse
|
||||
> profitto: la voce del 2026-08-25 dichiarava «giorno: +$1.414,57» quando $1.399 erano un deposito
|
||||
> USDC, e il «cumulato dall'arming» avrebbe mentito per sempre.»*
|
||||
|
||||
Quel difetto fu trovato e riparato nel giornale. **`trades_db.py:83` non ha mai ricevuto la
|
||||
riparazione**: calcola `100*(e1/e0-1)` sulla serie grezza e non chiama `movimenti_capitale()`, che
|
||||
è a un import di distanza e restituisce già `certi` e `ambigui` pronti da scorporare.
|
||||
|
||||
È una variante di **P1** — non un sorvegliante che ridichiara il proprio bersaglio, ma **una
|
||||
riparazione che non ha attraversato il confine fra due lettori della stessa serie**. Il primo
|
||||
strumento dice la verità; il secondo, interrogato con lo stesso comando pubblicato in CLAUDE.md §13
|
||||
(`trades_db.py --report`), stampa un numero che si legge come una performance ed è per il **96,3%**
|
||||
un bonifico.
|
||||
|
||||
**Danno oggi: nessuno sui soldi** — `--report` è in sola lettura e non decide niente. Il danno è di
|
||||
citazione: è esattamente la classe di numeri che §2 raccoglie sotto *«non citare»*, ed è l'unico che
|
||||
il progetto produce **su richiesta esplicita di un comando documentato**.
|
||||
|
||||
## 3. Cosa NON ho fatto
|
||||
|
||||
**Non ho toccato il codice.** La riparazione è piccola — chiamare `movimenti_capitale()` e stampare
|
||||
la riga scorporata accanto a quella grezza, come fa il giornale — ma sta su uno script che legge il
|
||||
libro di bordo vivo, e la richiesta era di aggiornare i documenti. Registrata come debito aperto.
|
||||
|
||||
Non ho scomposto la differenza fra il P&L dei round-trip (+$40,77) più il non realizzato (−$11,72)
|
||||
= **+$29,05** e i +$54,39 di equity al netto del versamento. Lo scarto di ~$25 sta in voci che il
|
||||
conteggio round-trip non copre — funding dei perp (non modellato in nessun backtest, §2), reward
|
||||
USDE, marcatura dell'USDE all'indice. A questa taglia non vale il costo della misura: si dichiara.
|
||||
@@ -0,0 +1,166 @@
|
||||
# 2026-09-01 — La chiave di scala esiste, ed è inerte
|
||||
|
||||
*Scritto il 2026-09-01. Implementazione dei punti 1-4 di `docs/research/SPEC-scale-key.md` §8.*
|
||||
**Nessun ordine. `config/live.json` NON è stato toccato: il libro che gira è bit-exact quello di ieri.**
|
||||
|
||||
## 0. Da dove nasce
|
||||
|
||||
Il progetto aveva una regola — *«ogni cambio di scala passa dal cap di config, non da `target_vol`»* —
|
||||
e CLAUDE.md la accompagnava da settimane con questa nota:
|
||||
|
||||
> ⚠️ **NON È IMPLEMENTABILE COME SCRITTA:** in `config/live.json` non esiste una chiave di scala.
|
||||
|
||||
Era un debito preciso: la regola prescriveva un percorso che non esisteva, quindi qualunque cambio
|
||||
di scala sarebbe finito su `WEIGHT`/`W_TP01`/`W_SKH` in `src/live/book.py` — cioè codice su un
|
||||
percorso con soldi veri, e per giunta nel posto sbagliato (`W_TP01`/`W_SKH` sono il **rapporto**
|
||||
75/25, non la taglia: moltiplicarli romperebbe la parità coi pesi del backtest).
|
||||
|
||||
La specifica era già scritta, 618 righe, con nove condizioni di gate e il prototipo che ne dimostra
|
||||
la meccanica. **Non ho progettato niente: ho eseguito.**
|
||||
|
||||
## 1. Il rischio che l'implementazione doveva rendere impossibile
|
||||
|
||||
La §0 della specifica lo dice meglio di come lo direi io:
|
||||
|
||||
> *Una chiave di scala è esattamente il tipo di parametro che si alza «solo un po'» dopo un mese
|
||||
> buono. È un numero, sta in un file di config, non richiede di capire niente per cambiarlo, e il
|
||||
> suo effetto è immediato e piacevole.*
|
||||
|
||||
E il motivo per cui il progetto non aveva già una guardia non è dimenticanza, è **aritmetica**:
|
||||
**lo Sharpe è invariante alla scala.** `deflated_sharpe` e `marginal_vs_tp01` leggono un numero che
|
||||
a k=1,00 e a k=2,00 è identico. Non falliscono: **non vedono**.
|
||||
|
||||
## 2. Cosa ho costruito
|
||||
|
||||
| pezzo | dove | cosa fa |
|
||||
|---|---|---|
|
||||
| `book_scale_k` | `config/live.json` — **assente** | la chiave. Assente = 1,00 = libro di sempre |
|
||||
| `_scala()` | `src/live/book.py` | legge, **valida sempre**, applica solo sul percorso fidato |
|
||||
| `book_net_target(..., scala=)` | `src/live/book.py` | applica la scala **DOPO il clamp** |
|
||||
| `LEVA_LORDA_MAX` 1,25 · `SCALA_LADDER` (1,00 · 1,25) · `DISASTER_SL_BUDGET` 0,50 | `src/live/book.py` | **costanti di CODICE** |
|
||||
| `ScalaNonAutorizzata` | `src/live/book.py` | eccezione, **non** un clamp |
|
||||
| riga «scala libro» + stop-e-allerta | `scripts/live/book_execute.py` | la scala è visibile a ogni giro |
|
||||
| `scale_watch` | `src/live/` + `scripts/live/` + `cron_daily.sh` | 3 domande, 3 azioni, 3 stati |
|
||||
| `scale_history.jsonl` | `data/live/` | giornale append-only (già nel backup) |
|
||||
| T1-T11 | `tests/test_book_scale.py` | **18 test, tutti verdi** |
|
||||
|
||||
### 2.1 Le quattro decisioni che non sono di comodo
|
||||
|
||||
**(a) La scala si applica DOPO il clamp.** Applicandola prima, il cap se la mangerebbe *proprio nei
|
||||
giorni di massima convinzione*: a `tp=1, sg=+1` il grezzo vale esattamente `cap`, quindi `k_eff`
|
||||
tornerebbe a 1,00 a ogni k. Il risultato non sarebbe «il libro a leva k» ma un libro che alza i
|
||||
giorni piccoli e lascia fermi i grandi — un cambio di **forma** travestito da cambio di taglia. E
|
||||
allora la curva `g(k)` con cui il gradino viene autorizzato **non descriverebbe quel libro**.
|
||||
*Autorizzare con una curva e implementarne un'altra è il modo più silenzioso di sbagliare.*
|
||||
Il prezzo, dichiarato: il cap smette di essere il tetto assoluto del nozionale e diventa il tetto
|
||||
del libro **unitario**. La guardia sulla leva lorda è ricostruita esplicitamente altrove.
|
||||
|
||||
**(b) Il tetto è sul PRODOTTO, e sta nel CODICE.** Un tetto sulla sola chiave lascia aperta la porta
|
||||
accanto: `frac` 0,625 × scala 1,25 = **1,562x**, che passerebbe. E un tetto in config sarebbe
|
||||
modificabile dalla stessa mano, nello stesso file, nello stesso momento — *un lucchetto con la chiave
|
||||
attaccata*. Con `LEVA_LORDA_MAX` in `src/live/book.py`, il gradino 1,25 → 1,50 richiede una modifica
|
||||
di codice, quindi una review. **Il tetto è basso apposta: è il meccanismo del cricchetto.**
|
||||
|
||||
**(c) Fuori scaletta o fuori tetto = STOP, non clamp.** `book_execute` si ferma, non invia, allerta.
|
||||
Tagliare silenziosamente farebbe girare una config che *dichiara* un numero e un libro che ne
|
||||
*esegue* un altro. E `SCALA_LADDER` esiste perché **una scala a gradini rende inesprimibile «solo un
|
||||
po'»**: 1,05 non è un valore prudente, è un valore fuori scaletta, e viene rifiutato.
|
||||
|
||||
**(d) La scala vive solo sul percorso fidato.** Equity reale illeggibile ⇒ scala 1,00. *Una scala è
|
||||
una decisione di rischio presa conoscendo il conto; quando non si sa quanto vale il conto, non si
|
||||
prende.* Così «il fallback non è più permissivo» è vero **per costruzione**, non per aritmetica.
|
||||
⚠️ Ma la **validazione** avviene comunque, anche sul percorso degradato: una config rotta è un
|
||||
errore di configurazione, non una condizione di mercato, e non deve nascondersi dietro un giro in
|
||||
cui l'equity non era leggibile.
|
||||
|
||||
### 2.2 La guardia che morde per prima, e non è quella che sembra
|
||||
|
||||
Il disaster-SL è un movimento di **prezzo** (−30% sul mark), ma il suo impatto in equity è quel
|
||||
movimento **moltiplicato per il lordo** — quindi cresce con k anche se `disaster_sl_pct` non cambia:
|
||||
|
||||
- dal peggior giorno possibile: `k ≤ 3,49x`
|
||||
- **dal costo di un disaster-SL: `k ≤ 1,67x`** ← **2,1× più stringente**
|
||||
|
||||
Da qui l'invariante `n_asset · frac · scala · disaster_sl_pct ≤ 0,50`, che scatta anche se qualcuno
|
||||
allarga lo stop invece di alzare la scala.
|
||||
|
||||
## 3. Il test che conta più degli altri
|
||||
|
||||
**T1b**, e la ragione per cui la specifica gli dedica un paragrafo: **a k=1 l'implementazione
|
||||
simmetrica e quella asimmetrica danno lo stesso identico numero.** Un test di simmetria scritto sul
|
||||
caso di default passa sempre e non controlla niente — potenza **zero**, la stessa firma dei test di
|
||||
`book_live` scoperti senza potenza a libro flat.
|
||||
|
||||
T1b quindi verifica due cose: *(a)* che a k=1 le due implementazioni siano davvero indistinguibili
|
||||
(cioè che il rischio sia reale), e *(b)* che fuori da k=1 l'asserzione di T1 le **separi**. Nel test
|
||||
c'è scritta un'implementazione asimmetrica apposta perché fallisca.
|
||||
|
||||
Gli altri: T2/T3 i due tetti letti da config coi bersagli **importati** dalla produzione; T4 il
|
||||
fallback mai più permessivo in ogni stato degradato; T5 il cap che morde ancora; T6 lo stop non
|
||||
clampato (più T6b sul prodotto e T6c sul disaster-SL); **T7 l'inerzia bit-exact**; T8 il report che
|
||||
la dichiara; T9 i bersagli derivati e non ridichiarati; T10 il **controllo positivo del
|
||||
sorvegliante** (con una config non autorizzata deve parlare, con config e giornale concordi deve
|
||||
tacere) più T10b sulla disciplina degli allarmi e T10c sui tre stati; T11 che il vecchio test sia
|
||||
stato **sostituito, non rilassato**.
|
||||
|
||||
## 4. Il sorvegliante, e cosa non può fare
|
||||
|
||||
`scale_watch` gira in `cron_daily` e risponde a tre domande che hanno **tre azioni diverse**:
|
||||
|
||||
| domanda | azione se 🚨 |
|
||||
|---|---|
|
||||
| la config dichiara una scala che il **giornale** non ha mai autorizzato? | ricostruire come ci si è arrivati, scendere a k0 |
|
||||
| il criterio che autorizzò la scala **corrente** passa ancora oggi? | scendere di un gradino |
|
||||
| il **prodotto** `frac · scala · n_asset` ha superato il tetto? | bloccare l'esecuzione |
|
||||
|
||||
Tre stati (`OK` / `ALLARME` / **`NON MISURABILE`**), una allerta per streak, e il marcatore «già
|
||||
detto» scritto **solo dopo un invio riuscito** (il debito #2 del 28/08: un 🚨 perso prima era perso
|
||||
per l'episodio intero).
|
||||
|
||||
Riporta anche la **frequenza del ramo di fallback**, che se superasse il 2% in 90 giorni
|
||||
invaliderebbe la regola (d): il libro girerebbe a una leva **mista** e `g(k)` smetterebbe di
|
||||
descriverlo. Misurata oggi: **0 giri su 1.676**. ⚠️ I 19 giri senza equity reale sono tutti
|
||||
«paper capital», cioè **pre-finanziamento**, dove il libro non invia affatto — il sorvegliante li
|
||||
conta e li dichiara separatamente invece di gonfiare la quota.
|
||||
|
||||
🚨 **Ciò che il sorvegliante NON può fare: impedire la modifica.** Chi ha accesso al file può
|
||||
scriverci dentro. Rende la modifica visibile entro 24 ore e attribuibile. È la stessa onestà di
|
||||
`venue_watch`: non protegge il saldo, **compra tempo**.
|
||||
|
||||
## 5. Verifica che il libro non è cambiato
|
||||
|
||||
```
|
||||
sizing base : $2,052.22 | cap/asset $1026 | min $5 | disaster-SL -30%
|
||||
scala libro : 1.00x (tetto 1.25x) | leva lorda max 1.000x dell'equity [chiave assente o 1,00 = libro invariato]
|
||||
BTC TP +0.461 · SKH +0(flat) -> net $+355 | pos $+357 -> HOLD (a target)
|
||||
ETH TP +0.278 · SKH +0(flat) -> net $+214 | pos $+212 -> HOLD (a target)
|
||||
```
|
||||
|
||||
Gli stessi target del cron delle 15:47. **Suite: 825 passati** (807 + 18 nuovi).
|
||||
|
||||
## 6. Cosa NON ho fatto, ed è deliberato
|
||||
|
||||
**Non ho scritto la chiave in config.** Il punto 2 della checklist lo prescrive esplicitamente, e T7
|
||||
dimostra bit-exact che senza la chiave il libro è quello di prima.
|
||||
|
||||
**Non ho eseguito GATE SCALA-01**, e non era possibile: A2 richiede che k0 sia in produzione con il
|
||||
criterio passato ogni giorno, e il punto 5 impone **≥30 giorni a 1,00 col sorvegliante attivo prima**
|
||||
di eseguire il gate. Cito la specifica perché la frase è il motivo:
|
||||
|
||||
> *Il punto 5 non è burocrazia: è l'unico modo di scoprire che il sorvegliante è rotto mentre la
|
||||
> leva è ancora 1,00.*
|
||||
|
||||
**Non ho rifatto `r0726_fee_sensitivity`** (A7). Serve solo *se* e *quando* il gradino viene chiesto:
|
||||
la sua conclusione «liquidation fee 1% irrilevante» era condizionata a lordo ≤1x, e a 1,25x una
|
||||
liquidazione costerebbe **1,25%** dell'equity. Non è un problema, è un numero da ricalcolare — e
|
||||
ereditarlo sarebbe l'errore.
|
||||
|
||||
📌 **A6 (anti-recency) oggi non morde**: `k_ammesso ≈ 3,5x` contro un gradino di 1,25x, e il vincolo
|
||||
che morde è il disaster-SL. È scritto adesso **perché adesso non è comodo per nessuno** — che è
|
||||
l'unico momento in cui una regola del genere si può scrivere onestamente.
|
||||
|
||||
## 7. Stato del gate
|
||||
|
||||
`GATE SCALA-01` è ora in CLAUDE.md §4 con data **non prima del 2026-10-01**. A8 e A9 sono fatti;
|
||||
A2 matura col tempo; A1/A4/A5 sono aritmetica già cablata; A3/A6/A7 si eseguono il giorno del gate,
|
||||
sui dati di quel giorno, non su questi.
|
||||
@@ -0,0 +1,137 @@
|
||||
# 2026-09-01 — PAVIMENTO-LEVA: il pavimento non licenzia taglia, e corregge quanto scritto stamattina
|
||||
|
||||
*Scritto il 2026-09-01. Ogni numero è riprodotto da `scripts/research/r0901c_pavimento_leva.py`,
|
||||
che riusa il motore di `r0901_btc_collar.py`. **Libro, pesi, cron, config INVARIATI. Nessun ordine.***
|
||||
|
||||
## 0. La domanda
|
||||
|
||||
> *«possiamo usare quanto conosciamo del pavimento per studiare una strategia»*
|
||||
|
||||
Cosa sapevamo, da COLLAR01 (§71, poche ore prima): il collar riduce il maxDD in 36/48 celle; a
|
||||
premio **zero** il pavimento vale moltissimo (maxDD 51,83% → 36,76%, drift +10,04% → +35,69%); ciò
|
||||
che uccise il collar fu il **tetto**, non il pavimento (C9); la put è l'ala cara.
|
||||
|
||||
**L'inversione.** COLLAR01 chiese *«quanto DD mi risparmia il pavimento a taglia fissa?»* e perse
|
||||
contro il de-levering. La domanda speculare è *«quanta TAGLIA mi autorizza il pavimento a DD
|
||||
fisso?»*. Non è la stessa, per due ragioni misurabili:
|
||||
|
||||
1. de-levare riduce taglia e rischio **in proporzione**; un pavimento cambia la **forma** — se taglia
|
||||
la coda sinistra più di quanto costi, scalare *in su* la struttura protetta può battere la base
|
||||
nuda alla stessa DD. È l'unica cosa che il de-levering **non può** comprare;
|
||||
2. il tetto di leva del progetto non è fissato dallo Sharpe ma dal **costo di un episodio** di
|
||||
disaster-SL (`n · frac · scala · sl ≤ 0,50` ⇒ k ≤ 1,67x). Il disaster-SL è **rotolante** e non
|
||||
limita la perdita (peggior DD dall'ingresso misurato −60,6%). Una put la limita per costruzione —
|
||||
*dentro la sua finestra*.
|
||||
|
||||
Quattro attese scritte prima di misurare (B1-B4). **Tutte e quattro confermate.**
|
||||
|
||||
## 1. Il risultato: 0 celle su 16
|
||||
|
||||
Griglia dichiarata: 4 δput × 2 tenor × 2 gate. Struttura: hold gated + **sola put**, premio reale
|
||||
(DVOL × skew × termine dalla catena — realistico per chi *compra*: M29 corregge chi vende a
|
||||
modello, non chi compra).
|
||||
|
||||
| gate | base nuda | pavimento a premio reale (16 celle) |
|
||||
|---|---|---|
|
||||
| FORTE | maxDD **51,83%** · drift +10,04% | maxDD **55,4% → 75,8%** · drift +3,8% → −17,0% |
|
||||
| LARGO | maxDD **71,94%** · drift +17,37% | maxDD **74,8% → 81,7%** · drift +9,9% → −13,9% |
|
||||
|
||||
**Il pavimento peggiora il maxDD in 16 celle su 16.** Quindi `k_f = 1,000` ovunque: non c'è taglia
|
||||
da licenziare, perché non c'è DD da spendere. Il null M5 classico concorda (0/16). Monotono nella
|
||||
protezione: più la put è vicina, peggio va — il bleed del premio *è* il drawdown.
|
||||
|
||||
## 2. 🚨 La correzione a COLLAR01
|
||||
|
||||
Stamattina ho scritto, in A1: *«il pavimento funziona davvero (e §46 è battuto sul suo motivo)»*.
|
||||
**La prima metà è sbagliata nell'attribuzione.** Il DD lo riduce **il collar**, non il pavimento:
|
||||
|
||||
- la put da sola, a premio reale, **peggiora** il DD (questo filone);
|
||||
- a premio zero lo riduce (il controllo del pranzo gratis) — quindi **tutto il beneficio del
|
||||
pavimento è mangiato dal suo premio, e oltre**;
|
||||
- nel collar la riduzione viene dal **tetto**: il suo premio compensa il bleed della put, e cappare
|
||||
l'upside **abbassa il picco** da cui il DD si misura — che non è protezione, è un picco più basso.
|
||||
|
||||
**Il premio di pareggio** lo quantifica: il pavimento inizia a ridurre il DD nudo solo se le put
|
||||
costano il **36-79% del reale** (secondo la cella). Il DVOL sta 1,32× sopra la vol realizzata, quindi
|
||||
un prezzo *equo* sarebbe ~76% del reale: **anche a prezzo equo il pareggio si sfiora nelle celle
|
||||
migliori e si manca nelle altre**. E il mercato non vende a prezzo equo.
|
||||
|
||||
La seconda metà della frase regge, ma va detta con precisione: **il motivo di §46 non si applica**
|
||||
(a beta 1,0 la put paga davvero: para nel 2-20% dei cicli), **ma il verdetto di §46 — *il maxDD
|
||||
SALE* — si riproduce a beta 1,0 per un motivo diverso.** Il motivo è la sezione seguente.
|
||||
|
||||
Corretti stesso giorno: il diario di COLLAR01 (titolo e A1), RESULTS §71, la memoria. M28:
|
||||
*quando due affermazioni della stessa memoria si contraddicono, la contraddizione è informazione* —
|
||||
qui erano due mie affermazioni a poche ore di distanza, e la seconda ha corretto la prima.
|
||||
|
||||
## 3. B2 — Il meccanismo: la put copre una finestra, BTC scende in grind
|
||||
|
||||
| gate | maxDD della base | da → a | durata |
|
||||
|---|---|---|---|
|
||||
| FORTE | −51,83% | 2021-07-20 → 2022-05-06 | **290 giorni** |
|
||||
| LARGO | −71,94% | 2021-07-20 → 2023-10-16 | **818 giorni** |
|
||||
|
||||
Una put a 7-14 giorni copre **una** finestra. Il drawdown che conta è lungo **20-60 finestre**. In
|
||||
un grind la put scade OTM settimana dopo settimana — il pavimento para nel **0-9%** dei cicli col
|
||||
gate FORTE — mentre il premio sanguina. E non protegge nemmeno la finestra peggiore: peggior 14
|
||||
giorni FORTE **−22,9% nudo → −23,6% col pavimento**, perché la put a 5 delta sta a ~22% dallo spot,
|
||||
cioè esattamente al bordo di ciò che è successo, e il premio la rende netta negativa.
|
||||
|
||||
**Su BTC il pavimento compra protezione contro la cosa sbagliata**: i crolli veloci dentro una
|
||||
finestra, mentre le perdite che definiscono il maxDD sono grind di mesi. Questo è ciò che §46 non
|
||||
poteva vedere (a beta 0,076 il libro non aveva né crolli né grind da proteggere) e che a beta 1,0
|
||||
diventa il fatto dominante.
|
||||
|
||||
## 4. B3 — La licenza del disaster-SL è di carta
|
||||
|
||||
L'invariante `n · frac · scala · sl ≤ 0,50` con `sl = 0,30` dà **1,67x**. Con la put a 5δ/14g il
|
||||
pavimento sta a **21,9%** dallo spot: sostituire `sl` con quella distanza darebbe **2,28x** —
|
||||
*1,4× in più*. È la proposta che qualcuno farà, e va uccisa ora:
|
||||
|
||||
| finestre consecutive | giorni | peggior perdita col pavimento | la put… |
|
||||
|---|---|---|---|
|
||||
| 1 | 14 | −23,6% | copre |
|
||||
| 2 | 28 | −25,9% | **non copre** |
|
||||
| 4 | 56 | −28,8% | **non copre** |
|
||||
| 8 | 112 | **−33,6%** | **non copre** |
|
||||
|
||||
L'invariante limita **un episodio**; la put limita **una finestra**; il massimo su finestre
|
||||
consecutive **non è limitato da nulla**. A 2,28x un grind di 112 giorni costerebbe il **77%
|
||||
dell'equity**. ⇒ **`disaster_sl_pct` non si sostituisce con la distanza di un pavimento**, e va
|
||||
scritto prima che sia comodo per qualcuno.
|
||||
|
||||
## 5. B4 — Il pavimento gated sull'IV: migliora, non basta
|
||||
|
||||
§46 dichiarava *«nessuna copertura dinamica gated su regime è stata provata»*. Provata: put ON solo
|
||||
quando DVOL/RV trailing sta sotto il 25° o 50° percentile dell'anno (il premio di varianza è
|
||||
sottile).
|
||||
|
||||
| gate | soglia | ON (giorni a mercato) | drift | vs base |
|
||||
|---|---|---|---|---|
|
||||
| FORTE | 25° pctl | 27% | **+8,39%** | +10,04% → **perde** |
|
||||
| FORTE | 50° pctl | 47% | +6,77% | perde |
|
||||
| LARGO | 25° pctl | 25% | **+15,31%** | +17,37% → **perde** |
|
||||
| LARGO | 50° pctl | 50% | +13,93% | perde |
|
||||
|
||||
Migliora molto rispetto al pavimento sempre acceso (+3,8% → +8,4% col gate FORTE) e il DD resta
|
||||
≥ della base (k_f = 1,000). ⚠️ L'incollatura ignora lo spread delle transizioni ON/OFF, **a favore**
|
||||
del gated: se perde così, perde a maggior ragione.
|
||||
|
||||
## 6. Cosa NON ho fatto, dichiarato
|
||||
|
||||
- Nessun DSR, nessun `study_family_honest`: il filone cade al primo gate, 0/16.
|
||||
- Nessuna lente reale (123 giorni, ~8 cicli): stessa ragione di COLLAR01.
|
||||
- Non ho provato il pavimento **a scadenza lunga** (30-90 giorni), che coprirebbe più di una
|
||||
finestra di grind — perché l'operatore ha vincolato la scadenza a **≤15 giorni**. È l'unica
|
||||
variante che B2 lascia aperta, e sta fuori dal vincolo dichiarato. Va detto: se il vincolo cadesse,
|
||||
sarebbe la prima cosa da misurare, e la memoria di §46 ha già l'apparato (tenor 30).
|
||||
|
||||
## 7. Verdetto
|
||||
|
||||
> **`IL PAVIMENTO NON LICENZIA TAGLIA A PREMIO REALE: 0/16. Ciò che il de-levering non può comprare,
|
||||
> la put lo vende a un prezzo che nessuna cella ripaga — e su BTC compra protezione contro i crolli
|
||||
> mentre le perdite sono grind.`** — REFUTATO.
|
||||
|
||||
**Cosa lo riapre:** una scadenza che copra il grind (fuori dal vincolo ≤15g), o un mercato che venda
|
||||
la put al 36-79% del prezzo attuale — cioè un premio di varianza *negativo*, che su BTC non si è
|
||||
mai misurato. Non lo riaprono delta, tenor ≤15 o gate sull'IV: sono misurati.
|
||||
@@ -0,0 +1,91 @@
|
||||
# 2026-09-01 — Soldi fermi: i modi esistono, valgono €5/mese, e uno di loro distruggerebbe lo split
|
||||
|
||||
*Scritto il 2026-09-01. Numeri da `scripts/research/r0901d_soldi_fermi.py`; tassi dal web con fonte e
|
||||
data (cutoff del modello 05/2026: non li sapevo). **Non sono pareri fiscali.** Nessun ordine.*
|
||||
|
||||
## 0. La domanda
|
||||
|
||||
> *«troviamo altri modi per fare guadagnare con soldi che sarebbero fermi. non per forza trading»*
|
||||
|
||||
## 1. Dove sta il capitale fermo — e cosa NON lo è
|
||||
|
||||
**Deribit (lettura 20:42Z):** USDC $1.406 · USDE 654 · libro a 0,27x. ⚠️ **L'USDC non è fermo**: è
|
||||
la base di sizing del libro (cap = equity × 0,5) e il cuscino di regolamento del disaster-SL. Spostarlo
|
||||
fuori rimpicciolisce le posizioni, non libera capitale.
|
||||
|
||||
**Fuori:** ~€6.043 in XEON «a ~0% reale netto» (memoria 27/07). È lo **split-cassa** — la protezione
|
||||
dal rischio venue, P(perso tutto) 18% → 3,5% a p=1% — e la memoria dichiara di non sapere quanto sia
|
||||
fondo d'emergenza. **Assunzione dichiarata: €6.000 investibili a 12+ mesi.**
|
||||
|
||||
## 2. On-venue: chiuso, e stavolta con l'elenco intero
|
||||
|
||||
`public/get_currencies` oggi: **4 valute su 50** con APR > 0 — USDE 4,32 · USDC 3,40 · BUIDL 3,20 ·
|
||||
STETH 2,22. Sono esattamente le quattro chiuse il 31/08 (al tetto · Italia esclusa · non
|
||||
acquistabile · è ETH). Non era un campione: era la lista.
|
||||
|
||||
## 3. I modi, in euro netti l'anno su €6.000
|
||||
|
||||
| opzione | lordo | fisco | **netto/anno** | vs XEON | rischio | split |
|
||||
|---|---|---|---|---|---|---|
|
||||
| XEON (status quo) | 2,10% | 26% | **€81** | — | ~0 | intatto |
|
||||
| **BOT 12 mesi** | 2,77% | **12,5%** | **€133** | **+€52** | ~0 (Stato) | intatto |
|
||||
| **Conto deposito vinc. 12m** | 3,50% | 26% | **€143** | **+€62** | ~0 (FITD) | intatto |
|
||||
| sUSDe diretto (Ethena) | ~9% | 33% | €350 | +€269 | **emittente + smart contract** | **distrutto** |
|
||||
| versare sul libro | ~10% atteso | — | ~€600 | +€519 | maxDD 11%, venue | **distrutto** |
|
||||
|
||||
Fonti: BOT 2,768% asta agosto 2026 (Banca d'Italia 14/08); BCE DFR 2,25% (23/07) ⇒ €STR ~2,2%;
|
||||
conti deposito 3,25-3,50% (Aidexa, Banca Progetto, Cherry, settembre 2026); sUSDe «high single
|
||||
digits» Q2 2026 (9,4% 7g / 11,8% 90g ad aprile). Bollo 0,2% su tutto.
|
||||
|
||||
## 4. La lettura
|
||||
|
||||
- **Fra le opzioni che lasciano intatto lo split, la migliore vale +€62/anno = €5/mese.** BOT e conto
|
||||
deposito distano €10/anno: il 12,5% del BOT compensa quasi tutto il lordo in meno. Dentro il
|
||||
rumore di *una* asta.
|
||||
- **sUSDe diretto rende ~2× Deribit** (che trattiene metà dell'APR Ethena) — ma metterebbe i €6k
|
||||
sullo **stesso emittente** dei 654 USDE già in conto. Lo split-cassa esiste per non essere
|
||||
correlato al venue e al crypto: questa opzione lo trasforma in concentrazione. Già a 24,2% di quota
|
||||
l'evento emittente vale 3,1× il maxDD dell'intero libro (memoria 30/08).
|
||||
- **Versare sul libro** è l'alternativa che la memoria indica come dominante (€10k = 2,45× in 13
|
||||
anni) — ma è trading, e distrugge lo split. Non risponde alla domanda: la cambia.
|
||||
|
||||
## 5. La taglia
|
||||
|
||||
Il miglior guadagno *sicuro* sullo status quo è **€62/anno**. **€100/mese di bonifico valgono
|
||||
+4,07%/anno di drift.** Il modo esiste, è reale, ed è due ordini di grandezza sotto la leva che il
|
||||
progetto ha misurato sei volte come binding.
|
||||
|
||||
## 6. Cosa resta all'operatore
|
||||
|
||||
1. **Quanto dei €6.043 è fondo d'emergenza.** Con quello, la tabella si applica al resto.
|
||||
2. Fra BOT e conto deposito: **preferenza di liquidità** (MOT vs vincolo 12 mesi) e di controparte
|
||||
(Stato vs FITD), non di rendimento — la differenza è €10/anno.
|
||||
3. On-venue non c'è niente da decidere. La chiave di scala a 1,25x (GATE SCALA-01, ottobre)
|
||||
libererebbe ~$400 → ~$10/anno: lo dico perché è l'unica via strutturale, non perché valga.
|
||||
|
||||
## 7. La conclusione dell'operatore (02/09, 06:00Z) — e le tre precisazioni
|
||||
|
||||
> *«quindi la miglior cosa è mettere gli XEON su deribit con il nostro sistema attivo»*
|
||||
|
||||
**Sì, ed è la conclusione che la memoria aveva già scritto il 27/07** (`r0727_lumpsum_split.py`:
|
||||
piano «€5.000 dei €6.043 in XEON + €500/mese»; il 25/08 sono entrati $1.399, non €5.000). La
|
||||
lettura della tabella è quella giusta: versare batte tenere fermo, ed è la leva binding. Ma il
|
||||
*come* ha tre vincoli, tutti già misurati:
|
||||
|
||||
1. **Non tutti: €5.000, non €6.043.** La protezione dal rischio venue **satura a qualunque quota
|
||||
> 0** (P(perso tutto) 18% → 3,5% tenendo fuori il 10%, e resta 3,5% al 25% e al 40%). L'ultimo
|
||||
migliaio dentro compra ~€100/anno attesi e butta via l'intera assicurazione. **~€1.000 restano
|
||||
fuori: è lo split, e costa zero.**
|
||||
2. **Il «~€600» non è un tasso.** È un'attesa a banda larga su una strategia a mercato il 22% dei
|
||||
giorni (live: 14/64, leva mediana 0,00x). Numeri onesti: TWR **+10,8% in 70 giorni**, Sharpe
|
||||
hold-out TP01 **~+0,05**, fisco d'accumulo **−30% a 10 anni**. Un anno piatto è possibile; il
|
||||
conto deposito non lo ha.
|
||||
3. **Il fondo d'emergenza** — non dichiarato dall'operatore, non noto alla memoria. Se una parte
|
||||
dei €6.043 lo è, resta fuori.
|
||||
|
||||
**Operativamente non serve nulla:** il sistema è live dal 20/06, il cap è dinamico (`equity × 0,5`),
|
||||
il rilevatore di salti ha già gestito il 25/08 senza falsi allarmi. Il deposito viene assorbito al
|
||||
giro successivo delle :47. Resta da scrivere **una riga di giornale con data e importo** — la fonte
|
||||
del cumulato dall'arming va scritta prima, non ricostruita dopo (debito #14). **In attesa di importo
|
||||
e data dall'operatore.** N9: le opzioni sicure (+€52-62/anno) sono state viste e messe da parte
|
||||
*con motivo*, non ignorate.
|
||||
@@ -0,0 +1,76 @@
|
||||
# 2026-09-02 — Debito 14: il report stampa il TWR, non il bonifico
|
||||
|
||||
*Scritto il 2026-09-02 fra le 14:16Z e le 14:22Z (commit `2f701b8`, 14:22:50Z; la prima stesura diceva «fino alle 14:30Z» senza averlo letto — corretto in revisione). Numeri dalla lettura di equity delle 13:47:01Z (1.678 letture) e dal report rilanciato dopo la riparazione.*
|
||||
|
||||
## Cosa c'era
|
||||
|
||||
`trades_db.py --report` calcolava `100*(e1/e0-1)` sulla serie grezza di equity e lo stampava come
|
||||
performance: **+243%**. La serie contiene un solo salto, il versamento di $1.399,39 del 25/08
|
||||
11:47Z: il 96,3% di quel numero era un bonifico. Il classificatore che lo scorpora esisteva già
|
||||
(`journal.movimenti_capitale`, dal 25/08) e non era mai arrivato al secondo lettore della stessa
|
||||
serie. Trovato e registrato il 01/09 (`2026-09-01-stato-trades.md`), non riparato perché lo script
|
||||
legge il libro vivo.
|
||||
|
||||
## Cosa ho fatto
|
||||
|
||||
| pezzo | dove | cosa |
|
||||
|---|---|---|
|
||||
| funzione unica | `src/live/journal.py` → `rendimento_twr(con, fino_ts)` | chiama `movimenti_capitale`, spezza la serie all'equity PRIMA e DOPO ogni movimento **certo**, moltiplica i segmenti. Gli **ambigui non spezzano** (P12): restano nel rendimento, dichiarati. Tre stati: numero, o `None` con `motivo` |
|
||||
| il report | `scripts/live/trades_db.py` → `report()` | chiama `rendimento_twr` (P1: non rifà il conto). Stampa TWR con i segmenti datati, i movimenti elencati con la classe, `trading da arming`, e il delta $ grezzo etichettato «movimenti di capitale INCLUSI». Il `%` grezzo non compare più |
|
||||
| test | `tests/test_journal.py` (+5) · `tests/test_trades_report.py` (+2) | versamento spezzato e grezzo no · senza movimenti = rendimento semplice · ambiguo non spezza · serie vuota → `None` con motivo · **riproduzione del +10,80% del diario 01/09** (M23) · il testo stampato contiene TWR e non il grezzo · `connect()` nudo deviato in tmp (conftest), verificato nel test |
|
||||
|
||||
## Il report, prima e dopo
|
||||
|
||||
```
|
||||
prima: equity : $598.06 -> $2,051.84 (+1453.78, +243.08%)
|
||||
|
||||
dopo: equity : $598.06 -> $2,048.40 (+1,450.34 di equity, movimenti di capitale INCLUSI) | picco $2,073.59 | 1678 letture
|
||||
movimenti capitale : +1,399.39 certi | +0.00 ambiguo/i (restano nel P&L, dichiarati)
|
||||
2026-08-25T11:47 +1,399.39 [movimento]
|
||||
trading da arming : +50.95 (equity al netto dei movimenti certi)
|
||||
TWR : +10.61% = +11.61% [2026-06-23 -> 2026-08-25] x -0.89% [2026-08-25 -> 2026-09-02]
|
||||
```
|
||||
|
||||
Il giornale del giorno concorda: «cumulato dall'arming $+1.450,34 — di cui $+1.399,39 versati ->
|
||||
trading **$+50,95**». Stessa base, stesso versamento, stessa funzione.
|
||||
|
||||
## Una cosa imparata riparando
|
||||
|
||||
Quattro test sono nati rossi con scenari a **+10% di trading** fra due letture: la soglia del
|
||||
rilevatore (`book.EQUITY_JUMP_ALERT`) è esattamente 0,10, e a mercato piatto un +10% supera «2× il
|
||||
massimo che il mercato poteva fare» (che è zero) — viene classificato **movimento**. Non è un difetto
|
||||
della riparazione: è il limite già dichiarato del rilevatore live (D5, nel docstring di
|
||||
`movimenti_capitale`). Nella serie vera non morde — fra due letture orarie consecutive il trading non
|
||||
fa +10% — ma un test che lo tocca lo rende visibile. I test ora usano +5%, e il limite è scritto
|
||||
nel test, nel CLAUDE.md e qui.
|
||||
|
||||
## Cosa NON ho fatto
|
||||
|
||||
- Il giornale (`pnl_giorno`, pagina markdown) **non stampa il TWR**: stampa già lo scorporo in
|
||||
dollari e la sua pagina ha una forma testata. Aggiungerlo è una riga, ma non era il debito.
|
||||
- Non ho scomposto lo scarto (~$25) fra round-trip + non realizzato e il trading al netto: era già
|
||||
fuori scope il 01/09 e lo resta.
|
||||
|
||||
## Revisione del codice, prima tornata (15:15Z, commit `835e0c8` 15:17:05Z) — tre cose trovate, tutte riparate
|
||||
|
||||
| trovato | vero? | riparazione |
|
||||
|---|---|---|
|
||||
| l'intervallo che contiene un movimento certo esce intero dal rendimento: il suo P&L di mercato finisce in `certi` e fuori da `trading`, e nessun documento lo diceva | sì, per costruzione. Errore massimo = metà del movimento riconosciuto (margine del classificatore). Il 25/08: ~$263 lordi a ±0,2% → ~$0,5 | dichiarato nel docstring, nel report, in CLAUDE.md §5.14 e in memoria 40. Non si stima (P12) |
|
||||
| a base di equity zero `trading` era un numero mentre `twr` era None, e sbagliato: il salto 0→X è invisibile al classificatore, `trading` valeva l'intero conto sotto l'etichetta "al netto dei versamenti" | sì. Non raggiungibile dal cron (non scrive mai 0), ma raggiungibile da un backfill | tre stati veri: `twr` e `trading` entrambi None con motivo; il report stampa n/d. Test in `test_journal.py` e `test_trades_report.py` |
|
||||
| movimento nel primo intervallo o due consecutivi → segmento `+0.00% [x -> x]` | sì, cosmetico | i segmenti a lunghezza zero non si producono; nessun tempo a mercato → TWR 0,0 dichiarato numero. Test |
|
||||
|
||||
Il numero pubblicato non cambia: +10,61% alle 14:02Z, +10,56% alle 14:47Z per la marcatura.
|
||||
|
||||
## Seconda tornata (15:30Z) — la piu' importante di tutte
|
||||
|
||||
| trovato | vero? | riparazione |
|
||||
|---|---|---|
|
||||
| il classificatore dei movimenti era **cieco ~23 ore al giorno**: il feed 1h si ferma alle 00:00, `asof` dava la stessa barra alle due letture, mercato «fermo» = 0, qualunque calo ≥10% del giorno diventava «movimento». Riprodotto dalla revisione su copia del DB: −12% appeso alle 14:47 → `certi` 1.399 → 1.153, TWR invariato invece di −2,71% | si'. Verificato: ultima barra 00:00, adesso 15:26 | nuovo stato **«mercato non misurabile»** (feed fermo, assente, o barra mancante) ⇒ `ambiguo` con la ragione; `twr` e `trading` None con motivo |
|
||||
| con `data/raw` assente (host ripristinato prima del rebuild) tutto era «ambiguo», `certi` 0, e il report stampava **+243% sotto l'etichetta TWR** | si' | stesso stato: n/d, mai il grezzo |
|
||||
| «`rendimento_twr` e' la funzione che entrambi chiamano» era **falso**: `pnl_giorno` rifaceva `cum − certi`, e la testata Telegram mandava il cumulato grezzo | si' | `pnl_giorno` chiama `rendimento_twr`; la pagina stampa il TWR accanto al cumulato; la testata dice «di cui versati → trading» |
|
||||
| `leva_tetto` del classificatore ridichiarava `frac × n` ignorando `book_scale_k` e `LEVA_LORDA_MAX` (P1) | si' | `min(frac × n × scala, LEVA_LORDA_MAX)`; fallback = tetto di codice |
|
||||
| il «limite ereditato: +10% di trading a mercato fermo» scritto la mattina era un **artefatto della fixture piatta** (a mercato misurato il trading non supera 2·max_mkt) | si' | cancellato ovunque; il limite vero e' quello sopra |
|
||||
| il bound di mercato guarda solo BTC/ETH, ma il 31% dell'equity e' USDE: un depeg pieno sarebbe un «prelievo» | si' | **non riparato**: l'indice USDE e' registrato una volta al giorno. Debito **§5.15** |
|
||||
| l'ora del report scritta in §2 (14:02Z) era l'ora di esecuzione, non quella della lettura (13:47:01Z); i tempi «scritto» dei diari erano posteriori ai commit | si' | corretti coi tempi veri |
|
||||
|
||||
Il numero pubblicato: TWR +10,61% alla lettura delle 13:47Z; +10,56% alle 14:47Z.
|
||||
@@ -0,0 +1,67 @@
|
||||
# 2026-09-02 — Debito 7: il docstring diceva 230 minuti, il cron gira ogni ora
|
||||
|
||||
*Scritto il 2026-09-02 fra le 14:36Z e le 14:38Z (commit `861cc7f`, 14:38:53Z; la prima stesura diceva «alle 14:45Z» senza averlo letto — corretto in revisione). Cadenza misurata dal log (`r0823_sl_anchor.py`: 1.443 giri in
|
||||
1.442 ore) e dalla crontab installata, letta dal test.*
|
||||
|
||||
## Il difetto
|
||||
|
||||
| fonte | cosa diceva | dal |
|
||||
|---|---|---|
|
||||
| `scripts/live/book_execute.py`, docstring | «va lanciato ogni ~230 minuti» | 20/06 |
|
||||
| `scripts/cron_book.sh`, intestazione | cadenza ORARIA, `47 * * * *` | 25/08 (prima `:07`, sempre orario) |
|
||||
| crontab della VPS | `47 * * * *` | 25/08 |
|
||||
|
||||
Era il **docstring** a sbagliare. E non era una svista innocua: sulla riga 4h (la più vicina ai 230
|
||||
minuti) BTC **raddoppia** gli scatti del disaster-SL rotolante (2 contro 1; a 24h ETH arriva a 5, con
|
||||
2 in 30 giorni). Il rischio era un lettore diligente che "allineasse" il cron al docstring.
|
||||
|
||||
## La riparazione
|
||||
|
||||
1. **Docstring riscritto**: `CADENZA: ORARIA`, la riga di crontab citata, la ragione (giro
|
||||
idempotente → girare più fitto della griglia non costa ordini; latenza ≤1h sugli ingressi/uscite
|
||||
software di SKH01; ri-ancoraggio orario del rotolante) e il divieto esplicito, con la data.
|
||||
2. **`tests/test_book_cadenza.py`, 10 test.** P1: non ridichiara «60 minuti», **deriva** le tre
|
||||
fonti e le confronta fra loro:
|
||||
- il docstring non contiene più «ogni ~230» e dichiara ORARIA;
|
||||
- la riga di crontab citata nel docstring è quella dichiarata in `cron_book.sh`;
|
||||
- la riga dichiarata è oraria (parser a 5 campi, solo casi regolari, rifiuta il resto),
|
||||
sotto i 230 della griglia e lontana dai 240 della riga peggiore, e fuori dal minuto `:00`;
|
||||
- **la crontab installata** (`crontab -l`) coincide con la dichiarata. Dove la crontab non è
|
||||
leggibile il test è **saltato**, non verde (P5). Sulla VPS oggi gira e passa.
|
||||
|
||||
## Numeri
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| test nuovi | 10, tutti verdi; suite dei moduli che toccano `book_execute`: 231 verdi |
|
||||
| cadenza dichiarata / installata | 60 min / 60 min |
|
||||
| griglia SKH01 | 230 min (la riga da non usare) |
|
||||
| riga peggiore misurata | 240 min (BTC 2 scatti contro 1) |
|
||||
|
||||
## Cosa NON ho fatto
|
||||
|
||||
- Non ho toccato il cron né `cron_book.sh`: la configurazione che gira era quella giusta.
|
||||
- Non ho aggiunto un sorvegliante a runtime sulla cadenza (contare i giri per ora nel log): il
|
||||
giornale già stampa «giri mancanti» ogni giorno, e il test copre la deriva di configurazione.
|
||||
|
||||
## Revisione del codice, prima tornata (15:15Z, commit `835e0c8`) — due cose trovate, tutte riparate
|
||||
|
||||
| trovato | vero? | riparazione |
|
||||
|---|---|---|
|
||||
| il docstring nuovo diceva «il disaster-SL rotolante si ri-ancora ogni ora»: falso. `ensure_disaster_sl` lascia il bracket com'è finché lo stop voluto è entro il 5% da quello piazzato e la taglia entro il 10%; il giro orario **controlla**, ri-ancora oltre la tolleranza (mark +5,263% / −4,762%). I «BTC 0 / ETH 1» di `r0823` sono misurati **con** questa isteresi | sì, verificato in `execution.py:233-235`. Propagato anche a CLAUDE.md §5.7 e memoria 40 | riscritto in tutti e tre i posti: «controllato ogni ora, ri-ancorato solo oltre la tolleranza; lo stop siede fra −26,3% e −33,3% dal mark corrente». Frase sbagliata su un meccanismo di sicurezza vivo: era la segnalazione più importante |
|
||||
| la guardia del test cercava due letterali («ogni ~230»): «ogni quattro ore» sarebbe passato | sì | la guardia estrae **ogni** prescrizione «ogni/every N unità» e pretende 60 minuti; controllo positivo (M15) su cinque frasi. Limite dichiarato (P13): una prosa che prescrive senza «ogni» passa |
|
||||
|
||||
## Seconda tornata (15:30Z)
|
||||
|
||||
| trovato | vero? | riparazione |
|
||||
|---|---|---|
|
||||
| la frase nuova di CLAUDE.md §5.7 era **invertita** («chi lo avesse corretto nel verso del cron»): correggere il docstring verso il cron e' proprio la riparazione; il pericolo e' correggere il CRON verso il docstring | si' | riscritta nel verso giusto |
|
||||
| la banda dello stop era sbagliata: con isteresi 5% sullo stop (0,70·mark) lo stop siede a 0,665–0,735 × mark, cioe' **−33,5% / −26,5%**, non −26,3/−33,3 | si' | corretta in docstring, CLAUDE.md, memoria |
|
||||
| «BTC raddoppia gli scatti» leggeva male `r0823`: il «2 contro 1» e' rotolante contro pavimento **dentro la riga 4h**; a 1h il rotolante ne fa 0, e la riga peggiore misurata e' **24h** (ETH 5) | si' (la revisione ha eseguito `r0823.simula`) | riscritto ovunque coi conteggi; nel test `RIGA_4H_MIN`, non «peggiore»; `LTF_MIN` importato da `skyhook` (P1) |
|
||||
| «girare piu' fitto non costa ordini» era falso: 22 dei 48 ordini live sono ri-taglie |Δ|≤$10 a segnale invariato; il deadband e' in valuta assoluta (C2) | si' (contati) | riscritto: «costa solo micro-ordini di ri-taglia», con la regola C2 |
|
||||
| «latenza ≤1h» senza condizione: vale solo con la feed 5m fresca; `fresh_5m` ripiega in silenzio sul giornaliero e `skh_feed_max_age_min` allerta e basta | si' | condizione scritta |
|
||||
| `_cron_installato` leggeva la **prima** riga: una seconda riga attiva sotto (`17 * * * *`, dentro lo slot di release) era invisibile | si' (simulato) | si contano **tutte** le righe attive, e devono essere una |
|
||||
| lo skip collassava tre stati: crontab illeggibile, leggibile senza il progetto, leggibile col progetto ma senza `cron_book.sh` attivo (= libro non schedulato) | si' | tre stati: SALTATO, SALTATO, **ROSSO** |
|
||||
| solo il vincolo 1 del `:47` era testato (minuto ≠ 00); il vincolo 2 (fuori dallo slot di release del martedi') no | si' | test con `venue_probe.in_release_window`, il :07 come controllo positivo |
|
||||
| `_cron_dichiarato` accettava solo `N * * * *` e falliva come «assente» su `*/30`; `cadenza_minuti` accettava `*/45`, `0 */5`, `*/0` | si' | regex a 5 campi qualunque, regolarita' e passo > 0 imposti, parametrizzati |
|
||||
| `r0823_sl_anchor.py` si rifiutava di girare nella finestra del **vecchio** `:07` e stampava a runtime che il docstring «prescrive ~230 min»; `cron_chain.sh` giustificava il :25 «fuori dal :07» | si' | guardia sul :47, prosa al passato, commento aggiornato |
|
||||
@@ -0,0 +1,103 @@
|
||||
# 2026-09-02 — Un flag sconosciuto non è l'azione di default
|
||||
|
||||
*Scritto fra le 15:45Z e le 16:05Z. Il difetto è stato misurato, non ipotizzato: la revisione del
|
||||
codice di questa sessione ha eseguito `trades_db.py --help` come sonda di tempi e lo ha visto
|
||||
sincronizzare il DB.*
|
||||
|
||||
## 1. Il difetto
|
||||
|
||||
Nessuno dei 18 script di `scripts/live/` usava argparse. Leggevano gli argomenti così:
|
||||
|
||||
```python
|
||||
if "--reconcile" in args: ...
|
||||
elif "--report" in args: ...
|
||||
else: sync() # <- qui finiva QUALUNQUE cosa non riconosciuta
|
||||
```
|
||||
|
||||
Un flag sbagliato non era un errore: era il ramo finale. Costo osservato e costi possibili:
|
||||
|
||||
| script | `--help` faceva | gravità |
|
||||
|---|---|---|
|
||||
| `trades_db.py` | `sync()`: riscriveva `meta.ultimo_sync` | osservato oggi, danno nullo |
|
||||
| `journal.py` | scriveva la pagina di oggi e una riga nel DB | non osservato, ma scrive |
|
||||
| `analista.py` | spendeva una chiamata al modello e mandava un Telegram | non osservato, ma spende |
|
||||
|
||||
## 2. La riparazione, e perché non è argparse
|
||||
|
||||
`src/live/cli.valida(nome, uso, flag=..., con_valore=...)`, chiamata come **prima istruzione** di
|
||||
ogni blocco `__main__` — prima di `connect()`, prima del sync, prima della rete.
|
||||
|
||||
- `--help` / `-h` → stampa l'uso, esce **0**;
|
||||
- flag non dichiarato, o valore mancante, o posizionale → stderr con **cosa** e **cosa si poteva**
|
||||
(P4), esce **2** (errore d'uso, distinto dall'1 con cui gli script segnalano un esito negativo);
|
||||
- tutto il resto invariato.
|
||||
|
||||
**Argparse no.** Cambierebbe messaggi d'errore, codici d'uscita e il comportamento di `--help` su
|
||||
18 script che il cron già invoca: romperebbe ciò che gira per riparare ciò che non gira mai. Qui
|
||||
serviva l'opposto — lasciare intatti i flag esistenti e rifiutare solo l'ignoto.
|
||||
|
||||
## 3. Il test guarda entrambe le metà del contratto
|
||||
|
||||
`tests/test_cli_flag.py`, 30 test:
|
||||
|
||||
- **l'elenco degli script si deriva dalla cartella** (P1): uno script nuovo che legge `sys.argv` a
|
||||
mano e non chiama `valida` fa fallire il test, non passa inosservato perché nessuno ha aggiornato
|
||||
una lista;
|
||||
- `valida` dev'essere la **prima istruzione** di `__main__` (uscire dopo l'effetto non è uscire);
|
||||
- l'uso deve **nominare** i flag che lo script accetta;
|
||||
- **i flag che il cron usa davvero devono restare accettati** (P15/P16, letti dai `cron_*.sh`): una
|
||||
guardia che ferma il libro di bordo la notte stessa sarebbe peggio del difetto;
|
||||
- end-to-end sui tre script che scrivono: `--help` esce 0 e **non tocca `trades.db`**; un flag
|
||||
ignoto esce 2 e non lo tocca (M15: il caso che ha causato il difetto, non un caso vicino).
|
||||
|
||||
Verificato a mano anche il contrario: `monitor_health.py --quiet`, `trades_db.py --sync --quiet`
|
||||
e `book_execute.py` in dry-run escono 0 come prima.
|
||||
|
||||
## 4. Il resto della sessione
|
||||
|
||||
**Indice USDE orario** (debito §5.15, primo passo). Il classificatore dei movimenti di capitale
|
||||
confronta due letture di equity a un'ora di distanza e il suo bound guarda solo BTC/ETH, mentre il
|
||||
31% dell'equity è USDE: un depeg sarebbe scorporato come un prelievo. `usde_watch` registra
|
||||
l'indice una volta al giorno — troppo rado. Ora `balance_watch` (orario, sola lettura) lo registra
|
||||
a ogni campione, `None` con la ragione se non leggibile, mai 1,0. Il cablaggio nel bound viene
|
||||
quando la serie ha storia: prima serve il dato, poi la regola.
|
||||
|
||||
**Pulizia lasciata dalla revisione.** `tests/helpers.carica_script` sostituisce la dodicesima copia
|
||||
del caricatore `importlib` (15 in giro, con nomi di modulo diversi per lo stesso file e differenze
|
||||
silenziose su `sys.modules`); il fill di prova si scrive con `upsert_fills` invece che con un
|
||||
`INSERT` a mano che lasciava `verified` NULL, una forma che la produzione non produce; le
|
||||
asserzioni sul testo del report non sono più ancorate al padding; `movimenti_capitale` accetta le
|
||||
righe già lette, così il report non fa due SELECT sulla stessa tabella a un'ora del cron — due
|
||||
letture ai due lati di una scrittura descriverebbero due istanti diversi.
|
||||
|
||||
## 5. Il README era fermo a quindici giorni prima del reset
|
||||
|
||||
Chiesto un aggiornamento della documentazione, ho aperto il README e ho trovato un documento del
|
||||
**2026-06-04**. Il reset v2.0.0 è del **19 giugno**. Per 75 giorni la prima pagina del repository ha
|
||||
descritto la libreria pre-reset come se fosse il presente:
|
||||
|
||||
| cosa diceva | stato vero |
|
||||
|---|---|
|
||||
| famiglie FADE / HONEST / PAIRS / TSMOM / SHAPE, strategie MR01…SH01 | archiviate in `Old/`, artefatto di feed contaminato |
|
||||
| PORT06: Sharpe **7,84 / 10,06**, DD 2,60%, CAGR ~79% | numeri che `CLAUDE.md` §2 elenca sotto «non citare» |
|
||||
| paper trader su `strategies.yml`, portafogli in `portfolios.yml` | **file inesistenti** |
|
||||
| `scripts/waste/`, `scripts/portfolios/`, `src/live/multi_runner.py` | **inesistenti** |
|
||||
| esecuzione shadow su Deribit **testnet** | il testnet è *la causa del reset*; oggi si esegue su mainnet con soldi veri |
|
||||
|
||||
Riscritto contro lo stato vero: cosa gira adesso, i numeri nella lente di §2 (TWR +10,6%, non la
|
||||
crescita del conto), il metodo e i sei requisiti, la struttura verificata file per file, i gate con
|
||||
le loro date, l'obiettivo con la sua onestà. Ogni percorso citato è stato controllato: esiste.
|
||||
Da 423 righe a 152.
|
||||
|
||||
**La lezione che ho registrato non è «aggiornare il README».** È che un reset invalida anche i
|
||||
documenti che nessuno rilegge, e l'inventario di cosa cita numeri morti va fatto il giorno del
|
||||
reset — non 75 giorni dopo, per caso, mentre si fa altro.
|
||||
|
||||
## 6. Cosa NON ho fatto
|
||||
|
||||
- I loader `importlib` dei test sulla **ricerca** (moduli in `scripts/research/`) restano come
|
||||
sono: sono a livello di modulo, con radici diverse, e toccarli non ripara niente.
|
||||
- Il bound USDE nel classificatore: manca la storia, non il codice.
|
||||
|
||||
- Non ho cercato altri documenti fermi al pre-reset fuori da `README.md` e `docs/`: l'inventario
|
||||
completo (ogni file che cita un numero morto) resta da fare, ed è la vera coda di questa scoperta.
|
||||
@@ -0,0 +1,91 @@
|
||||
# 2026-09-06 — USDE: dal 14,6% al 68,7%. Il tetto del venue del 30-31/08 non c'era piu'; il versamento di prova del 25/08 dichiarato
|
||||
|
||||
**Ore:** 20:56-21:16Z. **Autorizzazioni dell'operatore, in sessione:** *«usa piu' USDE se ne hai bisogno»*,
|
||||
poi *«cerca % massima raggiungibile in USDE»*, sulla decisione gia' presa il 30/08 (quota bersaglio 70%,
|
||||
`usde.quota_target`). E, a meta' sessione: *«i 25 euro erano un versamento di prova»*.
|
||||
|
||||
## 1. Il versamento del 04/09 aveva dimezzato la quota
|
||||
|
||||
Il versamento di **$2.410,14 del 04/09 14:47Z** (riconosciuto dal classificatore: mercato max 0,56% a leva
|
||||
piena contro un salto del +116,8%) ha portato l'equity a **~$4.495** e la quota USDE da 31,3% a **14,6%**.
|
||||
Nessun automatismo la riportava su.
|
||||
|
||||
## 2. Primo passo: al tetto di config (31,8%) — e la lettura SBAGLIATA che ne ho dato
|
||||
|
||||
`usde_convert.py --quota 0.318 --esegui`: 774 USDE in 8 ordini @ 1,0005 medio, zero rifiuti, quota 31,79%.
|
||||
Ho scritto in CLAUDE.md «il tetto ha tenuto la stessa frazione a equity 2,2× maggiore: terza conferma».
|
||||
**Falso**: nessun rifiuto fino al 31,8% dimostra che il tetto e' **≥** 31,8%, non che e' **=** 31,8%. Il
|
||||
dato non conteneva la conferma; l'ho letta perche' me l'aspettavo. Corretto venti minuti dopo, quando
|
||||
l'operatore ha chiesto la % massima e la sonda ha mostrato che la conferma non c'era.
|
||||
|
||||
## 3. La sonda: nessun tetto fino al 68,7%
|
||||
|
||||
`scripts/research/r0906_usde_tetto_sonda.py` — a saldo crescente, passi piccoli, stesso metodo dei diari
|
||||
30-31/08 (un probe unico di taglia sbagliata produce un falso negativo): sale finche' il venue non rifiuta,
|
||||
ripete il rifiuto dopo aver riletto il book (il messaggio `not_enough_funds_in_currency` e' lo stesso di un
|
||||
limite che non incrocia), scende di passo a ogni doppio rifiuto, e conferma il muro a saldo neutro
|
||||
(SELL 1 → BUY 1 ok → BUY 1 rifiutato). Guardie: prezzo dal book (ask+5 tick, tetto 1,0010), quota mai oltre
|
||||
il 70% e **USDC mai sotto il cuscino di regolamento** derivato da config (`usde_convert.cuscino_richiesto_usd`).
|
||||
|
||||
| corsa | passi | da → a (USDE) | quota | rifiuti |
|
||||
|---|---|---|---|---|
|
||||
| 21:02-21:09 | 30 × 2 | 1.428,7 → 1.488,7 | 31,8% → 33,1% | **0** (fermata dal mio limite +60) |
|
||||
| 21:11-21:15 | 16 × 100 | 1.488,7 → **3.088,7** | 33,1% → **68,7%** | **0** (fermata dal cuscino: +100 avrebbe superato il 70%) |
|
||||
|
||||
**Totale del giorno: 2.434 USDE in 39 ordini @ 1,0005, ~$1,2 di spread.** Conto finale: USDC **$1.407,40** +
|
||||
USDE **3.088,72** = $4.496; cuscino richiesto $1.349 → slack **+$58**. Registro: `data/live/usde_convert.jsonl`
|
||||
(due record `sonda_tetto`, con ogni ordine).
|
||||
|
||||
## 4. Cosa dice il risultato — e cosa non dice
|
||||
|
||||
1. **Il tetto del 30-31/08 era vero allora ed e' sparito ora.** Tre bracket riproducibili a 1 USDE (31,21-31,26% ·
|
||||
31,29-31,34% · 31,84-31,88%) contro 2.434 USDE accettati oggi senza un rifiuto. **Non e' scalato, e'
|
||||
sparito**: la frase del 31/08 «il 70% non e' raggiungibile ne' ora ne' mai» e' falsificata.
|
||||
2. **Non spiegato** (D5). Ipotesi non verificabili da qui (il gateway non espone il transaction log ne' i
|
||||
parametri del cap): un'allocazione per-conto del Cap ETHENA che varia col saldo aggregato dell'exchange;
|
||||
un vincolo sui fondi depositati di recente (ma l'USDC del 04/09 e' stato convertibile a 2 giorni); un
|
||||
cambio di policy. **Il tetto puo' tornare**: quando tornera', bloccherebbe gli ACQUISTI, mai le vendite
|
||||
(tutte le SELL sono sempre passate). `venue_cap_frac` → **null** con la misura in `venue_cap_misurato`;
|
||||
`usde_convert` si affida al chunk+backoff sui rifiuti, che e' cio' che ha sempre fatto.
|
||||
3. **Il limite che morde e' NOSTRO: il cuscino di regolamento.** P&L e funding dei perp si regolano in USDC;
|
||||
a 68,7% restano $58 sopra il cuscino. ⚠️ **Nessuno sorveglia il cuscino**: `usde_watch` allerta sulla
|
||||
quota (`quota_max_frac`, riportata a **0,85**, il valore che l'operatore aveva scelto il 30/08 «in previsione
|
||||
della quota al 70%»), non sull'USDC. Una perdita del libro fa salire la quota da sola: a −$58 il cuscino e'
|
||||
scoperto, e Deribit finanzia un saldo USDC negativo allo 0,05%/giorno (18%/anno). Debito nuovo, §5.17.
|
||||
4. **Rischio emittente**: a 24,2% l'evento Ethena valeva 3,1× il maxDD del libro (30/08); a 68,7% vale **~8,8×**.
|
||||
E' la decisione del 30/08 presa con l'informazione completa (r0830_usde_quota), oggi eseguita. Resa attesa
|
||||
~4,1% × $3.089 ≈ **$127/anno** (era $26): ancora meno di un mese di versamento.
|
||||
|
||||
## 5. Il versamento di prova del 25/08: dichiarato, scorporato, e il TWR scende di 4 punti
|
||||
|
||||
Trovato stamattina per differenza (equity 07:07→08:07 **+$21,11** con le posizioni a **−$3,3/−4,3**) e
|
||||
confermato dall'operatore: **€25 di prova** prima del bonifico da $1.399,39 delle 11:47. Il rilevatore non
|
||||
poteva vederlo (+3,3% contro soglia 10%) e abbassare la soglia farebbe di ogni ora di mercato un candidato:
|
||||
**l'unica fonte che sa l'importo e' l'operatore**. Costruito il canale:
|
||||
|
||||
- `data/live/movimenti_dichiarati.jsonl` (append-only, nel perimetro di backup — P11), letto da
|
||||
`journal.movimenti_dichiarati()`; `movimenti_capitale` lo fonde coi rilevati: fonte `dichiarato`, importo
|
||||
dell'operatore (non il salto), `dopo = prima + importo` cosi' il mercato dell'ora **resta** nel rendimento;
|
||||
se coincide con un salto rilevato l'importo dichiarato vince (`dichiarato+rilevato`); fuori dalle letture →
|
||||
`avvisi`, non applicato; riga rotta → `avvisi`, le altre restano (P3).
|
||||
- Importo registrato **$24,90 ± 0,50**: stimato per differenza (salto +21,11, mercato −3,3/−4,3 dal log del
|
||||
book, fee 0,03) — il gateway non espone `get_deposits`, quindi il numero USDC esatto non e' leggibile; la
|
||||
banda sta nel record e nel report (P12).
|
||||
- `tests/test_journal.py`: +6 (scorporo sotto soglia · dichiarato+rilevato · fuori letture · riga rotta ·
|
||||
senza file niente cambia · la nota di giornale lo dice); `conftest` isola il file nei test.
|
||||
|
||||
| | prima (20:47Z) | dopo (21:14Z) |
|
||||
|---|---|---|
|
||||
| movimenti certi | $3.809,53 | **$3.834,43** |
|
||||
| trading da arming | +$87,35 | **+$62,45** |
|
||||
| TWR | +11,98% | **+7,83%** = +8,14% × −0,62% × −0,12% × +0,46% |
|
||||
|
||||
**Un terzo del «trading» era un bonifico da 25 euro.** Regola nuova (CLAUDE.md §2): ogni versamento, anche
|
||||
di prova, si dichiara nel file il giorno stesso.
|
||||
|
||||
## 6. File toccati
|
||||
|
||||
`config/live.json` (usde: venue_cap_frac null, quota_max_frac 0.85, nota) · `src/live/journal.py` ·
|
||||
`scripts/live/trades_db.py` · `tests/conftest.py` · `tests/test_journal.py` (+6) ·
|
||||
`scripts/research/r0906_usde_tetto_sonda.py` (nuovo) · `data/live/movimenti_dichiarati.jsonl` (nuovo) ·
|
||||
`CLAUDE.md` §1 §2 §4 §5.
|
||||
@@ -0,0 +1,219 @@
|
||||
# 2026-09-09 — Guadagnare nei crolli: revisione dei sistemi, il crollo catturato, piu' monete su Deribit
|
||||
|
||||
**Richiesta dell'operatore:** *"fai analisi e revisione dei sistemi adottati e trova soluzione per
|
||||
guadagnare anche nei crolli (usa le opzioni se serve), anche + monete ma sempre in deribit"*.
|
||||
Analisi e revisione col modello `fable` (revisore fresco, agente separato), test con Opus.
|
||||
|
||||
**Esito in una riga:** nel **giorno** del crollo il libro live **perde** (−0,48%/g, positivo nel 18%
|
||||
dei giorni ≤ −5%); per **finestra** e' immune-o-positivo e **dal 2022 chiude in utile 8/8 peggiori
|
||||
finestre**, tutte per la gamba SHORT di SKH01 marcata all'uscita; la sola leva che aumenterebbe quel
|
||||
guadagno e' il peso di SKH01, che il gate del progetto boccia su una condizione in-sample; le
|
||||
opzioni, misurate per la prima volta su un **crollo catturato a quote vere**, danno un rapporto di
|
||||
credito identico al rally (f_net 0,74 vs 0,714) — **non** il `f` di stress che la regola del 19/06
|
||||
aspettava (crollo a vol bassa, gate di VRP01 chiuso, famiglia inverse); XRP, l'unica "altra
|
||||
moneta" liquida e non misurata di Deribit, **diluisce** esattamente come SOL. **Libro, pesi,
|
||||
config, cron INVARIATI. Nessun ordine.**
|
||||
|
||||
## 0. Prima di misurare: cosa era gia' morto (memoria letta, non riaperta)
|
||||
|
||||
| meccanismo "guadagnare nei crolli" | esito | perche' e' morto (chi riapre deve battere questo) |
|
||||
|---|---|---|
|
||||
| gamba short del trend a 1d (§42) | SCARTATO | −1,91%/anno di drift in 0/24 ancore; il 2022 lo tiene a galla a meta' |
|
||||
| put deep-OTM statica (§46) | REFUTATO 0/162 | beta del libro +0,076: non si assicura un libro gia' piatto |
|
||||
| collar ≤15g (§71) · pavimento come licenza (§72) | REFUTATI | il DD lo riduce il TETTO (troncatura, C9); la put sola peggiora il DD 16/16 |
|
||||
| gamma scalping long-vol | SCARTATO | paga il VRP ogni anno, ogni frequenza |
|
||||
| DVOL direzionale · gate macro · skew de-risk | HEDGE / RIDONDANTI | TP01 e' gia' flat nei crolli (Δ 0,00) |
|
||||
| SOL terza gamba (22/08) | ESCLUSO dall'operatore | hold-out −0,166 in 0/24; un anno solo (2023) |
|
||||
| XS01 su Deribit (§50) | MORTO per ampiezza | 11 gambe con ≥1 anno: 1,265 → 0,116 |
|
||||
|
||||
Condizione di riapertura scritta tre volte (§3 short-vol, §71, §72): **un `f` di stress reale su
|
||||
un crash catturato**. E' la cosa che oggi si poteva misurare e non era stata misurata.
|
||||
|
||||
## 1. Il libro nei crolli — `r0909_libro_nei_crolli.py`
|
||||
|
||||
Lente: sleeve di ricerca, ancora canonica, fee 0,10% RT, funding fuori; pesi importati da
|
||||
`src/live/book` (P1). ⚠️ **SKH01 e' marcato all'USCITA del trade** (`harness.backtest_signals`):
|
||||
la serie giornaliera e' zero nell'87,7% dei giorni, e uno short aperto nella finestra e chiuso
|
||||
fuori e' accreditato fuori — per questo la tabella per GIORNO mostra SKH01 ≈ 0 e quella per
|
||||
FINESTRA il guadagno. Indice 50/50 BTC/ETH; giorno di crollo ≤ −5%; episodio = 12 peggiori
|
||||
finestre di 20 giorni non sovrapposte + 9 episodi nominati prima. Classi ESCLUSIVE nell'ordine
|
||||
dichiarato: immune (|libro| < |idx|/10, col segno) → POSITIVO → PERDE.
|
||||
|
||||
| misura | valore |
|
||||
|---|---|
|
||||
| beta del libro all'indice | **+0,0769 sulla finestra di §46** (2021-03-24+; memoria +0,076: riprodotto al millesimo) · +0,086 su tutta la storia · +0,002 nei giorni ≤ −3% |
|
||||
| 160 giorni ≤ −5% (indice −7,76%/g) | libro **−0,48%/g**: TP01 −0,63, SKH01 +0,15; **libro > 0 nel 18%** dei giorni; somme per anno: 2024 −11,9%, 2025 −7,3%, 2022 +8,2%, 2026 +4,7% |
|
||||
| 24 giorni ≤ −10% (indice −14,4%/g) | libro −0,51%/g: TP01 −0,93, SKH01 **+0,42** |
|
||||
| 12 peggiori finestre 20g | **4 POSITIVO / 6 immune (4 col segno +) / 2 PERDE**; libro > 0 in 8/12; **dal 2022 in 8/8** (mediana +3,3% contro indice −29%); le 2 perdite sono 2019 e maggio 2021 (TP01 ancora long: il trend non si era girato) |
|
||||
| 9 episodi nominati | libro > 0 in LUNA (+4,3%), giugno 2022 (+3,1%), FTX (+0,7%), agosto 2024 (+0,4%), 5/02/2026 (+4,4%), 1-5/06/2026 (+3,3%); COVID −0,04%, feb 2025 −1,5%, maggio 2021 −3,4% |
|
||||
| da dove viene il guadagno | la gamba **SHORT di SKH01 = 108%** dei guadagni negli episodi con libro > 0 (scomposizione ESATTA: long + short == intera su 2735/2735 giorni, verificato con assert; TP01 contribuisce in negativo); per anno al peso 0,25: 2022 **+10,7%**, 2026 **+7,7%** |
|
||||
| peggior giorno del libro (lente canonica) | 2025-10-10 −3,38%, tutto TP01 con esposizione **0,505**, indice −9,7%: **e' un giorno di crollo con TP01 mezzo long** (lo squeeze di §33 sta sulla lente hourly e qui non si riproduce: dichiarato nel docstring) |
|
||||
| riga informativa 37,5/62,5 (§26) | batte il 75/25 in 12/12 episodi, **ma** Sharpe 1,81 vs 1,82 e maxDD 12,1% vs 9,4%: paga i crolli con gli squeeze |
|
||||
|
||||
🚨 **Errore catturato in revisione (fable):** la prima stesura provava "GUADAGNA" prima di
|
||||
"immune" e stampava **8/12 GUADAGNA**; con le classi nell'ordine dichiarato sono **4 POSITIVO +
|
||||
6 immune**. *L'ordine delle regole era il verdetto.* Ora il verdetto e' tre affermazioni separate
|
||||
(giorno / finestra / provenienza) e il conteggio nudo libro > 0 e' stampato accanto.
|
||||
|
||||
📌 **Il meccanismo "guadagnare nei crolli" esiste ed e' in produzione: e' SKH01 short** — ma
|
||||
guadagna **dopo** il giorno del crollo (marcato all'uscita), non dentro. Nel senso stretto della
|
||||
domanda dell'operatore il libro **perde nel giorno** e recupera nella finestra. Le due perdite
|
||||
storiche per finestra sono di TP01 nei crolli che arrivano *dentro* un trend rialzista (2019,
|
||||
2021): e' il costo del long-flat (§42 ha misurato che la short del trend lo aggrava).
|
||||
|
||||
**La leva che aumenterebbe il guadagno e' il peso di SKH01, e il gate la boccia:**
|
||||
`r0726_reeval_live_weight.py` rilanciato: argmax 0,35, plateau [0,25-0,50] (il live e' dentro),
|
||||
`weights_tilt_null` 0,25→0,35: `delta_insample −0,0026`, `pctl_hold 31,2`, **`gate_pass False`**.
|
||||
⚠️ Precisazione della revisione: lo script legge la cache `data/_cache/skh_sigtab/*` **ferma al
|
||||
2026-07-25** — sono le stesse cifre del diario 26/07, non un ri-esame coi segnali di oggi. La
|
||||
condizione che fallisce e' pero' **in-sample (pre-2025)**, che 46 giorni di dati nuovi non possono
|
||||
cambiare: la decisione 75/25 resta vincolante e la sua condizione di riapertura resta non
|
||||
soddisfatta, ma la data da citare e' il 26/07.
|
||||
|
||||
## 2. Il crollo catturato — `r0909_crash_catturato.py`
|
||||
|
||||
**Fatto nuovo:** `bite_archive.parquet` copre con bid E ask il crollo del **1-5 giugno 2026**
|
||||
(BTC −25% / ETH −30% dal 31/05 al minimo; ETH −11,1% il 05/06) su una scadenza (19/06, 14-21
|
||||
DTE). Il campione del 22/08 (§11) partiva **dopo**, dal fondo. Limiti dichiarati prima: n = 1
|
||||
crollo, 1 scadenza, tenor 14-21 g (VRP01 e' a 7), spot dal feed 1h certificato, regolamento allo
|
||||
spot dell'ora di scadenza, **famiglia INVERSE** (premi in ETH: VRP01 opererebbe sulla USDC-lineare,
|
||||
che il conto margina e che NON e' nell'archivio). Ancora d'ingresso = ogni snapshot 30/05-04/06;
|
||||
mediana e banda.
|
||||
|
||||
⚠️ **Non e' un crollo a vol alta:** DVOL pre 37 (BTC) / 49 (ETH) all'IV-rank 0,05 / 0,10; il
|
||||
**picco** del crollo (49 / 69) sta al **29° / 46° percentile storico** — sotto la mediana. Il gate
|
||||
IV-rank ≥ 0,30 di VRP01 e' **chiuso in tutte le ore** e il modello del sleeve segna 0,0 su tutte le
|
||||
settimane di giugno: VRP01 non sarebbe stato a mercato.
|
||||
|
||||
| ETH, ingressi PRE-crollo (286 snapshot = **4 strutture** di strike, δ −0,27/−0,09, larghezza 200) | reale | modello (BS su DVOL) | f |
|
||||
|---|---|---|---|
|
||||
| credito del put credit spread | | | **0,74** [0,65-0,82] — 22/08 in rally: 0,714 |
|
||||
| P&L a scadenza per unita' | **−$174** | −$168 | 1,04 — **tautologico nell'83%** dei trade: entrambe le gambe ITM ⇒ payoff = larghezza per reale e modello, resta solo f_net |
|
||||
| perdita sul rischio (fee incluse) | 101% [65-102] | | = perdita **MASSIMA** dello spread (+ fee); 65% per la 1900/1600 |
|
||||
| peggior MTM, chiusura **tagliata alla larghezza** | **−$172** | −$161 | **1,08** [1,07-1,31]; a quell'ora IV corta 68 = DVOL 68 |
|
||||
| put δ−0,10 comprata all'ask: costo | | | **1,92×** il modello (§46 a δ−0,10 su BTC_USDC: 1,27 — punto NUOVO e piu' alto, ETH inverse) |
|
||||
| put δ−0,10 a scadenza (ITM di $4: K 1700, ST 1694) | −$6 | ≈ $0 | 0,96 sul solo sottoinsieme con payoff modellato ≥ $5 |
|
||||
| put δ−0,10 al picco del percorso (look-ahead, limite sup.) | +$163 | +$173 | 0,94 |
|
||||
| debit spread −0,28/−0,10 a scadenza | +$169 | +$163 | 1,04 (stessa tautologia, specchio) |
|
||||
| ingressi DURANTE il crollo (02-04/06, 283 snapshot) | mediana +$5, **perde nel 46%**, p10 −$133 / p90 +$26 | | bimodale: la mediana nasconde le perdite |
|
||||
|
||||
BTC: **non misurabile pre-crollo** — la griglia di strike dell'archivio il 30/05-01/06 non
|
||||
contiene la δ−0,10 (78 snapshot fuori geometria, 0 in geometria prima del 02/06). Durante:
|
||||
247 snapshot, f_net 0,79, perde nel 37% (p10 −$1.718 / p90 +$818 su larghezza 5000).
|
||||
|
||||
🚨 **Tre correzioni della revisione, applicate:**
|
||||
1. *"Il percorso costa 2,6× il modello (f_mtm 2,58), la IV della gamba corta e' esplosa a 108 per lo
|
||||
skew"* — **artefatto di quote.** Il costo di chiusura `(ask corta − bid lunga)` superava la
|
||||
**larghezza** dello spread (una quota 0,208/0,376 ETH su un intrinseco di $296: 158% di spread
|
||||
relativo, a T→0) e `min()` pescava li'; uno spread a rischio definito non costa mai piu' della
|
||||
larghezza per chiudere. Tagliato: **f_mtm 1,08**. E la IV 108 e' del **18/06 alle 17:00** (deep
|
||||
ITM, T→0), non del crollo: all'ora del peggior MTM la IV e' 68 = DVOL.
|
||||
2. *"f a scadenza 1,04: il modello prezza bene la perdita"* — **tautologia**: con entrambe le gambe
|
||||
ITM il payoff e' la larghezza per entrambi; f_exp e' una funzione di f_net. E la "banda su 286
|
||||
snapshot" sono 4 strutture (N_eff = 1 crollo × 4).
|
||||
3. `S.asof(ts)` sul feed 1h **guardava un'ora avanti**: la barra e' etichettata all'apertura (la
|
||||
20:00 chiude col 5m delle 20:55). Corretto con `spot_causale` (indice +1h). ⚠️ **`cblib.spot_series`
|
||||
ha lo stesso difetto** → il f 0,714 di §11 e ogni numero di `cblib` con `asof` sullo spot; qui
|
||||
innocuo (ambo le gambe ITM per ogni ST < 1700) e nel MTM giocava contro l'autore. **Debito nuovo,
|
||||
registrato in CLAUDE.md §5.** Non toccato: cambierebbe numeri pubblicati.
|
||||
|
||||
📌 **Cosa dice davvero il crollo catturato (n = 1):** il **rapporto di credito reale/modello non
|
||||
cambia col regime** (0,74 nel crollo contro 0,714 nel rally); la put comprata paga nel crollo
|
||||
cio' che il modello dice (0,94-1,04) ma **costa 1,9× il modello all'ingresso** — il bleed e' dove
|
||||
il modello sbaglia, il payoff no (e' il motivo di §46/§72 visto dal lato del sinistro); il gate di
|
||||
VRP01 lo ha tenuto fuori. **La condizione di riapertura di §3 e' soddisfatta nella forma** (un
|
||||
archivio bite con bid/ask su un crollo) **e non nella sostanza**: crollo a vol bassa, sotto il gate,
|
||||
tenor 14-21, famiglia inverse, e l'unico f non tautologico (MTM) vale 1,08 su 4 strutture. **Non si
|
||||
riapre.** Primo punto di una distribuzione che `collect_chain` riempira' da solo; il secondo punto
|
||||
utile e' un crollo **con IV-rank > 0,30**.
|
||||
|
||||
## 3. Piu' monete, sempre in Deribit — `r0909_deribit_universo.py` · `r0909_xrp_terza_gamba.py`
|
||||
|
||||
**Il venue letto oggi (15:05Z):** 32 perpetual USDC-lineari aperti (+6 non aperti, fra cui **APT
|
||||
inactive**: e' il mancante rispetto al 23/08). Volume 24h ≥ $10M solo per **XRP ($38M, il piu'
|
||||
scambiato del venue, spread 4 bps, listato 2022-03-16), ETH, BTC, SOL ($13M)**; tutto il resto fra
|
||||
$0,01M e $7M (TAO, HYPE, ADA, AVAX, NEAR…) con spread 2-20 bps. XS01: **13/19** gambe — mancano
|
||||
ARB, OP, APT, INJ, TIA, SEI → §50 resta: morto per ampiezza. Opzioni USDC-lineari su 7 sottostanti
|
||||
(SOL 676, HYPE 442, XRP 442, TRX, AVAX) con la liquidita' misurata il 22/08 (SOL: 9% delle put a
|
||||
due lati). Trovati dalla revisione, registrati perche' il controllo costa €0 e nessuno li riapra:
|
||||
**`BTCDVOL_USDC-30SEP26`** (l'unico strumento long-vol diretto del venue: volume 0, OI 132, bid/ask
|
||||
39,1/43,9 = spread 12% → non negoziabile, e in contango sanguina come la put) e **PAXG perpetual**
|
||||
(oro, dal 2024-12: $0,18M/g, OI $8,3M — non misurato, non morto, stessi muri di SOL/XRP: 9 mesi di
|
||||
storia contro i 3 anni della decisione 22/08).
|
||||
|
||||
⇒ **L'unica "altra moneta" liquida su Deribit mai misurata come gamba direzionale e' XRP.** Misurata
|
||||
col harness di `r0822_sol_leg` (TP01 e SKH01 **congelati**, 24 ancore, differenze appaiate,
|
||||
de-levering, per anno), ipotesi a priori dichiarata: diluisce.
|
||||
|
||||
Certificazione (Coinbase dal rilisting 2023-07, Bitstamp prima — Coinbase aveva delistato XRP per
|
||||
la causa SEC): >1% dal riferimento 0,06% (2022) · **0,57% (2023)** · 0,01-0,02% (2024+); mediana
|
||||
5-6 bps. ⇒ **L-PULITA dal 2024** (stessa soglia e stesso anno sporco di SOL). ⚠️ Barre 5m flat
|
||||
**27-61%** (BTC/ETH 2021+: 0,1-2%; SOL 21%): il 5m su cui gira SKH01 e' 20-100× piu' flat.
|
||||
|
||||
| | L-FULL (2022-03+) | L-PULITA (2024+) |
|
||||
|---|---|---|
|
||||
| dSharpe FULL (mediana, 24 ancore) | **−0,206** [−0,26, −0,16], >0 in 0% | −0,066, >0 in 12% |
|
||||
| dSharpe hold-out | **−0,169** [−0,32, −0,15], **>0 in 0%** | −0,169, 0% |
|
||||
| dCAGR | −2,27 pp, 0% | −1,32 pp, 4% |
|
||||
| null del de-levering | Sh 3g 1,23 vs 2g×0,974 **1,44** → de-levering | 1,54 vs 1,53 → pari |
|
||||
| per anno (dSh) | 2022 −0,55 · 2023 −0,66 · **2024 +0,54** · 2025 −0,44 · 2026 −0,25 → **1/5** | |
|
||||
| corr(gamba XRP, book) | +0,35 / +0,37 (SOL 0,40-0,56) | |
|
||||
|
||||
**XRP DILUISCE** — lo stesso −0,17 di hold-out di SOL, in 0/24 ancore, con **un anno buono** (2024).
|
||||
TP01 XRP da solo: Sharpe −0,01, hold-out −0,49; SKH01 XRP maxDD 40,1% (il criterio di selezione di
|
||||
SKH01-V2-DD, maxDD<30%, fallisce anche qui, come SOL). Eseguibilita' non e' il vincolo (min 1 XRP =
|
||||
$1,42). Non misurato: **SKH01 solo-short su una terza moneta** (la domanda riguarda i crolli, dove
|
||||
paga lo short) — ma il filtro di direzione e' una famiglia nuova (M20) e il 5m di XRP e' peggio di
|
||||
SOL: il muro e' lo stesso. **Nessuna proposta.** Il dato vive in `data/raw/alt_xrp_*` (namespace di
|
||||
ricerca, non rinfrescato dal cron, `load_data("XRP")` fallisce; `tests/test_sol_leg.py` lo presidia
|
||||
via `DERIBIT_INSTR`). La cache del riferimento in `data/_cache` e' gitignored e fuori backup: la
|
||||
certificazione si riproduce **con rete** (N11 vale con rete).
|
||||
|
||||
## 4. Cosa cambia una decisione — niente; cosa si registra
|
||||
|
||||
| voce | prima | oggi |
|
||||
|---|---|---|
|
||||
| il libro nei crolli | "immune, beta 0,076" | perde nel GIORNO (−0,48%/g, 18% positivi), immune-o-positivo per finestra, **> 0 in 8/8 dal 2022** per SKH01 short marcato all'uscita; beta 0,0769 riprodotto |
|
||||
| f di stress delle opzioni | non misurato ("serve un crash catturato") | crollo catturato **a vol bassa, sotto il gate, inverse**: f_net 0,74 = rally; f_mtm 1,08; ali 1,92×. **Condizione non soddisfatta nella sostanza** |
|
||||
| peso SKH01 | gate fallisce (26/07) | invariato: la condizione che fallisce e' in-sample; cache segnali ferma al 25/07 |
|
||||
| XRP | mai misurato | diluisce come SOL (−0,169 hold-out, 0/24) |
|
||||
| universo Deribit | 14/19 (23/08) | 13/19 (APT inactive); liquidi solo BTC/ETH/XRP/SOL; BTCDVOL future non negoziabile; PAXG non misurato |
|
||||
| debito nuovo | — | `cblib.spot_series` + `asof` guarda un'ora avanti (feed 1h etichettato all'apertura) |
|
||||
|
||||
**Cosa riaprirebbe qualcosa:** (a) un secondo crollo catturato **a IV-rank > 0,30** (VRP01 a
|
||||
mercato), sulla famiglia USDC-lineare, per il f di stress; (b) `weights_tilt_null` che passa — si
|
||||
rigenera `skh_sigtab` e si rilancia `r0726_reeval_live_weight`, non si ridiscute; (c) per le
|
||||
"altre monete", un meccanismo che non sia TP01/SKH01 congelati, o 3 anni di dato 5m liquido.
|
||||
|
||||
## 5. Errori catturati in sessione (prima della revisione)
|
||||
|
||||
- Percorso ora per ora in un ciclo `groupby` per ancora: >10 minuti; riscritto su una tabella
|
||||
ora×strumento (`Quote`), ~3 minuti.
|
||||
- BTC "in geometria" senza guardia: la griglia rada faceva scegliere a `pick_legs` una δ−0,02 al
|
||||
posto della δ−0,10 (K72000/55000). Guardia `TOL_D` (±0,08/±0,06), scritta dopo aver visto BTC ma
|
||||
che per ETH scarta 2/288 e non cambia nessun verdetto (verificato dal revisore).
|
||||
- Coinbase restituiva un batch vuoto per XRP 2022 e il ciclo si fermava: certificazione vuota.
|
||||
Riscritto con avanzamento su batch vuoto e riferimento Bitstamp dove Coinbase manca.
|
||||
|
||||
## 6. Revisione (`fable`, agente fresco) — 21 segnalazioni, tutte verificate
|
||||
|
||||
Applicate: #1 artefatto del MTM (taglio alla larghezza; IV 108 e' a T→0) · #2 f_exp tautologico
|
||||
(quota stampata, N_eff = strutture) · #3 "101%" = perdita massima + fee · #4 `asof` un'ora avanti
|
||||
(`spot_causale`; debito su `cblib`) · #5 famiglia inverse dichiarata · #8 "durante" bimodale (p10/p90,
|
||||
quota che perde) · #9 ordine delle classi (immune → POSITIVO → PERDE, tre affermazioni nel verdetto)
|
||||
· #10 peggior giorno = crollo con TP01 0,505, docstring (c) corretto · #11 beta sulla finestra di §46
|
||||
(0,0769, tolleranza 0,01) · #12 scomposizione esatta con assert · #13 lente "marcata all'uscita"
|
||||
dichiarata · #14 confronto bit-exact con `_skyhook_returns` · #15 cache `skh_sigtab` ferma al 25/07
|
||||
· #16 1,92 e' un punto nuovo, non "gia' noto" · #17 il crollo e' sotto la mediana del DVOL: verdetto
|
||||
riscritto · #18-19 BTCDVOL future e PAXG registrati · #21 APT inactive, cache con rete.
|
||||
Non applicate: nessuna. Il revisore ha trovato **due conclusioni che non reggevano** (il 2,6× e
|
||||
l'"8/12 guadagna") e le ha smontate con numeri riprodotti: la regola *"chi rivede non e' chi ha
|
||||
scritto"* ha pagato in una sessione.
|
||||
|
||||
**Test (Opus, agente separato): `tests/test_r0909_*.py`, 92 test, suite intera 1008/1008 verdi**
|
||||
(erano 916). Coprono: ordine delle classi e riproduzione verbatim dei verdetti, `spot_causale` col
|
||||
controllo positivo sul difetto che ripara, taglio alla larghezza, geometria `in_geom` derivata da
|
||||
`VRP_CFG`, regola L-PULITA (anno pulito isolato ignorato), `_fetch_1h` su batch vuoti, P1 per
|
||||
identita' (`W_TP01`, `XS_UNIVERSE`), zero rete e zero scritture in `data/`. **Due difetti trovati da
|
||||
Opus nel verdetto di XRP, corretti:** un campione vuoto stampava "DILUISCE" (ora "NON MISURABILE",
|
||||
P5) e il criterio per anno era cablato al 2024 invece che derivato dall'anno della lente (P1).
|
||||
@@ -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 T−5 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 D−1 — 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 D−1 (+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 D−1 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.
|
||||
@@ -0,0 +1,98 @@
|
||||
# 2026-09-09c — Revisione settimanale in sola lettura: cosa si costruisce e cosa no
|
||||
|
||||
**Richiesta dell'operatore:** *«schedulare ogni x tempo un processo di auto revisione dei dati e della
|
||||
configurazione del sistema in modo che migliori con il tempo … autoregolazione e autoaggiornamento dei
|
||||
sistemi o anche creare nuovi sistemi … valutando dati macroeconomici … e poi informa me tramite
|
||||
Telegram»*. Dopo la discussione: *«fai»*.
|
||||
|
||||
**Esito in una riga:** costruita la **rilettura settimanale in sola lettura** con revisore diverso
|
||||
dall'autore (`claude-fable-5-1`), rapporto firmato in `docs/revisioni/` e Sintesi su Telegram, lunedi'
|
||||
06:15 UTC; **scartati** — con la memoria del progetto, non per prudenza generica — autoregolazione dei
|
||||
parametri, generazione automatica di strategie e macro da internet. Libro, config, pesi: invariati.
|
||||
|
||||
## 1. Perche' tre pezzi su quattro sono no
|
||||
|
||||
| pezzo | cosa dice la memoria | esito |
|
||||
|---|---|---|
|
||||
| auto-revisione periodica | nove sorveglianti, ognuno sulla sua grandezza; nessuno rilegge l'insieme; la revisione col secondo modello ha trovato oggi due conclusioni false e un look-ahead | **si'** |
|
||||
| autoregolazione dei parametri | TP01/SKH01 congelati (M20), 75/25 confermato 3 volte, `weights_tilt_null` fallisce; `scale_watch` esiste per rendere visibile ogni cambio di config; A8: una riga di config + una di giornale, decisione dell'operatore | **no** |
|
||||
| generazione e test di strategie nuove | 69 filoni in 6 ondate; ogni candidato e' un trial (M3) e deflaziona tutto; la ricerca non e' il vincolo binding dal 26/07 (+0,046 €/g il miglior lead contro +4,07%/anno per €100/mese) | **no, salvo freno**: prima si apre la memoria e si batte il motivo della morte; i candidati vanno in forward-monitor con gate e data, mai nel book |
|
||||
| dati macro da internet | gate macro / DVOL direzionale / skew de-risk: HEDGE o ridondanti (TP01 gia' flat nei crolli); un dato dal web non e' certificato (D1-D4; la APR USDC 3,40% pubblicata e mai incassata) | **no** |
|
||||
|
||||
Il centro dell'argomento: **il progetto ha gia' misurato che piu' ricerca non sposta il traguardo, i
|
||||
bonifici si'**. Un ciclo automatico che cerca strategie ottimizza la leva sbagliata e paga in trial bruciati.
|
||||
|
||||
## 2. Cosa gira
|
||||
|
||||
`scripts/cron_review.sh` (lunedi' **06:15 UTC**: fuori dal :00, fuori dal martedi' 09:00, dopo il
|
||||
`cron_daily` delle 00:30 cosi' il giornale di domenica e' chiuso) → `scripts/live/revisione.py --quiet`
|
||||
→ `src/live/revisione.scrivi`. Riga installata in crontab (`15 6 * * 1`), letta dal test.
|
||||
|
||||
**Materiale** (sola lettura, ~195k caratteri, 27 voci): CLAUDE.md intero · `config/live.json` ·
|
||||
`crontab -l` · `git log` 14 g + `git status` · `monitor_health` · stato dei sorveglianti (scale, vrp_f,
|
||||
usde, balance, venue_news, movimenti dichiarati, watermark) · 7 pagine di giornale · diari della
|
||||
settimana. Gli stati stanno **prima** delle voci grandi: alla prima stesura il tetto totale (320k) aveva
|
||||
tagliato `usde_convert.jsonl` a 2 caratteri — un sorvegliante invisibile per un limite di lunghezza.
|
||||
|
||||
**Prompt**: dieci regole vincolanti (sola lettura · ogni segnalazione cita la fonte · solo numeri del
|
||||
materiale · le decisioni di §3 non si ripropongono se la colonna «cosa la riapre» non e' soddisfatta ·
|
||||
un'idea nuova solo citando la memoria che ha ucciso la simile · i gate con data e criterio, senza
|
||||
anticipare · misurato ≠ dedotto · niente previsioni · URGENTE per cio' che serve all'operatore subito ·
|
||||
cinque titoli fissi, ~1.000 parole). L'ultimo titolo, «Cosa ho letto e cosa mi mancava», e' l'elenco da
|
||||
cui il materiale della settimana dopo migliora: e' l'unico «miglioramento col tempo» che questo sistema
|
||||
si concede, e passa da una modifica al codice, cioe' da una revisione.
|
||||
|
||||
**Uscita**: `docs/revisioni/<data>.md` firmato (modello, ora, «SOLA LETTURA», «lettore fallibile P13»,
|
||||
guardia sui numeri come `analista` con soglia 12, tabella del materiale letto). Telegram: la sola
|
||||
Sintesi, taglio dichiarato sotto i 4.096. Modello muto o risposta senza i titoli → rapporto «NON
|
||||
eseguita» col motivo e 🚨: il silenzio non e' una revisione (P5).
|
||||
|
||||
**Cosa non puo' fare, per costruzione**: scrivere altro che il rapporto (test sull'albero dei file:
|
||||
`--secco` non scrive niente, un giro scrive UN file), eseguire proposte, cambiare config. Il modello e'
|
||||
`claude-fable-5-1` per costante, e un test verifica che sia diverso da `analista.MODELLO_DEFAULT`.
|
||||
|
||||
## 3. Test — `tests/test_revisione.py` (16)
|
||||
|
||||
Finestra del materiale e file assenti dichiarati · ordine stati-prima-delle-voci-grandi · troncamento
|
||||
dichiarato · regole nel prompt · guardia (vuoto, titoli mancanti, numeri fuori con controllo positivo) ·
|
||||
rapporto firmato · modello muto → rapporto + 🚨 · Telegram con HTML neutralizzato e taglio dichiarato ·
|
||||
`--secco` non chiama e non scrive · un giro scrive un solo file · `--no-telegram` · revisore ≠ autore ·
|
||||
cadenza: il `.sh` dichiara «lunedi' HH:MM UTC», minuto ≠ 0, fuori dallo slot di release, e la crontab
|
||||
installata ha gli stessi cinque campi (skip se illeggibile). `test_cli_flag` ha preso lo script da solo
|
||||
(guardia `valida`, USO coi flag, flag del cron accettati).
|
||||
|
||||
## 4. Errori in sessione
|
||||
|
||||
- `_tronca(testo, n=MAX_CHARS_VOCE)`: il default e' catturato alla definizione, il test che patchava la
|
||||
costante non vedeva niente — la stessa trappola del debito 12 (`connect`). Letto a runtime.
|
||||
- Il tetto totale tagliava in coda: i sorveglianti erano gli ultimi e i primi a sparire. Riordinato, e
|
||||
il test lo presidia.
|
||||
|
||||
## 5. Primo giro reale (22:03Z) — e i tre difetti che solo il giro reale poteva trovare
|
||||
|
||||
Il primo lancio e' **morto prima di chiamare il modello**: `OSError: Argument list too long` — 195k
|
||||
caratteri di prompt passati come argomento alla CLI superano il limite del sistema, e `analista.interroga`
|
||||
non lo catturava: zero rapporto, zero Telegram, **il silenzio che il modulo esiste per vietare**. Nessun
|
||||
test l'avrebbe trovato (i test iniettano `interroga`): e' un difetto di trasporto, e il trasporto si valida
|
||||
sul trasporto (P2). Tre correzioni: (1) `revisione.interroga` proprio, prompt su **stdin**, ogni eccezione
|
||||
restituita come errore; (2) `scrivi` incapsula chiamata e invio: un'eccezione diventa un rapporto «NON
|
||||
eseguita» + 🚨; (3) `_cmd` (crontab, git) dichiara qualunque eccezione invece di propagarla. Test con
|
||||
controllo positivo su `OSError(7)`. Terzo difetto, trovato dal test: `analista.pulisci` **toglie le righe
|
||||
che iniziano con `#`**, cioe' i cinque titoli che la guardia richiede — usarlo avrebbe reso ogni revisione
|
||||
«rifiutata: titoli mancanti». Sostituito con `strip()`.
|
||||
|
||||
Secondo lancio: **rapporto scritto, stato `sospetta`**, 1.435 parole, `docs/revisioni/2026-09-09.md`.
|
||||
La guardia ha segnato 11 numeri «non nel materiale»: sono cifre in **formato italiano** («4.477,46»,
|
||||
«1.341») contro il materiale in formato USA («$4,477.46») — la normalizzazione di `analista` conosce una
|
||||
convenzione sola. E' un limite dichiarato, non un errore del revisore: si aggiusta quando si vede quanto
|
||||
spesso scatta. Contenuto: **nessun URGENTE**; tre verifiche in cima (una possibile attribuzione a reward di
|
||||
400 USDE che erano un acquisto in `usde_watch` del 07/09 — `n_trades` fermo a 50 contro 54 ordini nel
|
||||
registro; il cuscino USDC del debito §5.17 senza definizione balance/equity; il versamento del 04/09 non
|
||||
dichiarato in `movimenti_dichiarati.jsonl`); una contraddizione in CLAUDE.md §1 sull'haircut (M28); la data
|
||||
di arming che differisce fra §0, config e TWR; quattro sorveglianti senza timestamp o fuori da
|
||||
`monitor_health`; e, come richiesto dalla regola 5, **zero idee di strategia**. «Cosa mi mancava»: stati
|
||||
con ora di `edge/fee/venue/scale_watch`, l'output di `usde_watch.rendimento()`, i paper a oggi,
|
||||
`cron_daily.sh`, il registro fill della settimana — l'elenco da cui il materiale della prossima settimana
|
||||
migliora. Le segnalazioni sono dell'operatore e della prossima sessione: **nessuna e' stata applicata qui**
|
||||
(sarebbe l'agente che esegue le proposte del revisore nello stesso giro, cioe' il ciclo che si e' scartato).
|
||||
Costo: una chiamata, ~4 minuti.
|
||||
+30
-18
@@ -1,45 +1,57 @@
|
||||
# Giornale di bordo — 2026-08-25 · ⚠️ PARZIALE (giorno in corso)
|
||||
# Giornale di bordo — 2026-08-25
|
||||
|
||||
*Scritto 2026-08-25T08:54:46+00:00 — la giornata NON e' chiusa: la pagina copre 9 giri su 24 e **non si aggiorna da sola**. I numeri di GIORNO (P&L del giorno, letture, giri) sono di questa frazione, non del giorno; i CUMULATI sono corretti. La versione completa la scrive il cron dopo le 00:30 UTC del giorno seguente.*
|
||||
*Scritto 2026-08-26T07:05:51+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $78,810.00 | -0.22% | +21.82% | +20.58% | 43.4% | ↑ ↑ ↑ (3/3 su) | 43.5 (60° pctl 1a) |
|
||||
| **ETH** | $2,477.35 | -0.23% | +29.22% | +26.81% | 68.7% | ↑ ↑ ↑ (3/3 su) | 58.5 (49° pctl 1a) |
|
||||
| **BTC** | $78,503.50 | -0.61% | +21.35% | +20.11% | 43.5% | ↑ ↑ ↑ (3/3 su) | 43.3 (58° pctl 1a) |
|
||||
| **ETH** | $2,443.20 | -1.61% | +27.44% | +25.06% | 69.1% | ↑ ↑ ↑ (3/3 su) | 58.1 (48° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$667.88** · nozionale lordo $337 · leva lorda 0.50x · barra dati 2026-08-25 · 9 giri
|
||||
Equity **$2,056.59** · nozionale lordo $557 · leva lorda 0.27x · barra dati 2026-08-25 · 25 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.450 | flat | $+113 | $+192 | SELL $-79 |
|
||||
| **ETH** | +0.274 | LONG @ 2,436.4 | $+152 | $+145 | BUY $+7 |
|
||||
| **BTC** | +0.450 | flat | $+347 | $+345 | HOLD (a target) |
|
||||
| **ETH** | +0.274 | flat | $+212 | $+212 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+25.86** di equity (9 letture)
|
||||
- realizzato: +1.90 netto su 2 round-trip chiusi · 3 fill · fee 0.0585
|
||||
- cumulato dall'arming: **$+69.82**
|
||||
- giorno: **$+1,414.57** di equity (24 letture) — di cui **$+1,399.39 movimenti di capitale** -> trading **$+15.18**
|
||||
- realizzato: +3.00 netto su 6 round-trip chiusi · 7 fill · fee 0.3452
|
||||
- cumulato dall'arming: **$+1,458.53** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+59.14**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 9/9 *(giorno in corso)*
|
||||
- giri di `book_execute`: 25/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+192 (TP01 +0.450, SKH01 flat) e ETH $+145 (TP01 +0.274, SKH01 long).
|
||||
- `[leva]` Leva lorda **0.50x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.50x.
|
||||
- `[pnl]` Equity $+25.86 con 3 fill e 2 round-trip chiusi (realizzato $+1.90 netto).
|
||||
- `[concentrazione]` 📌 **Il 101% di tutto il P&L cumulato ($+69.82) viene dagli ultimi 7 giorni.** Oltre il 100% perche' **il periodo precedente era in perdita**: senza questi 7 giorni il libro sarebbe sotto. Un risultato concentrato in una finestra non e' un tasso di rendimento: e' un evento.
|
||||
- `[vol]` BTC: implicita 43.5 contro realizzata 30g 43.4 (sopra); DVOL al 60° pctl di un anno; IV-rank espandente 0.20 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 58.5 contro realizzata 30g 68.7 (sotto); DVOL al 49° pctl di un anno; IV-rank espandente 0.21 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **63 giorni, 16 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
- `[movimento]` 📌 **Movimento di capitale $+1,399.39** fra le 10:47 e le 11:47 UTC: salto +209.6% contro un massimo spiegabile dal mercato di ±0.2% al tetto di leva. Scorporato dal P&L di trading qui sotto.
|
||||
- `[stato]` A mercato su BTC $+345 (TP01 +0.450, SKH01 flat) e ETH $+212 (TP01 +0.274, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.27x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.73x.
|
||||
- `[pnl]` P&L di trading $+15.18 (equity $+1414.57 al lordo del movimento di capitale) con 7 fill e 6 round-trip chiusi (realizzato $+3.00 netto).
|
||||
- `[concentrazione]` 📌 **Il 102% di tutto il P&L cumulato ($+59.14) viene dagli ultimi 7 giorni.** Oltre il 100% perche' **il periodo precedente era in perdita**: senza questi 7 giorni il libro sarebbe sotto. Un risultato concentrato in una finestra non e' un tasso di rendimento: e' un evento.
|
||||
- `[drawdown]` 📌 Equity **-0.51%** sotto il picco ($2,067.23). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 43.3 contro realizzata 30g 43.5 (sotto); DVOL al 58° pctl di un anno; IV-rank espandente 0.20 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 58.1 contro realizzata 30g 69.1 (sotto); DVOL al 48° pctl di un anno; IV-rank espandente 0.20 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **63 giorni, 20 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-08-26T00:38:40+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto del giorno non e' il mercato, e' il conto. L'equity passa da $642.02 a $2,056.59, cioe' $+1,414.57, mentre il realizzato dei sei round-trip chiusi vale $+3.00 netto su $557 di nozionale lordo: le due grandezze non stanno nella stessa scala, e da questa pagina non e' determinabile da dove venga la differenza — un movimento di questa taglia il libro non lo produce. Vale la pena che un umano lo confermi prima che ieri e oggi vengano letti come una serie.
|
||||
|
||||
Se la differenza e' esterna, tutto cio' che il generatore costruisce sull'equity ne eredita il difetto: il cumulato $+1,458.53 e la sua concentrazione «100% negli ultimi 7 giorni» misurano il conto, non la strategia; il drawdown -0.51% e' calcolato contro un picco ($2,067.23) nato poche ore fa; la leva 0.27x scende perche' il denominatore e' cresciuto, non perche' il libro si sia ridotto — le posizioni sono a target, entrambe in HOLD.
|
||||
|
||||
Sul mercato, BTC -0.61% e ETH -1.61% nel giorno contro +21.35% e +27.44% a sette giorni: la finestra che porta tutto il P&L e' anche la settimana piu' forte, e a 63 giorni e 20 round-trip «edge» ed «essere stati lunghi» restano indistinguibili. L'esposizione e' interamente TP01: SKH01 e' flat su entrambi gli asset. Venticinque giri su 24: uno in piu', causa non misurabile qui.
|
||||
|
||||
## Nota
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-08-26
|
||||
|
||||
*Scritto 2026-08-27T00:37:47+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $79,031.00 | +0.67% | +14.00% | +24.05% | 42.0% | ↑ ↑ ↑ (3/3 su) | 40.4 (40° pctl 1a) |
|
||||
| **ETH** | $2,507.65 | +2.64% | +11.34% | +32.61% | 67.8% | ↑ ↑ ↑ (3/3 su) | 56.1 (41° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,065.45** · nozionale lordo $582 · leva lorda 0.28x · barra dati 2026-08-26 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.464 | flat | $+359 | $+364 | SELL $-5 |
|
||||
| **ETH** | +0.278 | flat | $+215 | $+218 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+8.86** di equity (24 letture)
|
||||
- realizzato: +0.29 netto su 2 round-trip chiusi · 4 fill · fee 0.0138
|
||||
- cumulato dall'arming: **$+1,467.39** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+68.00**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+364 (TP01 +0.464, SKH01 flat) e ETH $+218 (TP01 +0.278, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.28x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.72x.
|
||||
- `[pnl]` Equity $+8.86 con 4 fill e 2 round-trip chiusi (realizzato $+0.29 netto).
|
||||
- `[concentrazione]` 📌 **Il 101% di tutto il P&L cumulato ($+68.00) viene dagli ultimi 7 giorni.** Oltre il 100% perche' **il periodo precedente era in perdita**: senza questi 7 giorni il libro sarebbe sotto. Un risultato concentrato in una finestra non e' un tasso di rendimento: e' un evento.
|
||||
- `[vol]` BTC: implicita 40.4 contro realizzata 30g 42.0 (sotto); DVOL al 40° pctl di un anno; IV-rank espandente 0.14 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 56.1 contro realizzata 30g 67.8 (sotto); DVOL al 41° pctl di un anno; IV-rank espandente 0.17 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **64 giorni, 22 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-08-27T00:38:49+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto nuovo non sta nei numeri di oggi: e' il denominatore, cambiato ieri. L'equity passa da 642.02 (24/08) a 2,056.59 (25/08) a $2,065.45, ma il nozionale lordo resta $582 e le posizioni si muovono appena — BTC da $+345 a $+364, ETH da $+212 a $+218. La leva 0.28x su un tetto di 1.00x non e' quindi confrontabile con quella dei giorni precedenti: stessa esposizione, denominatore molto piu' grande. Chi rilegge fra mesi guardi $+1,399.39 versati e $+68.00 di trading prima di guardare l'equity.
|
||||
|
||||
La settimana che porta il 101% del cumulato e' la stessa in cui BTC ha fatto +14.00% e ETH +11.34%, TSMOM 3/3 su entrambi e il libro lungo su entrambe le gambe. «L'edge ha lavorato» e «un trend-following era lungo mentre il mercato saliva» spiegano quel dato ugualmente bene, e a 64 giorni e 22 round-trip non si separano. Anche l'$+8.86 di oggi e' quasi tutto rivalutazione: il realizzato e' $+0.29 su 2 round-trip, in un giorno da +0.67% e +2.64%.
|
||||
|
||||
Due cose insolite: SKH01 e' flat su entrambi gli asset dopo un mese da +24.05% e +32.61%, quindi il rischio e' tutto TP01; e l'implicita sta sotto la realizzata su entrambi (40.4 contro 42.0, 56.1 contro 67.8), con DVOL al 40° e 41° percentile.
|
||||
|
||||
Da controllare a mano: 4 fill in una giornata che chiude con un solo SELL $-5. Cosa sia successo fra i 24 giri non e' misurabile qui.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,60 @@
|
||||
# Giornale di bordo — 2026-08-27
|
||||
|
||||
*Scritto 2026-08-28T08:28:09+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $80,266.00 | +1.56% | +9.94% | +25.66% | 42.1% | ↑ ↑ ↑ (3/3 su) | 41.5 (47° pctl 1a) |
|
||||
| **ETH** | $2,511.75 | +0.16% | +7.96% | +30.79% | 67.9% | ↑ ↑ ↑ (3/3 su) | 57.1 (45° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,070.23** · nozionale lordo $573 · leva lorda 0.28x · barra dati 2026-08-27 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.463 | flat | $+359 | $+361 | HOLD (a target) |
|
||||
| **ETH** | +0.277 | flat | $+215 | $+212 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+4.78** di equity (24 letture)
|
||||
- realizzato: +0.13 netto su 1 round-trip chiusi · 1 fill · fee 0.0019
|
||||
- cumulato dall'arming: **$+1,472.17** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+72.78**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+361 (TP01 +0.463, SKH01 flat) e ETH $+212 (TP01 +0.277, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.28x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.72x.
|
||||
- `[pnl]` Equity $+4.78 con 1 fill e 1 round-trip chiusi (realizzato $+0.13 netto).
|
||||
- `[concentrazione]` 📌 **Il 82% di tutto il P&L cumulato ($+72.78) viene dagli ultimi 7 giorni.** Un risultato concentrato in una finestra non e' un tasso di rendimento: e' un evento.
|
||||
- `[vol]` BTC: implicita 41.5 contro realizzata 30g 42.1 (sotto); DVOL al 47° pctl di un anno; IV-rank espandente 0.16 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 57.1 contro realizzata 30g 67.9 (sotto); DVOL al 45° pctl di un anno; IV-rank espandente 0.19 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **65 giorni, 23 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-08-28T08:29:00+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto nuovo della settimana non sta nel P&L ma nella colonna equity: il salto del 25/08 da $642.02 a $2,056.59 è capitale entrato, non performance. Dei $+1,472.17 cumulati dall'arming, $+1,399.39 sono versamenti e solo $+72.78 è trading. Chi rileggerà la curva fra mesi non la legga come rendimento.
|
||||
|
||||
Merita un occhio umano il fatto che l'equity sia quasi triplicata ma il nozionale no: BTC $+361 / ETH $+212 oggi, contro $+345/$+212 del 25/08 e $+364/$+218 del 26/08 — praticamente fermi, con leva lorda scesa a 0.28x su un tetto di 1.00x. Se il target segua l'equity o l'intensità del segnale (TP01 +0.463 e +0.277) da questa pagina non è determinabile.
|
||||
|
||||
Il rischio è tutto su TP01: SKH01 flat su entrambe le gambe. Giornata di attività minima, 1 fill e 1 round-trip, $+4.78 di equity; quanto di quel numero sia mark-to-market delle gambe non è scomponibile qui.
|
||||
|
||||
Lettura alternativa della concentrazione: l'82% del cumulato sta negli ultimi 7 giorni, che sono anche i 7 giorni di BTC +9.94% ed ETH +7.96%. Un long-flat che guadagna in una settimana di trend sta facendo il suo mestiere; a 65 giorni e 23 round-trip non è separabile da un buon tratto di mercato.
|
||||
|
||||
Salute pulita: 24/24 giri, feed a 0 min. Su entrambi gli asset l'implicita sta sotto la realizzata, ma il gate di VRP01 blocca su un altro asse.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-08-28
|
||||
|
||||
*Scritto 2026-08-29T00:36:42+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $77,850.00 | -3.01% | -0.64% | +21.77% | 44.1% | ↑ ↑ ↑ (3/3 su) | 38.3 (25° pctl 1a) |
|
||||
| **ETH** | $2,443.35 | -2.72% | -2.93% | +27.98% | 68.9% | ↑ ↑ ↑ (3/3 su) | 50.9 (16° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,052.36** · nozionale lordo $571 · leva lorda 0.28x · barra dati 2026-08-28 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.464 | flat | $+357 | $+357 | HOLD (a target) |
|
||||
| **ETH** | +0.278 | flat | $+214 | $+214 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$-17.87** di equity (24 letture)
|
||||
- realizzato: +0.00 netto su 0 round-trip chiusi · 2 fill · fee 0.0054
|
||||
- cumulato dall'arming: **$+1,454.30** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+54.91**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+357 (TP01 +0.464, SKH01 flat) e ETH $+214 (TP01 +0.278, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.28x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.72x.
|
||||
- `[pnl]` Equity $-17.87 con 2 fill e 0 round-trip chiusi (realizzato $+0.00 netto).
|
||||
- `[drawdown]` 📌 Equity **-1.02%** sotto il picco ($2,073.59). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 38.3 contro realizzata 30g 44.1 (sotto); DVOL al 25° pctl di un anno; IV-rank espandente 0.09 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 50.9 contro realizzata 30g 68.9 (sotto); DVOL al 16° pctl di un anno; IV-rank espandente 0.12 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **66 giorni, 23 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-08-29T00:37:39+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Prima giornata in rosso dal versamento: dopo +8.86 e +4.78, l'equity fa $-17.87 con zero round-trip chiusi. Il libro non ha deciso niente — BTC $+357 e ETH $+214 restano a target, dove erano ieri ($+361 e $+212) — quindi la perdita e' marcatura di mercato sul giorno a -3.01% BTC e -2.72% ETH, non esecuzione; e con SKH01 flat su entrambi e' esposizione interamente TP01. I 2 fill con fee 0.0054 sono aggiustamenti: se siano ritocchi al target o altro, non e' distinguibile qui. Salute pulita (24/24 giri, feed a 0 min), quindi non spiega nulla della giornata.
|
||||
|
||||
Cio' che e' cambiato davvero e' la scala. Dal 25/08 l'equity e' $2,052.36, e il movimento di un solo giorno ($-17.87) e' dello stesso ordine dei $+54.91 di trading accumulati dall'arming in 66 giorni. Il P&L giornaliero e' ora piu' rumoroso della cosa che dovrebbe misurare, mentre i round-trip restano 23.
|
||||
|
||||
Due letture reggono ugualmente sugli stessi numeri: TSMOM 3/3 su entrambi con 30 giorni a +21.77% e +27.98% dice trend intatto e ritracciamento; i 7 giorni (-0.64% e -2.93%) dicono che la spinta si era gia' fermata e oggi se ne vede la parte visibile. Con questi dati non si sceglie.
|
||||
|
||||
Merita un'occhiata umana il picco $2,073.59: non compare fra le chiusure giornaliere, e il -1.02% e' misurato contro quella lettura oraria.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-08-29
|
||||
|
||||
*Scritto 2026-08-30T00:37:12+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $78,243.00 | +0.50% | +1.48% | +20.85% | 44.0% | ↑ ↑ ↑ (3/3 su) | 37.4 (19° pctl 1a) |
|
||||
| **ETH** | $2,458.25 | +0.61% | +1.45% | +28.20% | 68.9% | ↑ ↑ ↑ (3/3 su) | 51.1 (18° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,056.87** · nozionale lordo $560 · leva lorda 0.27x · barra dati 2026-08-29 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.444 | flat | $+342 | $+344 | HOLD (a target) |
|
||||
| **ETH** | +0.273 | flat | $+211 | $+216 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+4.51** di equity (24 letture)
|
||||
- realizzato: +0.02 netto su 1 round-trip chiusi · 1 fill · fee 0.0054
|
||||
- cumulato dall'arming: **$+1,458.81** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+59.42**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+344 (TP01 +0.444, SKH01 flat) e ETH $+216 (TP01 +0.273, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.27x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.73x.
|
||||
- `[pnl]` Equity $+4.51 con 1 fill e 1 round-trip chiusi (realizzato $+0.02 netto).
|
||||
- `[drawdown]` 📌 Equity **-0.81%** sotto il picco ($2,073.59). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 37.4 contro realizzata 30g 44.0 (sotto); DVOL al 19° pctl di un anno; IV-rank espandente 0.07 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 51.1 contro realizzata 30g 68.9 (sotto); DVOL al 18° pctl di un anno; IV-rank espandente 0.12 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **67 giorni, 24 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-08-30T00:38:12+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Giornata silenziosa: 1 fill, 1 round-trip, entrambe le gambe a target e posizioni ferme dal 25/08 ($+344 su BTC e $+216 su ETH, contro $+345 e $+212). Cio' che e' cambiato non e' il libro ma il denominatore: nozionale lordo $560 con equity $2,056.87 contro i $642.02 del 24/08, e leva lorda 0.27x su un tetto di 1.00x. Il capitale nuovo non ha mosso l'esposizione.
|
||||
|
||||
L'insolito sta nel mercato: TSMOM 3/3 su entrambi gli asset e +20.85% / +28.20% a 30 giorni, con l'implicita al 19° e 18° percentile dell'anno e sotto la realizzata (37.4 contro 44.0; 51.1 contro 68.9). Un rally con implicita compressa; VRP01 resta fermo per costruzione, IV-rank 0.07 e 0.12 sotto 0.30.
|
||||
|
||||
Lettura alternativa altrettanto compatibile con gli stessi numeri: il $+4.51 di oggi e il $-17.87 di ieri, a posizioni ferme, sono marcature di prezzo piu' che decisioni — il realizzato vale $+0.02. Su 67 giorni e 24 round-trip, dei $+1,458.81 cumulati il trading e' $+59.42.
|
||||
|
||||
Merita un occhio umano il drawdown: -0.81% dal picco $2,073.59 e' misurato su una serie che il 25/08 e' saltata di $+1,414.57, quindi ha pochi giorni di storia alla scala attuale. Da quanto SKH01 sia flat su entrambe le gambe non e' misurabile qui. Salute pulita: 24/24 giri, feed a 0 min.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-08-30
|
||||
|
||||
*Scritto 2026-08-31T00:36:38+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $77,702.00 | -0.69% | -0.06% | +23.66% | 42.3% | ↑ ↑ ↑ (3/3 su) | 37.0 (15° pctl 1a) |
|
||||
| **ETH** | $2,417.45 | -1.66% | -1.89% | +29.87% | 68.1% | ↑ ↑ ↑ (3/3 su) | 51.8 (20° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,047.39** · nozionale lordo $553 · leva lorda 0.27x · barra dati 2026-08-30 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.463 | flat | $+355 | $+348 | BUY $+8 |
|
||||
| **ETH** | +0.278 | flat | $+214 | $+205 | BUY $+9 |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$-9.48** di equity (24 letture)
|
||||
- realizzato: -0.08 netto su 2 round-trip chiusi · 5 fill · fee 0.0164
|
||||
- cumulato dall'arming: **$+1,449.33** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+49.94**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+348 (TP01 +0.463, SKH01 flat) e ETH $+205 (TP01 +0.278, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.27x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.73x.
|
||||
- `[pnl]` Equity $-9.48 con 5 fill e 2 round-trip chiusi (realizzato $-0.08 netto).
|
||||
- `[drawdown]` 📌 Equity **-1.26%** sotto il picco ($2,073.59). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 37.0 contro realizzata 30g 42.3 (sotto); DVOL al 15° pctl di un anno; IV-rank espandente 0.06 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 51.8 contro realizzata 30g 68.1 (sotto); DVOL al 20° pctl di un anno; IV-rank espandente 0.13 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **68 giorni, 26 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-08-31T00:37:31+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto nuovo non è la perdita ma la sua composizione: $-9.48 di equity contro $-0.08 di realizzato su 2 round-trip. Quasi tutto è mark-to-market su due long in una giornata con BTC -0.69% ed ETH -1.66%; una lettura altrettanto compatibile è che il libro stia semplicemente respirando col beta dei due asset, senza che il segnale abbia detto nulla. È il terzo segno meno da quando l'elenco comincia il 20/08, dopo il 22/08 ($-9.00) e il 28/08 ($-17.87). Insolito è invece il conto dei fill: cinque, con fee 0.0164, per spostare $+8 su BTC e $+9 su ETH, contro l'uno o due dei giorni scorsi. Vale un'occhiata umana a quale giro li abbia generati.
|
||||
|
||||
La posizione ETH scende da $+216 a $+205 mentre BTC sale da $+344 a $+348: se a muoverla sia il segnale TP01 o il prezzo non è separabile da questa pagina.
|
||||
|
||||
L'anomalia di mercato sta nella vol: +23.66% a 30 giorni su BTC e +29.87% su ETH, con DVOL al 15° e al 20° percentile dell'anno e implicita sotto la realizzata su entrambi. Il rally è arrivato senza che le opzioni lo prezzassero.
|
||||
|
||||
Il trading resta $+49.94 su 68 giorni e 26 round-trip: troppo poco per essere altro che rumore.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,56 @@
|
||||
# Giornale di bordo — 2026-08-31
|
||||
|
||||
*Scritto 2026-09-01T00:36:58+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $78,564.00 | +1.11% | -0.53% | +25.15% | 42.3% | ↑ ↑ ↑ (3/3 su) | 37.6 (21° pctl 1a) |
|
||||
| **ETH** | $2,467.80 | +2.08% | -0.62% | +33.88% | 67.9% | ↑ ↑ ↑ (3/3 su) | 51.3 (19° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,059.45** · nozionale lordo $569 · leva lorda 0.28x · barra dati 2026-08-31 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.461 | flat | $+356 | $+354 | HOLD (a target) |
|
||||
| **ETH** | +0.278 | flat | $+214 | $+215 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+12.06** di equity (24 letture)
|
||||
- realizzato: -0.48 netto su 3 round-trip chiusi · 4 fill · fee 0.0100
|
||||
- cumulato dall'arming: **$+1,461.39** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+62.00**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+354 (TP01 +0.461, SKH01 flat) e ETH $+215 (TP01 +0.278, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.28x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.72x.
|
||||
- `[pnl]` Equity $+12.06 con 4 fill e 3 round-trip chiusi (realizzato $-0.48 netto).
|
||||
- `[drawdown]` 📌 Equity **-0.68%** sotto il picco ($2,073.59). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 37.6 contro realizzata 30g 42.3 (sotto); DVOL al 21° pctl di un anno; IV-rank espandente 0.07 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 51.3 contro realizzata 30g 67.9 (sotto); DVOL al 19° pctl di un anno; IV-rank espandente 0.12 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **69 giorni, 29 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-01T00:38:01+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il $+12.06 di oggi non nasce da una decisione: le posizioni sono rimaste a target (BTC $+354 contro $+356, ETH $+215 contro $+214) e i 3 round-trip chiusi hanno realizzato $-0.48. Il guadagno e' marcatura di due long in una giornata di BTC +1.11% e ETH +2.08%; la lettura alternativa altrettanto compatibile e' che il libro sia stato semplicemente beta lungo, e in pagina non c'e' nulla che separi le due cose.
|
||||
|
||||
E' il delta piu' grande dal 25/08, che pero' era il versamento: un solo giorno pesa quindi molto sui $+62.00 di trading cumulato dall'arming, su un campione di 69 giorni e 29 round-trip, tre dei quali chiusi oggi. Rispetto a -17.87 del 28/08 e -9.48 del 30/08 il segno cambia, ma le posizioni sono ferme dal 25/08 fra $+344 e $+364 su BTC e fra $+205 e $+218 su ETH: si muove il prezzo, non il libro.
|
||||
|
||||
Insolito, e non confrontabile coi giorni precedenti che la volatilita' non la riportano: implicita sotto realizzata su entrambi (37.6 contro 42.3, 51.3 contro 67.9) con DVOL al 21° e 19° percentile dell'anno. Merita un'occhiata che siano serviti 4 fill per lasciare le posizioni dov'erano, e che il picco $2,073.59 da cui si misura il -0.68% non compaia in nessuna chiusura giornaliera.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-09-01
|
||||
|
||||
*Scritto 2026-09-02T00:36:47+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $77,432.50 | -1.44% | -1.36% | +21.89% | 42.9% | ↑ ↑ ↑ (3/3 su) | 37.9 (23° pctl 1a) |
|
||||
| **ETH** | $2,418.40 | -2.00% | -1.02% | +28.37% | 68.5% | ↑ ↑ ↑ (3/3 su) | 51.8 (21° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,050.77** · nozionale lordo $568 · leva lorda 0.28x · barra dati 2026-09-01 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.461 | flat | $+355 | $+357 | HOLD (a target) |
|
||||
| **ETH** | +0.278 | flat | $+213 | $+211 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$-8.68** di equity (24 letture)
|
||||
- realizzato: +0.00 netto su 0 round-trip chiusi · 1 fill · fee 0.0027
|
||||
- cumulato dall'arming: **$+1,452.71** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+53.32**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+357 (TP01 +0.461, SKH01 flat) e ETH $+211 (TP01 +0.278, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.28x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.72x.
|
||||
- `[pnl]` Equity $-8.68 con 1 fill e 0 round-trip chiusi (realizzato $+0.00 netto).
|
||||
- `[drawdown]` 📌 Equity **-1.10%** sotto il picco ($2,073.59). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 37.9 contro realizzata 30g 42.9 (sotto); DVOL al 23° pctl di un anno; IV-rank espandente 0.08 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 51.8 contro realizzata 30g 68.5 (sotto); DVOL al 21° pctl di un anno; IV-rank espandente 0.13 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **70 giorni, 29 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-02T00:37:44+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
La giornata non contiene decisioni: 1 fill, 0 round-trip chiusi, realizzato $+0.00, entrambe le gambe HOLD a target. Gli $-8.68 sono quindi marcatura di posizioni ferme, non esito di operazioni — ed e' la prima giornata senza alcun round-trip chiuso dal 28/08, dopo il 30/08 e il 31/08 che ne avevano chiusi 2 e 3 con 5 e 4 fill. Anche le posizioni cambiano poco: BTC $+357 ed ETH $+211 restano dentro la banda tenuta dal 26/08 (BTC $+344–364, ETH $+205–218), con leva 0.28x molto sotto l'1.00x.
|
||||
|
||||
Cio' che le regole non vedono: il rosso arriva con BTC −1.44% ed ETH −2.00% mentre i tre TSMOM restano 3/3 su e i 30 giorni segnano +21.89% e +28.37%. Lo stesso dato regge due letture — rumore dentro un mese in tendenza, oppure l'avvio di un rientro — e da qui non sono distinguibili: non misurabile con una giornata.
|
||||
|
||||
Insolito il quadro volatilita': implicita sotto la realizzata su entrambi (37.9 contro 42.9; 51.8 contro 68.5) e DVOL al 23°/21° percentile, quindi VRP01 resta fermo per l'IV-rank, non per il livello.
|
||||
|
||||
Vale un'occhiata umana il fatto che il trading cumulato, $+53.32 in 70 giorni, sia della stessa taglia delle singole oscillazioni giornaliere recenti (−17.87 il 28/08, +12.06 il 31/08).
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,60 @@
|
||||
# Giornale di bordo — 2026-09-02
|
||||
|
||||
*Scritto 2026-09-03T00:36:49+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $77,312.50 | -0.15% | -2.17% | +21.78% | 42.9% | ↑ ↑ ↑ (3/3 su) | 37.2 (16° pctl 1a) |
|
||||
| **ETH** | $2,391.55 | -1.11% | -4.63% | +28.63% | 68.5% | ↑ ↑ ↑ (3/3 su) | 50.9 (17° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,045.82** · nozionale lordo $560 · leva lorda 0.27x · barra dati 2026-09-02 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.454 | flat | $+348 | $+347 | HOLD (a target) |
|
||||
| **ETH** | +0.276 | flat | $+212 | $+213 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$-4.95** di equity (24 letture)
|
||||
- realizzato: -0.35 netto su 1 round-trip chiusi · 2 fill · fee 0.0045
|
||||
- cumulato dall'arming: **$+1,447.76** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+48.37** · TWR **+10.47%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+347 (TP01 +0.454, SKH01 flat) e ETH $+213 (TP01 +0.276, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.27x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.73x.
|
||||
- `[pnl]` Equity $-4.95 con 2 fill e 1 round-trip chiusi (realizzato $-0.35 netto).
|
||||
- `[drawdown]` 📌 Equity **-1.34%** sotto il picco ($2,073.59). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 37.2 contro realizzata 30g 42.9 (sotto); DVOL al 16° pctl di un anno; IV-rank espandente 0.06 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 50.9 contro realizzata 30g 68.5 (sotto); DVOL al 17° pctl di un anno; IV-rank espandente 0.12 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **71 giorni, 30 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-03T00:37:49+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto nuovo e' l'equity a $2,045.82: sotto ogni chiusura registrata dopo il versamento del 25/08, dove il minimo era $2,047.39 del 30/08. Non viene dal trading — realizzato $-0.35 su un round-trip, fee 0.0045, due fill — ma dal prezzo su due posizioni tenute a target per tutte le 24 letture.
|
||||
|
||||
Il picco da cui si misura il -1.34% ($2,073.59) non compare fra le chiusure giornaliere: e' una lettura oraria, quindi quel drawdown vive su una griglia piu' fine della tabella dei giorni.
|
||||
|
||||
La tensione del giorno e' fra le scale: TSMOM 3/3 su su entrambi gli asset, ma 1g e 7g negativi (BTC -2.17%, ETH -4.63% a 7g) contro 30g ancora +21.78% e +28.63%. Il libro resta long perche' la gamba lunga tiene; una lettura altrettanto compatibile e' che i 30g stiano semplicemente restituendo il rialzo accumulato. Quale delle due, non e' misurabile qui.
|
||||
|
||||
Insolito il pacchetto volatilita': implicita sotto la realizzata su entrambi (37.2 contro 42.9, 50.9 contro 68.5) con DVOL al 16° e 17° percentile dell'anno, mentre il gate di VRP01 legge l'IV-rank (0.06 e 0.12) e resta fermo — guarda una cosa diversa da quello scarto.
|
||||
|
||||
Meriterebbe un occhio umano il perche' il $-4.95 arrivi con salute perfetta, 24/24 giri e feed a 0 min. Il campione resta 71 giorni e 30 round-trip, con $+48.37 di trading sui $+1,447.76 cumulati.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-09-03
|
||||
|
||||
*Scritto 2026-09-04T00:37:16+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $81,286.00 | +5.14% | +1.27% | +26.90% | 45.5% | ↑ ↑ ↑ (3/3 su) | 39.8 (38° pctl 1a) |
|
||||
| **ETH** | $2,507.90 | +4.87% | -0.15% | +34.20% | 69.8% | ↑ ↑ ↑ (3/3 su) | 53.3 (27° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$2,074.42** · nozionale lordo $562 · leva lorda 0.27x · barra dati 2026-09-03 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.453 | flat | $+352 | $+349 | HOLD (a target) |
|
||||
| **ETH** | +0.274 | flat | $+213 | $+213 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+28.60** di equity (24 letture)
|
||||
- realizzato: -0.14 netto su 4 round-trip chiusi · 4 fill · fee 0.0095
|
||||
- cumulato dall'arming: **$+1,476.36** di equity — di cui $+1,399.39 versati/prelevati -> trading **$+76.97** · TWR **+12.02%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+349 (TP01 +0.453, SKH01 flat) e ETH $+213 (TP01 +0.274, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.27x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.73x.
|
||||
- `[pnl]` Equity $+28.60 con 4 fill e 4 round-trip chiusi (realizzato $-0.14 netto).
|
||||
- `[vol]` BTC: implicita 39.8 contro realizzata 30g 45.5 (sotto); DVOL al 38° pctl di un anno; IV-rank espandente 0.14 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 53.3 contro realizzata 30g 69.8 (sotto); DVOL al 27° pctl di un anno; IV-rank espandente 0.14 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evento]` 📌 BTC: giornata a +5.14%, **2.2 deviazioni** giornaliere (sd implicita dalla RV30 = 2.38%).
|
||||
- `[evidenza]` 📌 Campione a oggi: **72 giorni, 34 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-04T00:38:30+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto nuovo non e' il segno, e' la taglia: $+28.60 e un'equity a $2,074.42, la piu' alta delle dieci sedute mostrate, dopo giorni in cui il delta si e' mosso fra −17.87 e +12.06. Arriva da un BTC a +5.14%, 2.2 deviazioni, mentre il libro era gia' a target su entrambe le gambe: con nozionale lordo $562 e leva 0.27x, l'aumento e' rivalutazione di una posizione ferma, non l'esito di una decisione presa oggi. Il realizzato lo conferma — −0.14 netto su 4 round-trip, rumore di fee (0.0095) — e le posizioni restano $+349 e $+213, in linea con le sedute precedenti.
|
||||
|
||||
Insolito il quadro vol: entrambe le implicite sotto la realizzata 30g (39.8 contro 45.5, 53.3 contro 69.8), coi DVOL al 38° e 27° percentile dell'anno, in una giornata a due deviazioni. Non misurabile qui se sia salita la realizzata oggi o se l'implicita non abbia seguito.
|
||||
|
||||
Lettura alternativa, ugualmente compatibile: BTC fa +5.14% in un giorno ma +1.27% in sette, ed ETH −0.15%; buona parte del guadagno e' recupero dentro la settimana, non nuova direzione, benche' TSMOM sia 3/3 su su entrambi.
|
||||
|
||||
Un solo giorno pesa molto su un trading da $+76.97 in 72 giorni e 34 round-trip. Varrebbe un controllo umano sui 4 fill di una giornata chiusa a target: dovrebbero essere ri-taglie.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-09-04
|
||||
|
||||
*Scritto 2026-09-05T00:36:37+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $79,660.50 | -2.00% | +2.33% | +23.30% | 46.6% | ↑ ↑ ↑ (3/3 su) | 38.0 (24° pctl 1a) |
|
||||
| **ETH** | $2,457.05 | -2.03% | +0.56% | +28.81% | 70.5% | ↑ ↑ ↑ (3/3 su) | 51.1 (18° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$4,483.73** · nozionale lordo $1,173 · leva lorda 0.26x · barra dati 2026-09-04 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.428 | flat | $+719 | $+717 | HOLD (a target) |
|
||||
| **ETH** | +0.270 | flat | $+455 | $+456 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+2,409.31** di equity (24 letture) — di cui **$+2,410.14 movimenti di capitale** -> trading **$-0.83**
|
||||
- realizzato: -0.02 netto su 2 round-trip chiusi · 5 fill · fee 0.2328
|
||||
- cumulato dall'arming: **$+3,885.67** di equity — di cui $+3,809.53 versati/prelevati -> trading **$+76.14** · TWR **+11.70%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[movimento]` 📌 **Movimento di capitale $+2,410.14** fra le 13:47 e le 14:47 UTC: salto +116.8% contro un massimo spiegabile dal mercato di ±0.6% al tetto di leva. Scorporato dal P&L di trading qui sotto.
|
||||
- `[stato]` A mercato su BTC $+717 (TP01 +0.428, SKH01 flat) e ETH $+456 (TP01 +0.270, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.26x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.74x.
|
||||
- `[pnl]` P&L di trading $-0.83 (equity $+2409.31 al lordo del movimento di capitale) con 5 fill e 2 round-trip chiusi (realizzato $-0.02 netto).
|
||||
- `[vol]` BTC: implicita 38.0 contro realizzata 30g 46.6 (sotto); DVOL al 24° pctl di un anno; IV-rank espandente 0.08 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 51.1 contro realizzata 30g 70.5 (sotto); DVOL al 18° pctl di un anno; IV-rank espandente 0.12 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **73 giorni, 36 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-05T00:37:58+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto della giornata non è il mercato ma la cassa: l'equity passa da $2,074.42 a $4,483.73, e il salto di $+2,410.14 fra le 13:47 e le 14:47 UTC è il secondo dopo il delta +1,414.57 del 25/08. Il libro l'ha assorbito subito: le posizioni, ferme per dieci giorni fra $+344 e $+364 su BTC e fra $+205 e $+218 su ETH, oggi valgono $+717 e $+456, entrambe a target, a leva lorda 0.26x su un tetto di 1.00x. Nessuna scelta discrezionale: TP01 +0.428 e +0.270, SKH01 flat.
|
||||
|
||||
Il numero da maneggiare con cura è il $-0.83 di trading. BTC e ETH chiudono a -2.00% e -2.03% con $1,173 di nozionale lordo lungo: l'intervallo che contiene il versamento esce intero dal conteggio, quindi quel $-0.83 non copre la giornata piena. Il realizzato, $-0.02 netto su 2 round-trip e 0.2328 di fee, è compatibile sia con una giornata quasi neutra sia con una perdita di mercato finita fuori dal segmento — qui non si separa.
|
||||
|
||||
Insolito il quadro vol: implicita sotto la realizzata su entrambi (38.0 contro 46.6; 51.1 contro 70.5), DVOL al 24° e 18° percentile; VRP01 resta fermo per l'IV-rank (0.08 e 0.12), non per questo.
|
||||
|
||||
Salute piena, 24/24 giri e feed a 0 min; campione 73 giorni e 36 round-trip. Varrebbe che l'operatore confermi a mano importo e ora del versamento: la Nota è vuota.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,57 @@
|
||||
# Giornale di bordo — 2026-09-05
|
||||
|
||||
*Scritto 2026-09-06T00:37:28+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $79,835.00 | +0.22% | +2.03% | +24.19% | 46.4% | ↑ ↑ ↑ (3/3 su) | 38.7 (29° pctl 1a) |
|
||||
| **ETH** | $2,481.10 | +0.98% | +0.93% | +30.40% | 70.4% | ↑ ↑ ↑ (3/3 su) | 51.7 (21° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$4,490.66** · nozionale lordo $1,161 · leva lorda 0.26x · barra dati 2026-09-05 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.420 | flat | $+708 | $+711 | HOLD (a target) |
|
||||
| **ETH** | +0.268 | flat | $+451 | $+450 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+6.93** di equity (24 letture)
|
||||
- realizzato: -0.16 netto su 3 round-trip chiusi · 3 fill · fee 0.0068
|
||||
- cumulato dall'arming: **$+3,892.60** di equity — di cui $+3,809.53 versati/prelevati -> trading **$+83.07** · TWR **+11.87%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+711 (TP01 +0.420, SKH01 flat) e ETH $+450 (TP01 +0.268, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.26x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.74x.
|
||||
- `[pnl]` Equity $+6.93 con 3 fill e 3 round-trip chiusi (realizzato $-0.16 netto).
|
||||
- `[vol]` BTC: implicita 38.7 contro realizzata 30g 46.4 (sotto); DVOL al 29° pctl di un anno; IV-rank espandente 0.10 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 51.7 contro realizzata 30g 70.4 (sotto); DVOL al 21° pctl di un anno; IV-rank espandente 0.13 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **74 giorni, 39 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-06T00:38:25+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto della giornata non è di oggi: il 04/09 l'equity è passata da $2,074.42 a $4,483.73, +$2,409.31 in un giorno, e questa è la prima pagina intera scritta sul libro nuovo. La forma non è cambiata — leva lorda 0.26x, HOLD a target su entrambe le gambe, TP01 +0.420 e +0.268, SKH01 flat su tutte e due — ma le posizioni sì: $+711 e $+450 contro i $+347 e $+213 del 02/09. È cambiata la taglia, non il comportamento; e il $+6.93 di oggi va letto su una base doppia, dove la stessa cifra in dollari pesa meno di quanto pesava a fine agosto.
|
||||
|
||||
Il cumulato lo dice più chiaramente: $+3,892.60, di cui $+3,809.53 versati o prelevati e $+83.07 di trading in 74 giorni e 39 round-trip. La cifra citabile resta il TWR, +11.87%.
|
||||
|
||||
Insolito, e non spiegabile da questa pagina: implicita sotto realizzata su entrambi (38.7 contro 46.4, 51.7 contro 70.4) con DVOL al 29° e 21° percentile, mentre i 30 giorni segnano +24.19% e +30.40%. Lettura alternativa altrettanto compatibile col +$6.93: il mark-to-market di due long in una giornata salita (+0.22% e +0.98%), non un merito di selezione — con 3 fill e realizzato $-0.16 netto, qui non è distinguibile.
|
||||
|
||||
Un controllo umano lo merita il movimento del 04/09: che abbia la sua riga di giornale, non ricostruita dopo.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,57 @@
|
||||
# Giornale di bordo — 2026-09-06
|
||||
|
||||
*Scritto 2026-09-07T00:36:43+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $80,376.00 | +0.68% | +3.44% | +23.88% | 46.4% | ↑ ↑ ↑ (3/3 su) | 39.3 (35° pctl 1a) |
|
||||
| **ETH** | $2,515.10 | +1.37% | +4.04% | +31.44% | 70.4% | ↑ ↑ ↑ (3/3 su) | 53.2 (27° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$4,502.93** · nozionale lordo $1,175 · leva lorda 0.26x · barra dati 2026-09-06 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.420 | flat | $+710 | $+717 | SELL $-7 |
|
||||
| **ETH** | +0.268 | flat | $+453 | $+458 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$+12.27** di equity (24 letture)
|
||||
- realizzato: +0.13 netto su 1 round-trip chiusi · 1 fill · fee 0.0028
|
||||
- cumulato dall'arming: **$+3,904.87** di equity — di cui $+3,834.43 versati/prelevati -> trading **$+70.44** · TWR **+8.02%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+717 (TP01 +0.420, SKH01 flat) e ETH $+458 (TP01 +0.268, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.26x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.74x.
|
||||
- `[pnl]` Equity $+12.27 con 1 fill e 1 round-trip chiusi (realizzato $+0.13 netto).
|
||||
- `[vol]` BTC: implicita 39.3 contro realizzata 30g 46.4 (sotto); DVOL al 35° pctl di un anno; IV-rank espandente 0.12 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 53.2 contro realizzata 30g 70.4 (sotto); DVOL al 27° pctl di un anno; IV-rank espandente 0.14 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **75 giorni, 40 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-07T00:37:44+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto del giorno non e' il $+12.27: e' che dopo il salto del 04/09 il libro ha ripreso la sua forma abituale a una taglia nuova. Le posizioni sono cresciute con l'equity ($+717 e $+458 contro $+349 e $+213 del 03/09), non col segnale: TP01 sta a 0.420 e 0.268, parziale su entrambe le gambe, e la leva lorda resta 0.26x su un tetto di 1.00x. SKH01 e' flat su BTC e su ETH — il quarto del libro che fa breakout oggi non porta nulla, e da quanti giorni sia cosi' non e' misurabile qui.
|
||||
|
||||
L'attivita' e' scesa: 1 fill e 1 round-trip contro i 3-5 dei giorni scorsi, e l'unica azione e' un SELL $-7 su una posizione da $+717. Vale un'occhiata umana, senza allarme: le soglie d'ordine sono in dollari assoluti e l'equity ha cambiato scala.
|
||||
|
||||
Cumulato $+3,904.87, di cui $+3,834.43 versati; il trading vale $+70.44 su 75 giorni e 40 round-trip — abbastanza poco che un movimento di capitale scorporato male se lo mangerebbe intero.
|
||||
|
||||
Una lettura alternativa dello stesso dato: implicite sotto le realizzate su entrambi (39.3 contro 46.4, 53.2 contro 70.4) e DVOL al 35° e 27° percentile dopo trenta giorni a +23.88% e +31.44%. Le regole ci leggono il gate VRP01 chiuso; e' altrettanto compatibile un rialzo che il mercato delle opzioni non sta pagando.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,57 @@
|
||||
# Giornale di bordo — 2026-09-07
|
||||
|
||||
*Scritto 2026-09-08T00:36:58+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $79,100.00 | -1.59% | +0.68% | +21.85% | 47.1% | ↑ ↑ ↑ (3/3 su) | 38.8 (31° pctl 1a) |
|
||||
| **ETH** | $2,490.35 | -0.98% | +0.91% | +29.98% | 70.6% | ↑ ↑ ↑ (3/3 su) | 52.6 (25° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$4,484.18** · nozionale lordo $1,156 · leva lorda 0.26x · barra dati 2026-09-07 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.420 | flat | $+707 | $+704 | HOLD (a target) |
|
||||
| **ETH** | +0.268 | flat | $+451 | $+452 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$-18.75** di equity (24 letture)
|
||||
- realizzato: +0.10 netto su 1 round-trip chiusi · 3 fill · fee 0.0066
|
||||
- cumulato dall'arming: **$+3,886.12** di equity — di cui $+3,834.43 versati/prelevati -> trading **$+51.69** · TWR **+7.57%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+704 (TP01 +0.420, SKH01 flat) e ETH $+452 (TP01 +0.268, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.26x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.74x.
|
||||
- `[pnl]` Equity $-18.75 con 3 fill e 1 round-trip chiusi (realizzato $+0.10 netto).
|
||||
- `[vol]` BTC: implicita 38.8 contro realizzata 30g 47.1 (sotto); DVOL al 31° pctl di un anno; IV-rank espandente 0.11 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 52.6 contro realizzata 30g 70.6 (sotto); DVOL al 25° pctl di un anno; IV-rank espandente 0.13 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **76 giorni, 41 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-08T00:37:50+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
La giornata rompe tre chiusure positive di fila (+28.60, +6.93, +12.27) con $-18.75, la perdita giornaliera più grande della serie qui riportata. È quasi tutta valutazione, non attività: 3 fill, 1 round-trip, realizzato $+0.10 netto, con BTC -1.59% e ETH -0.98% e il libro long su entrambi. Il segno torna; la taglia non è verificabile qui, perché la pagina non scompone il P&L per asset.
|
||||
|
||||
Ciò che è cambiato davvero risale al salto del 04/09: le posizioni sono passate dai $+357 / $+214 di fine agosto ai $+704 / $+452 di oggi e ci restano, mentre la leva lorda resta 0.26x. A parità di rischio relativo la stessa percentuale di mercato si scrive con un numero di dollari più grande: le prossime giornate rosse sembreranno peggiori senza esserlo.
|
||||
|
||||
Insolito il quadro della volatilità: implicita sotto la realizzata su entrambi (38.8 contro 47.1; 52.6 contro 70.6) con DVOL al 31° e al 25° percentile di un anno. Il gate di VRP01 legge l'IV-rank (0.11 e 0.13) e resta chiuso, ma una lettura altrettanto compatibile è che il mercato tratti il movimento a 30 giorni (+21.85%, +29.98%) come già avvenuto.
|
||||
|
||||
TP01 porta segnali parziali (0.420, 0.268) e SKH01 è flat malgrado TSMOM 3/3 su: c'è tendenza, non breakout. Il cumulato $+3,886.12 è quasi tutto versato ($+3,834.43); resta $+51.69 di trading su 76 giorni e 41 round-trip, TWR +7.57% — campione che non separa edge e fortuna. Salute piena, 24/24 giri e feed a 0 min: niente da controllare a mano oggi.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-09-08
|
||||
|
||||
*Scritto 2026-09-09T00:36:48+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $78,470.50 | -0.80% | +1.34% | +20.97% | 47.3% | ↑ ↑ ↑ (3/3 su) | 39.9 (40° pctl 1a) |
|
||||
| **ETH** | $2,485.45 | -0.20% | +2.77% | +30.16% | 70.6% | ↑ ↑ ↑ (3/3 su) | 53.9 (33° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$4,477.46** · nozionale lordo $1,149 · leva lorda 0.26x · barra dati 2026-09-08 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.415 | flat | $+697 | $+698 | HOLD (a target) |
|
||||
| **ETH** | +0.268 | flat | $+449 | $+451 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$-6.72** di equity (24 letture)
|
||||
- realizzato: +0.00 netto su 1 round-trip chiusi · 2 fill · fee 0.0055
|
||||
- cumulato dall'arming: **$+3,879.40** di equity — di cui $+3,834.43 versati/prelevati -> trading **$+44.97** · TWR **+7.41%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+698 (TP01 +0.415, SKH01 flat) e ETH $+451 (TP01 +0.268, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.26x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.74x.
|
||||
- `[pnl]` Equity $-6.72 con 2 fill e 1 round-trip chiusi (realizzato $+0.00 netto).
|
||||
- `[drawdown]` 📌 Equity **-0.57%** sotto il picco ($4,502.93). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 39.9 contro realizzata 30g 47.3 (sotto); DVOL al 40° pctl di un anno; IV-rank espandente 0.14 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 53.9 contro realizzata 30g 70.6 (sotto); DVOL al 33° pctl di un anno; IV-rank espandente 0.15 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **77 giorni, 42 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-09T00:37:27+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Terzo giorno sotto il picco del 06/09 ($4,502.93), e secondo consecutivo in rosso dopo i $-18.75 del 07/09: oggi $-6.72. La differenza rispetto alla settimana precedente al versamento non e' il segno ma la scala — le posizioni sono passate da BTC $+347/ETH $+213 a BTC $+698/ETH $+451, quindi oscillazioni di equity dello stesso ordine in dollari valgono ora meno in percentuale. Con realizzato $+0.00 su 1 round-trip e HOLD a target su entrambe le gambe, la perdita del giorno e' rivalutazione delle posizioni aperte contro BTC -0.80% ed ETH -0.20%, non attivita' di trading.
|
||||
|
||||
L'elemento insolito e' altrove: TSMOM 3/3 su su entrambi gli asset, BTC +20.97% e ETH +30.16% a 30 giorni, e nonostante questo TP01 sta a +0.415 e +0.268 con SKH01 flat — leva lorda 0.26x su un tetto di 1.00x. Il libro e' poco investito dentro il trend che dovrebbe essere il suo caso favorevole; se questo sia disciplina di sizing o segnale debole non e' misurabile qui.
|
||||
|
||||
Lettura alternativa altrettanto compatibile: a 0.26x di esposizione il P&L giornaliero e' quasi interamente beta di mercato, e i 77 giorni / 42 round-trip non permettono di distinguerlo dall'edge.
|
||||
|
||||
Merita un'occhiata umana il round-trip chiuso con realizzato esattamente $+0.00 a fronte di fee 0.0055.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -0,0 +1,58 @@
|
||||
# Giornale di bordo — 2026-09-09
|
||||
|
||||
*Scritto 2026-09-10T00:38:03+00:00. Numeri misurati; nessuna interpretazione automatica.*
|
||||
|
||||
## Mercato
|
||||
|
||||
| | chiusura | 1g | 7g | 30g | RV30 ann. | TSMOM 30/90/180 | DVOL |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **BTC** | $78,283.50 | -0.24% | +1.26% | +22.45% | 46.8% | ↑ ↑ ↑ (3/3 su) | 40.2 (42° pctl 1a) |
|
||||
| **ETH** | $2,467.95 | -0.70% | +3.19% | +31.89% | 70.1% | ↑ ↑ ↑ (3/3 su) | 53.7 (32° pctl 1a) |
|
||||
|
||||
## Libro
|
||||
|
||||
Equity **$4,472.10** · nozionale lordo $1,157 · leva lorda 0.26x · barra dati 2026-09-09 · 24 giri
|
||||
|
||||
| | TP01 | SKH01 | target | posizione | azione |
|
||||
|---|---|---|---|---|---|
|
||||
| **BTC** | +0.418 | flat | $+701 | $+704 | HOLD (a target) |
|
||||
| **ETH** | +0.270 | flat | $+453 | $+453 | HOLD (a target) |
|
||||
|
||||
## P&L
|
||||
|
||||
- giorno: **$-5.36** di equity (24 letture)
|
||||
- realizzato: +0.00 netto su 0 round-trip chiusi · 2 fill · fee 0.0047
|
||||
- cumulato dall'arming: **$+3,874.04** di equity — di cui $+3,834.43 versati/prelevati -> trading **$+39.61** · TWR **+7.28%**
|
||||
|
||||
## Salute
|
||||
|
||||
- giri di `book_execute`: 24/24
|
||||
- eta' feed SKH all'ultimo giro: 0 min
|
||||
|
||||
## Lettura
|
||||
|
||||
*Generata da regole dichiarate in `src/live/journal.py` — combina i numeri qui sopra e nient'altro. L'id fra parentesi quadre dice quale regola ha parlato. Non e' un giudizio: quello sta nella Nota.*
|
||||
|
||||
- `[stato]` A mercato su BTC $+704 (TP01 +0.418, SKH01 flat) e ETH $+453 (TP01 +0.270, SKH01 flat).
|
||||
- `[leva]` Leva lorda **0.26x** su un tetto di 1.00x (`max_notional_per_asset_frac` 0.50 x 2 asset, letto da `config/live.json`): margine 0.74x.
|
||||
- `[pnl]` Equity $-5.36 con 2 fill e 0 round-trip chiusi (realizzato $+0.00 netto).
|
||||
- `[drawdown]` 📌 Equity **-0.68%** sotto il picco ($4,502.93). ⚠️ e' un DD su letture ORARIE, non sul minimo intra-giorno.
|
||||
- `[vol]` BTC: implicita 40.2 contro realizzata 30g 46.8 (sotto); DVOL al 42° pctl di un anno; IV-rank espandente 0.14 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[vol]` ETH: implicita 53.7 contro realizzata 30g 70.1 (sotto); DVOL al 32° pctl di un anno; IV-rank espandente 0.15 sotto la soglia 0.30 del gate di VRP01 (sleeve fermo).
|
||||
- `[evidenza]` 📌 Campione a oggi: **78 giorni, 42 round-trip**. A questa taglia il P&L non distingue l'edge dalla fortuna — il criterio di morte del libro (`edge_watch`) lavora su una finestra di 36 mesi.
|
||||
|
||||
## Analisi (agente)
|
||||
|
||||
*Scritta da `claude-opus-5` il 2026-09-10T00:39:20+00:00. Prosa di un modello: puo' sbagliare, e non e' una misura. Controllo sui numeri: **ok**.*
|
||||
|
||||
Il fatto nuovo non e' il segno del giorno ma la sua composizione: $-5.36 di equity con 2 fill, zero round-trip chiusi e realizzato $+0.00. La perdita e' tutta marcatura su posizioni aperte, e le posizioni sono a target su entrambe le gambe ($+704 contro $+701, $+453 contro $+453): il libro non ha deciso niente, ha ri-tagliato. Nella serie qui sopra l'unico altro giorno senza round-trip chiusi e' il 01/09; e' il terzo giorno di equity in discesa dopo il picco del 06/09 a $4,502.93, con BTC -0.24% e ETH -0.70%.
|
||||
|
||||
L'esposizione la porta interamente TP01 (+0.418 su BTC, +0.270 su ETH) mentre SKH01 e' flat su entrambi, e questo con TSMOM 3/3 su e 30 giorni a +22.45% e +31.89%: il trend c'e', il breakout no. Esecuzione pulita, 24/24 giri e feed SKH a 0 min.
|
||||
|
||||
L'implicita sotto la realizzata (40.2 contro 46.8; 53.7 contro 70.1) si legge in due modi ugualmente compatibili — vol a sconto, oppure realizzata gonfiata dalla corsa dei 30 giorni. Quale dei due, non e' misurabile qui.
|
||||
|
||||
Da guardare senza urgenza: il cumulato $+3,874.04 e' quasi tutto versamento ($+3,834.43), il trading vale $+39.61 su 42 round-trip in 78 giorni. A quella taglia il TWR +7.28% non e' ancora una misura di edge.
|
||||
|
||||
## Nota
|
||||
|
||||
*(vuota — campo dell'operatore)*
|
||||
@@ -315,19 +315,56 @@ Ogni numero va letto con la sua banda d'ancora e la sua lente.
|
||||
con turnover 38.5% del lordo/g su 50 alt anche illiquidi e **slippage NON modellato** (rischio #1).
|
||||
**Eseguibilita':** ticket/gamba $1.73 a $600 (**sotto min-order $5 → STAT-MODE oggi**), $5.76 a
|
||||
$2000, **$14.41 a $5000** → diventa reale a **~$5k**, non ai ~$20k di XS01.
|
||||
🚨 **IL GATE QUI SOTTO LEGGE UN MONITOR MAL TARATO (misurato 2026-08-23, §67): il pavimento del
|
||||
venue usato per l'haircut e' quello sbagliato — C\* vero $15.000-20.000, non i ~$3.000 riportati da
|
||||
HL-EXEC con un criterio piu' permissivo.** Le soglie **NON sono state toccate**: cambiarle guardando
|
||||
questa misura a due mesi dalla decisione sarebbe selection-on-holdout. Le due vie pulite: riscriverle
|
||||
**adesso** dichiarando che le si riscrive prima di vedere l'esito, oppure lasciarle e **citare il
|
||||
difetto al momento della decisione**. **Decisione dell'operatore.**
|
||||
**Gate pre-registrato 2026-10-23** (forward-day 0): deploy solo se Sharpe≥1.0 **E** haircut di
|
||||
eseguibilita' a $5000 ≤40% (se l'haircut sfonda → **RITIRO a prescindere dallo Sharpe**), poi
|
||||
weights_tilt_null e capitale ≥$5k. Monitor in `cron_daily.sh`, 3 libri (MODELED $2000 / REAL $600 /
|
||||
✅ **GATE RISCRITTO IL 2026-08-26 (decisione dell'operatore, opzione A: riscrivere adesso,
|
||||
dichiarandolo — diario `2026-08-26-xsr01-gate-riscritto.md`).** A 58 giorni dalla decisione,
|
||||
prima dell'esito, in direzione che stringe: (1) **capitale deploy $5k → $20k** — il gate del
|
||||
25/07 contraddiceva la decisione vincolante "100% Deribit fino a $20k" del 26/07, posteriore,
|
||||
che governa (M28); ora deploy XSR01 e scelta del venue coincidono in UN punto; (2) gamba
|
||||
haircut letta da **`r0826_xsr_haircut.py`** (N11) a **pavimento $10** (il vero, HL-EXEC) con
|
||||
la **frazione di ordini eseguiti** accanto (P7) — misurato: FULL 968 barre, floor $10 →
|
||||
haircut **−1,1%** con **22% di eseguiti**: la guardia da sola era vacua (un libro fermo ha
|
||||
haircut piccolo); ticket mediano **$3,34**/gamba, 84% sotto $10 — riproduce XSR-REPRO e
|
||||
sostituisce il "$14,41" senza script; (3) contesto 1.82 → 1.79 (lente dei gate). **Soglie
|
||||
numeriche INVARIATE** (Sharpe 1,0/0,3; haircut 40%): nessuna soglia nuova coi dati in mano.
|
||||
**Gate pre-registrato 2026-10-23** (forward-day 0): deploy solo se Sharpe≥1.0 **E** haircut
|
||||
≤40% (strumento onesto di cui sopra; se sfonda → **RITIRO a prescindere dallo Sharpe**), poi
|
||||
weights_tilt_null e **capitale ≥$20k**. Monitor in `cron_daily.sh`, 3 libri (MODELED $2000 / REAL $600 /
|
||||
REAL $5000). ⚠ Lezione: XSR01 **non e' nato da un'idea nuova ma dalla DIAGNOSI di un fallimento**
|
||||
(ampiezza effettiva 4.5 → c'e' un fattore comune da togliere); e i suoi null vanno confrontati
|
||||
**a fee zero**, perche' permutare un segnale ne fa esplodere il turnover e il null perderebbe per
|
||||
costo invece che per assenza di informazione (p-value trionfale e falso).
|
||||
🚨 **SOTTO LA LENTE *RENDITA PERPETUA* IL MECCANISMO VALE ~0 (2026-08-25, `r0825_xsr_rendita.py`).**
|
||||
Prima valutazione di XSR01 sul criterio della **perpetua** invece che su Sharpe o accumulo — N6:
|
||||
dichiarato l'obiettivo, cambia il criterio, e nessuno aveva rifatto il conto.
|
||||
Finestra comune libro↔XSR01 **2024-01-01 → 2026-08-22 (965 g, 2,64 anni = 35% della storia del
|
||||
libro)** → i muri qui sotto **non sono** il $313k pubblicato, si confrontano solo fra loro
|
||||
(base libro-solo: perpetua 3,30%, muro **$602.660**). XSR01 su questa finestra: drift **3,72%/a**
|
||||
(SE 1,46%, t +2,55), vol 2,34%, Sharpe **1,56**, maxDD −2,5%, corr col libro **+0,049**.
|
||||
· **iso-nozionale il muro SALE** (+0,3% a w=5% → +10,8% a w=50%): per una rendita il drift comanda.
|
||||
· **iso-rischio (M6)** si ribalta: w=25%, k **1,32x** → muro **$478.729 (−20,6%)**. Ma **l'argmax e'
|
||||
al BORDO** (w=50%, k=1,93x, muro scende monotonamente in w) → la griglia dice *"il piu' possibile"*,
|
||||
e cio' che si compra in quella direzione e' il **k**, non XSR01 (M8). Sopra 1,40x il libro non
|
||||
puo' eseguire (decisione 23/08 + nessuna chiave di scala in config).
|
||||
· **IL NULL, che e' il risultato.** A iso-vol `drift = Sharpe × vol_libro`, quindi tutto il
|
||||
guadagno di muro e' guadagno di **Sharpe da diversificazione**. Quattro sostituti a w=25%:
|
||||
**XSR01 mescolato** (stessi rendimenti, ordine casuale, 3 semi) → $475.933 / $483.846 / $484.421
|
||||
contro i **$478.729** del vero → **il meccanismo vale 0,8% di muro, dentro la risoluzione MC**
|
||||
(spread fra semi 0,151% di perpetua ≈ $17k). **Rumore a media e vol di XSR01** → mediana $499.263.
|
||||
**Rumore a drift ZERO** → mediana **$617.270, SOPRA la base**: la sola riduzione di varianza
|
||||
**non compra rendita**. Si compra il **drift scorrelato**, non la scorrelazione.
|
||||
· 🚨 **Un CONTO REMUNERATO allo stesso tasso lo eguaglia**: 3,72% a vol 0, corr 0, nessun secondo
|
||||
venue → **$477.607**; al 4,0% → **$469.353, meglio**. Ed e' un confronto **in svantaggio per il
|
||||
conto**: la lente L3 gli applica il 33% di `c-sexies` (un BOT paga 12,5%, un deposito 26%).
|
||||
· **L'haircut mangia esattamente cio' che il null dice di star comprando**: 3,72% lordi → 2,97% a
|
||||
haircut 20%, **2,23% alla soglia 40% del gate**, 1,49% al 60%. **Non pareggia un conto al 4,0%
|
||||
nemmeno a haircut ZERO**; pareggia il 2,0% solo sotto il **46%**. E l'haircut pubblicato ($14,41
|
||||
di ticket) e' ~2,4x ottimista e senza script che lo riproduca (XSR-REPRO).
|
||||
· `weights_tilt_null` (25% vs 0%) **PASS** (Δ_is +0,061 · Δ_hold +0,162 · pctl 25,8 < 90,0) — ma
|
||||
l'hold-out del gate e' 2025-01-01, quindi la gamba in-sample e' **un solo anno**: indizio, non prova.
|
||||
· **Il vincolo binding non e' il rendimento, e' il CAPITALE**: a $2.067 un 25% sono $517 contro un
|
||||
**C\* misurato $15.000-20.000**; per un 25% sopra C\* servono **$60.000 sul conto totale**.
|
||||
📌 **Cosa NON dice:** non che XSR01 sia morto — Sharpe standalone 1,56 e DSR 0,983 reggono, e sotto
|
||||
la lente **accumulo** il conto va rifatto. Dice che **per la RENDITA non e' lo strumento**.
|
||||
|
||||
- **Soffitto strutturale BTC/ETH-direzionale ~1.3** superato SOLO espandendo a un meccanismo diverso:
|
||||
cross-sectional su universo Hyperliquid certificato (XS01) → portafoglio Sharpe ~1.55.
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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):
|
||||
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%**
|
||||
@@ -1452,3 +1452,100 @@ una maschera a caso della stessa frequenza**.
|
||||
dall'aria seria. Se ne è accorta solo la riga `giorni in cui il LIBRO e' flat: 0 = 0.0%`.
|
||||
**REGOLA: stampare sempre la CARDINALITÀ di una maschera prima di usarla** — un filtro vuoto non
|
||||
solleva, produce il caso degenere e lo veste da risultato.
|
||||
|
||||
---
|
||||
|
||||
- **COLLAR01 "hold BTC gated dal trend + collar di opzioni <=15 giorni" — REFUTATO (2026-09-01)** —
|
||||
`scripts/research/r0901_btc_collar.py`, diario `2026-09-01-collar-btc.md`, registro §71.
|
||||
Chiesto dall'operatore in quattro battute: *hold BTC long o short coperto in opzioni* + *opzione
|
||||
max 15gg* + ***"ridurre la vincita, ma bloccare la perdita"*** (⇒ collar, non put protettiva) +
|
||||
*entrata gestita da indicatori (es. forte bull)*.
|
||||
🚨 **Si poteva riaprire dopo §46 perche' ne batte il MOTIVO, non perche' ne ripete la misura:**
|
||||
§46 cadde sul **beta +0,076 del libro** (*"non si assicura un libro che nei crash e' gia' quasi
|
||||
piatto"*), qui il sottostante e' un **hold di BTC, beta 1,0**. E §46 dichiarava non provata la
|
||||
copertura **gated su regime**, che e' questa.
|
||||
**L'entrata non ha aggiunto un solo parametro:** `tsmom_blend` media tre `np.sign()` su (30,90,180)
|
||||
⇒ valori in {−1,−1/3,+1/3,+1}, quindi *"forte bull"* = `|blend|==1` (52,5% dei giorni) contro il
|
||||
confronto dichiarato `|blend|>=1/3` (97,1%); il segno da' long **e** short.
|
||||
✅ **A1 CONFERMATA — il COLLAR riduce il maxDD: 36/48 celle** (§46: saliva in 162/162).
|
||||
🚨 **Corretto lo stesso giorno da PAVIMENTO-LEVA (§72): NON e' il pavimento a farlo.** La put da
|
||||
sola, a premio reale, **peggiora il DD in 16/16 celle**; nel collar la riduzione viene dal TETTO
|
||||
(compensa il bleed, e cappare l'upside abbassa il picco da cui il DD si misura).
|
||||
❌ **Ma il de-levering lo fa meglio in 45/48**, e il baratto costa **2-8 punti di drift per punto
|
||||
di DD** (Δdrift/ΔmaxDD **7,90** gate forte, **1,98** largo).
|
||||
🚨 **C9 in forma pura: il tetto taglia il 46,2% dei cicli VINCENTI, il pavimento para il 6,7% dei
|
||||
PERDENTI** — 7x piu' spesso sui vincenti: *troncatura, non protezione*.
|
||||
❌ **M1:** collar Sharpe 0,508 vs TP01 **0,852**; TP01+10% ⇒ **+0,000** di Sharpe e **+2,32pp** di
|
||||
maxDD — peggiora proprio cio' che dovrebbe proteggere.
|
||||
🚨 **IL RISULTATO TRASFERIBILE STA NELL'ESTENSIONE, NON NELLA GRIGLIA.** Le 3 celle vincenti
|
||||
stavano tutte **sul bordo**; estesa la famiglia vince **35/36** con il massimo **nell'angolo**
|
||||
(δput 0,02 / δcall 0,50, Sharpe **1,471**) — e il limite di quell'angolo e' *nessun pavimento,
|
||||
tetto ATM* = **una covered call**: **la pendenza porta fuori dalla domanda posta e dentro lo
|
||||
short-vol**. E quel 1,471 era **il prezzatore che si paga da solo**: **DVOL/RV-forward 1,320 a 7g,
|
||||
sopra nel 76,9% dei giorni** ⇒ riprezzato alla vol vera l'angolo cade a **0,511** (VRP = **72%**
|
||||
del drift; col gate largo **il 100%**, drift **−1,10%**). E **0,511 e' VRP01** (ShFULL 0,47 a
|
||||
f=0,73): *non una scoperta, VRP01 ri-trovato per una strada piu' lunga*. §3 lo blocca comunque.
|
||||
🚨 **USCITA ANTICIPATA (chiesta: 50-75% del tempo) — implementata e COSTA**, e produce il secondo
|
||||
risultato trasferibile. Cella onesta, gate FORTE: drift **+9,48% (scadenza) -> +3,84% (uscita al
|
||||
50%)**, esito da VINCE a **perde sotto 0,75**; gate LARGO **12,06% -> 1,88%**. Il meccanismo
|
||||
previsto c'e' (il VRP residuo scende **+3,38 -> +1,27pp**: uscire presto rinuncia davvero allo
|
||||
short-vol) **ma lo spread lo travolge**. 📌 **Corregge l'APPLICAZIONE di §46:** *"un roll anticipato
|
||||
non paga f"* vale per una copertura **solo LONG**; in un collar la **gamba venduta va RICOMPRATA**,
|
||||
quindi uscire prima paga f *esattamente* sulla parte che a scadenza si regolava gratis, ~2x piu'
|
||||
spesso. **L'asimmetria di §46 si INVERTE quando la struttura ha una gamba corta.**
|
||||
✅ **Controlli dell'apparato 3/3** (la lezione di §46): pranzo gratis riconosciuto, premio x10
|
||||
rifiutato, zero-cost finito.
|
||||
⚠️ **Cinque difetti miei catturati dai controlli:** bisezione zero-cost **invertita** (dava Sharpe
|
||||
−3,9); `dcall=NaN` che propagava NaN nella cassa; **C9 non consapevole della direzione** (`S1>S0`
|
||||
non e' "vincente" per un ciclo short — dava l'impossibile *"pavimento 0,0%"*); e due di
|
||||
contabilita' che **avrebbero adulato il collar** (la base che rollava lo spot pagando ~3,6%/a di
|
||||
fee inesistenti, e il roll che chiudeva lo spot senza motivo).
|
||||
📌 **Corregge un muro di §46:** il **tick da 5 USDC** e' della famiglia **USDC**; la catena che
|
||||
raccogliamo e' **100% inverse** ⇒ quel muro **non si applica** a questo filone.
|
||||
**Cosa lo riapre:** un **f di stress su un crash catturato** (la condizione che §3 pone allo
|
||||
short-vol), o un sottostante **senza coda destra grassa**. **Non** lo riaprono tenor, delta o
|
||||
griglie piu' fini: la pendenza e' monotona verso l'angolo, e l'angolo e' gia' misurato.
|
||||
|
||||
- **PAVIMENTO-LEVA "il pavimento come licenza di taglia" — REFUTATO 0/16 (2026-09-01)** —
|
||||
`r0901c_pavimento_leva.py`, diario `2026-09-01c-pavimento-leva.md`, registro §72. L'inverso di
|
||||
COLLAR01: non *quanto DD risparmia* ma *quanta TAGLIA autorizza a iso-DD*, l'unica cosa che il
|
||||
de-levering non puo' comprare. **La sola put a premio reale PEGGIORA il maxDD in 16/16 celle**
|
||||
⇒ `k_f = 1,000`, niente da licenziare. 🚨 **Corregge COLLAR01 lo stesso giorno**: il DD lo
|
||||
riduceva il **tetto**, non il pavimento (compensa il bleed e abbassa il picco). Pareggio del DD
|
||||
solo a **36-79% del premio reale**; prezzo equo ≈76%: non basta.
|
||||
📌 **Il meccanismo, trasferibile: i drawdown di BTC sono GRIND (290-818 giorni), la put copre
|
||||
UNA finestra** e para nel 0-9% dei cicli: compra protezione contro la cosa sbagliata.
|
||||
🚨 **La licenza del disaster-SL e' di CARTA**: sostituire `sl` con la distanza del pavimento
|
||||
darebbe 2,28x, ma su 8 finestre consecutive la perdita e' −33,6% → **77% dell'equity**.
|
||||
`disaster_sl_pct` NON si sostituisce con la distanza di un pavimento.
|
||||
**Gated sull'IV sottile** (la copertura dinamica che §46 non aveva provato): migliora, perde.
|
||||
**Cosa lo riapre:** scadenza lunga che copra il grind (fuori dal vincolo ≤15g dell'operatore),
|
||||
o un premio di varianza negativo su BTC. Non delta, tenor ≤15, gate IV: misurati.
|
||||
|
||||
- **CROLLI / CROLLO CATTURATO / XRP — 0 candidati, 1 debito, 2 conclusioni smontate in revisione (2026-09-09)** —
|
||||
`r0909_{libro_nei_crolli,crash_catturato,xrp_terza_gamba,deribit_universo}.py`, diario
|
||||
`2026-09-09-crolli-opzioni-monete.md`. Domanda dell'operatore: *"guadagnare anche nei crolli (opzioni se
|
||||
serve), piu' monete ma sempre in Deribit"*. **(1) Il libro nei crolli**: perde nel GIORNO (−0,48%/g su 160
|
||||
giorni ≤ −5%, positivo nel 18%), immune-o-positivo per finestra (4 POSITIVO / 6 immune / 2 PERDE su 12), **> 0
|
||||
in 8/8 peggiori finestre dal 2022** per la gamba short di SKH01 (108% dei guadagni, scomposizione esatta,
|
||||
marcata all'uscita). Beta 0,0769 sulla finestra di §46 (riprodotto). Peggior giorno canonico 2025-10-10
|
||||
−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
|
||||
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,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
|
||||
"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);
|
||||
ingressi durante il crollo bimodali (perde il 46%). **Crollo a vol BASSA** (picco DVOL 29°/46° pctl), gate
|
||||
IV-rank di VRP01 chiuso: **la condizione di §3 e' soddisfatta nella forma, non nella sostanza → non si
|
||||
riapre**. BTC pre-crollo non misurabile (griglia strike rada). **(3) XRP terza gamba** (harness di
|
||||
`r0822_sol_leg`, congelato): **diluisce come SOL** — hold-out −0,169 in 0/24, FULL −0,206 (L-FULL) /
|
||||
−0,066 (2024+), un anno buono (2024), de-levering; L-PULITA dal 2024 (2023 0,57% >1% dal riferimento;
|
||||
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
|
||||
inactive); `BTCDVOL_USDC` future esiste ma vol 0 / spread 12%; PAXG perp non misurato (9 mesi, $0,18M/g).
|
||||
**Debito §5.18** (`cblib.spot_series` + `asof` un'ora avanti) **riparato la sera stessa**, insieme al DVOL
|
||||
(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.
|
||||
|
||||
|
||||
@@ -765,3 +765,18 @@ veicolo di RENDITA**, dove serve **2,5× meno capitale** ($254k contro $646k) pe
|
||||
vive sul drawdown. La scelta del veicolo dipende da quale dei due è l'obiettivo (N6).
|
||||
⚠️ E il fisco favorisce l'ETF: UCITS ad accumulazione paga **26% alla vendita**, il book **33%
|
||||
ogni anno**. Il differimento è un vantaggio strutturale reale.
|
||||
|
||||
- 💰 **SOLDI FERMI — «altri modi, non per forza trading» (2026-09-01/02).** `r0901d_soldi_fermi.py`,
|
||||
diario `2026-09-01d-soldi-fermi.md`. **On-venue chiuso con l'ELENCO INTERO:** `get_currencies`
|
||||
remunera **4 valute su 50** (USDE 4,32 · USDC 3,40 · BUIDL 3,20 · STETH 2,22) = le quattro gia'
|
||||
chiuse il 31/08 — non era un campione. ⚠️ **L'USDC su Deribit non e' fermo**: e' la base di sizing
|
||||
(cap = equity x 0,5) e il cuscino del disaster-SL; spostarlo rimpicciolisce il libro. Il capitale
|
||||
fermo vero sta **fuori**: ~€6.043 in XEON (27/07), che e' lo **split-cassa**. Tassi dal web
|
||||
(BOT 2,768% asta 08/2026 · DFR 2,25% · conti deposito 3,25-3,50% · sUSDe ~9% Q2 2026), fisco dalla
|
||||
memoria (12,5 / 26 / 33%). **In euro netti su €6k:** XEON 81 · BOT **133** · conto deposito
|
||||
**143** · sUSDe 350 ma **stesso emittente dell'USDE gia' in conto: split distrutto** · libro
|
||||
~600 atteso ma **trading, split distrutto**. **Il miglior guadagno sicuro vale +€62/anno = €5/mese**,
|
||||
contro €100/mese di bonifico = +4,07%/anno di drift. ✅ **L'operatore ha scelto di versare** (02/09),
|
||||
che e' il piano del 27/07: **€5.000 dentro, ~€1.000 fuori** (la protezione satura a qualunque
|
||||
quota > 0), il «~€600» letto come attesa e non come tasso, fondo d'emergenza da dichiarare. Nessuna
|
||||
azione di config: il cap e' dinamico. **In attesa di importo e data.**
|
||||
|
||||
@@ -692,6 +692,27 @@ e i vincoli di deploy (PRIIPs/UCITS/broker).
|
||||
📌 **IL DB** (`data/live/trades.db`, dentro il perimetro del backup): `fills` col contesto del
|
||||
segnale a quel giro (tp_frac, skh_sign, entry, target, posizione, equity), `roundtrips`
|
||||
**derivati e ricalcolati da zero**, `equity` oraria, `journal`. Sync **orario** in `cron_book`.
|
||||
📌 **IL RENDIMENTO DEL LIBRO E' UN TWR, NON `e1/e0-1`** (debito 14, trovato il 01/09 e chiuso il
|
||||
02/09/2026). `--report` stampava «+243%» su una serie che conteneva **un solo salto**, il
|
||||
versamento di $1.399,39 del 25/08: il 96,3% del numero era un bonifico. La riparazione
|
||||
(`movimenti_capitale`, che classifica ogni salto oltre `EQUITY_JUMP_ALERT` come *movimento* se
|
||||
supera 2× il massimo che il mercato misurato poteva fare a tetto di leva, altrimenti *ambiguo*)
|
||||
viveva gia' nel giornale e **non era arrivata al secondo lettore della stessa serie** — una
|
||||
variante di P1 che non e' un sorvegliante che ridichiara, ma una riparazione che non si propaga.
|
||||
Ora `journal.rendimento_twr` e' la funzione unica: spezza la serie all'equity PRIMA e DOPO ogni
|
||||
movimento certo e moltiplica i segmenti (gli ambigui non spezzano: restano nel rendimento,
|
||||
dichiarati accanto — P12). Il report stampa TWR, segmenti con le date, movimenti elencati,
|
||||
trading al netto, e il delta $ grezzo solo etichettato «movimenti INCLUSI». Verifica M23: la
|
||||
macchina riproduce il +10,80% del diario 01/09 sui suoi quattro punti. **Due limiti trovati in revisione (02/09):** l'intervallo che contiene un movimento certo esce INTERO dal rendimento (il suo P&L di mercato va in `certi`; errore massimo META' del movimento, per costruzione; il 25/08 ~$0,5) e a base di equity zero `twr` e `trading` sono entrambi None, perche' il salto 0→X del primo versamento e' invisibile al classificatore. I segmenti a lunghezza zero (movimento nel primo intervallo, o due consecutivi) non si stampano. **Seconda tornata di revisione, stesso giorno:** il classificatore era **cieco ~23 ore al giorno** — il
|
||||
feed 1h si ferma alle 00:00, `asof` dava la stessa barra alle due letture, mercato «fermo» = 0, e
|
||||
qualunque calo ≥10% del giorno sarebbe stato un movimento (un crash stampato come prelievo, dal
|
||||
25/08); con feed assente tutto era «ambiguo» e il report stampava `e1/e0-1` sotto l'etichetta TWR.
|
||||
Ora «mercato non misurabile» è uno stato: `ambiguo` con la ragione, `twr`/`trading` None con motivo.
|
||||
Il «limite +10% a mercato fermo» scritto la mattina era un artefatto della fixture piatta, cancellato.
|
||||
`pnl_giorno` chiama `rendimento_twr` (la frase «entrambi la chiamano» è vera solo da questa tornata),
|
||||
la pagina stampa il TWR, la testata Telegram qualifica il cumulato «di cui versati», e il bound di
|
||||
leva include la scala e il tetto di codice. Resta aperto (§5.15): il bound guarda solo BTC/ETH, e il
|
||||
31% dell'equity è USDE — un depeg pieno sarebbe scorporato come prelievo.
|
||||
**Le tre fonti si INCROCIANO e non si sovrascrivono:** log (ora vera) x jsonl (i fill) x venue
|
||||
(autorevole ma **TRONCA** — 1 trade su BTC, 0 su ETH). `reconcile()` riporta le divergenze e
|
||||
**non ripara niente da solo**: fra due fonti che non concordano, una riparazione silenziosa e'
|
||||
@@ -790,6 +811,48 @@ SORGENTE** di `cron_daily.sh` — ogni chiamata a `journal.py` deve avere `--gio
|
||||
perche' `journal.py` **senza argomenti scrive OGGI**. La guardia **non vieta** di rigenerare
|
||||
spesso: vieta di scrivere la pagina **una volta e lasciarla li'**.
|
||||
|
||||
🚨 **RIPARATO E RIGENERATO (2026-08-26): `advance()` e i 6 forward-monitor (§5.1).**
|
||||
Il fix e' un filtro condiviso (`src/live/paper_guard.py`): barra open-labeled chiusa =
|
||||
`ts + cadenza ≤ adesso`, importato da tutti e sei i monitor — `paper_combo` compreso, che era
|
||||
sano *per caso* (aspettava la chiusura di una borsa, non per progetto) ed e' ora sano *per
|
||||
costruzione*. Rigenerazione con `scripts/live/paper_regen.py`: stesso `start_ts` pre-registrato,
|
||||
config congelata, replay del codice di produzione riparato (il metodo validato 2 volte
|
||||
dall'audit 22/08); evidenza del difetto archiviata in `*.pre_regen_20260826.*` dentro il
|
||||
perimetro di backup. Esiti (Sharpe rotto → vero): statarb **+1,95 → −1,61** (il ribaltamento
|
||||
del gate 27/09 previsto dall'audit) · dvolspread −14,73 → −4,41 · xsr −4,98 → −2,72 · prevday
|
||||
+0,39 → +0,40. `paper_portfolio` non rigenerato (GTAA legge ADJUSTED_LAST ri-aggiustato: il
|
||||
replay non sarebbe la serie registrata, P12): tolta solo la coda non chiusa, storia pre-fix
|
||||
dichiarata corrotta. La guardia raccomandata dall'audit e' **cablata in `monitor_health`**:
|
||||
stato **PREMATURO** (l'ultima barra chiude dopo l'mtime del file, grazia 5 min), derivato
|
||||
dalle spec esistenti (P1), con `open_labeled=False` per `collect_chain` che timbra giri e non
|
||||
barre (P14). Controllo positivo sul dato vivo: prima della rigenerazione segnala **esattamente
|
||||
i 5 rotti** e tace sui 2 sani; dopo, 7/7 OK. ⚠️ Lezione nel fix: la prima stesura di
|
||||
`indice_chiuso` usava `asi8` assumendo `ns` — pandas 3 usa `us`, e la barra del giorno in corso
|
||||
risultava "chiusa" **senza eccezioni** (D6, pagata di nuovo scrivendo il codice che doveva
|
||||
impedirla). Blindato con un test su tre risoluzioni (`ns/us/s`).
|
||||
|
||||
🚨 **RIPARATO (2026-08-26): il P&L di giornale contava i VERSAMENTI come profitto.**
|
||||
Il «giorno» era un delta di equity fra letture: la voce del 25/08 dichiarava **+$1.414,57 di
|
||||
P&L** quando $1.399,39 erano il deposito USDC dell'operatore, e il «cumulato dall'arming»
|
||||
avrebbe mentito per sempre (e al prossimo versamento da $3.000 avrebbe stampato «+$3.000 di
|
||||
giorno»). Riparazione in `src/live/journal.py::movimenti_capitale` con tre proprieta':
|
||||
(1) la **soglia e' importata** da `book.EQUITY_JUMP_ALERT`, non ridichiarata (P1 — il rilevatore
|
||||
di movimenti del progetto e' quello); (2) un salto e' `movimento` solo se supera di >2x il
|
||||
massimo che il **mercato misurato** (feed certificato, tetto di leva da config) avrebbe potuto
|
||||
produrre nell'intervallo fra le due letture; (3) altrimenti e' **`ambiguo`: dichiarato e NON
|
||||
scorporato** (P12 — un crash vero a tutta leva non deve trasformarsi in "prelievo").
|
||||
Scansione dell'intera storia reale: **1 evento, esattamente il deposito** (+209,6% contro un
|
||||
massimo di mercato di ±0,22%), zero falsi positivi. Anche la regola `concentrazione` ora
|
||||
lavora sul cumulato di **trading** (il 25/08 leggeva "il 100% del P&L viene dagli ultimi 7
|
||||
giorni" su un P&L che era il bonifico). Due scelte di design da non perdere: le regole sui
|
||||
movimenti stanno **sopra** l'early-return "nessun giro di book" (un versamento in un giorno
|
||||
senza giri esiste lo stesso: viene dal DB equity, non dal log del libro), e il **limite e'
|
||||
dichiarato** (D5): un movimento sotto soglia — es. $150 su un conto da $2.000 — non si
|
||||
distingue dal mercato e resta nel P&L, come nel rilevatore live. 7 prove nuove in
|
||||
`test_journal.py` (31 → 37), incluso il controllo M15 al contrario: un crash −14% a tutta
|
||||
leva resta AMBIGUO. Voce del 25/08 **rigenerata**: giorno +$1.414,57 di equity → trading
|
||||
**+$15,18**; cumulato +$1.458,53 → trading **+$59,14**.
|
||||
|
||||
⚠️ **Trovato girando la suite completa, NON riparato — e' un'altra cosa:**
|
||||
`tests/test_wave_0726.py::test_t1_canonical_riproduce_il_backtest_ufficiale` **fallisce**, e
|
||||
falliva gia' prima di questa modifica (verificato con `git stash`: stesso fallimento sull'albero
|
||||
@@ -859,6 +922,56 @@ Sulle 4 finestre osservate 3 sono rientrate entro l'ora → il `:47` ne avrebbe
|
||||
**Se al prossimo martedì il `:47` becca comunque la manutenzione, la previsione è sbagliata** e lo
|
||||
slot non è quello descritto in `venue_probe.RELEASE_*`: rileggerlo prima di spostare ancora.
|
||||
|
||||
### La cadenza è ORARIA, e ora lo dicono tutte e tre le fonti (debito §5.7, chiuso il 2026-09-02)
|
||||
|
||||
Il docstring di `book_execute.py` prescriveva «ogni ~230 minuti» (la griglia di SKH01) mentre il cron
|
||||
gira ogni ora. Non era cosmetico: `r0823_sl_anchor.py` misura che l'accumulo di scatti del disaster-SL
|
||||
rotolante è funzione della **cadenza del cron** (1h: BTC 0 / ETH 1 in 7-8 anni · 4h: BTC 2 · 24h: ETH 5
|
||||
con 2 in 30 giorni), quindi chi avesse "corretto" il CRON verso il docstring avrebbe spostato il libro
|
||||
dalla riga 1h (rotolante: BTC 0 / ETH 1) alla riga 4h (BTC 2 contro 1 del pavimento; la peggiore misurata è 24h, ETH 5). La riparazione è il docstring stesso — cadenza ORARIA, la ragione (giro
|
||||
idempotente: girare più fitto della griglia non costa ordini, e compra latenza ≤1h per gli
|
||||
ingressi/uscite software di SKH01 e un **controllo** orario del rotolante — che si ri-ancora solo oltre la tolleranza di `ensure_disaster_sl`, mark +5,263%/−4,762% o taglia >10%, quindi lo stop siede fra −33,5% e −26,5% dal mark corrente: la prima stesura diceva «si ri-ancora ogni ora» ed era sbagliata, corretta in revisione lo stesso giorno) e il divieto scritto —
|
||||
più `tests/test_book_cadenza.py`, che **deriva** (P1) le tre dichiarazioni e le confronta: la riga
|
||||
citata nel docstring, quella dichiarata nell'intestazione di `cron_book.sh` (`47 * * * *`) e la
|
||||
crontab **installata** (`crontab -l`; se non è leggibile il test è SALTATO, non verde — P5). Il
|
||||
parser accetta solo espressioni regolari e rifiuta il resto. Vincolo del minuto tondo verificato
|
||||
(≠ `:00`). La regola generale è la stessa del `:47`: **una cadenza si difende nella sua ragione, e
|
||||
un commento che dichiara una cadenza diversa da quella che gira si paga quando qualcuno lo "corregge"
|
||||
nel verso sbagliato.**
|
||||
|
||||
### Il README ha pubblicato per 75 giorni la libreria che il progetto aveva dichiarato falsa
|
||||
|
||||
Ultimo commit del README: **2026-06-04**. Reset v2.0.0: **2026-06-19**. In mezzo, quindici giorni; dopo,
|
||||
75 in cui la prima pagina del repository descriveva FADE/HONEST/PAIRS/TSMOM/SHAPE, i portafogli PORT01-06,
|
||||
il paper trader su `strategies.yml` e l'esecuzione shadow su testnet — con Sharpe 7,84/10,06 e CAGR ~79%
|
||||
presentati come risultati correnti. Metà dei file citati non esisteva più (`strategies.yml`,
|
||||
`portfolios.yml`, `scripts/waste/`, `scripts/portfolios/`, `src/live/multi_runner.py`); i numeri erano
|
||||
esattamente quelli che `CLAUDE.md` §2 elenca sotto «non citare», e provenivano dalla libreria che il
|
||||
reset aveva dichiarato artefatto di un feed contaminato.
|
||||
|
||||
Nessun danno sui soldi — il README non decide niente. Il danno è di **citazione**, ed è la forma peggiore:
|
||||
un lettore esterno (o un agente nuovo) parte da lì. **La lezione non è "tenere aggiornato il README"**,
|
||||
che nessuno fa più di quanto già non faccia: è che **un reset invalida anche i documenti che nessuno
|
||||
rilegge**, e l'inventario di cosa cita numeri morti va fatto il giorno del reset, quando si sa che sono
|
||||
morti — non 75 giorni dopo, quando qualcuno ci inciampa per caso. Riscritto il 2026-09-02 contro lo stato
|
||||
vero, con ogni riferimento a file verificato e i numeri nella lente di §2.
|
||||
|
||||
### Un flag sconosciuto non è l'azione di default (2026-09-02)
|
||||
|
||||
Nessuno dei 18 script di `scripts/live/` usava argparse: leggevano `sys.argv` con `in` e `index`,
|
||||
quindi un flag sbagliato non era un errore — era il ramo `else`. Il costo si è misurato lo stesso
|
||||
giorno, dentro una revisione: `trades_db.py --help` è caduto in `sync()` e ha riscritto
|
||||
`meta.ultimo_sync`. Sui due script che scrivono davvero sarebbe stato peggio: la pagina di giornale
|
||||
e una riga di DB, oppure una chiamata al modello più un Telegram. **La riparazione non è argparse** —
|
||||
cambierebbe messaggi d'errore, codici d'uscita e `--help` di script che il cron già chiama, cioè
|
||||
romperebbe ciò che gira per riparare ciò che non gira mai. È una funzione di venti righe
|
||||
(`src/live/cli.valida`) che rifiuta ciò che non è dichiarato ed esce **prima di qualunque effetto**:
|
||||
`--help` → 0, flag ignoto → 2 con l'elenco dei previsti (P4). Il test deriva l'elenco degli script
|
||||
dalla cartella (P1: un file nuovo senza guardia fallisce, non passa inosservato), controlla che
|
||||
`valida` sia la prima istruzione di `__main__` (uscire dopo `connect()` sarebbe uscire dopo
|
||||
l'effetto) e — l'altra metà del contratto, P15/P16 — che **i flag che il cron usa davvero restino
|
||||
accettati**: una guardia che ferma il libro di bordo la notte stessa sarebbe peggio del difetto.
|
||||
|
||||
### Cosa resta scoperto
|
||||
|
||||
1. **La sonda dice di chi è il guasto, non lo aggira.** Col gateway giù il libro continua ad
|
||||
@@ -898,3 +1011,39 @@ impedire.** Danno reale quel giorno: nessuno — alle 09:47 l'equity era leggibi
|
||||
che si accende se la fixture viene rimossa.
|
||||
- ⚠️ **Resta il principio più largo:** oggi è deviato **solo** il watermark. `data/live/trades.db` e
|
||||
`data/live/book_executions.jsonl` sono esposti allo stesso errore (§5.12).
|
||||
|
||||
### Revisione settimanale in sola lettura (2026-09-09)
|
||||
|
||||
L'operatore ha chiesto «un processo di auto-revisione dei dati e della configurazione, che si autoregola,
|
||||
si autoaggiorna, crea sistemi nuovi valutando mercato e dati macro, e mi informa su Telegram». La risposta,
|
||||
misurata contro la memoria, e' stata in quattro pezzi: **la rilettura periodica manca e vale** (i nove
|
||||
sorveglianti guardano ognuno la sua grandezza; nessuno rilegge il sistema intero, e la revisione con un
|
||||
secondo modello ha trovato in una sessione due conclusioni false e un look-ahead); **l'autoregolazione dei
|
||||
parametri no** (e' cio' che `scale_watch`/`venue_watch` trattano come anomalia; A8 della chiave di scala:
|
||||
una riga di config, una di giornale, decisione dell'operatore); **la generazione automatica di strategie
|
||||
solo con un freno** (ogni candidato e' un trial, M3, e la ricerca non e' il vincolo binding dal 26/07: il
|
||||
miglior lead vale +0,046 €/giorno contro +4,07%/anno di drift per €100/mese in piu'); **il macro da
|
||||
internet no** (gate macro, DVOL direzionale e skew de-risk sono gia' HEDGE/ridondanti; un dato dal web
|
||||
non e' certificato — la APR USDC «pubblicata» e mai incassata). L'operatore ha scelto la prima («fai»).
|
||||
|
||||
Cosa gira: `scripts/cron_review.sh` (lunedi' 06:15 UTC, fuori dal :00 e dal martedi' 09:00, dopo il
|
||||
`cron_daily` delle 00:30) → `scripts/live/revisione.py --quiet` → `src/live/revisione.scrivi`. Materiale
|
||||
(~195k caratteri, sola lettura, ordine: regole e stato dichiarato, cio' che gira, cio' che e' successo):
|
||||
CLAUDE.md intero, `config/live.json`, `crontab -l`, `git log` 14 g + `git status`, `monitor_health`,
|
||||
stato dei sorveglianti (scale, vrp_f, usde, balance, venue_news, movimenti dichiarati, watermark — messi
|
||||
PRIMA delle voci grandi, cosi' il tetto totale non li taglia mai: alla prima stesura `usde_convert` era
|
||||
finito a 2 caratteri), 7 pagine di giornale, i diari della settimana. Prompt con dieci regole: sola
|
||||
lettura, ogni segnalazione cita la fonte, solo numeri del materiale (guardia `numeri_non_supportati` di
|
||||
`analista`, soglia 12), le decisioni vincolanti di §3 non si ripropongono, un'idea nuova si propone solo
|
||||
citando la memoria che ha ucciso la simile, i gate si elencano con data e criterio senza anticipare il
|
||||
verdetto, misurato ≠ dedotto, niente previsioni, URGENTE per cio' che serve all'operatore subito, cinque
|
||||
titoli fissi. Rapporto in `docs/revisioni/<data>.md` firmato (modello, data, «lettore fallibile P13»,
|
||||
tabella del materiale letto con caratteri e troncature); Telegram = la sola Sintesi, con taglio dichiarato.
|
||||
Modello `claude-fable-5-1` via `analista.interroga` (CLI `claude -p`, `--allowed-tools ""`, timeout 900 s):
|
||||
**non il modello che scrive il codice**, ed e' un test (`test_il_revisore_non_e_il_modello_che_scrive_il_codice`).
|
||||
Un modello muto o una risposta senza i titoli produce un rapporto «NON eseguita» col motivo e un 🚨: il
|
||||
silenzio non e' una revisione (P5). Guardie: `valida` di `cli` come prima istruzione (`test_cli_flag` lo
|
||||
deriva dalla cartella), `--secco` non chiama e non scrive (test sull'albero dei file), un giro scrive **UN
|
||||
solo file** (test), la cadenza e' dichiarata nel `.sh` e letta dalla crontab installata (`test_revisione`,
|
||||
stesso schema di `test_book_cadenza`). Costo: una chiamata a settimana. Cio' che il sorvegliante non puo'
|
||||
fare: eseguire. E' voluto.
|
||||
|
||||
@@ -157,3 +157,25 @@
|
||||
`fresh_5m`). **REGOLA: un guasto IN CORSO si misura al GIORNO PEGGIORE, non in media** — la prima
|
||||
stesura confrontava "tutto" con "ultimi 7g" e diluiva un guasto di 2 giorni al 13.5%, commettendo
|
||||
a 7 giorni lo stesso errore che dichiarava di evitare a 3 mesi. Diario `2026-07-30-vrp-quote-reali.md`.
|
||||
|
||||
- 🚨 **IL FEED 1h E' ETICHETTATO ALL'APERTURA, e `asof(ts)` guarda un'ora avanti (trovato in revisione
|
||||
il 2026-09-09).** La barra 1h etichettata 20:00 chiude col 5m delle 20:55 (verificato: close 1h 20:00 =
|
||||
close 5m 20:55 = 1603,5, mentre il 5m 19:55 fa 1572,95). Quindi `S.asof(ts)` su una serie indicizzata
|
||||
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
|
||||
di `cblib` con lo spot, compreso `ST = S.asof(exp)` che e' il prezzo di un'ora DOPO la scadenza.
|
||||
✅ **RIPARATO la sera stessa in `cblib`** (`causale(s, cadenza)`: spot +1h, e **anche il DVOL
|
||||
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
|
||||
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`).
|
||||
Certificazione per anno vs riferimento USD: **Coinbase non ha XRP fra 2021-01 e 2023-07** (delisting per
|
||||
la causa SEC) → riferimento **Bitstamp** prima, Coinbase dopo; >1% 0,06% (2022) · 0,57% (2023) · ≤0,02%
|
||||
(2024+); mediana 5-6 bps; **5m flat 27-61%** (BTC/ETH 0,1-2%, SOL 21%): il 5m e' illiquido. Cache del
|
||||
riferimento in `data/_cache/` (gitignored, fuori backup: si riproduce con rete).
|
||||
|
||||
|
||||
@@ -16,6 +16,10 @@ null de-levering superato + eseguibilita' al capitale dichiarato.
|
||||
| 6 | OI-PIN | **SCARTATO** | il max-pain batte uno strike casuale ma **non batte mai (0/24) la media a 7 giorni dello spot**, un livello che non usa NESSUN dato di opzioni: non e' pinning, e' reversione verso il centro recente con l'OI come stimatore rumoroso di quel centro |
|
||||
| 8 | VOL-SIZE | **LEAD RIDIMENSIONATO** — gate 22/12 **gia' fallito oggi** | dare a SKH01 una size per-trade regge a **23/23 ancore**, 8/8 anni, null di permutazione e trasferimento su V1 — ma vale **+0,07 di Sharpe di libro, un terzo della fortuna d'ancora del libro stesso (+0,196)**. Il vol-target di **libro** e' invece falsificato: compra peso SKH gia' respinto e peggiora l'eseguibilita' |
|
||||
| — | **XSR-REPRO** (integrita') | 🚨 **DIFETTO DI PRODUZIONE** | il numero 1.82 e' SPIEGATO e non era sbagliato (era su una **terza** lente, e su una barra non ancora chiusa) — ma cercandone la causa e' emerso che **`paper_xsr` registra ~41 minuti di mercato al giorno**, non un giorno. Tre gate pre-registrati leggono serie costruite cosi' |
|
||||
| 70 | **XSR-RENDITA** | **SCARTATO sotto la lente RENDITA** (non come sleeve) | il muro scende del 20,6% a iso-rischio, ma **mescolare i rendimenti di XSR01 da' lo stesso muro** (il meccanismo vale 0,8%, dentro la risoluzione MC) e un **conto remunerato allo stesso tasso lo eguaglia a vol 0 e senza secondo venue**. A drift zero il muro **sale**: si compra un drift scorrelato, non la scorrelazione. E l'haircut non pareggia un conto al 4% **nemmeno a haircut ZERO** |
|
||||
| 71 | **COLLAR01** | hold BTC gated dal trend, coperto da un collar (pavimento comprato, tetto venduto) a scadenza <=15g: "ridurre la vincita, bloccare la perdita" batte il de-levering? | **REFUTATO** — ✅ il pavimento FUNZIONA (maxDD scende in **36/48**, §46 battuto sul suo motivo: qui beta=1,0) ❌ ma costa **2-8 punti di drift per punto di DD** e il de-levering vince **45/48**. 🚨 Le 3 celle vincenti stanno sul **bordo**: estesa la famiglia vince 35/36 **nell'angolo covered-call**, e **il 72-100% di quell'edge e' il PREMIO DI VARIANZA** (DVOL sta **32% sopra** la RV-forward): riprezzato alla vol vera l'angolo cade da Sharpe **1,471 a 0,511** — che e' VRP01 (0,47), non una scoperta |
|
||||
| 72 | **PAVIMENTO-LEVA** | il pavimento (sola put, ≤15g) come LICENZA DI TAGLIA a iso-DD — l'inverso di §71 | **REFUTATO 0/16**, e **corregge §71**: la put da sola a premio reale **peggiora il maxDD in 16/16** (FORTE 51,8% → 55-76%; LARGO 71,9% → 75-82%) — nel collar il DD lo riduceva il **TETTO**, non il pavimento. Meccanismo (B2): **i drawdown di BTC sono grind di 290-818 giorni**, la put copre una finestra e para nel 0-9% dei cicli. Pareggio del DD solo a **36-79% del premio reale** (prezzo equo ≈76%: non basta). **La licenza del disaster-SL e' di carta** (B3): sostituire `sl` con la distanza del pavimento darebbe 2,28x, ma su 8 finestre la perdita e' −33,6% → **77% dell'equity**. Gated su IV sottile: migliora (+3,8 → +8,4%) ma **perde** ancora |
|
||||
| 73 | **SOLDI-FERMI** | altri modi per far rendere il capitale fermo, non per forza trading | **on-venue CHIUSO con l'elenco intero** (4/50 valute = le 4 del 31/08); l'USDC sul conto non e' fermo (sizing + cuscino); fuori, su €6k netti/anno: XEON 81 · **BOT 133** · **conto deposito 143** · sUSDe 350 (**stesso emittente dell'USDE: split distrutto**) · libro ~600 (trading). Miglior guadagno sicuro **+€62/anno**. ✅ L'operatore ha scelto di **versare** (piano 27/07: €5k dentro, ~€1k fuori) |
|
||||
| 5 | SKEW | **SCARTATO** (Q1, Q2) + **LEAD** (Q3, gate 2027-02-22) | il prezzo muove lo skew (t 3,1-9,4 su 8/8 test), **non il contrario** (max |t| in avanti 2,35 contro 2,08 atteso dal rumore). Ma Q3 e' grosso: **il f=0,73 di VRP01 e' per il 42% STRUTTURA A TERMINE e solo per il 25% skew** |
|
||||
| — | **HL-EXEC** (audit di fatto) | **3 falsificazioni misurate** | il pavimento vero e' **$10 (non $5)** e il taker **4,50 bps (non 5,0)** — ma il *"XS01 serve ~$20k"* e' **refutato del tutto** (nessuna soglia da min-order), il *"XSR01 ~$5.000"* e' **conservativo di 1,7x** (vero ~$3.000), e lo **slippage "rischio #1" di XSR01 e' refutato** con margine **21x** |
|
||||
| — | **SLIP-AUDIT** (audit di fatto) | **SCARTATO** = nessun costo nascosto a questa taglia | i backtest a 10 bps RT restano **conservativi di ~1,5 bps/lato**; nessuna evidenza di impatto sopravvive al null (p=0,274; estremi avversi **1/18 contro 4,7 attesi**). **Ma la misura ha una data di scadenza** |
|
||||
@@ -193,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%.
|
||||
- ✅ **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**.
|
||||
✅ **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
|
||||
**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
|
||||
@@ -370,6 +386,13 @@ nuovo: **va decisa PRIMA del 23/10, non quel giorno.**
|
||||
(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
|
||||
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`
|
||||
osservato compare un **falso lead** a +15m (0,102 contro 0,031) — *"e' l'errore che avrei
|
||||
pubblicato senza quel controllo"*.
|
||||
@@ -630,6 +653,11 @@ serviva a diagnosticare l'altro guasto.*
|
||||
📌 **`paper_combo` e' sano PER CASO:** aspetta la chiusura di una **borsa**, non per progetto.
|
||||
Nessuna riga di codice chiede la barra chiusa — **se la sua griglia cambiasse diventerebbe rotto in
|
||||
silenzio.**
|
||||
✅ **CHIUSO IL 2026-08-26:** `advance()` riparato in tutti e 6 i monitor
|
||||
(`src/live/paper_guard.py`), serie rigenerate dallo stesso `start_ts`
|
||||
(`scripts/live/paper_regen.py`; statarb +1,95 → **−1,61**), guardia PREMATURO cablata in
|
||||
`monitor_health` (5/6 segnalati prima della rigenerazione, 7/7 OK dopo). Dettaglio nel diario
|
||||
`2026-08-26-advance-riparato-e-debiti.md`.
|
||||
✅ **Guardia raccomandata (NON implementata), e ha il pregio di essere banale:**
|
||||
`ts_ultima_barra + cadenza <= mtime del file di serie`, grazia 5 min. **O(1)**, nessuna strategia da
|
||||
rieseguire, usa solo cio' che il monitor gia' scrive; segnala **5/6** e **tace sul sano**; controllo
|
||||
@@ -3730,3 +3758,303 @@ pubblicato, e nessuno dei tre produce reddito.
|
||||
|
||||
**Con questa sono 69 filoni e 0 candidati promossi.** I vincoli binding restano due: **il capitale che
|
||||
entra** e **il conto che non sparisce**.
|
||||
|
||||
---
|
||||
|
||||
## 70 — XSR-RENDITA (r0825_xsr_rendita.py) — XSR01 sul criterio della PERPETUA
|
||||
|
||||
**Domanda dell'operatore:** *"rendita perpetua ma non ho ancora capito se serve XSR01"*. XSR01 era
|
||||
stato giudicato su **ammissione**, **gate di deploy** e **accumulo**, mai sulla **rendita** — e sono
|
||||
criteri diversi (l'accumulo premia il drift, la rendita la **perpetua**). N6: dichiarato l'obiettivo,
|
||||
si rifa' il conto.
|
||||
|
||||
**Finestra.** Un MIX esiste solo dove esistono ENTRAMBE le serie → **2024-01-01 → 2026-08-22**
|
||||
(965 g, 2,64 anni, **35%** della storia del libro). ⚠️ **I muri di questo filone NON sono il $313k
|
||||
pubblicato**: base libro-solo su questa finestra = perpetua 3,30%, muro **$602.660**.
|
||||
XSR01, lente dei gate su sole barre chiuse: drift **3,72%/a** (SE 1,46% → t +2,55), vol 2,34%,
|
||||
Sharpe **1,56**, maxDD −2,5%, corr col libro **+0,049**.
|
||||
|
||||
**(a) iso-nozionale: il muro SALE** (+0,3% a w=5% → **+10,8%** a w=50%). Il mix e' piu' liscio e piu'
|
||||
povero, e per una rendita il drift comanda.
|
||||
**(b) iso-rischio (M6): si ribalta** — w=25%, k **1,32x** → muro **$478.729 (−20,6%)**.
|
||||
⚠️ **argmax al BORDO** (w=50%, k=1,93x; il muro scende **monotonamente** in w): la griglia non indica
|
||||
un ottimo, dice *"il piu' possibile"*, e in quella direzione si compra il **k**, non XSR01 (M8).
|
||||
Sopra 1,40x il libro **non puo' eseguire** (decisione 23/08; nessuna chiave di scala in config).
|
||||
|
||||
🚨 **(c) IL NULL — ed e' il risultato.** A iso-vol `drift = Sharpe × vol_libro`: la colonna "drift"
|
||||
della tabella iso-rischio **e' la colonna "Sharpe" ri-etichettata**, quindi tutto il guadagno di muro
|
||||
e' guadagno di **Sharpe da diversificazione**. Quattro sostituti a w=25%, stessa ri-scalatura:
|
||||
|
||||
| sostituto | muro | Δ vs base |
|
||||
|---|---|---|
|
||||
| **XSR01 (vero)** | **$478.729** | **−20,6%** |
|
||||
| XSR01 **mescolato** (3 semi) | $475.933 · $483.846 · $484.421 | −21,0 · −19,7 · −19,6% |
|
||||
| rumore a media e vol di XSR01 (3 semi) | $457.751 · $499.263 · $531.158 | −24,0 · −17,2 · −11,9% |
|
||||
| rumore a **drift ZERO** (3 semi) | $558.080 · $617.270 · $662.436 | −7,4 · **+2,4** · **+9,9%** |
|
||||
| **conto remunerato 3,72%, vol 0** | **$477.607** | **−20,8%** |
|
||||
| conto remunerato 4,0%, vol 0 | $469.353 | −22,1% |
|
||||
|
||||
📌 **Il meccanismo di XSR01 vale 0,8% di muro** — dentro la risoluzione Monte Carlo (spread fra semi
|
||||
**0,151%** di perpetua ≈ $17k, M23). **Mescolarne i rendimenti non cambia niente.**
|
||||
📌 **A drift zero il muro SALE**: la sola riduzione di varianza **non compra rendita**. Cio' che si
|
||||
compra e' un **drift scorrelato**, non la scorrelazione.
|
||||
🚨 **Un conto remunerato allo stesso tasso lo eguaglia**, a vol 0, corr 0 e **senza un secondo venue**
|
||||
— e il confronto e' **in svantaggio per il conto**: la lente L3 gli applica il 33% di `c-sexies`,
|
||||
mentre un BOT paga 12,5% e un deposito 26%.
|
||||
|
||||
**(d) L'haircut mangia esattamente cio' che si sta comprando.** 3,72% lordi → 2,97% a haircut 20%,
|
||||
**2,23% alla soglia 40% del gate**, 1,49% al 60%. **Non pareggia un conto al 4,0% nemmeno a haircut
|
||||
ZERO**; pareggia il 2,0% solo sotto il **46%**. L'haircut pubblicato ($14,41/ticket) e' ~2,4x
|
||||
ottimista e **senza script che lo riproduca** (XSR-REPRO).
|
||||
|
||||
**(e) `weights_tilt_null` (25% vs 0%): PASS** (Δ_is +0,061 · Δ_hold +0,162 · pctl 25,8 < soglia 90,0)
|
||||
— ma l'hold-out del gate e' 2025-01-01 e la gamba in-sample e' **un solo anno**: indizio, non prova.
|
||||
|
||||
**(f) Il vincolo binding non e' il rendimento, e' il CAPITALE.** XSR01 gira su Hyperliquid: il suo
|
||||
peso e' capitale che **esce** da Deribit. A $2.067 un 25% sono **$517** contro un **C\* misurato
|
||||
$15.000-20.000**; per un 25% sopra C\* servono **$60.000 sul conto totale**.
|
||||
|
||||
**VERDETTO 4/6 — ma la lettura non e' il conteggio:** sotto la lente rendita XSR01 fa una cosa reale
|
||||
(porta un drift scorrelato a bassa vol) **in modo non proprietario**. **Non dice che e' morto**
|
||||
(Sharpe 1,56 e DSR 0,983 reggono, e sotto la lente accumulo il conto va rifatto): dice che **per la
|
||||
rendita non e' lo strumento**, e che il gate del 23/10 sta per decidere su una gamba che a questo
|
||||
capitale **non e' comprabile**.
|
||||
|
||||
**Con questa sono 70 filoni e 0 candidati promossi.**
|
||||
|
||||
---
|
||||
|
||||
## 71 — COLLAR01 (hold BTC gated dal trend, coperto da un collar a <=15 giorni)
|
||||
|
||||
`scripts/research/r0901_btc_collar.py`. **Libro, pesi, cron, config INVARIATI. Nessun ordine.**
|
||||
Domanda dell'operatore, con tre precisazioni in corso d'opera che ne cambiano l'oggetto: *"hold BTC
|
||||
long o short con copertura in opzioni"* + *"opzione max 15gg"* + *"ridurre la vincita, ma bloccare
|
||||
la perdita"* (⇒ **collar**, non put protettiva) + *"l'entrata gestita da indicatori (es. forte bull)"*.
|
||||
|
||||
🚨 **PERCHE' SI POTEVA RIAPRIRE DOPO §46.** §46 fu refutato perche' **il beta del libro al
|
||||
sottostante e' +0,076** — *"non si assicura un libro che nei crash e' gia' quasi piatto"*. Qui il
|
||||
sottostante e' un **hold di BTC nudo, beta 1,0**: il motivo non si applica. E §46 dichiara di non
|
||||
aver provato la copertura **gated su regime**, che e' esattamente questa.
|
||||
|
||||
**L'entrata non aggiunge parametri:** `tsmom_blend` media tre `np.sign()` su (30, 90, 180) ⇒ valori
|
||||
in {−1, −1/3, +1/3, +1}, quindi *"forte bull"* = `|blend|==1` (3/3 orizzonti, a mercato **52,5%**
|
||||
dei giorni) contro il confronto dichiarato `|blend|>=1/3` (**97,1%**). Il segno da' la direzione:
|
||||
long **e** short, come chiesto.
|
||||
|
||||
**CALIBRAZIONE dalla catena vera** (161.964 quote a due lati, 95 giorni, DTE 1-15). **Lo skew e' il
|
||||
fatto strutturale:** IV_put/ATM contro IV_call/ATM = **1,249 vs 0,959** a |δ|0,10 · 1,132 vs 0,950 a
|
||||
0,20 · 1,072 vs 0,960 a 0,30 ⇒ **un collar delta-simmetrico e' un DEBITO netto**, si compra l'ala
|
||||
cara e si vende quella a buon mercato. 📌 **Corregge un muro di §46:** il **tick da 5 USDC** e' della
|
||||
famiglia **USDC**; la catena che raccogliamo e' **100% inverse** ⇒ **quel muro non si applica qui**.
|
||||
|
||||
| esito | misura |
|
||||
|---|---|
|
||||
| ✅ **A1 — il COLLAR riduce il maxDD** | scende in **36/48** celle (§46: saliva in 162/162). 🚨 **Corretto da §72 lo stesso giorno: NON e' il pavimento** — la put da sola, a premio reale, **peggiora il DD in 16/16**; la riduzione viene dal TETTO (compensa il bleed e abbassa il picco) |
|
||||
| ❌ **A3 — ma il de-levering lo fa meglio** | il collar batte il null a iso-maxDD in **3/48** |
|
||||
| ❌ **A2 — il prezzo del baratto** | Δdrift/ΔmaxDD mediano **7,90** (gate forte) / **1,98** (largo) |
|
||||
| 🚨 **C9 — troncatura, non protezione** | il tetto taglia il **46,2%** dei cicli **vincenti**, il pavimento para il **6,7%** dei **perdenti**: 7x piu' spesso sui vincenti |
|
||||
| ❌ **M1 — dentro il libro non aggiunge** | collar Sharpe 0,508 vs TP01 **0,852**; TP01+10% ⇒ Sharpe **+0,000** e maxDD **+2,32pp** |
|
||||
|
||||
🚨 **IL FATTO CHE VALE PIU' DEL VERDETTO — e che si vede solo estendendo la griglia (M4/M8).** Tutte
|
||||
e 3 le celle vincenti stanno sul **BORDO** (δput min, δcall max). Estesa la famiglia con 36 trial
|
||||
dichiarati verso l'angolo: **vince 35/36**, massimo **nell'angolo** (FORTE 7g, δput 0,02 / δcall
|
||||
0,50) = Sharpe **1,471**, maxDD 18,12%, drift **+29,99%/a**. Il limite di quell'angolo e' *nessun
|
||||
pavimento, tetto ATM* = **una covered call**: **la pendenza porta FUORI da cio' che l'operatore ha
|
||||
chiesto e DENTRO lo short-vol.**
|
||||
🚨 **E quel Sharpe e' il prezzatore che si paga da solo.** **DVOL / RV-forward mediana 1,320 a 7g**
|
||||
(DVOL sta sopra nel **76,9%** dei giorni), 1,253 a 14g. Riprezzando alla vol **realizzata**
|
||||
(look-ahead dichiarato, valore equo ex-post): l'angolo cade **1,471 -> 0,511** di Sharpe (VRP
|
||||
**+21,54pp = 72%** del drift) col gate FORTE, e **1,141 -> 0,114** col LARGO (VRP **+33,26pp = tutto**;
|
||||
drift **−1,10%**). Le celle con un pavimento VERO (δ0,10/0,30) passano da VINCE a **perde**.
|
||||
📌 **Cio' che sopravvive — Sharpe 0,511 — e' il numero che il progetto GIA' HA:** VRP01 a f=0,73 vale
|
||||
ShFULL **0,47**. *Non e' una strategia nuova: e' VRP01 ri-scoperto per una strada piu' lunga*, e §3
|
||||
lo blocca comunque (**"niente short-vol da modello in deploy"**).
|
||||
|
||||
🚨 **USCITA ANTICIPATA (chiesta dall'operatore: 50-75% del tempo) — IMPLEMENTATA, E COSTA.**
|
||||
`exit_frac` su tenor 14g (uscita a 7/9/10 giorni). Cella onesta δ0,10/0,30 gate FORTE: drift
|
||||
**+9,48% (scadenza) -> +8,59% (0,75) -> +6,11% (0,625) -> +3,84% (0,50)**, e l'esito passa da VINCE a
|
||||
**perde sotto 0,75**; gate LARGO **12,06% -> 1,88%**. ⚠️ **Il meccanismo previsto c'e'** — il VRP
|
||||
residuo scende da +3,38 a **+1,27pp**, quindi uscire presto rinuncia davvero allo short-vol — **ma lo
|
||||
spread lo travolge**. 📌 **E CORREGGE L'APPLICAZIONE DI §46:** *"f si paga solo sulla parte di valore
|
||||
che converge a intrinseco, quindi un roll anticipato non lo paga"* vale per una copertura **SOLO
|
||||
LONG**; in un collar c'e' una **gamba VENDUTA da RICOMPRARE**, e uscire prima paga f *esattamente*
|
||||
sulla parte che a scadenza si sarebbe regolata gratis, ~2x piu' spesso per unita' di tempo.
|
||||
**L'asimmetria di §46 si INVERTE quando la struttura ha una gamba corta.**
|
||||
|
||||
✅ **CONTROLLI DELL'APPARATO 3/3** (M15, la lezione di §46): pavimento a **premio zero** riconosciuto
|
||||
come vittoria (maxDD 51,83%→**36,76%**, drift +10,04%→**+35,69%**); premio **x10** rifiutato (drift
|
||||
**−33,96%/a**); zero-cost costruibile e finito.
|
||||
|
||||
⚠️ **CINQUE DIFETTI MIEI, tutti catturati dai controlli e non a occhio.** (i) **bisezione dello
|
||||
zero-cost invertita** — il premio cresce col delta, quindi a incasso insufficiente il tetto va
|
||||
*avvicinato*: dava Sharpe −3,0/−3,9 e drift −53%/a; (ii) `dcall=NaN` propagava NaN nella cassa allo
|
||||
smontaggio anticipato; (iii) **C9 non consapevole della direzione** — `S1>S0` non e' "vincente" per
|
||||
un ciclo **short**: era la causa dell'impossibile *"il pavimento para nello 0,0% dei perdenti"* con
|
||||
una put a 10 delta; (iv) la base senza opzioni **rollava lo spot** ogni tenor pagando ~3,6%/a di fee
|
||||
inesistenti (**avrebbe adulato il collar**); (v) al roll delle opzioni chiudevo e riaprivo **anche lo
|
||||
spot**, che non ha motivo di muoversi.
|
||||
|
||||
⚠️ **NON MISURATO, DICHIARATO:** la **lente reale** (quote vere, 123 giorni = ~8 cicli) non e' stata
|
||||
girata come backtest — sotto-potenziata, e su una finestra in cui BTC e' salito da ~64,7k a ~77,5k,
|
||||
**avversa a un collar per costruzione**; la catena e' servita a **calibrare**, dove 161.964 quote
|
||||
hanno potenza. Nessun DSR e nessun `study_family_honest`: il filone cade al **primo** gate (M5), e M2
|
||||
si spende su cio' che il primo gate lascia in piedi. A6/A7 restano previsioni non misurate. Il
|
||||
**funding** non e' nel motore (−2,16%/a) ma colpisce base e collar quasi allo stesso modo: abbassa
|
||||
entrambi, **non cambia il segno**.
|
||||
|
||||
**VERDETTO: `IL PAVIMENTO FUNZIONA — E' IL TETTO CHE NON SI PUO' PAGARE; E CIO' CHE VINCE SUL BORDO
|
||||
NON E' LA PROTEZIONE, E' IL PREMIO DI VARIANZA (VRP01)`** — REFUTATO.
|
||||
**Cosa lo riapre:** un **f di stress misurato su un crash catturato** (la stessa condizione che §3
|
||||
pone allo short-vol), o un sottostante **senza coda destra grassa** — il tetto costa perche' BTC vive
|
||||
li'. **Non** lo riaprono tenor, delta o griglie piu' fini: la pendenza e' monotona verso l'angolo, e
|
||||
l'angolo e' gia' misurato.
|
||||
|
||||
---
|
||||
|
||||
## 72 — PAVIMENTO-LEVA (il pavimento come licenza di taglia)
|
||||
|
||||
`scripts/research/r0901c_pavimento_leva.py` (riusa il motore di §71). **Libro, pesi, cron, config
|
||||
INVARIATI. Nessun ordine.** Domanda dell'operatore: *"possiamo usare quanto conosciamo del pavimento
|
||||
per studiare una strategia?"*. L'inversione di §71: non *quanto DD mi risparmia* ma *quanta TAGLIA
|
||||
mi autorizza a iso-DD* — l'unica cosa che il de-levering non puo' comprare, e la grandezza su cui
|
||||
vive il tetto di leva del progetto (`n · frac · scala · sl ≤ 0,50` ⇒ 1,67x).
|
||||
|
||||
❌ **0/16.** Sola put a premio reale, 4 δ × 2 tenor × 2 gate: **il maxDD SALE in 16/16 celle**
|
||||
(FORTE 51,83% → 55,4-75,8%, LARGO 71,94% → 74,8-81,7%), quindi `k_f = 1,000` ovunque e non c'e' taglia
|
||||
da licenziare. Monotono: piu' la put e' vicina, peggio va — **il bleed del premio E' il drawdown**.
|
||||
|
||||
🚨 **CORREGGE §71.** Stamattina A1 diceva *"il pavimento funziona"*. **E' il COLLAR a ridurre il DD,
|
||||
non il pavimento**: a premio zero la put lo riduce (51,83 → 36,76), a premio reale lo peggiora ⇒
|
||||
tutto il beneficio e' mangiato dal premio, e oltre. Nel collar la riduzione viene dal **tetto** (il
|
||||
suo premio compensa il bleed, e cappare l'upside **abbassa il picco** da cui il DD si misura).
|
||||
**Premio di pareggio: 36-79% del reale** (secondo la cella); il DVOL sta 1,32× sopra la RV, quindi
|
||||
un prezzo equo (~76%) sfiora il pareggio nelle celle migliori e lo manca nelle altre. *Il motivo di
|
||||
§46 (beta) non si applica a beta 1,0 — la put paga davvero — ma il verdetto di §46, il maxDD SALE,
|
||||
si riproduce per un motivo diverso.* Corretti §71, il suo diario e la memoria.
|
||||
|
||||
📌 **B2, IL MECCANISMO — e' il risultato trasferibile:** **i drawdown di BTC sono GRIND**, non
|
||||
crolli. maxDD FORTE da 2021-07-20 a 2022-05-06 (**290 giorni**), LARGO fino al 2023-10-16 (**818**).
|
||||
Una put a 7-14g copre UNA finestra; il DD che conta dura 20-60 finestre; la put scade OTM ogni
|
||||
settimana (para nel **0-9%** dei cicli, gate FORTE) mentre il premio sanguina. E non protegge
|
||||
nemmeno la finestra peggiore (14g FORTE: −22,9% nudo → **−23,6%** col pavimento: la put a 5δ sta a
|
||||
~22% dallo spot, al bordo esatto di cio' che e' successo). **Su BTC il pavimento compra protezione
|
||||
contro la cosa sbagliata.**
|
||||
|
||||
🚨 **B3 — LA LICENZA DEL DISASTER-SL E' DI CARTA, e va scritto prima che sia comodo.** Con la put a
|
||||
5δ/14g a **21,9%** dallo spot, sostituire `sl` con quella distanza nell'invariante darebbe **2,28x**
|
||||
(1,4× in piu'). Ma l'invariante limita UN episodio, la put limita UNA finestra, e il massimo su
|
||||
finestre consecutive **non e' limitato da nulla**: 1 finestra −23,6% (coperta), 2 −25,9%, 4 −28,8%,
|
||||
**8 finestre −33,6%** (non coperte). A 2,28x un grind di 112 giorni costerebbe il **77%
|
||||
dell'equity**. ⇒ **`disaster_sl_pct` NON si sostituisce con la distanza di un pavimento.**
|
||||
|
||||
**B4 — gated sull'IV** (§46: *"nessuna copertura dinamica e' stata provata"* — ora lo e'): put ON solo
|
||||
sotto il 25°/50° pctl di DVOL/RV. Migliora molto (FORTE +3,8% → **+8,4%**; LARGO +9,9% → +15,3%),
|
||||
**ma resta sotto la base** (+10,04% / +17,37%) e il DD resta ≥. L'incollatura ignora lo spread delle
|
||||
transizioni **a favore** del gated.
|
||||
|
||||
⚠️ **Non misurato, dichiarato:** nessun DSR (cade al primo gate); nessuna lente reale; **nessun
|
||||
pavimento a scadenza lunga** (30-90g, che coprirebbe piu' finestre di grind) perche' l'operatore ha
|
||||
vincolato la scadenza a **≤15 giorni** — e' l'unica variante che B2 lascia aperta, e sta fuori dal
|
||||
vincolo. Se il vincolo cadesse, sarebbe la prima misura da fare.
|
||||
|
||||
**VERDETTO: `IL PAVIMENTO NON LICENZIA TAGLIA: 0/16 — SU BTC COMPRA PROTEZIONE CONTRO I CROLLI, E
|
||||
LE PERDITE SONO GRIND`** — REFUTATO. **Cosa lo riapre:** una scadenza che copra il grind (fuori dal
|
||||
vincolo), o un premio di varianza *negativo* su BTC (mai misurato). Non lo riaprono delta, tenor ≤15
|
||||
o gate sull'IV.
|
||||
|
||||
---
|
||||
|
||||
## 73 — SOLDI-FERMI (altri modi, non per forza trading)
|
||||
|
||||
`scripts/research/r0901d_soldi_fermi.py`, diario `2026-09-01d-soldi-fermi.md`. **Nessun ordine.**
|
||||
*Non sono pareri fiscali.* Tassi dal web con fonte e data (cutoff del modello 05/2026).
|
||||
|
||||
✅ **On-venue chiuso con l'ELENCO INTERO:** `public/get_currencies` remunera **4 valute su 50** —
|
||||
USDE 4,32 · USDC 3,40 · BUIDL 3,20 · STETH 2,22 — esattamente le quattro chiuse il 31/08 (al tetto ·
|
||||
Italia esclusa · non acquistabile · e' ETH). ⚠️ **L'USDC su Deribit non e' fermo:** base di sizing
|
||||
(cap = equity x 0,5) e cuscino del disaster-SL. Il fermo vero sta **fuori**: ~€6.043 in XEON, lo
|
||||
**split-cassa** (P(perso tutto) 18% → 3,5%).
|
||||
|
||||
**In euro netti l'anno su €6.000** (BOT 2,768% asta 08/2026 · DFR 2,25% · conti deposito 3,25-3,50% ·
|
||||
sUSDe ~9%; fisco 12,5 / 26 / 33%; bollo 0,2%): XEON **81** · BOT **133** · conto deposito **143** ·
|
||||
sUSDe **350** ma **stesso emittente dell'USDE gia' in conto** ⇒ split distrutto · libro **~600 atteso**
|
||||
ma trading ⇒ split distrutto. **Il miglior guadagno sicuro e' +€62/anno = €5/mese**, contro €100/mese
|
||||
di bonifico = +4,07%/anno di drift. BOT e conto deposito distano €10/anno: scelta di liquidita' e
|
||||
controparte, non di rendimento.
|
||||
|
||||
✅ **Decisione dell'operatore (02/09): versare sul libro.** E' il piano del 27/07. Tre precisazioni,
|
||||
tutte gia' misurate: **€5.000 dentro, ~€1.000 fuori** (la protezione venue **satura a qualunque
|
||||
quota > 0**: l'ultimo migliaio compra ~€100/anno e butta via l'assicurazione); il «~€600» e' un'attesa
|
||||
a banda larga, non un tasso (libro a mercato il 22% dei giorni, TWR +10,8% in 70g, hold-out TP01
|
||||
~+0,05, fisco −30% a 10a); fondo d'emergenza da dichiarare. Operativamente nulla: cap dinamico,
|
||||
rilevatore di salti collaudato il 25/08. **In attesa di importo e data.**
|
||||
|
||||
---
|
||||
|
||||
## 74 — LIBRO-NEI-CROLLI (cosa fanno TP01 e SKH01 nei crolli, misurato invece che ricordato)
|
||||
|
||||
`scripts/research/r0909_libro_nei_crolli.py`, diario `2026-09-09-crolli-opzioni-monete.md`. **Libro,
|
||||
pesi, config, cron INVARIATI.** Lente: sleeve di ricerca, ancora canonica, pesi importati da
|
||||
`src/live/book` (P1); ⚠️ SKH01 marcato all'USCITA del trade (zero nell'87,7% dei giorni). Indice 50/50;
|
||||
giorno di crollo ≤ −5%; 12 peggiori finestre di 20 g non sovrapposte + 9 episodi nominati prima.
|
||||
|
||||
**Nel GIORNO del crollo il libro PERDE** (−0,48%/g su 160 giorni, positivo nel 18%; ≤ −10%: −0,51%/g con
|
||||
SKH01 +0,42). **Per finestra**: 4 POSITIVO / 6 immune / 2 PERDE; libro > 0 in 8/12 e **in 8/8 dal 2022**
|
||||
(mediana +3,3% contro indice −29%); le due perdite (2019, maggio 2021) sono TP01 long dentro un trend
|
||||
non girato. **La gamba short di SKH01 fa il 108% dei guadagni** (scomposizione ESATTA long+short==intera,
|
||||
assert). Beta **+0,0769** sulla finestra di §46 (riprodotto al millesimo). Peggior giorno canonico
|
||||
2025-10-10 −3,38%: un crollo con TP01 a 0,505 (lo squeeze di §33 sta sulla lente hourly, non si riproduce
|
||||
qui). 37,5/62,5 (§26) batte 12/12 episodi, Sharpe 1,81 vs 1,82, maxDD 12,1% vs 9,4%;
|
||||
`r0726_reeval_live_weight` da' `gate_pass False` su `delta_insample −0,0026` (cache segnali ferma al
|
||||
25/07: stesse cifre del 26/07, e la condizione che fallisce e' in-sample).
|
||||
🚨 **Errore dell'autore smontato in revisione:** la prima stesura provava GUADAGNA prima di immune e
|
||||
stampava **8/12 GUADAGNA**; nell'ordine dichiarato sono 4+6. *L'ordine delle regole era il verdetto.*
|
||||
**VERDETTO: `PERDE NEL GIORNO, POSITIVO PER FINESTRA DAL 2022, TUTTO SKH01 SHORT`** — nessun meccanismo
|
||||
nuovo: quello che guadagna nei crolli e' gia' in produzione, e la leva che lo aumenta (il peso) e' chiusa dal gate.
|
||||
|
||||
## 75 — CROLLO-CATTURATO (il f delle opzioni su un crollo a quote vere)
|
||||
|
||||
`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
|
||||
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**
|
||||
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
|
||||
non misurabile (griglia strike rada). **Crollo a vol BASSA**: picco DVOL al 29°/46° percentile, gate
|
||||
IV-rank di VRP01 CHIUSO in tutte le ore. ⇒ la condizione di §3 e' soddisfatta nella forma, **non nella
|
||||
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
|
||||
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 §5.18 RIPARATO la sera stessa** in `cblib`
|
||||
(spot +1h, e anche il DVOL giornaliero, +1 giorno): ETH pre-crollo f_net **0,76 [0,71-0,86]** (era 0,74),
|
||||
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.
|
||||
|
||||
## 76 — XRP-TERZA-GAMBA (l'unica altra moneta liquida di Deribit, coi meccanismi congelati)
|
||||
|
||||
`scripts/research/r0909_xrp_terza_gamba.py` (harness IMPORTATO da `r0822_sol_leg`). Dato in
|
||||
`data/raw/alt_xrp_*` (namespace di ricerca, non rinfrescato dal cron). Certificazione: >1% dal
|
||||
riferimento 0,06% (2022) · **0,57% (2023)** · 0,01-0,02% (2024+) — Coinbase dal rilisting 2023-07
|
||||
(delisting SEC), Bitstamp prima; **L-PULITA dal 2024**, come SOL; 5m flat **27-61%**.
|
||||
dSharpe hold-out **−0,169 in 0/24 ancore** (SOL −0,166); FULL −0,206 (L-FULL) / −0,066 (2024+); dCAGR
|
||||
−2,27 / −1,32 pp; de-levering: L-FULL 1,23 vs 2g×0,974 **1,44**, 2024+ 1,54 vs 1,53 pari; per anno 2022
|
||||
−0,55 · 2023 −0,66 · **2024 +0,54** · 2025 −0,44 · 2026 −0,25 (**1/5**). TP01 XRP Sharpe −0,01 /
|
||||
hold-out −0,49; SKH01 XRP maxDD 40,1% (il criterio di selezione di SKH01-V2-DD fallisce, come SOL);
|
||||
corr gamba-book +0,35/+0,37. Eseguibilita' non e' il vincolo (min 1 XRP = $1,42).
|
||||
⚠️ Due difetti del verdetto trovati dai test (Opus) e corretti: campione vuoto stampava «DILUISCE»
|
||||
(ora NON MISURABILE, P5); criterio per anno cablato al 2024 invece che derivato dalla lente (P1).
|
||||
**VERDETTO: `XRP DILUISCE COME SOL — UN ANNO BUONO, HOLD-OUT 0/24`** — nessuna proposta.
|
||||
|
||||
## 77 — UNIVERSO-DERIBIT (lettura del venue, €0)
|
||||
|
||||
`scripts/research/r0909_deribit_universo.py` (API pubblica, JSON con ora in `data/_cache`, `--fresh`).
|
||||
Lettura 2026-09-09 15:05Z: **32 perpetual USDC** aperti (+6 non aperti, fra cui **APT inactive**);
|
||||
liquidi ≥ $10M/g **solo XRP ($38M, spread 4 bps), ETH, BTC, SOL ($13M)**; tutto il resto $0,01-7M
|
||||
(TAO, HYPE, ADA, AVAX, NEAR…). XS01 **13/19** (mancano ARB, OP, APT, INJ, TIA, SEI → §50 resta).
|
||||
Opzioni USDC-lineari su 7 sottostanti (SOL 676, HYPE 442, XRP 442, TRX, AVAX). Trovati in revisione:
|
||||
**`BTCDVOL_USDC-30SEP26`** (unico long-vol diretto del venue: vol 0, OI 132, spread 12% → non
|
||||
negoziabile) e **PAXG perp** (oro, dal 2024-12, $0,18M/g: non misurato, non morto, muri di SOL/XRP).
|
||||
**VERDETTO: `QUATTRO STRUMENTI LIQUIDI, TRE GIA' NEL LIBRO O MISURATI`** — l'universo "piu' monete" di Deribit e' finito.
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
# SPEC — la CHIAVE DI SCALA del libro live (`book_scale_k`)
|
||||
|
||||
**Filone SCALE-SPEC, ondata 2026-08-22, branch `research/wave-0822`.**
|
||||
**Questo documento e' una SPECIFICA. Non e' stata implementata: `src/`, `config/`, `scripts/live/`,
|
||||
`scripts/cron_*.sh` e `tests/` non sono stati toccati.** Il prototipo che dimostra la meccanica e'
|
||||
> ✅ **IMPLEMENTATA IL 2026-09-01 — punti 1-4 della checklist §8.** `src/live/book.py`
|
||||
> (`book_scale_k`, `_scala`, `LEVA_LORDA_MAX`, `SCALA_LADDER`, `DISASTER_SL_BUDGET`,
|
||||
> `ScalaNonAutorizzata`, `scala` nel report), `scripts/live/book_execute.py` (riga «scala libro» +
|
||||
> stop-e-allerta), `src/live/scale_watch.py` + `scripts/live/scale_watch.py` in `cron_daily.sh`,
|
||||
> `data/live/scale_history.jsonl`, `tests/test_book_scale.py` (T1-T11, **18 test verdi**).
|
||||
> 🚨 **`config/live.json` NON e' stato toccato**, come prescrive il punto 2: la chiave e' assente,
|
||||
> vale 1,00, e T7 dimostra bit-exact che il libro e' quello di sempre (`max|diff| = 0.0`).
|
||||
> **Restano i punti 5-7:** ≥30 giorni a 1,00 col sorvegliante attivo, poi `GATE SCALA-01`, poi
|
||||
> `r0726_fee_sensitivity` rifatto. Il punto 5 non e' burocrazia — e' l'unico modo di scoprire che il
|
||||
> sorvegliante e' rotto mentre la leva e' ancora 1,00.
|
||||
|
||||
**Questo documento era una SPECIFICA scritta prima dell'implementazione.** Il prototipo che dimostra la meccanica e'
|
||||
`scripts/research/r0822e_scale_proto.py` (isolato, non importato da niente, non scrive nulla; la
|
||||
sua sezione (0) verifica entrambe le cose a runtime invece di dichiararle).
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# Revisione settimanale — 2026-09-09
|
||||
|
||||
*Scritta il 2026-09-09T21:59:07Z dal modello `claude-fable-5-1`, revisore diverso dall'autore del codice, in SOLA LETTURA: nessun file del repo, nessuna config, nessun ordine e' stato toccato. E' l'opinione di un lettore fallibile (P13): ogni numero e ogni proposta vanno verificati alla fonte prima di agire. Guardia sui numeri: **sospetta** — 11 numeri non presenti nel materiale: 1.011, 1.341, 1.38279, 1.40085, 2.034, 2.43408, 4.47099, 4.47746, 400, 40008, 6879.*
|
||||
|
||||
## Sintesi
|
||||
|
||||
Revisione del 2026-09-09 21:59Z (primo giro, manuale: il cron e' del lunedi'). **Nessun URGENTE.** Nella settimana 02-08/09 il libro ha fatto 24/24 giri ogni giorno con feed SKH a 0 min, leva lorda 0,26x, equity $4.477,46 e TWR +7,41% (giornale 08/09). Tre cose da verificare, in ordine:
|
||||
|
||||
1. **`usde_watch` del 07/09 ha stimato un reward di 400,08 USDE**, dove i giorni normali (08-09/09) danno 0,38. E' la differenza fra i 2.434,08 USDE entrati e i 2.034,0 contati come trade, con `n_trades` fermo a **50**. Il registro `usde_convert.jsonl` mostra 8 + 30 + 16 = 54 ordini: ne mancano 4 da 100 = 400. Un lettore che vede al massimo 50 trade attribuisce al reward cio' che era un acquisto (D2, P2). Se `rendimento()` usa quella finestra, il tasso che `quota_da_decidere()` stampa ogni giorno e' inquinato.
|
||||
2. **Il cuscino USDC (debito §5.17) resta senza sorvegliante, e la sua misura non e' definita**: a `balance_watch` 21:42Z l'USDC ha balance 1.400,85 ma equity 1.382,79; con l'equity totale 4.470,99 (watermark) il cuscino al 30% vale ~$1.341, quindi lo slack e' **+$59 sul balance o +$41 sull'equity USDC**. Quale dei due conta va deciso prima di scrivere il sorvegliante.
|
||||
3. **Operatore**: il versamento di $2.410,14 del 04/09 e' solo *rilevato* (salto 13:47→14:47); `movimenti_dichiarati.jsonl` contiene il solo €25 del 25/08 e la Nota di giornale e' vuota 7/7 giorni. CLAUDE.md §2 chiede la dichiarazione il giorno stesso, e §1 («soldi fermi») dice ancora «in attesa di importo e data» per il piano €5.000: non si capisce se il 04/09 ne e' il primo pezzo.
|
||||
|
||||
## Cosa non torna
|
||||
|
||||
- **«2.434 USDE in 39 ordini»** (diario `2026-09-06-usde-quota-topup` §3, CLAUDE.md §1) non si riproduce dal registro: 8 ordini nel primo record, 30 eventi nella prima sonda, 16 nella seconda. Il totale USDE torna, il conteggio no (N11).
|
||||
- **Haircut, due affermazioni opposte in CLAUDE.md §1** (M28): una dice che il 5% e' confermato dal passaggio a X:SM (`available_funds` $2.011,09 contro $2.010,90 attesi *con* haircut 5%); poche righe sotto «oggi comunque non si applica», frase del mattino del 31/08 sotto S:SM. `config/live.json` `_nota_usde` dice ancora «il modello ATTIVO e' Segregated» e giustifica `depeg_crit 0.95` come «meta' del buffer di haircut (10%)»: col 5% il criterio dichiarato (P6) non descrive piu' la soglia, e la nota e' una diagnosi cablata (P4).
|
||||
- **GATE SCALA-01: data e condizione A2 non concordano** (CLAUDE.md §4). «Non prima del 2026-10-01» sono 30 giorni dal 01/09; A2 chiede k0 «≥180 g col criterio passato ogni giorno», e il criterio lo misura solo `scale_watch`, attivo dal 01/09 (`scale_history.jsonl`). 180 giorni dal 01/09 cadono nel 2027; dal 20/06 a fine 2026. Da quando si conta va chiarito prima del 01/10, non quel giorno.
|
||||
- **Sorveglianti di cui il materiale non prova il funzionamento** (P5): `venue_news.jsonl` ha 5 voci tutte `seed: true` viste il 2026-08-31T07:40Z e nulla dopo; `scale_watch_state.json` non ha timestamp (streak 0 puo' essere «non ho girato»); `edge_watch` e' citato a «stato 27/07» (CLAUDE.md §4); `fee_watch` non compare. `monitor_health` copre 8 monitor, nessuno di questi quattro.
|
||||
- **Data di arming**: CLAUDE.md §0 «LIVE dal 2026-06-20», `config/live.json` «ARMATO 2026-06-23», primo segmento TWR dal 2026-06-23 (diario `2026-09-02-debito-14`). Una delle tre non e' la stessa data.
|
||||
- **Richieste dell'analista senza risposta**: i 4 fill del 03/09 («dovrebbero essere ri-taglie»), la conferma del versamento 04/09, il SELL $−7 del 06/09, il round-trip a $+0,00 con fee 0,0055 del 08/09. Nessuna Nota. Non e' un guasto: e' un canale che non chiude (N9).
|
||||
- **CLAUDE.md §13 dice 1.011 test**; il diario `2026-09-09c` ne aggiunge 16 (`test_revisione.py`). E `scripts/cron_review.sh`, `scripts/live/revisione.py`, `src/live/revisione.py` sono `??` in `git status` mentre la crontab li chiama lunedi' 06:15 (P16 li fa girare, P11 non li salva).
|
||||
|
||||
## Cosa e' maturo da decidere
|
||||
|
||||
| gate | data | criterio scritto | stato nel materiale |
|
||||
|---|---|---|---|
|
||||
| STATARB | 2026-09-27 (fra 18 g) | soglie 24/07 + diagnostica sempre-short | monitor OK; Sharpe −1,61 al 26/08, nessuna lettura piu' recente. Si legge il 27/09 |
|
||||
| SCALA-01 | non prima del 2026-10-01 (22 g) | 9 condizioni tutte necessarie | A8/A9 fatti; A2 non maturo e ambiguo (sopra); A3, A6, A7 non risultano fatti |
|
||||
| USDE-01 | chiuso 28/08 | — | quota 68,79% (09/09) sotto `quota_max_frac` 0,85: dentro la decisione dell'operatore del 30/08 e 06/09 |
|
||||
| PREVDAY-01 | **senza data** | 10 condizioni, 8/10 | N10: un gate senza data non si chiude |
|
||||
|
||||
XSR01 (23/10) e DVOLSPREAD (24/10) cadono oltre 30 giorni. `edge_watch` e' continuo ma l'ultimo stato e' del 27/07: non determinabile se sia ancora 8/8.
|
||||
|
||||
Decisioni dell'operatore, oggi: (a) dichiarare il 04/09; (b) dire se il piano €5.000 e' iniziato; (c) definire il cuscino (balance o equity USDC). Nessuna decisione di §3 ha la colonna «cosa la riapre» soddisfatta: `weights_tilt_null` fallisce ancora (26/07, in-sample); il crollo catturato e' a vol bassa, sotto il gate e inverse (diario 09/09 §2).
|
||||
|
||||
## Proposte (nessuna azione eseguita)
|
||||
|
||||
1. **Agente**: verificare in `usde_watch` il limite di lettura dei trade e come `rendimento()` tratta la finestra del 07/09; rigenerare la stima con i 54 ordini (P2).
|
||||
2. **Agente**: riallineare `_nota_usde` (modello attivo X:SM, criterio di `depeg_crit` al 5%, `venue_cap_frac` null) e sciogliere la contraddizione di §1 datando le due frasi (M28).
|
||||
3. **Agente + operatore**: il sorvegliante del cuscino e' gia' descritto in §5.17 (`USDC − cuscino_richiesto` derivato da config, P1); manca solo la scelta balance/equity.
|
||||
4. **Agente**: uno stato senza timestamp non distingue «zero» da «non ho girato» (P5): `scale_watch_state.json` e `venue_news` meritano l'ora dell'ultimo giro, e i quattro sorveglianti fuori da `monitor_health` una riga li'.
|
||||
5. **Agente**: committare i file della revisione, aggiornare il conteggio test.
|
||||
6. **Operatore**: dare una data a PREVDAY-01 o chiuderlo (N10).
|
||||
7. Nessuna idea di strategia: la settimana ha gia' misurato crolli, opzioni e XRP contro `20-ondate` (diario 09/09 §0) e non ho nulla che batta i motivi elencati li'.
|
||||
|
||||
## Cosa ho letto e cosa mi mancava
|
||||
|
||||
Letto: CLAUDE.md intero; `config/live.json`; crontab; git log dal 26/08 e status; `monitor_health`; stati di scale_watch, scale_history, vrp_f_watch, usde_watch, usde_convert, balance_watch, venue_news, movimenti_dichiarati, watermark; giornali 02-08/09; diari 02/09 (tre), 06/09, 09/09 (tre).
|
||||
|
||||
Mancava: (1) stato con timestamp di `edge_watch`, `fee_watch`, `venue_watch`, `scale_watch`; (2) l'output di `usde_watch.rendimento()` e `quota_da_decidere()`; (3) la lettura corrente di `paper_statarb` (Sharpe a oggi, non al 26/08), dvolspread, xsr; (4) il contenuto di `cron_daily.sh`, cioe' quali sorveglianti chiama; (5) `venue_news` dopo il 31/08; (6) il diff di CLAUDE.md e memoria 40 non committati; (7) il registro fill della settimana (per i 4 fill del 03/09 e il +0,00 del 08/09); (8) un registro depositi del gateway, che «non espone get_deposits».
|
||||
|
||||
---
|
||||
## Firma e materiale
|
||||
|
||||
- revisore: `claude-fable-5-1` · cadenza settimanale · giornale 7 g · diari 7 g · commit 14 g
|
||||
- scritto da `scripts/live/revisione.py` (cron `scripts/cron_review.sh`); l'unico file scritto e' questo
|
||||
|
||||
| voce | caratteri | troncata |
|
||||
|---|---|---|
|
||||
| CLAUDE.md (stato, book, numeri, decisioni vincolanti, gate, debiti, regole) | 80,310 | |
|
||||
| config/live.json (unica autorita' dei guardrail live) | 7,184 | |
|
||||
| crontab installata (crontab -l) | 1,312 | |
|
||||
| git log dal 2026-08-26 + stato del working tree | 4,543 | |
|
||||
| monitor_health (i forward-monitor stanno registrando?) | 522 | |
|
||||
| stato scale_watch_state.json | 149 | |
|
||||
| stato scale_history.jsonl (ultime 3) | 259 | |
|
||||
| stato vrp_f_watch/state.json | 453 | |
|
||||
| stato usde_watch.jsonl (ultime 3) | 1,339 | |
|
||||
| stato usde_convert.jsonl (ultime 3) | 9,979 | |
|
||||
| stato balance_watch.jsonl (ultime 2) | 578 | |
|
||||
| stato venue_news.jsonl (ultime 5) | 1,564 | |
|
||||
| stato movimenti_dichiarati.jsonl | 405 | |
|
||||
| stato equity_seen.json (watermark) | 73 | |
|
||||
| giornale 2026-09-02 | 3,824 | |
|
||||
| giornale 2026-09-03 | 3,745 | |
|
||||
| giornale 2026-09-04 | 3,999 | |
|
||||
| giornale 2026-09-05 | 3,645 | |
|
||||
| giornale 2026-09-06 | 3,630 | |
|
||||
| giornale 2026-09-07 | 3,846 | |
|
||||
| giornale 2026-09-08 | 3,765 | |
|
||||
| diario 2026-09-02-debito-14-report-twr.md | 6,952 | |
|
||||
| diario 2026-09-02b-debito-7-cadenza-docstring.md | 6,061 | |
|
||||
| diario 2026-09-02c-flag-e-pulizia.md | 5,874 | |
|
||||
| diario 2026-09-06-usde-quota-topup.md | 6,571 | |
|
||||
| diario 2026-09-09-crolli-opzioni-monete.md | 17,894 | |
|
||||
| diario 2026-09-09b-debito-18-cblib-causale.md | 14,150 | |
|
||||
| diario 2026-09-09c-revisione-settimanale.md | 5,514 | |
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
# Campionamento ORARIO del BALANCE per valuta (USDC/USDE) — v2.0.0+.
|
||||
# Serve a rispondere a UNA domanda: l'APR USDC pubblicata da Deribit (3,4000%) e' accreditata
|
||||
# sul nostro conto? Il balance e' l'unica serie dove si vede: l'equity e' arrotondata a 2
|
||||
# decimali in trades.db e contiene il P&L non realizzato, e il gateway non espone il
|
||||
# Transaction Log (debito #11). Cadenza oraria per STRINGERE la finestra intorno all'accredito
|
||||
# (i reward USDE arrivano in un colpo verso le 12:00 UTC), non per campionare di piu'.
|
||||
# Minuto :42 = libero fra :25 (catena), :35 (usde), :47 (book); fuori dal minuto tondo
|
||||
# (rate-limit per-IP). SOLA LETTURA: nessun ordine, nessuna scrittura sul percorso soldi.
|
||||
export PATH="/home/adriano/.local/bin:$PATH"
|
||||
cd /opt/docker/PythagorasGoal || exit 1
|
||||
mkdir -p logs
|
||||
{
|
||||
echo "===== $(date -u '+%Y-%m-%dT%H:%M:%SZ') cron_balance ====="
|
||||
uv run python scripts/live/balance_watch.py || true
|
||||
echo "===== done $(date -u '+%H:%M:%SZ') ====="
|
||||
} >> logs/cron_balance.log 2>&1
|
||||
@@ -8,7 +8,7 @@
|
||||
# MINUTO :25, e la scelta NON e' arbitraria:
|
||||
# :00 cerbero-bite sparava la sua spazzata full-chain (~44 chiamate/s) e saturava il rate limit
|
||||
# Deribit per-IP — 12.186 risposte 429 in 26 ore, il 96% nel minuto tondo.
|
||||
# :07 cron_book.sh (esecuzione del book live). Il feed 5m di SKH01 passa da li': e' la cosa che
|
||||
# :47 cron_book.sh (esecuzione del book live; era :07 fino al 25/08). Il feed 5m di SKH01 passa da li': e' la cosa che
|
||||
# NON deve trovare l'IP occupato.
|
||||
# :25 nessun altro job. Il giro dura ~3 minuti a 4 chiamate/s, quindi finisce ben prima del :30.
|
||||
#
|
||||
|
||||
@@ -31,10 +31,22 @@ mkdir -p logs
|
||||
# Schema fee Deribit (nuovo dal 2026-08-01, annunciato senza numeri): legge il tier BASE
|
||||
# dall'endpoint pubblico e applica la regola decisa in anticipo (<=5bps nulla, >10bps peso SKH01).
|
||||
uv run python scripts/live/fee_watch.py --quiet || true
|
||||
# Cosa il venue ANNUNCIA (debito #8: la sonda venue_probe legge se RISPONDE, non cosa dichiara).
|
||||
# Feed RSS pubblico degli exchange-update. NON interpreta: classifica solo l'urgenza, su parole
|
||||
# DERIVATE da deribit._CONTRACT e config/live.json. In una settimana quattro cambi di policy
|
||||
# materiali erano arrivati a mano; le specifiche dei perp USDC cambiate il 18/08 erano annunciate
|
||||
# il 14/08, e check_specs() se ne accorge solo DOPO.
|
||||
uv run python scripts/live/venue_news.py --quiet || true
|
||||
# f delle strutture VRP dalle quote REALI (canonico vs candidato bocciato dal gate 30/07).
|
||||
# Criterio di sufficienza pre-registrato (>=40 coppie, IC95 <=0.12): avvisa da solo quando
|
||||
# il campione basta, invece di lasciare la misura appesa a un promemoria.
|
||||
uv run python scripts/live/vrp_f_watch.py --quiet || true
|
||||
# La CHIAVE DI SCALA del libro: la config dichiara una scala che il giornale non ha autorizzato?
|
||||
# Il criterio che la autorizzo' passa ancora? Il PRODOTTO frac x scala x n_asset ha superato il
|
||||
# tetto? Tre domande, tre azioni diverse. Non impedisce la modifica: la rende visibile entro 24h
|
||||
# e attribuibile — una chiave di scala e' il parametro che si alza "solo un po'" dopo un mese
|
||||
# buono, e lo Sharpe (invariante alla scala) non lo vedrebbe mai. SPEC-scale-key §6.4.
|
||||
uv run python scripts/live/scale_watch.py --quiet || true
|
||||
# I forward-monitor stanno registrando? Tre gate pre-registrati (27/09, 23/10, 24/10) si
|
||||
# decidono su queste serie: un monitor fermo produce silenzio, e il silenzio sembra uno zero.
|
||||
# Va DOPO tutti i monitor, altrimenti misura lo stato di ieri.
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Revisione SETTIMANALE in sola lettura (dal 2026-09-09): un revisore diverso dall'autore
|
||||
# (claude-fable-5-1) rilegge CLAUDE.md, config, crontab, commit, monitor_health, giornale e diari,
|
||||
# e scrive docs/revisioni/<data>.md + una sintesi Telegram. Propone, non esegue: l'unico file
|
||||
# scritto e' il rapporto. Cadenza: lunedi' 06:15 UTC — fuori dal minuto :00 (rate-limit) e dallo
|
||||
# slot di release Deribit (martedi' 09:00), dopo il cron_daily delle 00:30 (giornale di domenica chiuso).
|
||||
export PATH="/home/adriano/.local/bin:$PATH"
|
||||
cd /opt/docker/PythagorasGoal || exit 1
|
||||
mkdir -p logs
|
||||
{
|
||||
echo "===== $(date -u '+%Y-%m-%dT%H:%M:%SZ') cron_review ====="
|
||||
uv run python scripts/live/revisione.py --quiet || true
|
||||
echo "===== done $(date -u '+%H:%M:%SZ') ====="
|
||||
} >> logs/cron_review.log 2>&1
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Sorveglianza collaterale USDE (reward/depeg/quota) — v2.0.0+. GIORNALIERA alle 12:35 UTC:
|
||||
# DOPO la finestra reward Deribit (~12:00), fuori dal minuto :00 (rate-limit per-IP, misura
|
||||
# 2026-07-30), dal :25 (catena opzioni) e dal :47 (book). Sola lettura, mai blocca nulla.
|
||||
export PATH="/home/adriano/.local/bin:$PATH"
|
||||
cd /opt/docker/PythagorasGoal || exit 1
|
||||
mkdir -p logs
|
||||
{
|
||||
echo "===== $(date -u '+%Y-%m-%dT%H:%M:%SZ') cron_usde ====="
|
||||
uv run python scripts/live/usde_watch.py --quiet || true
|
||||
echo "===== done $(date -u '+%H:%M:%SZ') ====="
|
||||
} >> logs/cron_usde.log 2>&1
|
||||
@@ -115,7 +115,21 @@ def scrivi(con, g: date, modello: str, secco: bool = False, verbose: bool = True
|
||||
return stato
|
||||
|
||||
|
||||
USO = """uso: analista.py [--giorno AAAA-MM-GG] [--modello NOME] [--secco] [--no-telegram] [--quiet]
|
||||
|
||||
(nessun flag) scrive l'analisi di OGGI nel campo `analisi` e manda la notifica
|
||||
--giorno G analizza il giorno G
|
||||
--modello M modello da usare (default: src.live.analista.MODELLO_DEFAULT)
|
||||
--secco stampa e NON salva, niente Telegram
|
||||
--no-telegram salva ma non manda la notifica
|
||||
--quiet non stampa
|
||||
|
||||
⚠️ SPENDE una chiamata al modello e MANDA un messaggio Telegram, salvo --secco/--no-telegram."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("analista.py", USO, flag=("--secco", "--no-telegram", "--quiet"),
|
||||
con_valore=("--giorno", "--modello"))
|
||||
con = T.connect()
|
||||
g = date.fromisoformat(_arg("--giorno", datetime.now(timezone.utc).date().isoformat()))
|
||||
st = scrivi(con, g, modello=_arg("--modello", A.MODELLO_DEFAULT),
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env python
|
||||
"""balance_watch.py — registra il BALANCE esatto per valuta. SOLA LETTURA, nessun ordine.
|
||||
|
||||
PERCHE' ESISTE. Il 2026-08-30 e' emerso che Deribit pubblica una APR anche per l'USDC
|
||||
(3,4000%, contro 4,1071% dell'USDE): se fosse accreditata, il guadagno del passaggio a USDE
|
||||
non sarebbe il tasso ma lo SPREAD (0,71 punti), e la pista USDE varrebbe ~$4,6/anno invece di
|
||||
~$21. La domanda "l'USDC frutta davvero SUL NOSTRO conto?" non era rispondibile, perche':
|
||||
|
||||
* il gateway NON espone il Transaction Log (provati get_transaction_log,
|
||||
get_settlement_history, get_deposits, get_transfers, get_interest_history: tutti 404 —
|
||||
e' il debito #11, si ha solo cio' che il gateway espone);
|
||||
* l'unica serie storica del conto e' `trades.db.equity`, che e' EQUITY **arrotondata a 2
|
||||
decimali**: $0,13/giorno di interesse atteso ci sparisce dentro, e l'equity contiene
|
||||
comunque il P&L non realizzato, che vale mille volte tanto.
|
||||
|
||||
Il `balance` no: cambia solo per P&L REALIZZATO, fee, funding, movimenti di fondi e interessi.
|
||||
Su USDE (nessuna posizione denominata in USDE) e' gia' stato il rivelatore dei reward. Questa
|
||||
serie fa lo stesso per l'USDC — ed e' il dato che mancava, non un'analisi nuova.
|
||||
|
||||
COME SI SEPARA L'INTERESSE DAL RESTO (P7: il numero si etichetta con la sua configurazione):
|
||||
* P&L realizzato e fee -> noti ESATTAMENTE da `trades.db.fills`; ogni record porta il
|
||||
conteggio dei fill dall'ultimo campione, cosi' una finestra sporca si riconosce invece di
|
||||
essere mediata dentro;
|
||||
* funding -> proporzionale al NOZIONALE, quindi **zero quando il libro e' FLAT**. Il libro e'
|
||||
flat il 28% dei giorni: la lettura pulita arriva da sola, non va costruita;
|
||||
* interesse -> proporzionale al BALANCE.
|
||||
Una finestra con 0 fill e nozionale 0 misura l'interesse e basta. Le altre si etichettano.
|
||||
|
||||
MISURATO GIA' ORA (30/08, tre letture a 8 decimali durante le conversioni USDE): il balance USDC
|
||||
si e' mosso **esattamente** dell'importo degli ordini (1555.84720621 - 10.002 = 1545.84520621,
|
||||
poi -134.0268 = 1411.81840621). Su ~10 minuti con $568 di posizioni aperte: **ne' funding ne'
|
||||
interesse arrivano in continuo sul balance**. Se l'interesse esiste, e' un accredito PERIODICO —
|
||||
come i reward USDE, che compaiono in un colpo intorno alle 12:00 UTC. Percio' la cadenza e' ORARIA:
|
||||
serve a stringere la finestra intorno all'accredito, non a campionare di piu'.
|
||||
|
||||
uv run python scripts/live/balance_watch.py # un campione + lettura della serie
|
||||
uv run python scripts/live/balance_watch.py --report # solo la lettura, nessun campione
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sqlite3
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live.deribit import DeribitRead # noqa: E402
|
||||
|
||||
STATE = ROOT / "data" / "live" / "balance_watch.jsonl"
|
||||
TRADES_DB = ROOT / "data" / "live" / "trades.db"
|
||||
VALUTE = ("USDC", "USDE", "BUIDL")
|
||||
APR_ATTESE = {"USDC": 0.0340, "USDE": 0.042143, "BUIDL": 0.032018} # public/get_currencies
|
||||
|
||||
|
||||
def fills_da(ts_iso: str | None) -> int | None:
|
||||
"""Quanti fill dopo `ts_iso` (None se il DB non e' leggibile: non si inventa 0)."""
|
||||
if ts_iso is None:
|
||||
return None
|
||||
try:
|
||||
con = sqlite3.connect(f"file:{TRADES_DB}?mode=ro", uri=True)
|
||||
try:
|
||||
return con.execute("select count(*) from fills where ts_utc > ?", (ts_iso,)).fetchone()[0]
|
||||
finally:
|
||||
con.close()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def campiona(c: DeribitRead) -> dict:
|
||||
"""Un campione: balance per valuta (8 decimali), nozionale lordo, posizioni. Mai solleva."""
|
||||
rec = {"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), "valute": {}}
|
||||
for cur in VALUTE:
|
||||
try:
|
||||
s = c.account_summary(cur)
|
||||
rec["valute"][cur] = {"balance": float(s["balance"]), "equity": float(s["equity"])}
|
||||
except Exception as e:
|
||||
rec["valute"][cur] = {"errore": f"{type(e).__name__}"}
|
||||
try:
|
||||
pos = c.positions("USDC")
|
||||
rec["nozionale_lordo"] = round(sum(abs(float(p.get("size") or 0)) for p in pos), 2)
|
||||
rec["n_posizioni"] = sum(1 for p in pos if abs(float(p.get("size") or 0)) > 1)
|
||||
except Exception:
|
||||
rec["nozionale_lordo"], rec["n_posizioni"] = None, None
|
||||
# INDICE usde_usdc, ORARIO (aggiunto 2026-09-02, debito §5.15). `usde_watch` lo registra una
|
||||
# volta al giorno (12:35Z): troppo rado per il classificatore dei movimenti di capitale, che
|
||||
# confronta due letture di equity a un'ora di distanza e oggi guarda solo BTC/ETH — con il
|
||||
# 31% dell'equity in USDE, un depeg sarebbe scorporato come un PRELIEVO. Questa e' la serie
|
||||
# che manca; il cablaggio nel bound viene dopo, quando ci sara' storia. None se non leggibile
|
||||
# (P5: "non vedo" non e' "il peg tiene").
|
||||
try:
|
||||
from src.live.usde import prezzo_indice
|
||||
rec["usde_usdc"] = prezzo_indice()
|
||||
except Exception as e:
|
||||
rec["usde_usdc"], rec["usde_usdc_errore"] = None, f"{type(e).__name__}"
|
||||
return rec
|
||||
|
||||
|
||||
def serie() -> list[dict]:
|
||||
if not STATE.exists():
|
||||
return []
|
||||
out = []
|
||||
for line in STATE.read_text().splitlines():
|
||||
try:
|
||||
out.append(json.loads(line))
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def leggi(righe: list[dict]) -> None:
|
||||
print(f" campioni: {len(righe)}"
|
||||
+ (f" da {righe[0]['ts']} a {righe[-1]['ts']}" if righe else ""))
|
||||
if len(righe) < 2:
|
||||
print(" → serve almeno un secondo campione: la prima differenza e' la prima misura.")
|
||||
return
|
||||
print(f"\n {'finestra':>34s} {'ore':>5s} {'Δ USDC':>13s} {'Δ USDE':>13s} {'fill':>5s} {'lordo':>9s} lettura")
|
||||
pulite = []
|
||||
for a, b in zip(righe, righe[1:]):
|
||||
ba, bb = a["valute"].get("USDC", {}), b["valute"].get("USDC", {})
|
||||
ea, eb = a["valute"].get("USDE", {}), b["valute"].get("USDE", {})
|
||||
if "balance" not in ba or "balance" not in bb:
|
||||
continue
|
||||
t0 = datetime.strptime(a["ts"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
|
||||
t1 = datetime.strptime(b["ts"], "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
|
||||
ore = (t1 - t0).total_seconds() / 3600
|
||||
d_usdc = bb["balance"] - ba["balance"]
|
||||
d_usde = (eb.get("balance", 0) - ea.get("balance", 0)) if "balance" in eb and "balance" in ea else None
|
||||
nf = b.get("fills_da_ultimo")
|
||||
lordo = b.get("nozionale_lordo")
|
||||
pulita = (nf == 0) and (lordo is not None and lordo < 1)
|
||||
nota = "PULITA (0 fill, libro flat) → il Δ USDC E' interesse" if pulita else (
|
||||
"sporca: fill nella finestra" if nf else
|
||||
"0 fill ma libro esposto → Δ = funding + interesse" if nf == 0 else "fill ignoti")
|
||||
if pulita and ore > 0:
|
||||
pulite.append((d_usdc, ba["balance"], ore))
|
||||
print(f" {a['ts'][5:16]}→{b['ts'][5:16]:>16s} {ore:5.1f} {d_usdc:+13.8f} "
|
||||
f"{(f'{d_usde:+13.8f}' if d_usde is not None else ' n/d')} "
|
||||
f"{('' if nf is None else nf):>5} {('' if lordo is None else f'${lordo:,.0f}'):>9} {nota}")
|
||||
print()
|
||||
if pulite:
|
||||
tot_d = sum(d for d, _, _ in pulite)
|
||||
tot_h = sum(h for _, _, h in pulite)
|
||||
bal = sum(b * h for _, b, h in pulite) / tot_h
|
||||
apr = (tot_d / bal) * (8760 / tot_h) if bal and tot_h else float("nan")
|
||||
print(f" ⇒ FINESTRE PULITE: {len(pulite)}, {tot_h:.1f} ore, Δ totale {tot_d:+.8f} USDC "
|
||||
f"su balance medio ${bal:,.2f}")
|
||||
print(f" APR implicita {apr:+.4%} (attesa da public/get_currencies: "
|
||||
f"{APR_ATTESE['USDC']:.4%})")
|
||||
print(f" verdetto: {'FRUTTA' if tot_d > 1e-6 else 'NON frutta (Δ nullo su finestra pulita)'}")
|
||||
else:
|
||||
print(" ⇒ nessuna finestra PULITA ancora (serve il libro FLAT e 0 fill: succede il 28% dei giorni).")
|
||||
print(" Nel frattempo le finestre a 0 fill misurano funding+interesse insieme.")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--report", action="store_true", help="solo lettura, nessun campione nuovo")
|
||||
a = ap.parse_args()
|
||||
righe = serie()
|
||||
if not a.report:
|
||||
rec = campiona(DeribitRead())
|
||||
rec["fills_da_ultimo"] = fills_da(righe[-1]["ts"] if righe else None)
|
||||
STATE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with STATE.open("a") as f:
|
||||
f.write(json.dumps(rec) + "\n")
|
||||
righe.append(rec)
|
||||
u = rec["valute"].get("USDC", {}); e = rec["valute"].get("USDE", {})
|
||||
print(f" campione {rec['ts']}: USDC balance {u.get('balance')} · USDE {e.get('balance')}"
|
||||
f" · lordo ${rec.get('nozionale_lordo')}")
|
||||
print("=" * 78)
|
||||
print(" BALANCE WATCH — l'USDC frutta sul nostro conto?")
|
||||
print("=" * 78)
|
||||
leggi(righe)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -10,9 +10,24 @@ DOPPIO GATE DI SICUREZZA (entrambi necessari per inviare ordini reali):
|
||||
Senza entrambi e' un DRY-RUN (stampa il piano, NON invia). Reconciliation dopo ogni ordine; log in
|
||||
data/live/book_executions.jsonl.
|
||||
|
||||
⚠️ CADENZA: SKH01 decide su griglia 230m -> questo script va lanciato ogni ~230 minuti con la feed
|
||||
fresca all'ultima barra chiusa (NON il cron giornaliero, che mancherebbe gli ingressi). Gli exit di
|
||||
SKH sono SOFTWARE (latenza fino a fine barra 230m); solo il disaster-SL (-30%) e' on-book.
|
||||
⚠️ CADENZA: ORARIA — `47 * * * *` in `scripts/cron_book.sh` (il minuto e' spiegato li'). SKH01
|
||||
decide su griglia 230m, ma il giro e' IDEMPOTENTE (riconcilia al target netto corrente; sotto
|
||||
`min_order_usd` $5 -> HOLD). Girare piu' fitto della griglia costa solo MICRO-ORDINI di ri-taglia:
|
||||
il target e' ricalcolato ogni ora sull'equity marcata, e 22 dei 48 ordini live sono |delta|<=$10
|
||||
a segnale invariato — spiccioli oggi, ma il deadband e' in valuta ASSOLUTA (regola C2: a capitale
|
||||
maggiore un movimento dello 0,15% lo supera ogni ora). Compra due cose misurate: (1) gli
|
||||
ingressi/uscite SOFTWARE di SKH01 arrivano con latenza <=1h — CON LA FEED 5m FRESCA: se e' stantia
|
||||
`livefeed.fresh_5m` ripiega in silenzio sul feed giornaliero (latenza ~1 giorno) e
|
||||
`skh_feed_max_age_min` 30 ALLERTA, non blocca; (2) il disaster-SL rotolante (-30%, l'unico on-book)
|
||||
viene CONTROLLATO ogni ora e ri-ancorato solo oltre la tolleranza di `ensure_disaster_sl` (stop a
|
||||
>5% da quello piazzato, cioe' mark +5,263%/-4,762%, o taglia >10%) — con questa isteresi
|
||||
(`r0823_sl_anchor.py`): a cadenza 1h in 7-8 anni BTC 0 scatti / ETH 1; a 4h BTC 2; a 24h ETH 5,
|
||||
con 2 in 30 giorni. Lo stop siede quindi fra -33,5% e -26,5% dal mark corrente (0,665-0,735 x mark),
|
||||
non a -30% dall'ultima ora: e' rotolante, non un massimo di perdita (CLAUDE.md §1).
|
||||
🚨 NON "correggere" la cadenza a ~230 minuti: fino al 2026-09-02 questo docstring lo prescriveva,
|
||||
ed era il docstring a essere sbagliato, non il cron (debito §5.7). `tests/test_book_cadenza.py`
|
||||
tiene d'accordo docstring, `cron_book.sh` e crontab installata. NON il cron giornaliero: le entrate
|
||||
di SKH01 le mancherebbe.
|
||||
|
||||
uv run python scripts/live/book_execute.py # DRY-RUN (piano, nessun ordine)
|
||||
uv run python scripts/live/book_execute.py --execute # esegue SOLO se execution_enabled=true
|
||||
@@ -30,7 +45,7 @@ import pandas as pd
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from src.live.book import book_report
|
||||
from src.live.book import ScalaNonAutorizzata, book_report
|
||||
from src.live.execution import DeribitTrader
|
||||
from src.live.notifier import notify
|
||||
from src.live.venue_probe import diagnose, errori_dal_report
|
||||
@@ -78,7 +93,20 @@ def _run():
|
||||
min_order = float(cfg["min_order_usd"])
|
||||
sl_pct = float(cfg["disaster_sl_pct"])
|
||||
|
||||
r = book_report(live_feed=True) # target NETTO + conto/posizioni reali (feed SKH fresco)
|
||||
try:
|
||||
r = book_report(live_feed=True) # target NETTO + conto/posizioni reali (feed SKH fresco)
|
||||
except ScalaNonAutorizzata as e:
|
||||
# G1(c): NON si taglia al tetto. Un clamp silenzioso farebbe girare una config che dichiara
|
||||
# un numero e un libro che ne esegue un altro. Si ferma, non invia, e allerta.
|
||||
msg = (f"🚨 SCALA NON AUTORIZZATA — book_execute FERMO, nessun ordine inviato.\n{e}\n"
|
||||
f"Ripara: riporta `book_scale_k` a un gradino di SCALA_LADDER dentro il tetto, "
|
||||
f"oppure rimuovi la chiave (assente = 1,00 = libro di oggi).")
|
||||
print(msg)
|
||||
try:
|
||||
notify(msg)
|
||||
except Exception as ne: # P3: l'errore si registra dove lo si ingoia
|
||||
print(f" ⚠️ allerta NON inviata: {type(ne).__name__}: {ne}")
|
||||
return
|
||||
equity = r["equity"]
|
||||
|
||||
print("=" * 88)
|
||||
@@ -89,7 +117,15 @@ def _run():
|
||||
print(f" modo : {mode}")
|
||||
print(f" gate : execution_enabled={enabled} | --execute={want_execute}")
|
||||
print(f" conto reale : ${r['real_equity']:,.2f}" if r["real_equity"] else f" conto: {r['eq_basis']}")
|
||||
print(f" sizing base : ${equity:,.2f} | cap/asset ${r['cap_per_asset']:.0f} | min ${min_order:.0f} | disaster-SL -{sl_pct*100:.0f}%")
|
||||
_sc = r.get("scala", 1.0)
|
||||
_lordo = len(r["assets"]) * float(cfg.get("max_notional_per_asset_frac") or 0.0) * _sc
|
||||
print(f" sizing base : ${equity:,.2f} | cap/asset ${r['cap_per_asset']:.0f} | "
|
||||
f"min ${min_order:.0f} | disaster-SL -{sl_pct*100:.0f}%")
|
||||
# La scala e' stampata SEMPRE, anche a 1,00: un parametro che muove il nozionale e non compare
|
||||
# nel log e' invisibile proprio nel giro in cui e' cambiato (SPEC-scale-key §2.2).
|
||||
print(f" scala libro : {_sc:.2f}x (tetto {r.get('leva_lorda_max', 1.25):.2f}x) | "
|
||||
f"leva lorda max {_lordo:.3f}x dell'equity"
|
||||
+ ("" if _sc != 1.0 else " [chiave assente o 1,00 = libro invariato]"))
|
||||
print(f" ultima barra : {r['last_data']}\n")
|
||||
|
||||
if r.get("skh_error"): # SKH feed fallito -> book.py ha forzato flat IN SILENZIO
|
||||
@@ -319,5 +355,14 @@ def main():
|
||||
raise
|
||||
|
||||
|
||||
USO = """uso: book_execute.py [--execute]
|
||||
|
||||
uv run python scripts/live/book_execute.py # DRY-RUN (piano, nessun ordine)
|
||||
uv run python scripts/live/book_execute.py --execute # esegue SOLO se execution_enabled=true
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("book_execute.py", USO, flag=("--execute",), con_valore=())
|
||||
main()
|
||||
|
||||
@@ -93,5 +93,13 @@ def main() -> None:
|
||||
print(" regime QUIET: carry non raccoglibile (coerente con luglio 2026).")
|
||||
|
||||
|
||||
USO = """uso: cc01_regime_watch.py
|
||||
|
||||
(nessun flag) sorveglia il regime di CC01 e stampa lo stato.
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("cc01_regime_watch.py", USO)
|
||||
main()
|
||||
|
||||
@@ -144,5 +144,15 @@ def main() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
USO = """uso: edge_watch.py [--quiet]
|
||||
|
||||
edge_watch.py — sorveglianza dell'EDGE del book live. Riporta e allerta; non tocca nulla.
|
||||
uv run python scripts/live/edge_watch.py # report
|
||||
uv run python scripts/live/edge_watch.py --quiet # solo se qualcosa scatta (cron)
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("edge_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -252,5 +252,15 @@ def main() -> int:
|
||||
return 2 if r["level"] == "AZIONE" else 0
|
||||
|
||||
|
||||
USO = """uso: fee_watch.py [--quiet]
|
||||
|
||||
fee_watch.py — sorveglia lo schema fee di Deribit e applica la regola DECISA IN ANTICIPO.
|
||||
uv run python scripts/live/fee_watch.py # report
|
||||
uv run python scripts/live/fee_watch.py --quiet # stampa/allerta solo se qualcosa cambia
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("fee_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -41,7 +41,19 @@ def una(con, g: date, nota: str | None = None, verbose: bool = True) -> Path:
|
||||
return f
|
||||
|
||||
|
||||
USO = """uso: journal.py [--giorno AAAA-MM-GG] [--backfill N] [--nota "testo"] [--quiet]
|
||||
|
||||
(nessun flag) scrive la voce di OGGI (pagina marcata PARZIALE: il giorno non e' chiuso)
|
||||
--giorno G scrive la voce del giorno G
|
||||
--backfill N ricostruisce gli ultimi N giorni, dal piu' vecchio
|
||||
--nota "..." annota il giorno (campo dell'operatore, mai riscritto da un ricalcolo)
|
||||
--quiet non stampa la riga di riepilogo
|
||||
|
||||
⚠️ SCRIVE: una riga nel DB e una pagina in docs/journal/. Sola lettura su feed, log e venue."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("journal.py", USO, flag=("--quiet",), con_valore=("--giorno", "--backfill", "--nota"))
|
||||
# sincronizzare PRIMA: altrimenti la sezione 'Libro' (letta dal log) e la sezione 'P&L'
|
||||
# (letta dal DB) descrivono due istanti diversi e la pagina si contraddice da sola.
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "live"))
|
||||
|
||||
@@ -137,5 +137,14 @@ def main():
|
||||
raise
|
||||
|
||||
|
||||
USO = """uso: live_execute.py [--execute]
|
||||
|
||||
uv run python scripts/live/live_execute.py # DRY-RUN (piano, nessun ordine)
|
||||
uv run python scripts/live/live_execute.py --execute # esegue SOLO se execution_enabled=true
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("live_execute.py", USO, flag=("--execute",), con_valore=())
|
||||
main()
|
||||
|
||||
@@ -74,5 +74,15 @@ def main():
|
||||
(f"{len(r['orders'])} ordine/i costruito/i sopra." if r["orders"] else "Target flat: 0 ordini."))
|
||||
|
||||
|
||||
USO = """uso: live_trend.py [--equity V] [--no-net]
|
||||
|
||||
uv run python scripts/live/live_trend.py # shadow su mainnet reale
|
||||
uv run python scripts/live/live_trend.py --equity 2000 # forza la base di sizing
|
||||
uv run python scripts/live/live_trend.py --no-net # offline: solo matematica + parita'
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("live_trend.py", USO, flag=("--no-net",), con_valore=("--equity",))
|
||||
main()
|
||||
|
||||
@@ -88,5 +88,14 @@ def main():
|
||||
print(" Validato: invio ordine reale, fill, fee reali, reconciliation, ritorno a flat.")
|
||||
|
||||
|
||||
USO = """uso: microtest.py [--live]
|
||||
|
||||
uv run python scripts/live/microtest.py # DRY-RUN: nessun ordine inviato
|
||||
uv run python scripts/live/microtest.py --live # invia il round-trip REALE
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("microtest.py", USO, flag=("--live",), con_valore=())
|
||||
main()
|
||||
|
||||
@@ -55,5 +55,15 @@ def main() -> int:
|
||||
return 0
|
||||
|
||||
|
||||
USO = """uso: monitor_health.py [--quiet]
|
||||
|
||||
monitor_health.py — i forward-monitor stanno registrando? Riporta e allerta, non tocca nulla.
|
||||
uv run python scripts/live/monitor_health.py # report
|
||||
uv run python scripts/live/monitor_health.py --quiet # stampa/allerta solo se scatta
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("monitor_health.py", USO, flag=("--quiet",), con_valore=())
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -18,6 +18,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
|
||||
import numpy as np, pandas as pd
|
||||
from src.portfolio.sleeves import _tp01_returns, _tp01_positions
|
||||
from src.portfolio.gtaa import gtaa_returns, gtaa_weights
|
||||
from src.live import paper_guard as PG
|
||||
|
||||
STATE_DIR = PROJECT_ROOT / "data" / "paper_combo"
|
||||
STATE = STATE_DIR / "state.json"
|
||||
@@ -77,6 +78,10 @@ def advance():
|
||||
return st
|
||||
last = pd.Timestamp(st["last"])
|
||||
nn = naked[naked.index > last]; gg = guard[guard.index > last]
|
||||
# SOLO giorni CHIUSI. L'audit 22/08 ha trovato questo monitor sano PER CASO (aspetta la
|
||||
# chiusura di una borsa, non per progetto): il filtro lo rende sano PER COSTRUZIONE.
|
||||
m = PG.indice_chiuso(nn.index, PG.MS_1D)
|
||||
nn, gg = nn[m], gg[PG.indice_chiuso(gg.index, PG.MS_1D)]
|
||||
if len(nn):
|
||||
e = st["equity"]; pk = st["peak"]; dd = st["max_dd"]
|
||||
eg = st["equity_g"]; pkg = st["peak_g"]; ddg = st["max_dd_g"]; lines = []
|
||||
@@ -113,5 +118,13 @@ def main():
|
||||
print(f" GTAA (IB, asof {asof}): " + ", ".join(f"{k} {v:.0%}" for k, v in gw.items() if v) + f" | cash {cash:.0%}")
|
||||
|
||||
|
||||
USO = """uso: paper_combo.py [--reset] [--status]
|
||||
|
||||
uv run python scripts/live/paper_combo.py [--status|--reset]
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("paper_combo.py", USO, flag=("--status", "--reset"), con_valore=())
|
||||
main()
|
||||
|
||||
@@ -85,6 +85,7 @@ sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "alt"))
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "ortho"))
|
||||
|
||||
import altlib as al # noqa: E402
|
||||
from src.live import paper_guard as PG # noqa: E402
|
||||
import ortholib as ol # noqa: E402
|
||||
from r0726_dvolspread_gate import make_book # noqa: E402 (fabbrica ESATTA del T2)
|
||||
|
||||
@@ -152,7 +153,9 @@ def init_state(P: dict) -> dict:
|
||||
|
||||
def advance(st: dict, P: dict) -> dict:
|
||||
ts, dt = P["ts"], P["dt"]
|
||||
new = [i for i in range(len(ts)) if ts[i] > st["last_ts"]]
|
||||
# SOLO barre CHIUSE: il pannello arriva da `resample_tf` col giorno in corso dentro —
|
||||
# su uno spread BTC-ETH la barra troncata e' quasi solo rumore (~2 min/g, r0822_monitor_audit)
|
||||
new = PG.nuove_chiuse(ts, st["last_ts"], PG.MS_1D)
|
||||
if not new:
|
||||
return st
|
||||
hm, hr = st["wb_modeled"], st["wb_real"] # peso BTC TENUTO (w_eth = -w_btc)
|
||||
|
||||
@@ -17,6 +17,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
|
||||
import numpy as np, pandas as pd
|
||||
from src.portfolio.portfolio import StrategyPortfolio
|
||||
from src.portfolio.sleeves import active_sleeves
|
||||
from src.live import paper_guard as PG
|
||||
|
||||
STATE_DIR = PROJECT_ROOT / "data" / "paper_portfolio"
|
||||
STATE = STATE_DIR / "state.json"
|
||||
@@ -51,6 +52,10 @@ def advance():
|
||||
return st
|
||||
last = pd.Timestamp(st["last"])
|
||||
new = r[r.index > last]
|
||||
# SOLO giorni CHIUSI: l'ultima riga dei rendimenti giornalieri e' il giorno in corso
|
||||
# (~19 min/giorno registrati, r0822_monitor_audit). E' un monitor da dashboard, ma la
|
||||
# regola D2 vale anche qui.
|
||||
new = new[PG.indice_chiuso(new.index, PG.MS_1D)]
|
||||
if len(new):
|
||||
eq = st["equity"]; peak = st["peak"]; dd = st["max_dd"]
|
||||
lines = []
|
||||
@@ -82,5 +87,15 @@ def main():
|
||||
print(f" posizioni correnti: {pf.current_positions()}")
|
||||
|
||||
|
||||
USO = """uso: paper_portfolio.py [--reset] [--status]
|
||||
|
||||
uv run python scripts/live/paper_portfolio.py # avanza (init al 1o run)
|
||||
uv run python scripts/live/paper_portfolio.py --status # solo stato
|
||||
uv run python scripts/live/paper_portfolio.py --reset # azzera (riparte da ora)
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("paper_portfolio.py", USO, flag=("--status", "--reset"), con_valore=())
|
||||
main()
|
||||
|
||||
@@ -38,6 +38,7 @@ PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from src.backtest.harness import load # noqa: E402
|
||||
from src.live import paper_guard as PG # noqa: E402
|
||||
from src.strategies.prevday_breakout import target as prevday_target # noqa: E402
|
||||
from src.strategies import prevday_breakout as pb # noqa: E402
|
||||
|
||||
@@ -93,7 +94,10 @@ def advance(st: dict, dfs: dict) -> dict:
|
||||
dt=pd.to_datetime(df["datetime"]).values, r=r,
|
||||
tgt=prevday_target(df))
|
||||
common = sorted(set(data["BTC"]["ts"]).intersection(data["ETH"]["ts"]))
|
||||
new_ts = [t for t in common if t > st["last_ts"]]
|
||||
# SOLO barre 1h CHIUSE: il parquet 1h puo' contenere l'ora in corso (1 barra su 24 —
|
||||
# r0822_monitor_audit); la si lascia al giro successivo
|
||||
new_ts = [t for t in common
|
||||
if t > st["last_ts"] and PG.chiusa(int(t), PG.MS_1H)]
|
||||
if not new_ts:
|
||||
return st
|
||||
idx = {a: {int(t): i for i, t in enumerate(data[a]["ts"])} for a in ASSETS}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
"""paper_regen.py — RIGENERA le serie dei forward-monitor rotti da `advance()` (§5.1).
|
||||
|
||||
PERCHE' RIGENERARE E NON AZZERARE. L'audit r0822_monitor_audit ha provato due volte, in modo
|
||||
indipendente, che il replay sul pannello di oggi E' la serie corretta sulla stessa finestra:
|
||||
(a) `paper_prevday` ha 1427/1488 barre identiche al bit dopo 62 notti di riscrittura del feed;
|
||||
(b) le 6 barre chiuse di `paper_statarb` recuperate dopo il guasto EPERM coincidono al bit.
|
||||
Quindi la finestra forward NON va persa: si rigenera dallo STESSO `start_ts` pre-registrato,
|
||||
con la config congelata e il codice di produzione riparato — e nessuna data di gate si sposta.
|
||||
|
||||
COSA FA, per ciascun monitor (statarb · dvolspread · xsr · prevday):
|
||||
1. archivia i file correnti come *.pre_regen_<data>.* (NON li cancella: sono l'evidenza
|
||||
del difetto, e stanno nel perimetro di backup);
|
||||
2. ricostruisce lo stato iniziale all'ORIGINALE `start_ts` (stessa costruzione dell'audit);
|
||||
3. riesegue `advance()` di produzione — che ora consuma SOLO barre chiuse (paper_guard);
|
||||
4. salva lo stato e stampa il verdetto a runtime (N11): barre, Sharpe vecchio vs nuovo,
|
||||
e la prova che l'ultima barra registrata e' chiusa.
|
||||
|
||||
`paper_portfolio` e `paper_combo` NON si rigenerano qui, e la scelta e' dichiarata: la gamba
|
||||
GTAA legge `ADJUSTED_LAST` di IB ri-aggiustato all'indietro, quindi il replay NON sarebbe la
|
||||
serie che un monitor sano avrebbe registrato (P12: meglio una storia corrotta e dichiarata di
|
||||
una riscritta e spacciata per registrata). Sono dashboard senza gate; dal fix in poi scrivono
|
||||
solo barre chiuse.
|
||||
|
||||
uv run python scripts/live/paper_regen.py # tutti e 4
|
||||
uv run python scripts/live/paper_regen.py --solo paper_statarb
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
ANN = float(np.sqrt(365.0))
|
||||
SUFFISSO = datetime.now(timezone.utc).strftime("pre_regen_%Y%m%d")
|
||||
|
||||
|
||||
def live(name: str):
|
||||
spec = importlib.util.spec_from_file_location(f"_regen_{name}",
|
||||
ROOT / "scripts" / "live" / f"{name}.py")
|
||||
m = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
return m
|
||||
|
||||
|
||||
def sharpe_jsonl(path: Path, campo: str = "net_modeled") -> tuple[float, int]:
|
||||
if not path.exists():
|
||||
return float("nan"), 0
|
||||
r = []
|
||||
for ln in path.read_text().splitlines():
|
||||
ln = ln.strip()
|
||||
if ln:
|
||||
r.append(float(json.loads(ln)[campo]))
|
||||
r = np.asarray(r)
|
||||
if len(r) < 2 or r.std() == 0:
|
||||
return float("nan"), len(r)
|
||||
return float(r.mean() / r.std() * ANN), len(r)
|
||||
|
||||
|
||||
def archivia(d: Path, nomi: tuple[str, ...]) -> None:
|
||||
for n in nomi:
|
||||
f = d / n
|
||||
if f.exists():
|
||||
dest = d / f"{f.stem}.{SUFFISSO}{f.suffix}"
|
||||
if dest.exists():
|
||||
raise SystemExit(f" {dest} esiste gia': rigenerazione doppia nello stesso "
|
||||
"giorno? Verificare a mano prima di riprovare.")
|
||||
shutil.move(str(f), str(dest))
|
||||
print(f" archiviato {f.name} -> {dest.name}")
|
||||
|
||||
|
||||
def verdetto(nome: str, d: Path, mod, st: dict, sh_prima: float, n_prima: int,
|
||||
cadenza_ms: int) -> None:
|
||||
from src.live.paper_guard import chiusa
|
||||
sh_dopo, n_dopo = sharpe_jsonl(d / "returns.jsonl")
|
||||
ult = st["last_ts"] if "last_ts" in st else None
|
||||
ok_chiusa = chiusa(int(ult), cadenza_ms) if ult else False
|
||||
print(f" barre: {n_prima} registrate (rotte) -> {n_dopo} rigenerate (chiuse)")
|
||||
print(f" Sharpe ann. net_modeled: {sh_prima:+.2f} (serie rotta) -> {sh_dopo:+.2f} "
|
||||
f"(serie vera)")
|
||||
print(f" ultima barra {pd.Timestamp(int(ult), unit='ms', tz='UTC').date() if ult else '?'} "
|
||||
f"chiusa: {'SI' if ok_chiusa else 'NO — QUALCOSA NON VA'}")
|
||||
if not ok_chiusa:
|
||||
raise SystemExit(f" {nome}: la rigenerazione ha consumato una barra NON chiusa")
|
||||
|
||||
|
||||
def regen_statarb() -> None:
|
||||
print("\n== paper_statarb (gate 2026-09-27) " + "=" * 50)
|
||||
d = ROOT / "data" / "paper_statarb"
|
||||
ps = live("paper_statarb")
|
||||
st_old = json.loads((d / "state.json").read_text())
|
||||
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
|
||||
j = ps.build_joint("1d")
|
||||
ts, _, pos, _ = ps._signal(j)
|
||||
i0 = int(np.where(ts == st_old["start_ts"])[0][0])
|
||||
archivia(d, ("returns.jsonl", "trades.jsonl", "state.json"))
|
||||
st = dict(start_ts=st_old["start_ts"], last_ts=st_old["start_ts"], n_bars=0,
|
||||
pos_modeled=float(pos[i0]), pos_real=float(pos[i0]),
|
||||
cap_modeled=ps.MODELED_CAPITAL, cap_real=ps.REAL_CAPITAL,
|
||||
peak_modeled=ps.MODELED_CAPITAL, peak_real=ps.REAL_CAPITAL,
|
||||
dd_modeled=0.0, dd_real=0.0, n_trades=0)
|
||||
st = ps.advance(st, j)
|
||||
ps._state_io(st)
|
||||
verdetto("paper_statarb", d, ps, st, sh0, n0, 86_400_000)
|
||||
|
||||
|
||||
def regen_dvolspread() -> None:
|
||||
print("\n== paper_dvolspread (kill 2026-10-24 / decisione 2027-01-24) " + "=" * 24)
|
||||
d = ROOT / "data" / "paper_dvolspread"
|
||||
pv = live("paper_dvolspread")
|
||||
st_old = json.loads((d / "state.json").read_text())
|
||||
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
|
||||
P = pv._panel()
|
||||
i0 = int(np.where(P["ts"] == st_old["start_ts"])[0][0])
|
||||
archivia(d, ("returns.jsonl", "state.json"))
|
||||
st = dict(start_ts=st_old["start_ts"], last_ts=st_old["start_ts"], n_bars=0, n_active=0,
|
||||
n_flat_signal=0, n_flat_nodata=0,
|
||||
wb_modeled=float(P["wb"][i0]), wb_real=float(P["wb"][i0]),
|
||||
cap_modeled=pv.MODELED_CAPITAL, cap_real=pv.REAL_CAPITAL,
|
||||
peak_modeled=pv.MODELED_CAPITAL, peak_real=pv.REAL_CAPITAL,
|
||||
dd_modeled=0.0, dd_real=0.0, n_flips=0, frozen=pv.FROZEN)
|
||||
st = pv.advance(st, P)
|
||||
pv._state_io(st)
|
||||
verdetto("paper_dvolspread", d, pv, st, sh0, n0, 86_400_000)
|
||||
|
||||
|
||||
def regen_xsr() -> None:
|
||||
print("\n== paper_xsr (gate 2026-10-23) " + "=" * 54)
|
||||
d = ROOT / "data" / "paper_xsr"
|
||||
px = live("paper_xsr")
|
||||
st_old = json.loads((d / "state.json").read_text())
|
||||
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
|
||||
archivia(d, ("returns.jsonl", "state.json"))
|
||||
st = dict(start_ts=st_old["start_ts"], last_ts=st_old["start_ts"], n_bars=0,
|
||||
syms=st_old["syms"],
|
||||
modeled=px._book(px.MODELED_CAPITAL, len(st_old["syms"])),
|
||||
reals={n: px._book(c, len(st_old["syms"])) for c, n in px.REAL_BOOKS})
|
||||
st = px.advance(st)
|
||||
px._state_io(st)
|
||||
verdetto("paper_xsr", d, px, st, sh0, n0, 86_400_000)
|
||||
|
||||
|
||||
def regen_prevday() -> None:
|
||||
print("\n== paper_prevday (griglia ORARIA, nessun gate a data fissa) " + "=" * 24)
|
||||
d = ROOT / "data" / "paper_prevday"
|
||||
pp = live("paper_prevday")
|
||||
st_old = json.loads((d / "state.json").read_text())
|
||||
sh0, n0 = sharpe_jsonl(d / "returns.jsonl")
|
||||
dfs = pp.build_bars()
|
||||
S = st_old["start_ts"]
|
||||
pos = {a: pp.pb.current_target(dfs[a][dfs[a]["timestamp"] <= S]) for a in pp.ASSETS}
|
||||
archivia(d, ("returns.jsonl", "trades.jsonl", "state.json"))
|
||||
st = dict(start_ts=S, last_ts=S, n_bars=0, pos_modeled=pos, pos_real=dict(pos),
|
||||
cap_modeled=pp.MODELED_CAPITAL, cap_real=pp.REAL_CAPITAL,
|
||||
peak_modeled=pp.MODELED_CAPITAL, peak_real=pp.REAL_CAPITAL,
|
||||
dd_modeled=0.0, dd_real=0.0, n_trades=0)
|
||||
st = pp.advance(st, dfs)
|
||||
pp._state_io(st)
|
||||
verdetto("paper_prevday", d, pp, st, sh0, n0, 3_600_000)
|
||||
|
||||
|
||||
def trim_portfolio() -> None:
|
||||
"""`paper_portfolio` NON si rigenera (la gamba GTAA legge ADJUSTED_LAST ri-aggiustato:
|
||||
il replay non sarebbe la serie registrata — P12). Ma la sua CODA contiene la riga del
|
||||
giorno in corso, scritta dal codice vecchio: senza toglierla la guardia PREMATURO
|
||||
resterebbe accesa per sempre su un difetto gia' riparato (P14). Il taglio e' onesto:
|
||||
la storia registrata resta com'e' (troncata e DICHIARATA in memoria), va via solo la
|
||||
riga che dice di essere un giorno e non lo e' — la versione chiusa la riscrive il
|
||||
prossimo giro del cron col codice riparato."""
|
||||
from src.live.paper_guard import MS_1D, indice_chiuso
|
||||
print("\n== paper_portfolio (dashboard — TRIM della coda non chiusa, non rigenerazione) ==")
|
||||
d = ROOT / "data" / "paper_portfolio"
|
||||
righe = (d / "equity.csv").read_text().strip().splitlines()
|
||||
testa, dati = righe[0], righe[1:]
|
||||
date = pd.DatetimeIndex([r.split(",")[0] for r in dati])
|
||||
m = indice_chiuso(date, MS_1D)
|
||||
tolte = [r for r, k in zip(dati, m) if not k]
|
||||
if not tolte:
|
||||
print(" coda gia' pulita: niente da fare")
|
||||
return
|
||||
archivia(d, ("equity.csv", "state.json"))
|
||||
dati_ok = [r for r, k in zip(dati, m) if k]
|
||||
(d / "equity.csv").write_text("\n".join([testa] + dati_ok) + "\n")
|
||||
eq = [float(r.split(",")[1]) for r in dati_ok]
|
||||
peak, dd = 0.0, 0.0
|
||||
for e in eq:
|
||||
peak = max(peak, e)
|
||||
dd = max(dd, (peak - e) / peak if peak > 0 else 0.0)
|
||||
st_old = json.loads((d / f"state.{SUFFISSO}.json").read_text())
|
||||
st = dict(st_old, last=dati_ok[-1].split(",")[0], equity=eq[-1], peak=peak, max_dd=dd,
|
||||
n_days=st_old["n_days"] - len(tolte))
|
||||
(d / "state.json").write_text(json.dumps(st, indent=2))
|
||||
for r in tolte:
|
||||
print(f" tolta riga non chiusa: {r}")
|
||||
print(f" ultima riga ora: {dati_ok[-1]} (peak/max_dd ricalcolati dalla serie)")
|
||||
|
||||
|
||||
REGEN = dict(paper_statarb=regen_statarb, paper_dvolspread=regen_dvolspread,
|
||||
paper_xsr=regen_xsr, paper_prevday=regen_prevday,
|
||||
paper_portfolio=trim_portfolio)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 90)
|
||||
print(" RIGENERAZIONE forward-monitor — stesso start_ts, config congelata, solo barre chiuse")
|
||||
print("=" * 90)
|
||||
solo = None
|
||||
if "--solo" in sys.argv:
|
||||
solo = sys.argv[sys.argv.index("--solo") + 1]
|
||||
if solo not in REGEN:
|
||||
raise SystemExit(f"monitor sconosciuto: {solo} (validi: {', '.join(REGEN)})")
|
||||
for nome, fn in REGEN.items():
|
||||
if solo and nome != solo:
|
||||
continue
|
||||
fn()
|
||||
print("\n fatto. I file *.%s.* sono l'evidenza del difetto e restano nel backup." % SUFFISSO)
|
||||
|
||||
|
||||
USO = """uso: paper_regen.py [--solo V]
|
||||
|
||||
paper_regen.py — RIGENERA le serie dei forward-monitor rotti da `advance()` (§5.1).
|
||||
uv run python scripts/live/paper_regen.py # tutti e 4
|
||||
uv run python scripts/live/paper_regen.py --solo paper_statarb
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("paper_regen.py", USO, flag=(), con_valore=("--solo",))
|
||||
main()
|
||||
@@ -51,6 +51,7 @@ sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research"))
|
||||
|
||||
# Segnale ESATTO dello sweep (nessuna reimplementazione → identità garantita col backtest).
|
||||
from orthogonal_signals import build_joint, f_statarb_resid, spread_ret # noqa: E402
|
||||
from src.live import paper_guard as PG # noqa: E402
|
||||
|
||||
STATE_DIR = PROJECT_ROOT / "data" / "paper_statarb"
|
||||
STATE_FILE = STATE_DIR / "state.json"
|
||||
@@ -99,7 +100,9 @@ def init_state(j: pd.DataFrame) -> dict:
|
||||
|
||||
def advance(st: dict, j: pd.DataFrame) -> dict:
|
||||
ts, dt, pos, sr = _signal(j)
|
||||
new = [i for i in range(len(ts)) if ts[i] > st["last_ts"]]
|
||||
# SOLO barre CHIUSE: `build_joint('1d')` passa da `resample_tf`, che NON scarta il giorno
|
||||
# in corso (1 barra 1h su 24) — consumarlo faceva registrare ~4 min/giorno (r0822_monitor_audit)
|
||||
new = PG.nuove_chiuse(ts, st["last_ts"], PG.MS_1D)
|
||||
if not new:
|
||||
return st
|
||||
pm, pr = st["pos_modeled"], st["pos_real"] # posizioni TENUTE (decise alla barra precedente)
|
||||
|
||||
@@ -65,6 +65,7 @@ sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research"))
|
||||
|
||||
# Segnale ESATTO dello studio (nessuna reimplementazione -> niente drift col backtest).
|
||||
from r0725_statarb_multi import BASE, MIN_BARS, load_hl, signal, universe # noqa: E402
|
||||
from src.live import paper_guard as PG # noqa: E402
|
||||
|
||||
STATE_DIR = PROJECT_ROOT / "data" / "paper_xsr"
|
||||
STATE_FILE = STATE_DIR / "state.json"
|
||||
@@ -158,7 +159,9 @@ def advance(st: dict) -> dict:
|
||||
print(f" [XSR01] universo cambiato ({len(st['syms'])} -> {len(syms)}): "
|
||||
"la finestra forward richiede universo costante. Usa --reset per ripartire.")
|
||||
return st
|
||||
new = [i for i in range(len(ts)) if ts[i] > st["last_ts"]]
|
||||
# SOLO barre CHIUSE: l'ultima riga del parquet HL e' il giorno IN CORSO, e consumarla
|
||||
# e' il difetto che faceva registrare ~41 minuti di mercato al giorno (r0822_monitor_audit)
|
||||
new = PG.nuove_chiuse(ts, st["last_ts"], PG.MS_1D)
|
||||
if not new:
|
||||
return st
|
||||
for i in new:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Revisione settimanale in SOLA LETTURA: un modello DIVERSO dall'autore rilegge il sistema.
|
||||
|
||||
uv run python scripts/live/revisione.py # chiama il revisore, scrive docs/revisioni/<data>.md, Telegram
|
||||
uv run python scripts/live/revisione.py --secco # solo la taglia del materiale: niente modello, niente file
|
||||
uv run python scripts/live/revisione.py --quiet # per il cron (scripts/cron_review.sh, lunedi' 06:15 UTC)
|
||||
|
||||
Cosa scrive: SOLO il rapporto. Cosa non tocca: config, libro, cron, codice, dati. Le proposte
|
||||
restano proposte; decide l'operatore. Dettagli e ragioni in `src/live/revisione.py`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
USO = """uso: revisione.py [--giorno AAAA-MM-GG] [--modello NOME] [--secco] [--no-telegram] [--quiet]
|
||||
--giorno G data della revisione (default: adesso, UTC)
|
||||
--modello M revisore (default: src.live.revisione.MODELLO = claude-fable-5-1 — NON il modello che scrive il codice)
|
||||
--secco stampa la taglia del materiale e si ferma: niente modello, niente file, niente Telegram
|
||||
--no-telegram scrive il rapporto ma non manda la sintesi
|
||||
--quiet stampa solo l'esito
|
||||
⚠️ SPENDE una chiamata al modello e MANDA un messaggio Telegram, salvo --secco/--no-telegram.
|
||||
Scrive un solo file: docs/revisioni/<data>.md."""
|
||||
|
||||
|
||||
def _arg(flag: str, default=None):
|
||||
a = sys.argv[1:]
|
||||
return a[a.index(flag) + 1] if flag in a and a.index(flag) + 1 < len(a) else default
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("revisione.py", USO, flag=("--secco", "--no-telegram", "--quiet"),
|
||||
con_valore=("--giorno", "--modello"))
|
||||
from src.live import revisione as R
|
||||
g = _arg("--giorno")
|
||||
adesso = (datetime.fromisoformat(g).replace(tzinfo=timezone.utc) if g
|
||||
else datetime.now(timezone.utc))
|
||||
esito = R.scrivi(ROOT, adesso, modello=_arg("--modello", R.MODELLO),
|
||||
secco="--secco" in sys.argv, telegram="--no-telegram" not in sys.argv,
|
||||
quiet="--quiet" in sys.argv)
|
||||
if "--quiet" in sys.argv and not esito.get("secco"):
|
||||
print(f"revisione {esito['data']}: {esito.get('stato')}"
|
||||
+ (f" — {esito.get('errore')}" if esito.get("errore") else "") + f" {esito.get('file')}")
|
||||
raise SystemExit(0 if esito.get("secco") or esito.get("stato") in ("ok", "sospetta") else 1)
|
||||
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sorveglianza giornaliera della CHIAVE DI SCALA del libro (SPEC-scale-key §6.4).
|
||||
|
||||
uv run python scripts/live/scale_watch.py # osserva e stampa, invia se ALLARME
|
||||
uv run python scripts/live/scale_watch.py --secco # osserva e stampa, NON invia nulla
|
||||
uv run python scripts/live/scale_watch.py --quiet # una riga sola (per il cron)
|
||||
|
||||
Tre domande, tre azioni diverse, tre stati (OK / ALLARME / NON MISURABILE).
|
||||
Non impedisce la modifica: la rende visibile entro 24 ore e attribuibile.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from src.live.notifier import notify # noqa: E402
|
||||
from src.live.scale_watch import ALLARME, OK, run_once # noqa: E402
|
||||
|
||||
|
||||
def _sender(testo: str):
|
||||
try:
|
||||
notify(testo)
|
||||
return True, "telegram"
|
||||
except Exception as e: # P3: si registra dove lo si ingoia
|
||||
return False, f"{type(e).__name__}: {e}"
|
||||
|
||||
|
||||
def main() -> None:
|
||||
secco = "--secco" in sys.argv
|
||||
quiet = "--quiet" in sys.argv
|
||||
rep = run_once(sender=None if secco else _sender)
|
||||
icona = {OK: "✅", ALLARME: "🚨"}.get(rep["stato"], "⚠️")
|
||||
if quiet:
|
||||
fb = rep["fallback"]
|
||||
q = f"{100*fb['quota']:.2f}%" if "quota" in fb else "n/d"
|
||||
print(f" scale_watch: {icona} {rep['stato']} · scala {rep['scala_dichiarata']:.2f}x · "
|
||||
f"fallback {q} · {rep['invio']}")
|
||||
return
|
||||
print("=" * 88)
|
||||
print(f" SCALE WATCH — la chiave di scala del libro {icona} {rep['stato']}")
|
||||
print("=" * 88)
|
||||
print(f" scala dichiarata : {rep['scala_dichiarata']:.2f}x "
|
||||
f"(scaletta {rep['ladder']}, tetto {rep['tetto']:.2f}x — costanti di CODICE)")
|
||||
for nome, d in rep["domande"].items():
|
||||
print(f"\n {nome} [{d['stato']}] {d['motivo']}")
|
||||
if d.get("azione") and d["azione"] != "nessuna":
|
||||
print(f" → azione: {d['azione']}")
|
||||
fb = rep["fallback"]
|
||||
if fb.get("stato") == "NON MISURABILE":
|
||||
print(f"\n fallback [{fb['stato']}] {fb.get('motivo')}")
|
||||
else:
|
||||
print(f"\n fallback [{fb['stato']}] equity illeggibile in {fb['fallback_veri']}/{fb['giri']} "
|
||||
f"giri ({100*fb['quota']:.2f}%, soglia {100*fb['soglia']:.0f}%) — "
|
||||
f"{fb['paper']} giri pre-finanziamento esclusi, li' il libro non invia affatto")
|
||||
print(f"\n lettore di produzione [{rep['lettore']['stato']}] {rep['lettore']['motivo'][:150]}")
|
||||
print(f"\n invio: {rep['invio']}")
|
||||
|
||||
|
||||
USO = """uso: scale_watch.py [--quiet] [--secco]
|
||||
|
||||
uv run python scripts/live/scale_watch.py # osserva e stampa, invia se ALLARME
|
||||
uv run python scripts/live/scale_watch.py --secco # osserva e stampa, NON invia nulla
|
||||
uv run python scripts/live/scale_watch.py --quiet # una riga sola (per il cron)
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("scale_watch.py", USO, flag=("--quiet", "--secco"), con_valore=())
|
||||
main()
|
||||
@@ -146,5 +146,14 @@ def main() -> None:
|
||||
print("inviato" if send(txt) else "NON inviato (config Telegram assente o rete KO)")
|
||||
|
||||
|
||||
USO = """uso: telegram_daily.py [--dry-run]
|
||||
|
||||
uv run python scripts/live/telegram_daily.py # calcola e invia
|
||||
uv run python scripts/live/telegram_daily.py --dry-run # stampa e basta, non invia
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("telegram_daily.py", USO, flag=("--dry-run",), con_valore=())
|
||||
main()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Sincronizza `data/live/trades.db` — il libro di bordo dei trade, allineato col tempo.
|
||||
|
||||
uv run python scripts/live/trades_db.py --sync # idempotente, da cron
|
||||
uv run python scripts/live/trades_db.py --report # stato + P&L a video
|
||||
uv run python scripts/live/trades_db.py --report # stato + P&L a video (TWR, non e1/e0)
|
||||
uv run python scripts/live/trades_db.py --reconcile # incrocio delle tre fonti
|
||||
|
||||
SOLA LETTURA sul venue e sui log: non manda ordini, non tocca il libro.
|
||||
@@ -78,10 +78,41 @@ def report() -> None:
|
||||
print(f"\n fill registrati : {n} dal {p['a'][:16]} al {p['b'][:16]}")
|
||||
eq = con.execute("SELECT ts_utc, equity FROM equity ORDER BY ts_utc").fetchall()
|
||||
if eq:
|
||||
# Il rendimento NON e' `e1/e0-1` sulla serie grezza: quel numero era per il 96,3% un
|
||||
# bonifico (debito #14, 2026-09-01). Lo scorporo e' quello del giornale — si CHIAMA,
|
||||
# non si rifa' (P1): `rendimento_twr` spezza la serie sui movimenti di capitale certi.
|
||||
from src.live.journal import rendimento_twr
|
||||
e0, e1 = eq[0]["equity"], eq[-1]["equity"]
|
||||
picco = max(r["equity"] for r in eq)
|
||||
print(f" equity : ${e0:,.2f} -> ${e1:,.2f} ({e1 - e0:+.2f}, {100*(e1/e0-1):+.2f}%)"
|
||||
f" | picco ${picco:,.2f} | {len(eq)} letture")
|
||||
r = rendimento_twr(con, eq[-1]["ts_utc"])
|
||||
print(f" equity : ${e0:,.2f} -> ${e1:,.2f} ({e1 - e0:+,.2f} di equity, "
|
||||
f"movimenti di capitale INCLUSI) | picco ${picco:,.2f} | {len(eq)} letture")
|
||||
if r["eventi"]:
|
||||
print(f" movimenti capitale : {r['certi']:+,.2f} certi | {r['ambigui']:+,.2f} ambiguo/i"
|
||||
f" (restano nel P&L, dichiarati)")
|
||||
for ev in r["eventi"]:
|
||||
mk = ev.get("mercato_max_pct")
|
||||
if ev.get("dichiarazione"):
|
||||
dettaglio = ev["dichiarazione"]
|
||||
elif mk is None:
|
||||
dettaglio = ev.get("mercato_nota") or "mercato non misurabile"
|
||||
else:
|
||||
dettaglio = f"mercato max {100 * mk:.2f}% a leva piena, salto {100 * ev['pct']:+.1f}%"
|
||||
print(f" {ev['ts_dopo'][:16]} {ev['delta']:+,.2f} [{ev['classe']}: {dettaglio}]")
|
||||
for av in r.get("avvisi") or []:
|
||||
print(f" ⚠️ {av}")
|
||||
if r["trading"] is None:
|
||||
print(f" trading da arming : n/d ({r['motivo']})")
|
||||
else:
|
||||
print(f" trading da arming : {r['trading']:+,.2f} (equity al netto dei movimenti "
|
||||
f"certi; l'ora di un movimento RILEVATO esce intera, quella di un DICHIARATO "
|
||||
f"resta — vedi journal.rendimento_twr)")
|
||||
if r["twr"] is None:
|
||||
print(f" TWR : n/d ({r['motivo']})")
|
||||
else:
|
||||
segs = " x ".join(f"{100*s['ret']:+.2f}% [{s['da'][:10]} -> {s['a'][:10]}]"
|
||||
for s in r["segmenti"])
|
||||
print(f" TWR : {100*r['twr']:+.2f}% = {segs}")
|
||||
rt = con.execute("SELECT * FROM roundtrips ORDER BY ts_out").fetchall()
|
||||
lordo = sum(r["pnl_lordo"] for r in rt)
|
||||
fee_rt = sum(r["fee_quota"] for r in rt)
|
||||
@@ -101,8 +132,16 @@ def report() -> None:
|
||||
con.close()
|
||||
|
||||
|
||||
USO = """uso: trades_db.py [--sync] [--report] [--reconcile] [--quiet]
|
||||
|
||||
--sync (default) legge log e jsonl e aggiorna data/live/trades.db — idempotente
|
||||
--report stato del libro di bordo + P&L (TWR, non e1/e0)
|
||||
--reconcile incrocio delle tre fonti sui fill (log x jsonl x venue)
|
||||
--quiet con --sync: non stampa il riepilogo"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = sys.argv[1:]
|
||||
from src.live.cli import valida
|
||||
args = valida("trades_db.py", USO, flag=("--sync", "--report", "--reconcile", "--quiet"))
|
||||
if "--reconcile" in args:
|
||||
reconcile()
|
||||
elif "--report" in args:
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python
|
||||
"""usde_convert.py — porta la QUOTA di collaterale USDE a un bersaglio dichiarato. INVIA ORDINI VERI.
|
||||
|
||||
PERCHE' ESISTE. La conversione del 26/08 (test di eligibilita', 500 USDE) fu una chiamata a mano al
|
||||
gateway: nessuno script, nessuna guardia, nessuna traccia se non il diario. Il gate USDE-01 prevede
|
||||
entrambe le direzioni — IDONEO -> si decide la quota; NON IDONEO -> "si riconverte" — e nessuna delle
|
||||
due aveva un attrezzo. Qui c'e', e vale in tutti e due i versi (quota piu' alta = compra USDE, piu'
|
||||
bassa = vende).
|
||||
|
||||
PERCHE' NON PASSA DA DeribitTrader. `execution.ALLOWED` ammette solo i due perp del libro: e' il
|
||||
guardrail anti-fat-finger del percorso soldi del book e NON va allargato per far entrare uno spot che
|
||||
col libro non c'entra. Questo script parla al gateway per conto suo e porta le proprie guardie.
|
||||
|
||||
LE GUARDIE (tutte BLOCCANTI, tutte dichiarate qui):
|
||||
* banda di prezzo [0.995, 1.005] — la stessa del 26/08. Fuori banda non si converte: un USDE a
|
||||
0.98 e' esattamente il momento in cui NON si compra, e uno a 1.02 non esiste (si clampa a 1.0).
|
||||
* quota bersaglio in [0, QUOTA_HARD_MAX] — il gate USDE-01 dice "mai 100%: R1 emittente non
|
||||
recuperabile". Il tetto e' HARD e non si passa da riga di comando.
|
||||
* TETTO DEL VENUE (`usde.venue_cap_frac`). Deribit rifiuta gli acquisti oltre una frazione
|
||||
dell'equity totale — ~31,2%, MISURATA il 30-31/08 e non documentata da nessuna parte, col
|
||||
messaggio fuorviante `not_enough_funds_in_currency`. E' una frazione, non un livello: fatta
|
||||
scendere l'equity di $8, il tetto e' sceso con lei. Senza questa guardia il piano si dichiara
|
||||
VALIDO e poi il venue lo rifiuta a raffica — che e' esattamente il giro di venti minuti costato
|
||||
il 30/08. Il valore vive in config, non qui (P1: il bersaglio si deriva, non si ridichiara).
|
||||
* CUSCINO DI REGOLAMENTO. Il P&L e il funding dei perp USDC-lineari si regolano in USDC, non nel
|
||||
collaterale: a quota alta il saldo USDC va negativo alla prima perdita del libro e Deribit lo
|
||||
finanzia a interesse. Il margine NON e' il vincolo (r0830_usde_quota: l'haircut non morde fino
|
||||
al 90%) — il vincolo e' questo. Il cuscino richiesto e' il disaster-SL sulla MASSIMA esposizione
|
||||
che il libro puo' prendere: n_asset * frac * disaster_sl_pct * equity, tutto letto da
|
||||
config/live.json (P1: il bersaglio si deriva, non si ridichiara).
|
||||
* dry-run DI DEFAULT: senza --esegui stampa il piano e non manda niente.
|
||||
|
||||
IL TETTO PER-ORDINE (misurato il 30/08, non documentato da nessuna parte). Il venue rifiuta gli
|
||||
ordini spot grandi con `not_enough_funds_in_currency` ANCHE quando i fondi ci sono: 100 accettato
|
||||
e 300 rifiutato con $1.442 disponibili (e 500 era passato il 26/08 con piu' saldo). Il gateway non
|
||||
espone `available_withdrawal_funds` ne' `get_order_book`, quindi la causa vera NON e' leggibile da
|
||||
qui e il messaggio del venue e' fuorviante: si tratta il tetto come ignoto e si CHUNKA, dimezzando
|
||||
il blocco a ogni rifiuto. Costa zero: il book ha 3.990 unita' al miglior ask e la commissione spot
|
||||
e' 0, quindi N fill piccoli e un fill grande pagano lo stesso prezzo.
|
||||
|
||||
NON E' NE' LA TAGLIA NE' IL TEMPO: E' IL PREZZO. Diagnosi finale (30/08 20:02, tre ordini da 1
|
||||
USDE): SELL @1.0000 passa, BUY @1.0003 passa, BUY @1.0002 e BUY market falliscono. L'ask si era
|
||||
mosso da 1.0002 a 1.0003 e l'ordine aveva smesso di INCROCIARE: Deribit rifiuta un limite BUY non
|
||||
marcabile sullo spot con `not_enough_funds_in_currency`, che e' un messaggio fuorviante e ha fatto
|
||||
inseguire per venti minuti due ipotesi sbagliate (tetto di size, rate-limit). Il difetto vero era
|
||||
qui: il prezzo dell'ordine si derivava dall'INDICE (1.0001 + 1 tick) invece che dal BOOK. Sono due
|
||||
prezzi diversi con due mestieri diversi — l'indice MARCA il collaterale (usde.valuta, mediana
|
||||
multi-exchange: la lezione Binance 10/10), il book PREZZA lo scambio. Confonderli non sbaglia la
|
||||
valutazione, sbaglia l'esecuzione.
|
||||
|
||||
LO STORICO DELLE IPOTESI SBAGLIATE, tenuto apposta: 943.98 rifiutato per "Invalid params" (vero:
|
||||
min_trade_amount=1, il passo e' intero) -> 933 rifiutato per fondi con $1.541 disponibili (falso
|
||||
indizio) -> 100 passa e 300 no (sembrava un tetto di size: era l'ask che si muoveva in mezzo) ->
|
||||
anche 1 rifiutato otto volte (sembrava rate-limit: era sempre l'ask). Chi rilegge non deve rifare
|
||||
questo giro. Misurato subito dopo: a raffica il venue rifiuta anche 1 USDE,
|
||||
otto volte di fila, mentre 100 era appena passato. Il messaggio `not_enough_funds_in_currency` e'
|
||||
FUORVIANTE — il vincolo e' la cadenza (regolamento spot asincrono / rate-limit), non il saldo ne'
|
||||
la size. Percio' si SPAZIANO gli ordini (`--pausa`) e si fa backoff sul TEMPO, riducendo il blocco
|
||||
solo dopo rifiuti ripetuti. Dimezzare la size a ogni rifiuto, come faceva la prima versione, e' la
|
||||
reazione sbagliata al sintomo giusto: consuma i tentativi senza toccare la causa.
|
||||
|
||||
uv run python scripts/live/usde_convert.py --quota 0.70 # piano, nessun ordine
|
||||
uv run python scripts/live/usde_convert.py --quota 0.70 --esegui # manda l'ordine
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live import usde as U # noqa: E402
|
||||
from src.live.deribit import DeribitRead # noqa: E402
|
||||
|
||||
LOG = ROOT / "data" / "live" / "usde_convert.jsonl"
|
||||
PX_MIN, PX_MAX = 0.995, 1.005 # banda di sicurezza sul prezzo (26/08)
|
||||
QUOTA_HARD_MAX = 0.95 # gate USDE-01: "mai 100%"
|
||||
MIN_ORDER_USD = 5.0
|
||||
TOLL = 0.005 # entro mezzo punto di quota si considera gia' a bersaglio
|
||||
EPS_CUSCINO = 0.01 # 1 centesimo: il criterio e' ">= cuscino", e una quota derivata DAL
|
||||
# cuscino ci cade sopra esatta (0.30*E da entrambi i lati). Senza
|
||||
# questo epsilon il rumore float boccia il caso di pareggio.
|
||||
|
||||
|
||||
def cuscino_richiesto_usd(equity_tot: float) -> tuple[float, str]:
|
||||
"""Cuscino USDC di REGOLAMENTO = disaster-SL sulla massima esposizione lorda del libro.
|
||||
Derivato da config/live.json + src/live/book.py, mai ridichiarato qui."""
|
||||
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
||||
sl = float(cfg["disaster_sl_pct"])
|
||||
from src.live import book as B
|
||||
frac, n_asset = float(B.WEIGHT), 2
|
||||
lordo_max = equity_tot * frac * n_asset
|
||||
return lordo_max * sl, f"{n_asset} asset x frac {frac} x SL {sl:.0%} su ${equity_tot:,.2f}"
|
||||
|
||||
|
||||
def book_top(timeout: float = 5.0) -> tuple[float | None, float | None]:
|
||||
"""Miglior bid/ask dello spot USDE_USDC dall'API PUBBLICA Deribit (tokenless: il gateway non
|
||||
espone get_order_book). -> (bid, ask), (None, None) se non leggibile. Mai solleva.
|
||||
|
||||
NON e' il prezzo di `usde.valuta`: quello e' l'INDICE e serve a marcare il collaterale. Questo
|
||||
serve a far INCROCIARE l'ordine, ed e' l'unico che il matching engine guarda."""
|
||||
try:
|
||||
import requests
|
||||
r = requests.get("https://www.deribit.com/api/v2/public/get_order_book",
|
||||
params={"instrument_name": U.SPOT, "depth": 5}, timeout=timeout).json()
|
||||
res = r.get("result", r)
|
||||
bid = float(res["bids"][0][0]) if res.get("bids") else None
|
||||
ask = float(res["asks"][0][0]) if res.get("asks") else None
|
||||
return bid, ask
|
||||
except Exception:
|
||||
return None, None
|
||||
|
||||
|
||||
def piano(eq_usdc: float, eq_usde: float, px: float, quota_target: float,
|
||||
equity_tot: float, bid: float | None = None, ask: float | None = None) -> dict:
|
||||
"""PURA. -> piano di conversione, con verdetto e motivo. Non manda niente."""
|
||||
val_usde, _ = U.valuta(eq_usde, px)
|
||||
quota_ora = val_usde / equity_tot if equity_tot > 0 else 0.0
|
||||
val_target = quota_target * equity_tot
|
||||
delta_usd = val_target - val_usde # >0 compra USDE, <0 vende
|
||||
side = "buy" if delta_usd > 0 else "sell"
|
||||
# MARCABILE CON MARGINE. Il book REST pubblico e' in RITARDO sul matching engine: misurato il
|
||||
# 30/08, un BUY a ask+1tick (1.0003) e' stato rifiutato e uno a 1.0004 accettato un minuto dopo,
|
||||
# col book che riportava ask 1.0002x3855 in entrambi i casi. Percio' si incrocia con 5 tick di
|
||||
# margine e un TETTO DURO: il fill avviene al prezzo del LIBRO, non al limite, quindi il margine
|
||||
# non si paga se il book non si e' mosso, e se si e' mosso il tetto limita il danno a ~9 bps.
|
||||
# Senza book leggibile si RINUNCIA — non si tira a indovinare (P5).
|
||||
TICK, MARGINE = 0.0001, 5
|
||||
CAP_BUY, CAP_SELL = 1.0010, 0.9990
|
||||
rif = ask if side == "buy" else bid
|
||||
if rif is None:
|
||||
px_ord = None
|
||||
elif side == "buy":
|
||||
px_ord = round(min(rif + MARGINE * TICK, CAP_BUY), 4)
|
||||
else:
|
||||
px_ord = round(max(rif - MARGINE * TICK, CAP_SELL), 4)
|
||||
# PASSO DI SIZE = 1 USDE, misurato sul venue (30/08): 500 e 10 accettati, 943.98 e 933.99
|
||||
# rifiutati con "Invalid params". `get_instrument` non e' esposto dal gateway (404), quindi il
|
||||
# passo e' EMPIRICO e dichiarato tale. Si quantizza per DIFETTO: meno USDE = piu' USDC, cioe'
|
||||
# dal lato del cuscino di regolamento. Sul lato vendita il difetto va nello stesso verso.
|
||||
amount = float(math.floor(abs(delta_usd) / (px_ord or px)))
|
||||
|
||||
cfg = U.cfg()
|
||||
cap_frac = float(cfg.get("venue_cap_frac") or 0)
|
||||
cap_usd = cap_frac * equity_tot if cap_frac else None
|
||||
cuscino, come = cuscino_richiesto_usd(equity_tot)
|
||||
usdc_dopo = eq_usdc - delta_usd
|
||||
motivi = []
|
||||
if px_ord is None:
|
||||
motivi.append("book USDE_USDC non leggibile — non si prezza l'ordine sull'indice (l'indice"
|
||||
" marca il collaterale, il book prezza lo scambio)")
|
||||
elif not (PX_MIN <= px_ord <= PX_MAX):
|
||||
motivi.append(f"prezzo d'ordine {px_ord:.4f} fuori banda [{PX_MIN}, {PX_MAX}]")
|
||||
if not (PX_MIN <= px <= PX_MAX):
|
||||
motivi.append(f"prezzo {px:.4f} fuori banda [{PX_MIN}, {PX_MAX}]")
|
||||
if not (0.0 <= quota_target <= QUOTA_HARD_MAX):
|
||||
motivi.append(f"quota {quota_target:.0%} fuori dal tetto HARD {QUOTA_HARD_MAX:.0%} (gate USDE-01)")
|
||||
if abs(quota_ora - quota_target) <= TOLL:
|
||||
motivi.append(f"gia' a bersaglio ({quota_ora:.1%} vs {quota_target:.0%}, toll {TOLL:.1%})")
|
||||
if abs(delta_usd) < MIN_ORDER_USD:
|
||||
motivi.append(f"delta ${abs(delta_usd):,.2f} sotto il minimo ${MIN_ORDER_USD:,.2f}")
|
||||
if cap_usd is not None and val_target > cap_usd + EPS_CUSCINO:
|
||||
motivi.append(
|
||||
f"TETTO DEL VENUE: bersaglio ${val_target:,.2f} sopra ${cap_usd:,.2f} "
|
||||
f"({cap_frac:.1%} dell'equity, misurato {cfg.get('venue_cap_misurato')}). "
|
||||
f"Massimo raggiungibile: quota {cap_frac:.1%}")
|
||||
if usdc_dopo < cuscino - EPS_CUSCINO:
|
||||
motivi.append(f"cuscino di regolamento: USDC dopo ${usdc_dopo:,.2f} < ${cuscino:,.2f} richiesti ({come})")
|
||||
|
||||
return {"cap_usd": cap_usd, "cap_frac": cap_frac,
|
||||
"quota_ora": quota_ora, "quota_target": quota_target, "val_usde": val_usde,
|
||||
"equity_tot": equity_tot, "delta_usd": delta_usd, "side": side, "amount": amount,
|
||||
"px": px, "px_ordine": px_ord, "usdc_dopo": usdc_dopo, "cuscino": cuscino,
|
||||
"cuscino_come": come, "ok": not motivi, "motivi": motivi}
|
||||
|
||||
|
||||
def leggi_stato(c: DeribitRead) -> tuple[float, float, float | None, str]:
|
||||
eq_usdc = float(c.account_summary("USDC")["equity"])
|
||||
try:
|
||||
eq_usde = float(c.account_summary("USDE")["equity"])
|
||||
except Exception:
|
||||
eq_usde = 0.0
|
||||
px, fonte = U.prezzo(client=c)
|
||||
return eq_usdc, eq_usde, px, fonte
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--quota", type=float, required=True, help="quota USDE bersaglio, es. 0.70")
|
||||
ap.add_argument("--esegui", action="store_true", help="manda l'ordine (senza: solo il piano)")
|
||||
ap.add_argument("--chunk", type=float, default=100.0, help="blocco massimo per ordine (USDE)")
|
||||
ap.add_argument("--pausa", type=float, default=12.0, help="secondi fra un ordine e il successivo")
|
||||
a = ap.parse_args()
|
||||
|
||||
c = DeribitRead()
|
||||
eq_usdc, eq_usde, px, fonte = leggi_stato(c)
|
||||
if px is None:
|
||||
print(" ✗ prezzo USDE non leggibile — non si converte al buio (P5)")
|
||||
return 2
|
||||
val_usde, _ = U.valuta(eq_usde, px)
|
||||
equity_tot = eq_usdc + val_usde
|
||||
bid, ask = book_top()
|
||||
p = piano(eq_usdc, eq_usde, px, a.quota, equity_tot, bid, ask)
|
||||
|
||||
print("=" * 78)
|
||||
print(f" USDE CONVERT — {datetime.now(timezone.utc):%Y-%m-%dT%H:%M:%SZ}"
|
||||
f" {'ESECUZIONE' if a.esegui else 'PIANO (dry-run)'}")
|
||||
print("=" * 78)
|
||||
print(f" prezzo : indice {px:.4f} ({fonte}) · book bid {bid} / ask {ask}"
|
||||
f" -> ordine @ {p['px_ordine']}, banda [{PX_MIN}, {PX_MAX}]")
|
||||
print(f" equity : USDC ${eq_usdc:,.2f} + USDE {eq_usde:,.4f} (${val_usde:,.2f})"
|
||||
f" = ${equity_tot:,.2f}")
|
||||
print(f" quota : {p['quota_ora']:.1%} -> {p['quota_target']:.0%}"
|
||||
+ (f" · tetto venue {p['cap_frac']:.1%} = ${p['cap_usd']:,.2f}" if p.get("cap_usd") else ""))
|
||||
print(f" ordine : {p['side'].upper()} {p['amount']:,.2f} USDE_USDC @ limit {p['px_ordine']:.4f}"
|
||||
f" (${abs(p['delta_usd']):,.2f})")
|
||||
slack = p["usdc_dopo"] - p["cuscino"]
|
||||
print(f" USDC dopo : ${p['usdc_dopo']:,.2f} · cuscino richiesto ${p['cuscino']:,.2f}"
|
||||
f" ({p['cuscino_come']})")
|
||||
print(f" margine : ${slack:+,.2f} di slack sul cuscino"
|
||||
+ (" ⚠️ la quota e' ESATTAMENTE sul limite: zero slack" if abs(slack) <= EPS_CUSCINO else ""))
|
||||
for m in p["motivi"]:
|
||||
print(f" ✗ {m}")
|
||||
if not p["ok"]:
|
||||
print(" → NON si converte.")
|
||||
return 1
|
||||
if not a.esegui:
|
||||
print(" → piano valido. Rilancia con --esegui per mandarlo.")
|
||||
return 0
|
||||
|
||||
LOG.parent.mkdir(parents=True, exist_ok=True)
|
||||
chunk, fatti, tot_filled, prezzi, falliti = a.chunk, [], 0.0, [], 0
|
||||
for _ in range(60):
|
||||
eq_usdc, eq_usde, px, _f = leggi_stato(c)
|
||||
if px is None:
|
||||
print(" ✗ prezzo non piu' leggibile — mi fermo qui"); break
|
||||
val, _ = U.valuta(eq_usde, px)
|
||||
bid, ask = book_top()
|
||||
pp = piano(eq_usdc, eq_usde, px, a.quota, eq_usdc + val, bid, ask)
|
||||
if not pp["ok"]:
|
||||
print(f" · fine: {pp['motivi'][0]}")
|
||||
break
|
||||
amt = float(min(pp["amount"], math.floor(chunk)))
|
||||
if amt < 1:
|
||||
print(" ✗ blocco sceso sotto 1 USDE — mi fermo"); break
|
||||
resp = c._unwrap(c._post("/mcp-deribit/tools/place_order",
|
||||
{"instrument_name": U.SPOT, "side": pp["side"], "amount": amt,
|
||||
"type": "limit", "price": pp["px_ordine"],
|
||||
"label": f"usde-quota-{a.quota:.2f}"})) or {}
|
||||
if not isinstance(resp, dict) or resp.get("error") or "order" not in resp:
|
||||
err = resp.get("error", resp) if isinstance(resp, dict) else resp
|
||||
falliti += 1
|
||||
attesa = min(a.pausa * (2 ** falliti), 90.0) # backoff sul TEMPO, non sulla taglia
|
||||
if falliti >= 3: # solo allora sospetto la size
|
||||
chunk = max(1.0, math.floor(chunk / 2))
|
||||
print(f" · rifiutato {amt:,.0f}: {err} -> attendo {attesa:.0f}s"
|
||||
+ (f", blocco a {chunk:,.0f}" if falliti >= 3 else ""))
|
||||
time.sleep(attesa)
|
||||
with LOG.open("a") as f:
|
||||
f.write(json.dumps({"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"rifiutato": err, "amount": amt, "nuovo_chunk": chunk}) + "\n")
|
||||
if falliti >= 8:
|
||||
print(" ✗ troppi rifiuti consecutivi — mi fermo"); break
|
||||
continue
|
||||
falliti = 0
|
||||
order, trades = resp["order"], resp.get("trades", []) or []
|
||||
filled = float(order.get("filled_amount") or 0) or sum(float(t.get("amount", 0) or 0) for t in trades)
|
||||
tot_filled += filled
|
||||
prezzi += [(float(t["price"]), float(t["amount"])) for t in trades if t.get("price") and t.get("amount")]
|
||||
fatti.append({"order_id": order.get("order_id"), "state": order.get("order_state"),
|
||||
"amount": amt, "filled": filled})
|
||||
print(f" · {pp['side']} {amt:,.0f} -> {order.get('order_state')} filled {filled:,.0f}"
|
||||
f" (quota {pp['quota_ora']:.1%} -> bersaglio {a.quota:.0%})")
|
||||
if filled <= 0:
|
||||
print(" ✗ ordine accettato ma non fillato — mi fermo"); break
|
||||
time.sleep(a.pausa)
|
||||
|
||||
px_med = sum(q * v for q, v in prezzi) / sum(v for _, v in prezzi) if prezzi else None
|
||||
eq_usdc, eq_usde, px, _f = leggi_stato(c)
|
||||
val, _ = U.valuta(eq_usde, px)
|
||||
tot = eq_usdc + val
|
||||
print(f"\n ESITO : {tot_filled:,.0f} USDE in {len(fatti)} ordini"
|
||||
+ (f" @ {px_med:.4f} medio" if px_med else ""))
|
||||
print(f" conto : USDC ${eq_usdc:,.2f} + USDE {eq_usde:,.4f} (${val:,.2f}) = ${tot:,.2f}")
|
||||
print(f" quota : {val / tot:.2%} (bersaglio {a.quota:.0%})")
|
||||
with LOG.open("a") as f:
|
||||
f.write(json.dumps({"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"quota_target": a.quota, "tot_filled": tot_filled, "px_medio": px_med,
|
||||
"ordini": fatti, "quota_finale": val / tot if tot else None,
|
||||
"usdc": eq_usdc, "usde": eq_usde}) + "\n")
|
||||
print(f" registrato : {LOG.relative_to(ROOT)}")
|
||||
return 0 if tot_filled > 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Executable
+344
@@ -0,0 +1,344 @@
|
||||
#!/usr/bin/env python
|
||||
"""usde_watch.py — sorveglianza giornaliera del collaterale USDE: reward, depeg, quota. SOLA LETTURA.
|
||||
|
||||
PERCHE' ESISTE. Dal 2026-08-26 il conto tiene USDE come collaterale a rendimento (test di
|
||||
eligibilita': 500 USDE @ 1.0003, regola pre-registrata nel diario 2026-08-26-usde-analisi).
|
||||
Tre cose vanno sorvegliate e nessuna aveva un posto:
|
||||
|
||||
1. i REWARD giornalieri (~12:00 UTC) — sono la ragione della posizione, e il gateway non
|
||||
espone il Transaction Log: si rilevano per DELTA di equity USDE al netto dei trade spot.
|
||||
Metodo DICHIARATO, coi suoi limiti: un delta con trade nel mezzo si riporta con
|
||||
`con_trade=true` e se i trade non sono leggibili il delta resta NON ATTRIBUITO invece
|
||||
di essere inventato (P12: una riparazione silenziosa e' un'invenzione).
|
||||
2. il DEPEG — soglie e criteri in config/live.json `_nota_usde` (P6: dichiarati prima):
|
||||
warn = fuori dalla banda operativa dello spot e oltre il clamp per-fonte dell'indice;
|
||||
crit = meta' del buffer di haircut consumata.
|
||||
3. la QUOTA sul totale — N4: la quota e' l'unica leva contro il rischio emittente
|
||||
(-100% = 25 anni di resa, non recuperabile). Sopra `quota_max_frac` allerta, e vale
|
||||
anche per deriva PASSIVA: il libro perde -> la quota sale da sola.
|
||||
|
||||
VERDETTO DI ELIGIBILITA' (regola pre-registrata il 26/08, PRIMA dell'esito): >=1 reward
|
||||
entro il 29/08 -> IDONEO (si apre la decisione di quota, che e' dell'OPERATORE); zero reward
|
||||
alla lettura del 29/08 dopo la finestra delle 12:00 UTC -> NON IDONEO, si riconverte e la
|
||||
pista si chiude. Questo script APPLICA la regola, non la decide.
|
||||
|
||||
LIMITE DICHIARATO (P6: la latenza si confronta con la durata del fenomeno). Cadenza
|
||||
giornaliera (cron 12:35 UTC, dopo la finestra reward): il depeg Binance del 10/10/2025 duro'
|
||||
~8h, questa sorveglianza puo' MANCARLO. Accettato perche' la protezione di SIZING e' oraria
|
||||
per costruzione (shadow._collaterale_usde legge l'indice a ogni giro del book) e il danno e'
|
||||
limitato dalla quota. Qui si ALLERTA, non si protegge.
|
||||
|
||||
ALLARMI (P9: il massimo si spende per l'evento vero): 🚨 solo depeg<crit, ripetuto finche'
|
||||
attivo; ⚠️ per warn/quota/BLIND SOLO alla transizione (il giorno che compaiono); 📌 per il
|
||||
verdetto IDONEO. BLIND = conto non leggibile, registrato e detto (P5: "non vedo" non e'
|
||||
"va tutto bene"). La serie data/live/usde_watch.jsonl e' dentro il perimetro di backup ed
|
||||
e' sorvegliata da monitor_health (un watch fermo = niente allarmi depeg, e il silenzio
|
||||
si legge come "va tutto bene").
|
||||
|
||||
uv run python scripts/live/usde_watch.py # report completo
|
||||
uv run python scripts/live/usde_watch.py --quiet # stampa/allerta solo transizioni
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live import usde as U # noqa: E402
|
||||
from src.live.notifier import notify # noqa: E402
|
||||
|
||||
STATE = ROOT / "data" / "live" / "usde_watch.jsonl"
|
||||
EPS_REWARD = 1e-3 # USDE: sotto e' rumore di lettura; il reward atteso a $500 e' ~0.055/giorno
|
||||
|
||||
# --- finestra di eligibilita', DALLA REGOLA PRE-REGISTRATA (diario 2026-08-26-usde-analisi) ---
|
||||
FILL_TS = "2026-08-26T13:04:32Z" # conversione eseguita: 500 USDE @ 1.0003
|
||||
VERDETTO_DOPO = "2026-08-29T12:30:00Z" # ultima finestra reward del 29/08 conclusa
|
||||
|
||||
# La QUOTA e' dell'operatore, e il 2026-08-29 e' stata RINVIATA a lunedi' 31/08 per decidere su
|
||||
# piu' di una finestra invece che su una (il 27/08 aveva pagato ZERO, il 28/08 +0.052 USDE: fra
|
||||
# le due letture ci sono 3,80% e 1,90% annuo, cioe' tutta la differenza che conta). N9: una
|
||||
# decisione rinviata senza un promemoria e' rinviata per sempre — da lunedi' il watch lo dice
|
||||
# ogni giorno finche' la riga non viene tolta.
|
||||
DECISIONE_QUOTA_DAL = "2026-08-31" # lunedi': si decide la quota
|
||||
EPS_GIORNI = 0.5 # sotto mezza giornata l'APR non si annualizza
|
||||
|
||||
|
||||
def _parse(ts: str) -> datetime:
|
||||
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
|
||||
|
||||
def leggi(path: Path = STATE) -> list[dict]:
|
||||
if not path.exists():
|
||||
return []
|
||||
out = []
|
||||
for ln in path.read_text().splitlines():
|
||||
ln = ln.strip()
|
||||
if ln:
|
||||
out.append(json.loads(ln))
|
||||
return out
|
||||
|
||||
|
||||
def analizza(prev: dict | None, eq_now: float, trades_usde: float | None) -> dict:
|
||||
"""PURA. Reward per delta di equity USDE al netto dei trade spot fra le due letture.
|
||||
|
||||
prev senza equity (primo giro, o giro BLIND) -> baseline: nessun delta da attribuire.
|
||||
trades_usde None = storia trade non leggibile -> il delta si riporta ma NON si attribuisce
|
||||
a reward (P12): meglio un giorno di latenza che un reward inventato."""
|
||||
if prev is None or prev.get("eq_usde") is None:
|
||||
return dict(delta=None, reward_stimato=None, reward_rilevato=False, con_trade=None)
|
||||
delta = round(eq_now - float(prev["eq_usde"]), 8)
|
||||
if trades_usde is None:
|
||||
return dict(delta=delta, reward_stimato=None, reward_rilevato=False, con_trade=None)
|
||||
stima = round(delta - trades_usde, 8)
|
||||
return dict(delta=delta, reward_stimato=stima,
|
||||
reward_rilevato=bool(stima > EPS_REWARD),
|
||||
con_trade=bool(abs(trades_usde) > 0))
|
||||
|
||||
|
||||
def verdetto(records: list[dict], now: datetime) -> tuple[str, str]:
|
||||
"""PURA. Applica la regola pre-registrata del 26/08. -> (stato, motivo)."""
|
||||
for r in records:
|
||||
if r.get("reward_rilevato"):
|
||||
return "IDONEO", (f"reward rilevato il {r.get('data')} "
|
||||
f"(+{r.get('reward_stimato')} USDE)")
|
||||
if now >= _parse(VERDETTO_DOPO):
|
||||
return "NON_IDONEO", "zero reward entro la finestra del 29/08 -> riconvertire, pista chiusa"
|
||||
return "IN_ATTESA", f"finestre reward ~12:00 UTC, verdetto dopo {VERDETTO_DOPO}"
|
||||
|
||||
|
||||
def rendimento(records: list[dict]) -> dict:
|
||||
"""PURA. Il rendimento sulle finestre OSSERVATE, col loro NUMERO accanto.
|
||||
|
||||
E' il numero che serve alla decisione di quota, e l'unico modo onesto di darlo e' con `n`
|
||||
attaccato: un pagamento non e' un tasso. Il denominatore e' il TEMPO VERO fra la prima e
|
||||
l'ultima lettura misurabile, non il conteggio delle finestre — cosi' una finestra che non
|
||||
paga abbassa la stima invece di sparire (P5: il silenzio in una serie di ritorni si legge
|
||||
come zero, e qui e' proprio uno zero).
|
||||
|
||||
Si escludono le letture con trade nel mezzo (`con_trade`): quel delta non e' solo reward, e
|
||||
una riparazione silenziosa sarebbe un'invenzione (P12).
|
||||
"""
|
||||
usabili = [r for r in records
|
||||
if r.get("reward_stimato") is not None and not r.get("con_trade")]
|
||||
pagate = [r for r in usabili if r.get("reward_rilevato")]
|
||||
if not usabili:
|
||||
return dict(apr=None, finestre=0, pagate=0, giorni=0.0, tot=0.0,
|
||||
nota="nessuna finestra misurabile")
|
||||
tot = float(sum(r["reward_stimato"] for r in usabili))
|
||||
eq = [float(r["eq_usde"]) for r in usabili if r.get("eq_usde")]
|
||||
eq_med = sum(eq) / len(eq) if eq else 0.0
|
||||
# tempo vero coperto: dalla lettura PRIMA della prima misurabile, all'ultima
|
||||
i0 = records.index(usabili[0])
|
||||
t0 = records[max(0, i0 - 1)]["ts"]
|
||||
giorni = (usabili[-1]["ts"] - t0) / 86_400_000.0
|
||||
if giorni < EPS_GIORNI or eq_med <= 0:
|
||||
return dict(apr=None, finestre=len(usabili), pagate=len(pagate), giorni=giorni, tot=tot,
|
||||
nota=f"solo {giorni:.2f} giorni osservati: non si annualizza")
|
||||
apr = tot / eq_med / giorni * 365.0
|
||||
return dict(apr=apr, finestre=len(usabili), pagate=len(pagate), giorni=giorni, tot=tot,
|
||||
nota=f"{len(pagate)}/{len(usabili)} finestre hanno pagato")
|
||||
|
||||
|
||||
def quota_da_decidere(now: datetime, verdetto_corrente: str) -> bool:
|
||||
"""PURA. La decisione di quota e' dovuta? Solo se il conto e' IDONEO e il rinvio e' scaduto."""
|
||||
if verdetto_corrente != "IDONEO":
|
||||
return False
|
||||
return now.date().isoformat() >= DECISIONE_QUOTA_DAL
|
||||
|
||||
|
||||
def condizioni(rec: dict | None, c: dict) -> set[str]:
|
||||
"""PURA. Condizioni di allerta attive su un record."""
|
||||
if not rec:
|
||||
return set()
|
||||
if rec.get("stato") == "BLIND":
|
||||
return {"BLIND"}
|
||||
att = set()
|
||||
px = rec.get("px")
|
||||
if px is not None:
|
||||
if px < c["depeg_crit"]:
|
||||
att.add("DEPEG_CRIT")
|
||||
elif px < c["depeg_warn"]:
|
||||
att.add("DEPEG_WARN")
|
||||
q = rec.get("quota")
|
||||
if q is not None and q > c["quota_max_frac"]:
|
||||
att.add("QUOTA_OVER")
|
||||
return att
|
||||
|
||||
|
||||
def _safe_client():
|
||||
try:
|
||||
from src.live.deribit import DeribitRead
|
||||
return DeribitRead()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _trades_spot_da(client, ts_ms: int) -> tuple[float | None, int | None]:
|
||||
"""Somma con segno (buy +, sell -) dell'USDE scambiato spot dopo ts_ms. None = non leggibile."""
|
||||
if client is None:
|
||||
return None, None
|
||||
try:
|
||||
rows = client.trade_history(U.SPOT, limit=50)
|
||||
except Exception:
|
||||
return None, None
|
||||
tot, n = 0.0, 0
|
||||
for t in rows:
|
||||
if int(t.get("timestamp") or 0) <= ts_ms:
|
||||
continue
|
||||
amt = float(t.get("amount") or 0)
|
||||
tot += amt if (t.get("direction") or "").lower() == "buy" else -amt
|
||||
n += 1
|
||||
return round(tot, 8), n
|
||||
|
||||
|
||||
def main() -> int:
|
||||
quiet = "--quiet" in sys.argv
|
||||
c = U.cfg()
|
||||
records = leggi()
|
||||
prev = records[-1] if records else None
|
||||
now = datetime.now(timezone.utc)
|
||||
client = _safe_client()
|
||||
|
||||
stato, motivo_blind, eq_usde, eq_usdc = "OK", None, None, None
|
||||
if client is None:
|
||||
stato, motivo_blind = "BLIND", "gateway non raggiungibile"
|
||||
else:
|
||||
try:
|
||||
eq_usde = float(client.account_summary("USDE").get("equity") or 0)
|
||||
except Exception as e:
|
||||
stato, motivo_blind = "BLIND", f"conto USDE non leggibile ({type(e).__name__})"
|
||||
try:
|
||||
eq_usdc = float(client.account_summary("USDC").get("equity") or 0)
|
||||
except Exception:
|
||||
eq_usdc = None # quota non computabile: dichiarato, non 0
|
||||
|
||||
px, px_fonte = U.prezzo(client)
|
||||
usd = None
|
||||
if eq_usde is not None:
|
||||
usd, _nota = U.valuta(eq_usde, px)
|
||||
|
||||
trades_usde, n_trades = (None, None)
|
||||
an = dict(delta=None, reward_stimato=None, reward_rilevato=False, con_trade=None)
|
||||
if stato == "OK" and prev is not None and prev.get("eq_usde") is not None:
|
||||
trades_usde, n_trades = _trades_spot_da(client, int(prev["ts"]))
|
||||
an = analizza(prev, eq_usde, trades_usde)
|
||||
|
||||
quota = None
|
||||
if usd is not None and eq_usdc is not None and (usd + eq_usdc) > 0:
|
||||
quota = round(usd / (usd + eq_usdc), 4)
|
||||
|
||||
rec = dict(
|
||||
ts=int(now.timestamp() * 1000),
|
||||
data=now.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
stato=stato, motivo_blind=motivo_blind,
|
||||
eq_usde=eq_usde, px=px, px_fonte=px_fonte, usd=usd,
|
||||
eq_usdc=eq_usdc, quota=quota,
|
||||
trades_usde=trades_usde, n_trades=n_trades, **an,
|
||||
)
|
||||
v, v_motivo = verdetto(records + [rec], now)
|
||||
rec["verdetto"], rec["verdetto_motivo"] = v, v_motivo
|
||||
|
||||
STATE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with STATE.open("a") as fh:
|
||||
fh.write(json.dumps(rec) + "\n")
|
||||
|
||||
# --- allarmi: transizioni (warn/quota/blind), ripetizione solo per il crit ---
|
||||
att_now, att_prev = condizioni(rec, c), condizioni(prev, c)
|
||||
nuove = att_now - att_prev
|
||||
inviati = []
|
||||
if "DEPEG_CRIT" in att_now:
|
||||
notify("🚨 USDE DEPEG", {"indice": px, "soglia": c["depeg_crit"],
|
||||
"collaterale": f"${usd:,.0f}" if usd else "?",
|
||||
"azione": "valutare riconversione — la quota e' il controllo (N4)"})
|
||||
inviati.append("DEPEG_CRIT")
|
||||
if "DEPEG_WARN" in nuove:
|
||||
notify("⚠️ USDE sotto la pari", {"indice": px, "soglia": c["depeg_warn"],
|
||||
"nota": "entra gia' nel sizing orario del book"})
|
||||
inviati.append("DEPEG_WARN")
|
||||
if "QUOTA_OVER" in nuove:
|
||||
notify("⚠️ quota USDE sopra il tetto", {"quota": f"{quota:.1%}" if quota else "?",
|
||||
"tetto": f"{c['quota_max_frac']:.0%}",
|
||||
"nota": "anche una deriva passiva conta (il libro perde -> quota sale)"})
|
||||
inviati.append("QUOTA_OVER")
|
||||
if "BLIND" in nuove:
|
||||
notify("⚠️ usde_watch BLIND", {"motivo": motivo_blind or "?",
|
||||
"nota": "'non vedo' non e' 'va tutto bene' (P5)"})
|
||||
inviati.append("BLIND")
|
||||
v_prev = prev.get("verdetto") if prev else "IN_ATTESA"
|
||||
if v != v_prev:
|
||||
if v == "IDONEO":
|
||||
notify("📌 USDE: conto IDONEO ai reward", {"dettaglio": v_motivo,
|
||||
"prossimo passo": "decisione di quota dell'operatore (tetto allerta 50%)"})
|
||||
elif v == "NON_IDONEO":
|
||||
notify("⚠️ USDE: conto NON idoneo", {"dettaglio": v_motivo})
|
||||
inviati.append(f"VERDETTO->{v}")
|
||||
|
||||
# La QUOTA e' dell'operatore ed e' stata rinviata a lunedi': da quel giorno lo si dice OGNI
|
||||
# giorno, non solo alla transizione. Ripetersi qui e' voluto — non e' un allarme che chiede
|
||||
# un'azione impossibile (P14), e' una domanda che da lunedi' si puo' finalmente rispondere,
|
||||
# con accanto il numero per rispondere. Si smette togliendo `DECISIONE_QUOTA_DAL`.
|
||||
rend = rendimento(records + [rec])
|
||||
if quota_da_decidere(now, v):
|
||||
apr = "n/d" if rend["apr"] is None else f"{rend['apr']*100:.2f}% annuo"
|
||||
notify("📌 USDE: la decisione di QUOTA e' dovuta", {
|
||||
"quota attuale": f"{quota:.1%}" if quota is not None else "n/d",
|
||||
"tetto di allerta": f"{c['quota_max_frac']:.0%} (mai 100%: R1 emittente non recuperabile)",
|
||||
"rendimento osservato": f"{apr} — {rend['nota']}, {rend['giorni']:.1f} giorni",
|
||||
"nota": "un pagamento non e' un tasso: leggere l'APR con il suo n"})
|
||||
inviati.append("QUOTA_DA_DECIDERE")
|
||||
|
||||
cambiato = bool(inviati) or v != v_prev
|
||||
if not quiet or cambiato:
|
||||
print("=" * 78)
|
||||
print(f" USDE WATCH — {rec['data']} (config: {c['fonte']})")
|
||||
print("=" * 78)
|
||||
if stato == "BLIND":
|
||||
print(f" stato : BLIND — {motivo_blind}")
|
||||
else:
|
||||
print(f" equity USDE : {eq_usde:,.4f} @ {px if px is not None else 'n/d'}"
|
||||
f" ({px_fonte}) -> ${usd:,.2f}" if usd is not None else " equity USDE : n/d")
|
||||
if quota is not None:
|
||||
# Il conto e' Segregated Standard Margin (letto sulla pagina margini il 31/08):
|
||||
# l'USDE NON fa margine per i perp USDC-settled, e l'haircut non si applica. Il
|
||||
# margine del book e' il solo silo USDC. Si stampa anche cosa DAREBBE il cross,
|
||||
# perche' e' la differenza che rende la cosa una decisione invece che un dettaglio.
|
||||
cross = eq_usdc + usd * (1.0 - c["haircut"])
|
||||
print(f" quota : {quota:.1%} del totale (tetto allerta {c['quota_max_frac']:.0%})")
|
||||
print(f" margine : ~${eq_usdc:,.0f} — il solo USDC (conto SEGREGATO: l'USDE non"
|
||||
f" fa margine). Con cross X:SM sarebbero ~${cross:,.0f} (haircut"
|
||||
f" {c['haircut']:.0%})")
|
||||
if an["delta"] is not None:
|
||||
attr = ("non attribuibile (trade non leggibili)" if an["reward_stimato"] is None
|
||||
else f"reward stimato {an['reward_stimato']:+.6f} USDE"
|
||||
+ (" [con trade nel mezzo]" if an["con_trade"] else ""))
|
||||
print(f" delta : {an['delta']:+.6f} USDE dall'ultima lettura -> {attr}")
|
||||
print(f" verdetto : {v} — {v_motivo}")
|
||||
if rend["finestre"]:
|
||||
apr = "n/d" if rend["apr"] is None else f"{rend['apr']*100:.2f}% annuo"
|
||||
print(f" rendimento : {apr} su {rend['giorni']:.1f} giorni — {rend['nota']}"
|
||||
f" (tot {rend['tot']:+.6f} USDE)")
|
||||
if rend["finestre"] < 4:
|
||||
print(f" ⚠️ n={rend['finestre']}: un pagamento non e' un tasso")
|
||||
if v == "IDONEO" and not quota_da_decidere(now, v):
|
||||
print(f" quota : decisione RINVIATA a {DECISIONE_QUOTA_DAL} (operatore)")
|
||||
if inviati:
|
||||
print(f" allarmi : {', '.join(inviati)}")
|
||||
return 0
|
||||
|
||||
|
||||
USO = """uso: usde_watch.py [--quiet]
|
||||
|
||||
usde_watch.py — sorveglianza giornaliera del collaterale USDE: reward, depeg, quota. SOLA LETTURA.
|
||||
uv run python scripts/live/usde_watch.py # report completo
|
||||
uv run python scripts/live/usde_watch.py --quiet # stampa/allerta solo transizioni
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("usde_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python
|
||||
"""venue_news.py — legge cosa il venue ANNUNCIA. SOLA LETTURA, non blocca mai nulla.
|
||||
|
||||
PERCHE' ESISTE (2026-08-31). Il debito #8 dice, testuale: *«Resta scoperto il Rulebook vero e
|
||||
proprio: la sonda legge se il venue RISPONDE, non cosa il venue ANNUNCIA.»* Questa e' quella
|
||||
meta'. In una settimana quattro cambi di policy Deribit materiali sono arrivati **a mano**, perche'
|
||||
l'operatore ha incollato dei link — non perche' qualcosa li avesse visti:
|
||||
|
||||
* tetto sull'USDE (~31,2% dell'equity) * esclusione dell'Italia dai reward USDC
|
||||
* permesso mancante su BUIDL * fine della Proof of Reserves quotidiana
|
||||
|
||||
E il caso che decide la questione: le **specifiche dei perpetual lineari USDC** sono cambiate il
|
||||
18/08, annunciate il **14/08**. `check_specs()` rileva la deriva DOPO che e' avvenuta ed e' nato
|
||||
proprio da quella svista; il feed l'avrebbe detto **quattro giorni prima**. Rilevare a posteriori e
|
||||
sapere in anticipo non sono la stessa cosa: la prima volta e' andata bene solo perche' i cambi
|
||||
erano RIDUZIONI (un valore piu' grosso resta conforme). Il giorno che Deribit ALZA un minimo, gli
|
||||
ordini vengono rifiutati.
|
||||
|
||||
COSA E' E COSA NON E'. E' un promemoria che legge un feed RSS pubblico e dice *«e' uscito questo,
|
||||
guardalo»*. **Non interpreta e non decide**: nessun automatismo su un testo di marketing (P13 — una
|
||||
guardia sui NUMERI non copre il RAGIONAMENTO, e la prosa si legge come opinione di un lettore
|
||||
fallibile). L'unica cosa che classifica e' l'URGENZA, e lo fa su parole DERIVATE dal codice
|
||||
sorvegliato, non ridichiarate qui (P1): gli strumenti vengono da `deribit._CONTRACT`, le valute di
|
||||
collaterale da `config/live.json`. Chi aggiunge uno strumento o una valuta allarga da solo la
|
||||
sorveglianza, senza toccare questo file.
|
||||
|
||||
uv run python scripts/live/venue_news.py # report + allerta sui nuovi
|
||||
uv run python scripts/live/venue_news.py --quiet # solo allerta (per il cron)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import html
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live.notifier import notify # noqa: E402
|
||||
|
||||
FEED = "https://insights.deribit.com/exchange-updates/feed/"
|
||||
STATE = ROOT / "data" / "live" / "venue_news.jsonl"
|
||||
|
||||
# Parole che alzano un annuncio a 🚨. Le prime sono FISSE (categorie di rischio che ci toccano
|
||||
# per costruzione); le altre si DERIVANO dal codice, sotto.
|
||||
_CHIAVI_FISSE = (
|
||||
"contract specification", "margin", "collateral", "fee", "delist", "discontinu",
|
||||
"reserve", "maintenance", "liquidation", "settlement", "withdrawal", "reward",
|
||||
"jurisdiction", "eligib", "insurance fund", "socialized", "adl", "rulebook",
|
||||
)
|
||||
|
||||
|
||||
def chiavi() -> list[str]:
|
||||
"""Parole-chiave DERIVATE dal codice sorvegliato (P1), piu' quelle fisse."""
|
||||
out = set(_CHIAVI_FISSE)
|
||||
try:
|
||||
from src.live.deribit import _CONTRACT
|
||||
for nome in _CONTRACT: # BTC_USDC-PERPETUAL -> "btc", "usdc"
|
||||
for pezzo in re.split(r"[_\-]", nome.lower()):
|
||||
if len(pezzo) >= 3 and pezzo != "perpetual":
|
||||
out.add(pezzo)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
cfg = json.loads((ROOT / "config" / "live.json").read_text())
|
||||
out.add(str(cfg.get("usde", {}).get("index_name", "")).split("_")[0].lower())
|
||||
except Exception:
|
||||
pass
|
||||
return sorted(k for k in out if k)
|
||||
|
||||
|
||||
def scarica(url: str = FEED, timeout: float = 20.0) -> str | None:
|
||||
"""Feed RSS pubblico (tokenless). None se non leggibile. Mai solleva."""
|
||||
try:
|
||||
import requests
|
||||
r = requests.get(url, timeout=timeout,
|
||||
headers={"User-Agent": "PythagorasGoal/venue_news (+read-only)"})
|
||||
return r.text if r.status_code == 200 else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def voci(xml: str) -> list[dict]:
|
||||
"""PURA. Estrae (guid, titolo, data, link) dal RSS. Mai solleva."""
|
||||
out = []
|
||||
for blocco in re.findall(r"<item>(.*?)</item>", xml or "", re.S):
|
||||
def campo(tag):
|
||||
m = re.search(rf"<{tag}[^>]*>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?</{tag}>", blocco, re.S)
|
||||
return html.unescape(m.group(1).strip()) if m else ""
|
||||
g = campo("guid") or campo("link")
|
||||
if g:
|
||||
out.append({"guid": g, "titolo": campo("title"),
|
||||
"data": campo("pubDate"), "link": campo("link")})
|
||||
return out
|
||||
|
||||
|
||||
def rilevante(titolo: str, ks: list[str]) -> list[str]:
|
||||
"""PURA. Quali parole-chiave compaiono nel titolo."""
|
||||
t = titolo.lower()
|
||||
return [k for k in ks if k in t]
|
||||
|
||||
|
||||
def gia_visti() -> set[str]:
|
||||
if not STATE.exists():
|
||||
return set()
|
||||
out = set()
|
||||
for riga in STATE.read_text().splitlines():
|
||||
try:
|
||||
out.add(json.loads(riga)["guid"])
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--quiet", action="store_true")
|
||||
a = ap.parse_args()
|
||||
|
||||
xml = scarica()
|
||||
if xml is None:
|
||||
if not a.quiet:
|
||||
print(" ✗ feed non leggibile — 'non vedo' non e' 'niente di nuovo' (P5)")
|
||||
return 2
|
||||
|
||||
ks, viste, nuove = chiavi(), gia_visti(), []
|
||||
primo_giro = not STATE.exists()
|
||||
tutte = voci(xml)
|
||||
STATE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with STATE.open("a") as f:
|
||||
for v in tutte:
|
||||
if v["guid"] in viste:
|
||||
continue
|
||||
v["match"] = rilevante(v["titolo"], ks)
|
||||
v["visto_il"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
v["seed"] = primo_giro
|
||||
f.write(json.dumps(v) + "\n")
|
||||
nuove.append(v)
|
||||
|
||||
# Primo giro: si semina lo storico SENZA allertare (P9: l'allarme massimo non si spende
|
||||
# per un arretrato di 10 voci, o non verra' letto il giorno che e' vero).
|
||||
if not primo_giro:
|
||||
for v in nuove:
|
||||
if v["match"]:
|
||||
notify("🚨 annuncio Deribit sul NOSTRO percorso",
|
||||
{"titolo": v["titolo"], "tocca": ", ".join(v["match"][:6]),
|
||||
"data": v["data"], "link": v["link"],
|
||||
"azione": "leggerlo: questo sorvegliante NON interpreta"})
|
||||
else:
|
||||
notify("📌 nuovo annuncio Deribit",
|
||||
{"titolo": v["titolo"], "data": v["data"], "link": v["link"]})
|
||||
|
||||
if not a.quiet:
|
||||
print("=" * 78)
|
||||
print(f" VENUE NEWS — {len(tutte)} voci nel feed, {len(nuove)} nuove"
|
||||
+ (" (PRIMO GIRO: storico seminato, nessun allarme)" if primo_giro else ""))
|
||||
print("=" * 78)
|
||||
for v in tutte:
|
||||
m = rilevante(v["titolo"], ks)
|
||||
segno = "🚨" if m else ("·" if v["guid"] in viste else "📌")
|
||||
print(f" {segno} {v['data'][:16]:17s} {v['titolo'][:70]}")
|
||||
if m:
|
||||
print(f" tocca: {', '.join(m[:8])}")
|
||||
print(f"\n parole derivate dal codice: {', '.join(ks)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
+39
-15
@@ -16,14 +16,37 @@ ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live.deribit import check_specs # noqa: E402
|
||||
from src.live.notifier import notify # noqa: E402
|
||||
from src.live.notifier import notify, ultimo_errore # noqa: E402
|
||||
from src.live.venue_watch import (PERSIST_HOURS, THRESHOLD_BPS, # noqa: E402
|
||||
run_once)
|
||||
|
||||
|
||||
def _titolo(rep: dict) -> str:
|
||||
"""⚠️ Il titolo dice cosa FARE, non solo cosa e' successo: chi lo legge sul telefono deve
|
||||
sapere il primo passo senza aprire il repo (runbook completo in src/live/venue_watch.py).
|
||||
⚠️ Dal 19/08 DIPENDE dalla gravita'. Prima era sempre il 🚨 col prelievo di prova, anche per
|
||||
un blocco da manutenzione annunciata: il 18/08 sono usciti quattro messaggi identici che
|
||||
chiedevano di prelevare, tutti dopo che il book aveva gia' ripreso. Un allarme massimo speso
|
||||
per un evento atteso e' un allarme che non verra' letto il giorno che e' vero."""
|
||||
if rep.get("severity") == "warn":
|
||||
return "⚠️ VENUE WATCH — Deribit non operativa, ma spiegata. Nessuna azione"
|
||||
return "🚨 VENUE WATCH — possibile stress su Deribit. PRIMO PASSO: prelievo di prova"
|
||||
|
||||
|
||||
def _manda(rep: dict) -> tuple[bool, str]:
|
||||
"""Il sender iniettato in `run_once`: e' lui a decidere se i marcatori 'gia' detto' si
|
||||
scrivono. `tentativi=3` perche' su questo percorso il 6,9% degli invii si perde, e qui
|
||||
perderne uno significa perdere l'episodio intero."""
|
||||
ok = notify(_titolo(rep), {f"alert {i+1}": a for i, a in enumerate(rep["alerts"])},
|
||||
tentativi=3)
|
||||
if ok:
|
||||
return True, "ok"
|
||||
return False, (ultimo_errore() or "motivo non registrato")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
quiet = "--quiet" in sys.argv
|
||||
rep = run_once()
|
||||
rep = run_once(sender=_manda)
|
||||
|
||||
if not quiet:
|
||||
print("=" * 78)
|
||||
@@ -65,24 +88,25 @@ def main() -> int:
|
||||
print(f" SPEC: {k}: {v}")
|
||||
|
||||
if rep["alerts"]:
|
||||
# ⚠️ Il titolo dice cosa FARE, non solo cosa e' successo: chi lo legge sul telefono deve
|
||||
# sapere il primo passo senza aprire il repo (runbook completo in src/live/venue_watch.py).
|
||||
# ⚠️ Dal 19/08 il titolo DIPENDE dalla gravita'. Prima era sempre il 🚨 col prelievo di
|
||||
# prova, anche per un blocco da manutenzione annunciata: il 18/08 sono usciti quattro
|
||||
# messaggi identici che chiedevano di prelevare, tutti dopo che il book aveva gia'
|
||||
# ripreso. Un allarme massimo speso per un evento atteso e' un allarme che non verra'
|
||||
# letto il giorno che e' vero — ed e' l'unico giorno che conta.
|
||||
if rep.get("severity") == "warn":
|
||||
titolo = "⚠️ VENUE WATCH — Deribit non operativa, ma spiegata. Nessuna azione"
|
||||
else:
|
||||
titolo = ("🚨 VENUE WATCH — possibile stress su Deribit. "
|
||||
"PRIMO PASSO: prelievo di prova")
|
||||
notify(titolo, {f"alert {i+1}": a for i, a in enumerate(rep["alerts"])})
|
||||
# L'invio l'ha gia' fatto `run_once` attraverso `_manda`: qui si stampa e si riporta
|
||||
# l'ESITO. Un invio fallito si vede nel log del cron invece di sparire — e lo stato su
|
||||
# disco e' stato disfatto, quindi l'ora prossima ci riprova da solo.
|
||||
for a in rep["alerts"]:
|
||||
print(f" ALERT: {a}")
|
||||
print(f" telegram: {rep.get('invio')}")
|
||||
return 2 if rep.get("severity") == "alert" else 0
|
||||
return 0
|
||||
|
||||
|
||||
USO = """uso: venue_watch.py [--quiet]
|
||||
|
||||
venue_watch.py — runner ORARIO del tripwire di venue. Allerta su Telegram, NON blocca nulla.
|
||||
uv run python scripts/live/venue_watch.py # un giro, stampa il report
|
||||
uv run python scripts/live/venue_watch.py --quiet # solo in caso di allarme (per il cron)
|
||||
|
||||
Dettaglio nel docstring in testa al file."""
|
||||
|
||||
if __name__ == "__main__":
|
||||
from src.live.cli import valida
|
||||
valida("venue_watch.py", USO, flag=("--quiet",), con_valore=())
|
||||
raise SystemExit(main())
|
||||
|
||||
@@ -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
|
||||
del **18.4%**, quindi sul credito NETTO l'effetto e' minore: f_net **0.852** contro **0.718**.
|
||||
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
|
||||
[-0.047, +0.193]**, cioe' **compatibile con zero**. Serve piu' campione, e "quando ce ne sara'
|
||||
|
||||
@@ -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
|
||||
skew, prezzata dal modello a vol ATM) -> misurare una gamba sola da' la risposta sbagliata con
|
||||
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
|
||||
|
||||
@@ -65,23 +69,48 @@ def puts(asset: str, df: pd.DataFrame | None = None) -> pd.DataFrame:
|
||||
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)
|
||||
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
|
||||
px = load_tf(asset, "1h")
|
||||
s = pd.Series(px["close"].values.astype(float),
|
||||
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
|
||||
return s
|
||||
return causale(s, "1h")
|
||||
|
||||
|
||||
@lru_cache(maxsize=8)
|
||||
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 D−1, 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")
|
||||
s = pd.Series(d["close"].values.astype(float),
|
||||
index=pd.to_datetime(d["timestamp"], unit="ms", utc=True)).sort_index()
|
||||
s.index = pd.DatetimeIndex(s.index).as_unit("ns")
|
||||
return s
|
||||
return causale(s, "1D")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ prezzo
|
||||
|
||||
@@ -129,7 +129,21 @@ def main():
|
||||
sys.exit(2)
|
||||
ib = IB()
|
||||
try:
|
||||
ib.connect("127.0.0.1", 4002, clientId=90, timeout=15)
|
||||
# `readonly=True`: questo client SCARICA STORICO e non manda ordini mai. Non e' solo
|
||||
# igiene — in `ib_async.IB.connectAsync` le richieste "open orders" e "completed orders"
|
||||
# esistono SOLO se il client non e' readonly (`if not readonly: reqs[...]`), e sul gateway
|
||||
# paper non rispondono: ogni notte, 63 giri su 63, il log del cron si prendeva
|
||||
# open orders request timed out
|
||||
# completed orders request timed out
|
||||
# Due righe d'errore innocue ripetute per sempre sono il modo in cui un errore VERO
|
||||
# smette di farsi notare (P14). Qui si toglie la CAUSA, non si filtra il messaggio.
|
||||
# ⚠️ OSSERVATO il 2026-08-28: a meta' pomeriggio il gateway NON serve storico —
|
||||
# `reqHistoricalData` va in timeout e lo script stampa "0 barre (subscription?)" per ogni
|
||||
# simbolo. Quattro tentativi fra le 13:05 e le 15:35 UTC, tutti a zero, con e senza
|
||||
# `readonly`; il giro del cron delle 00:30 riesce ogni notte. La causa sta nel gateway,
|
||||
# non qui, e non e' stata diagnosticata. Non e' pericoloso: con 0 barre lo script NON
|
||||
# sovrascrive i parquet, quindi un giro fallito lascia il dato di ieri invece di romperlo.
|
||||
ib.connect("127.0.0.1", 4002, clientId=90, timeout=15, readonly=True)
|
||||
except Exception as e:
|
||||
print(f"[CONNESSIONE FALLITA] 127.0.0.1:4002 -> {repr(e)[:120]}\n Avvia: docker compose up -d ib-gateway")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -11,6 +11,23 @@ deflated-Sharpe 0.985, corr ~0 a tutti e 5 gli sleeve. Sopravvive a tre null fee
|
||||
test di lag. NON e' nel book per tre motivi dichiarati: storia corta e monotona crescente,
|
||||
`weights_tilt_null` fallito, e margine di costo sottile con slippage NON modellato.
|
||||
|
||||
⚠️ MODIFICA DICHIARATA 2026-08-26 (diario `2026-08-26-xsr01-gate-riscritto.md`) — decisa
|
||||
dall'operatore PRIMA di vedere l'esito, a 58 giorni dalla data, e in direzione che STRINGE:
|
||||
1. CAPITALE per il deploy: $5.000 -> $20.000. Non e' tuning sull'esito: riconcilia il gate
|
||||
(scritto il 25/07) con la decisione vincolante "100% Deribit fino a $20k" (26/07), che e'
|
||||
POSTERIORE e lo governa — sotto $20k nessun capitale esce da Deribit, quindi il criterio
|
||||
a $5k autorizzava una cosa che un'altra decisione gia' vietava (M28).
|
||||
2. GAMBA HAIRCUT: il numero decisivo alla decisione e' quello dello script committato
|
||||
`r0826_xsr_haircut.py` a pavimento $10 (il pavimento VERO misurato, HL-EXEC 22/08 — il
|
||||
libro REAL-$5000 del monitor gira a $5 ed e' ottimista per costruzione), CITATO ACCANTO
|
||||
alla frazione di ordini eseguiti (P7): a $10 si esegue ~1 ordine su 5, e un haircut
|
||||
piccolo con pochi ordini eseguiti non e' eseguibilita', e' un libro fermo. La SOGLIA
|
||||
resta il 40% pre-registrato: nessuna soglia nuova viene scritta oggi coi dati in mano.
|
||||
3. Il riferimento "in-sample 1.82" era una TERZA lente (XSR-REPRO 22/08): la lente dei gate
|
||||
da' 1.79 alla scoperta. Corretto il contesto, non il criterio.
|
||||
La serie forward e' quella RIGENERATA il 26/08 (advance() riparato, §5.1): stessa finestra,
|
||||
stesso forward-day 0, barre finalmente VERE.
|
||||
|
||||
REGOLA (immutabile — ogni modifica va motivata nel diario come violazione):
|
||||
- Data decisione: 2026-10-23 (90 giorni forward dal 2026-07-25). Non prima.
|
||||
- Metrica primaria: Sharpe annualizzato di `net_modeled` su TUTTA la finestra forward.
|
||||
@@ -22,7 +39,7 @@ REGOLA (immutabile — ogni modifica va motivata nel diario come violazione):
|
||||
- Esiti:
|
||||
Sharpe >= 1.0 AND haircut$5000 <= 40% -> CANDIDATO SLEEVE: proporre peso {5,10}% e
|
||||
giudicarlo con `weights_tilt_null` (che oggi NON passa) + maxDD combinato.
|
||||
Deploy solo se il tilt-null passa E il capitale e' >= $5000.
|
||||
Deploy solo se il tilt-null passa E il capitale e' >= $20.000 (mod. 26/08).
|
||||
0.3 <= Sh < 1.0 -> ESTENDI una sola volta di 90g (2027-01-21).
|
||||
Sharpe < 0.3 OR haircut > 40% -> RITIRO dal forward-monitor.
|
||||
- Guardie accessorie: config invariata (il test `tests/test_paper_xsr.py` la blocca);
|
||||
@@ -47,7 +64,8 @@ DECISION_EXT = date(2027, 1, 21)
|
||||
SH_DEPLOY = 1.0
|
||||
SH_RETIRE = 0.3
|
||||
HAIRCUT_MAX = 0.40
|
||||
IS_SHARPE_NET = 1.82 # riferimento in-sample, per contesto
|
||||
CAPITALE_DEPLOY = 20_000.0 # mod. dichiarata 26/08: riconcilia col muro "100% Deribit" (26/07)
|
||||
IS_SHARPE_NET = 1.79 # lente dei gate alla scoperta (il "1.82" era una terza lente)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -78,7 +96,10 @@ def main() -> None:
|
||||
print(f" Sharpe fwd (mod) : {sh:+.2f} (in-sample netta era {IS_SHARPE_NET:.2f})")
|
||||
print(f" maxDD fwd : {dd:.1%}")
|
||||
print(f" ritorno MODELED : {tot_m*100:+.2f}% REAL-$5000: {tot_5*100:+.2f}%")
|
||||
print(f" haircut eseguib. : {haircut*100:.1f}% (guardia <= {HAIRCUT_MAX*100:.0f}%)")
|
||||
print(f" haircut monitor : {haircut*100:.1f}% (pavimento $5 — ottimista per costruzione)")
|
||||
print(f" guardia <= {HAIRCUT_MAX*100:.0f}%: il numero DECISIVO e' quello di "
|
||||
f"`r0826_xsr_haircut.py` a pavimento $10,")
|
||||
print( " citato accanto alla frazione di ordini eseguiti (mod. 26/08)")
|
||||
print(f" data decisione : {DECISION} (proroga unica: {DECISION_EXT})")
|
||||
|
||||
if today < DECISION:
|
||||
@@ -89,7 +110,8 @@ def main() -> None:
|
||||
print("\n -> RITIRO: l'haircut di eseguibilita' supera la guardia. E' costo, non edge.")
|
||||
elif sh >= SH_DEPLOY:
|
||||
print("\n -> CANDIDATO SLEEVE: proporre peso {5,10}% e passarlo a weights_tilt_null "
|
||||
"(oggi NON passa) + maxDD combinato. Deploy solo con capitale >= $5000.")
|
||||
f"(oggi NON passa) + maxDD combinato. Deploy solo con capitale >= "
|
||||
f"${CAPITALE_DEPLOY:,.0f} (mod. 26/08, allineato a '100% Deribit fino a $20k').")
|
||||
elif sh >= SH_RETIRE:
|
||||
print(f"\n -> SOTTO SOGLIA ma >= {SH_RETIRE}: estensione unica fino al {DECISION_EXT}.")
|
||||
else:
|
||||
|
||||
@@ -310,11 +310,15 @@ def boot_ci(x: np.ndarray, n: int = 4000, seed: int = 822) -> tuple[float, float
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
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()
|
||||
|
||||
print(__doc__.split("\n\n")[0])
|
||||
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)
|
||||
|
||||
# ------------------------------------------------------------ §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"
|
||||
"la strategia — contiene la sua astensione.\n")
|
||||
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())]
|
||||
hist = V[V.index < win.index[0]].to_numpy(float)
|
||||
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"
|
||||
" numero non e' un criterio, quindi eccolo (IV-rank espandente causale, come nel sleeve):")
|
||||
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)
|
||||
ivr = np.full(len(vals), np.nan)
|
||||
for i in range(1, len(vals)):
|
||||
@@ -753,13 +757,17 @@ def main() -> None:
|
||||
|
||||
# ------------------------------------------------------------ §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.
|
||||
|
||||
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
|
||||
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
|
||||
(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
|
||||
@@ -770,13 +778,13 @@ def main() -> None:
|
||||
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
|
||||
qui per la prima volta su questa struttura: su 9 ancore lo Sharpe canonico BTC 32.58
|
||||
sta all'89 percentile, la MEDIANA ONESTA e' 1.45 e la banda tocca il NEGATIVO (-0.35).
|
||||
Su ETH 3.90 -> 2.41. Il numero da citare per BTC e' 1.45, non 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' 2.12 e la banda tocca il NEGATIVO (-0.19).
|
||||
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
|
||||
vol campionaria a ballare: fra le ancore la MEDIA settimanale BTC va da -1.35% a
|
||||
+11.22% del rischio — CAMBIA SEGNO — e la deviazione standard di 11x (ETH: media 2.9x,
|
||||
sd 2.4x). Spostare l'ora d'ingresso cambia lo snapshot e quindi quali strike combaciano
|
||||
vol campionaria a ballare: fra le ancore la MEDIA settimanale BTC va da -0.66% a
|
||||
+11.44% del rischio — CAMBIA SEGNO — e la deviazione standard di 9.2x (ETH: media 2.1x,
|
||||
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
|
||||
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
|
||||
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
|
||||
~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
|
||||
con un percorso diverso.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ SOLA LETTURA. Questo script:
|
||||
- non importa nulla che invii ordini se non per ISPEZIONARLO (la sottoclasse di §1 ha `_post` che
|
||||
SOLLEVA: se il codice di produzione toccasse la rete, il test fallirebbe rumorosamente);
|
||||
- tocca il venue solo con `--venue`, solo `get_open_orders` (lettura), 2 richieste, e si rifiuta
|
||||
di partire nei minuti :05-:10 (cron_book) e :24-:30 (cron_chain).
|
||||
di partire nei minuti :45-:50 (cron_book, al :47 dal 25/08) e :24-:30 (cron_chain).
|
||||
|
||||
uv run python scripts/research/r0823_sl_anchor.py # attesa + log + simulazione
|
||||
uv run python scripts/research/r0823_sl_anchor.py --venue # + riscontro sul conto (2 letture)
|
||||
@@ -283,8 +283,8 @@ def riscontro_venue(runs_info: dict):
|
||||
diritte di Deribit appartengono a un ALTRO progetto: usarle sarebbe inventare un percorso, e la
|
||||
domanda non ne ha bisogno. Si usa quindi cio' che il percorso sanzionato offre: `get_open_orders`.""")
|
||||
m = datetime.now(timezone.utc).minute
|
||||
if 5 <= m <= 10 or 24 <= m <= 30:
|
||||
print(f"\n ⏸ minuto :{m:02d} — finestra di cron_book (:07) / cron_chain (:25). Non interrogo.")
|
||||
if 45 <= m <= 50 or 24 <= m <= 30: # cron_book al :47 dal 2026-08-25 (era :07), cron_chain :25
|
||||
print(f"\n ⏸ minuto :{m:02d} — finestra di cron_book (:47) / cron_chain (:25). Non interrogo.")
|
||||
return
|
||||
from src.live.deribit import DeribitRead
|
||||
r = DeribitRead()
|
||||
@@ -395,10 +395,11 @@ def conseguenza():
|
||||
alta dopo una salita, lo stop siede vicino sotto il mercato, il crollo lo prende, si rientra e
|
||||
la gamba successiva lo riprende. **Il rischio di accumulo e' funzione della CADENZA DEL CRON,
|
||||
non di k**: il gradino di leva non lo crea e non lo amplifica.
|
||||
· ⚠️ E qui c'e' un aggancio operativo: il docstring di `scripts/live/book_execute.py` prescrive
|
||||
«ogni ~230 minuti» (la griglia di SKH01) mentre il cron gira **ogni ora** (misurato sopra: 1443
|
||||
giri in 1442 ore). Chi "correggesse" la cadenza verso il docstring sposterebbe il libro dalla
|
||||
riga 1h alla riga 4h — quella in cui BTC raddoppia gli scatti.
|
||||
· ⚠️ Aggancio operativo (RIPARATO il 2026-09-02, `tests/test_book_cadenza.py`): fino a quel
|
||||
giorno il docstring di `scripts/live/book_execute.py` prescriveva «ogni ~230 minuti» (la
|
||||
griglia di SKH01) mentre il cron gira **ogni ora** (misurato sopra: 1443 giri in 1442 ore).
|
||||
Chi avesse "corretto" il CRON verso il docstring avrebbe spostato il libro dalla riga 1h
|
||||
(rotolante: 0 scatti) alla riga 4h (2 scatti del rotolante contro 1 del pavimento).
|
||||
|
||||
(*) ⚠️ CAVEAT sulla colonna, e sul suo artefatto: in questa lente la posizione e' SEMPRE aperta
|
||||
dal 2018, quindi in modo `roll` l'"ingresso" e' un prezzo di anni prima e il -48%/-61% e' il
|
||||
@@ -501,9 +502,9 @@ def costo(out: dict):
|
||||
|
||||
⚠️ E c'e' un accoppiamento nuovo da registrare: l'accumulo di episodi e' funzione della CADENZA
|
||||
DEL CRON (1h: no · 4h: BTC 2 · 24h: ETH 5 con 2 in 30 giorni). Oggi la cadenza e' misurata sana,
|
||||
ma nessun sorvegliante la controlla contro questa conseguenza, e il docstring di `book_execute`
|
||||
ne prescrive una (~230 min) che cade nella riga peggiore. Non e' un'azione richiesta qui: e' il
|
||||
parametro da rileggere prima del gradino, insieme a G2.""")
|
||||
e dal 2026-09-02 `tests/test_book_cadenza.py` tiene d'accordo docstring, cron_book.sh e crontab
|
||||
(il docstring ne prescriveva una, ~230 min, nella riga 4h). Resta il parametro da rileggere prima
|
||||
del gradino, insieme a G2.""")
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
"""r0825_versamento_3k.py — cosa compra un versamento di $3.000 ADESSO, dal conto vero.
|
||||
|
||||
DOMANDA (operatore, 2026-08-25 sera): *"rianalizza e vediamo come procedere. potrei versare a
|
||||
breve altri 3k"*. Il conto passerebbe da ~$2.065 a ~$5.065 — esattamente a cavallo della soglia
|
||||
$5k su cui sta scritto il criterio di capitale del gate XSR01 (23/10), e a un terzo del muro
|
||||
$15-20k che e' insieme il C* vero di Hyperliquid E la soglia della decisione vincolante
|
||||
"100% Deribit fino a $20k" (26/07).
|
||||
|
||||
ASSUNZIONI DICHIARATE:
|
||||
- "3k" = 3.000 USDC = $3.000 (i versamenti precedenti dell'operatore sono in USDC);
|
||||
- il lump atterra OGGI. "A breve" puo' voler dire fra settimane: ritardarlo di un mese sposta
|
||||
i numeri di poco e sempre nello stesso verso (un po' meno beneficio). Non lo modello.
|
||||
- equity di partenza: 2.065,00 — letta dal watermark alle 18:47:13Z di oggi (l'istante
|
||||
dell'evento, non l'istante della scrittura).
|
||||
|
||||
LENTE: L3 CONGIUNTA (funding esatto + fisco d'accumulo + ancora de-luckata), la stessa di
|
||||
`r0825_piano_10a_500` — stessi semi, stessi path: ogni confronto qui e' APPAIATO (M22).
|
||||
⚠️ Stessi difetti ereditati e dichiarati la': 121 versamenti in 10 anni, `versato` scalare.
|
||||
⚠️ M23: N_PATHS 3000, blocchi 20g. La banda del muro resta [$187k – $1,14M]: i numeri assoluti
|
||||
qui sotto stanno sulla lente `hourly`+L3 e vanno letti con quella etichetta (P7).
|
||||
|
||||
uv run python scripts/research/r0825_versamento_3k.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0725_capcurve as CC # noqa: E402
|
||||
import r0807_piano_netto as PN # noqa: E402
|
||||
import r0822d_piano_vero as PV # noqa: E402
|
||||
import r0823_deposit_timing as DT # noqa: E402
|
||||
|
||||
START_OGGI = 2065.00 # watermark 2026-08-25T18:47:13Z
|
||||
LUMP_USD = 3000.0 # il versamento ipotizzato, in USD
|
||||
START_LUMP = START_OGGI + LUMP_USD
|
||||
DEP = 500.0 # EUR/mese, il piano dichiarato
|
||||
ORIZZONTI = (5, 10, 15, 20)
|
||||
N_PATHS = 3000
|
||||
SEED = PV.SEED_TRAJ
|
||||
BLOCK = PV.BLOCK
|
||||
GATE_XSR = 59 # giorni da oggi al 2026-10-23 (25/08 -> 23/10)
|
||||
MILESTONE = (5_000.0, 15_000.0, 20_000.0) # criterio gate · C* Hyperliquid · muro "100% Deribit"
|
||||
|
||||
|
||||
def sez(t: str) -> None:
|
||||
print(f"\n{'='*100}\n {t}\n{'='*100}")
|
||||
|
||||
|
||||
def eur_g(cap: np.ndarray, perp: float) -> np.ndarray:
|
||||
return cap * perp / 365.0 / CC.EURUSD
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(f" r0825 — IL VERSAMENTO DA $3.000: ${START_OGGI:,.0f} -> ${START_LUMP:,.0f}")
|
||||
print("=" * 100)
|
||||
|
||||
B = PV.costruisci_serie()
|
||||
r = PV.deluck(B["fund"].values.astype(float))
|
||||
lente = PV.Lente("L3 congiunta", r, lordizza=False)
|
||||
lente.perp, lente.muro = PV.muro_di(lente)
|
||||
print(f"\n lente L3: drift {lente.drift:.2%} · vol {lente.vol:.2%} · perpetua {lente.perp:.2%}"
|
||||
f" · muro ${lente.muro:,.0f}")
|
||||
|
||||
# ---------------------------------------------------- (1) cosa compra il lump, appaiato
|
||||
sez(f"(1) COSA COMPRA IL LUMP — €{DEP:.0f}/mese, stessi path, da ${START_OGGI:,.0f} vs "
|
||||
f"${START_LUMP:,.0f}")
|
||||
print(f" {'oriz.':>6} {'senza lump':>13} {'con lump':>13} {'guadagno':>10} "
|
||||
f"{'€/g senza':>11} {'€/g con':>9} {'P(50) senza':>12} {'P(50) con':>10}")
|
||||
print(" " + "-" * 92)
|
||||
d10 = {}
|
||||
for anni in ORIZZONTI:
|
||||
rng = np.random.default_rng(SEED)
|
||||
ii = DT.boot_idx(len(r), N_PATHS, anni * 365, BLOCK, rng)
|
||||
paths = r[ii]
|
||||
a = PN.accumula(paths, DEP, lente.muro, lente.aliq, lente.patr, start=START_OGGI)
|
||||
b = PN.accumula(paths, DEP, lente.muro, lente.aliq, lente.patr, start=START_LUMP)
|
||||
ea, eb = eur_g(a["cap"], lente.perp), eur_g(b["cap"], lente.perp)
|
||||
pa, pb = float((ea >= 50).mean()), float((eb >= 50).mean())
|
||||
if anni == 10:
|
||||
d10 = dict(a=a, b=b, ea=ea, eb=eb, pa=pa, pb=pb)
|
||||
print(f" {anni:>4}a ${np.median(a['cap']):>11,.0f} ${np.median(b['cap']):>11,.0f} "
|
||||
f"{np.median(b['cap'])/np.median(a['cap'])-1:>9.1%} {np.median(ea):>10.2f} "
|
||||
f"{np.median(eb):>8.2f} {pa:>11.1%} {pb:>9.1%}")
|
||||
lump_in_mesi = LUMP_USD / CC.EURUSD / DEP
|
||||
print(f"\n il lump equivale a {lump_in_mesi:.1f} mesi di versamenti anticipati in un colpo.")
|
||||
print(f" N3: la leva 'versare' resta la prima — questo lump E' quella leva, esercitata.")
|
||||
|
||||
# ---------------------------------------------------- (2) pietre miliari
|
||||
sez("(2) QUANDO ARRIVANO LE PIETRE MILIARI (€500/mese, primo passaggio, 10 anni di path)")
|
||||
rng = np.random.default_rng(SEED)
|
||||
ii = DT.boot_idx(len(r), N_PATHS, 10 * 365, BLOCK, rng)
|
||||
paths10 = r[ii]
|
||||
print(f" {'soglia':>9} {'cosa sblocca':<44} {'senza lump':>11} {'con lump':>9}")
|
||||
print(" " + "-" * 82)
|
||||
naming = {5_000: "criterio di capitale del gate XSR01",
|
||||
15_000: "C* vero di Hyperliquid (pavimento misurato)",
|
||||
20_000: "decisione '100% Deribit fino a $20k' si riapre"}
|
||||
for ms in MILESTONE:
|
||||
riga = []
|
||||
for st in (START_OGGI, START_LUMP):
|
||||
out = PN.accumula(paths10, DEP, ms, lente.aliq, lente.patr, start=st)
|
||||
if st >= ms:
|
||||
riga.append("gia' li'")
|
||||
continue
|
||||
d = PV.leggi(out["colpito"], anni_sim=10)
|
||||
riga.append("oltre 10a" if not np.isfinite(d["med"]) else f"{d['med']:.1f}a")
|
||||
print(f" ${ms:>7,.0f} {naming[int(ms)]:<44} {riga[0]:>11} {riga[1]:>9}")
|
||||
|
||||
# ---------------------------------------------------- (3) il gate del 23/10
|
||||
sez(f"(3) GATE XSR01 (2026-10-23, fra {GATE_XSR} giorni) — P(capitale >= $5.000 quel giorno)")
|
||||
rng = np.random.default_rng(SEED)
|
||||
ii = DT.boot_idx(len(r), N_PATHS, GATE_XSR, BLOCK, rng)
|
||||
pg = r[ii]
|
||||
for st, nome in ((START_OGGI, "senza lump"), (START_LUMP, "con lump")):
|
||||
out = PN.accumula(pg, DEP, 5_000.0, lente.aliq, lente.patr, start=st)
|
||||
p = float((out["cap"] >= 5_000.0).mean())
|
||||
print(f" {nome:<11} start ${st:>7,.0f} -> P(>= $5k al 23/10) = {p:>6.1%} "
|
||||
f"(mediana ${np.median(out['cap']):,.0f}, p10 ${np.percentile(out['cap'],10):,.0f})")
|
||||
print(f"\n ⚠️ Il criterio FORMALE diventa raggiungibile col lump — ma il C* VERO del venue e'")
|
||||
print(f" $15.000-20.000 (§5.3), il 25% a ${START_LUMP:,.0f} sono ${START_LUMP*0.25:,.0f} su")
|
||||
print(f" Hyperliquid, e la decisione vincolante del 26/07 dice 100% Deribit fino a $20k.")
|
||||
print(f" Il lump rende il gate LEGGIBILE, non rende XSR01 comprabile.")
|
||||
|
||||
# ---------------------------------------------------- (4) meccanica live a $5.065
|
||||
sez(f"(4) COSA CAMBIA NELLA MECCANICA LIVE, DA SOLO, A ${START_LUMP:,.0f}")
|
||||
print(f" cap per-asset (equity x 0,5, nessun tetto assoluto sul percorso normale):")
|
||||
print(f" ${START_OGGI*0.5:,.0f} -> ${START_LUMP*0.5:,.0f} per asset — nessuna config da toccare,")
|
||||
print(f" il profilo di rischio RELATIVO non cambia (leva lorda max resta 1,0x; realizzata")
|
||||
print(f" max 0,52x: il vincolo e' il segnale, non il cap)")
|
||||
print(f" min_order $5: passa dal {5/(START_OGGI*0.5):.2%} al {5/(START_LUMP*0.5):.2%} del cap")
|
||||
print(f" per-asset (C2: un parametro in valuta assoluta pesa meno al crescere del conto)")
|
||||
print(f" fallback a equity illeggibile: min($3.000, watermark x 0,5) = "
|
||||
f"min(3000, {START_LUMP*0.5:,.0f}) -> ${min(3000.0, START_LUMP*0.5):,.0f}")
|
||||
print(f" disaster-SL -30%: invariato — resta rotolante e simmetrico (peggior DD misurato")
|
||||
print(f" dall'ingresso -60,6%/-61,5%: il nome inganna anche a $5k)")
|
||||
print(f" ⚠️ slip-audit: misurato su fill a $597-2.067; a ${START_LUMP:,.0f} la taglia relativa")
|
||||
print(f" raddoppia -> la misura VA RIFATTA dopo i primi fill alla taglia nuova (C2/C5),")
|
||||
print(f" lo script c'e' gia' (`r0822_slip_audit.py`, normalizzazione per-fill)")
|
||||
|
||||
# ---------------------------------------------------- verdetto
|
||||
sez("VERDETTO (runtime)")
|
||||
med_a, med_b = np.median(d10["a"]["cap"]), np.median(d10["b"]["cap"])
|
||||
print(f" a 10 anni con €500/mese: ${med_a:,.0f} -> ${med_b:,.0f} ({med_b/med_a-1:+.1%}), "
|
||||
f"€/g {np.median(d10['ea']):.2f} -> {np.median(d10['eb']):.2f}")
|
||||
print(f" P(>=50 €/g a 10a): {d10['pa']:.1%} -> {d10['pb']:.1%}")
|
||||
print(f" il lump anticipa il piano di ~{lump_in_mesi:.0f} mesi e rende leggibile il gate del")
|
||||
print(f" 23/10; NON rende comprabile XSR01 (C* $15-20k) e NON riapre la scelta del venue ($20k).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,372 @@
|
||||
"""r0825_xsr_rendita.py — XSR01 serve alla RENDITA PERPETUA? (non all'accumulo, non allo Sharpe)
|
||||
|
||||
DOMANDA (operatore, 2026-08-25): *"rendita perpetua ma non ho ancora capito se serve XSR01"*.
|
||||
|
||||
PERCHE' E' UNA MISURA NUOVA E NON UNA CITAZIONE. XSR01 e' stato giudicato tre volte — ammissione
|
||||
(25/07), gate di deploy (23/10, pre-registrato), accumulo (oggi) — e **mai sotto la lente della
|
||||
rendita**. Sono criteri diversi: l'accumulo premia il DRIFT, la rendita premia la **PERPETUA**,
|
||||
cioe' il prelievo piu' alto che sopravvive a 20 anni con P>=90% (definizione 25/07, invariata).
|
||||
La perpetua e' una funzione di drift **e** di coda: un diversificatore che non aggiunge drift puo'
|
||||
comunque alzarla, ed e' esattamente il caso che nessuno ha misurato. N6: l'obiettivo si dichiara
|
||||
prima di ottimizzarlo — qui e' dichiarato, quindi si misura quello.
|
||||
|
||||
COSA MI ASPETTO PRIMA DI MISURARE (M12: una previsione dichiarata va misurata, non assunta):
|
||||
(a) a ISO-NOZIONALE il mix perde drift (XSR01 rende 4,2%/a contro i 15,2% del libro) e quindi
|
||||
**alza il muro** anche se abbassa la vol -> XSR01 sembrera' inutile;
|
||||
(b) a ISO-RISCHIO (M6, la lente obbligatoria per un diversificatore a basso CAGR) il mix puo'
|
||||
essere ri-scalato fino alla vol del libro e allora il verdetto puo' **ribaltarsi**;
|
||||
(c) il vincolo che decide non sara' ne' (a) ne' (b) ma il **capitale**.
|
||||
Se (c) e' vero, le prime due sezioni sono aritmetica su una cosa che non si puo' comprare.
|
||||
|
||||
⚠️ TRE DIFETTI EREDITATI, DICHIARATI PRIMA DEI NUMERI (nessuno riparato qui):
|
||||
1. **La finestra comune e' corta.** Il libro ha ~7,4 anni, XSR01 ~2,6 (Hyperliquid parte 2024).
|
||||
Regola del 07/08, gia' violata una volta: **un MIX esiste solo dove esistono ENTRAMBE le
|
||||
serie**. Quindi il muro del mix NON e' confrontabile col $313k pubblicato (7,4 anni): il
|
||||
confronto valido e' **libro-sulla-finestra-comune vs mix-sulla-finestra-comune**, appaiato.
|
||||
2. **Il numero di titolo di XSR01 (Sharpe 1.82) non e' la sua serie.** XSR-REPRO (22/08): 1.82
|
||||
viene da una TERZA lente (divisore fisso 50); la lente che ha girato i gate da' **1.79** alla
|
||||
scoperta e **1.63** a oggi. Qui si usa la **lente dei gate su sole barre chiuse**, ed e' l'unica
|
||||
citabile — sempre con la coppia (lente, ultima barra chiusa).
|
||||
3. **Il forward monitor di XSR01 e' rotto** (`advance()` registra ~41 min di mercato al giorno,
|
||||
vol registrata 0,46% contro 2,74% ricalcolata): i 31 giorni di `data/paper_xsr/returns.jsonl`
|
||||
**non sono usabili** e non entrano in questo script. Qui si misura il BACKTEST, non il vivo.
|
||||
|
||||
uv run python scripts/research/r0825_xsr_rendita.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0725_capcurve as CC # noqa: E402
|
||||
import r0807_piano_netto as PN # noqa: E402
|
||||
import r0822_xsr_repro as XR # noqa: E402
|
||||
import r0822d_piano_vero as PV # noqa: E402
|
||||
from src.portfolio.portfolio import weights_tilt_null # noqa: E402
|
||||
|
||||
ANN = np.sqrt(365.0)
|
||||
N_WALL = 1500 # M23: la risoluzione del Monte Carlo si dichiara — misurata in sezione 6
|
||||
SEED_WALL = PV.SEED_WALL
|
||||
BLOCK = PV.BLOCK
|
||||
PESI = (0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.40, 0.50)
|
||||
|
||||
|
||||
def sez(t: str) -> None:
|
||||
print(f"\n{'='*100}\n {t}\n{'='*100}")
|
||||
|
||||
|
||||
def _sh(r: np.ndarray) -> float:
|
||||
r = np.asarray(r, float)
|
||||
return float(r.mean() / r.std() * ANN) if r.std() > 0 else 0.0
|
||||
|
||||
|
||||
def _dd(r: np.ndarray) -> float:
|
||||
eq = np.cumprod(1.0 + np.asarray(r, float))
|
||||
return float((eq / np.maximum.accumulate(eq) - 1.0).min())
|
||||
|
||||
|
||||
def perpetua(r: np.ndarray, aliq: float, patr: float, n_paths: int = N_WALL,
|
||||
seed: int = SEED_WALL) -> float:
|
||||
"""Il prelievo annuo (frazione del capitale) piu' alto con P(cap a 20a >= cap iniziale) >= 90%.
|
||||
|
||||
Bisezione IDENTICA a `PV.muro_di` — non re-implementata a mano: stessa `PN.sopravvivenza`,
|
||||
stessi 13 passi, stesso criterio `p10_end >= cap0`. Cambia solo che qui la serie e' un
|
||||
argomento invece che un attributo di `Lente`, perche' vanno confrontati molti mix."""
|
||||
lo, hi = 0.0, 0.40
|
||||
for _ in range(13):
|
||||
mid = (lo + hi) / 2
|
||||
s = PN.sopravvivenza(r, 1e6, 1e6 * mid, 20, aliq, patr, n_paths=n_paths, seed=seed)
|
||||
lo, hi = (mid, hi) if s["p10_end"] >= 1e6 else (lo, mid)
|
||||
return lo
|
||||
|
||||
|
||||
def muro_da(perp: float, prelievo: float) -> float:
|
||||
return prelievo / perp if perp > 0.002 else float("inf")
|
||||
|
||||
|
||||
def se_drift(r: np.ndarray, n: int = 2000, seed: int = 20260825) -> float:
|
||||
"""SE annualizzata del drift, block-bootstrap a blocchi di 20 giorni (la stessa dipendenza
|
||||
seriale che usa il muro). Serve a dire quanto e' RISOLTA la differenza fra due mix."""
|
||||
rng = np.random.default_rng(seed)
|
||||
B = CC._boot_paths(np.asarray(r, float), n, len(r), BLOCK, rng)
|
||||
return float(B.mean(axis=1).std() * 365.0)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(" r0825 — XSR01 SERVE ALLA RENDITA PERPETUA?")
|
||||
print("=" * 100)
|
||||
|
||||
# ---------------------------------------------------------------- (0) le due serie
|
||||
sez("(0) LE DUE SERIE E LA FINESTRA IN CUI ESISTONO ENTRAMBE")
|
||||
|
||||
B = PV.costruisci_serie()
|
||||
libro_full = pd.Series(PV.deluck(B["fund"].values.astype(float)), index=B.index)
|
||||
lente_full = PV.Lente("L3 congiunta", libro_full.values, lordizza=False)
|
||||
prelievo = lente_full.prelievo
|
||||
aliq, patr = lente_full.aliq, lente_full.patr
|
||||
|
||||
# XSR01: lente dei GATE (basket_from_positions, demean) su sole barre CHIUSE.
|
||||
P, S = XR.panel(partial_last=False)
|
||||
xsr_full = XR.lens_L1(P, S).dropna()
|
||||
ultima = xsr_full.index.max()
|
||||
# l'ultima barra su disco e' quella del giorno IN CORSO (difetto §5.1): si scarta.
|
||||
xsr_full = xsr_full[xsr_full.index < pd.Timestamp.utcnow().normalize()]
|
||||
chiusa = xsr_full.index.max()
|
||||
print(f" libro L3 (75/25, funding dentro, ancora de-luckata): "
|
||||
f"{libro_full.index.min().date()} -> {libro_full.index.max().date()} "
|
||||
f"({len(libro_full)} giorni)")
|
||||
print(f" XSR01 lente-dei-gate, sole barre chiuse: "
|
||||
f"{xsr_full.index.min().date()} -> {chiusa.date()} ({len(xsr_full)} giorni)")
|
||||
print(f" (ultima barra su disco {ultima.date()} = giorno IN CORSO, scartata — difetto §5.1)")
|
||||
print(f" Sharpe XSR01 a oggi, questa lente: {_sh(xsr_full.values):.2f} "
|
||||
f"[il '1.82' pubblicato e' una TERZA lente — XSR-REPRO 22/08]")
|
||||
|
||||
ix = libro_full.index.normalize().intersection(xsr_full.index.normalize())
|
||||
lb = pd.Series(libro_full.values, index=libro_full.index.normalize()).reindex(ix)
|
||||
xs = pd.Series(xsr_full.values, index=xsr_full.index.normalize()).reindex(ix)
|
||||
lb, xs = lb.dropna(), xs.dropna()
|
||||
ix = lb.index.intersection(xs.index)
|
||||
lb, xs = lb.reindex(ix).values, xs.reindex(ix).values
|
||||
print(f"\n FINESTRA COMUNE: {ix.min().date()} -> {ix.max().date()} ({len(ix)} giorni, "
|
||||
f"{len(ix)/365:.2f} anni)")
|
||||
print(f" copre il {len(ix)/len(libro_full):.0%} della storia del libro -> il muro calcolato "
|
||||
f"qui NON e' il $313k pubblicato (7,4 anni): si confronta solo con se stesso.")
|
||||
|
||||
# ---------------------------------------------------------------- (1) le due gambe
|
||||
sez("(1) LE DUE GAMBE SULLA FINESTRA COMUNE — e quanto e' diversa da tutta la storia")
|
||||
print(f" {'serie':<34} {'drift/a':>9} {'vol/a':>8} {'Sharpe':>8} {'maxDD':>9} {'SE drift':>10}")
|
||||
print(" " + "-" * 84)
|
||||
for nome, r in (("libro L3 — TUTTA la storia", libro_full.values),
|
||||
("libro L3 — finestra comune", lb),
|
||||
("XSR01 — finestra comune", xs)):
|
||||
print(f" {nome:<34} {r.mean()*365:>8.2%} {r.std()*ANN:>7.2%} {_sh(r):>8.2f} "
|
||||
f"{_dd(r):>8.1%} {se_drift(r):>9.2%}")
|
||||
corr = float(np.corrcoef(lb, xs)[0, 1])
|
||||
print(f"\n correlazione libro <-> XSR01 sulla finestra comune: {corr:+.3f}")
|
||||
# ⚠️ la maschera dei FLAT si prende dalla serie GREZZA, non da quella de-luckata: `deluck`
|
||||
# sottrae una costante e gli zeri esatti spariscono (difetto trovato e riparato il 25/08).
|
||||
grezza = pd.Series(B["fund"].values.astype(float), index=B.index.normalize()).reindex(ix)
|
||||
flat = (grezza.abs() < 1e-12).values
|
||||
assert flat.sum() > 0, "maschera flat vuota: si sta guardando la serie sbagliata"
|
||||
print(f" giorni in cui il libro e' FLAT: {flat.mean():.1%} ({flat.sum()} giorni) "
|
||||
f"— li' XSR01 rende {xs[flat].mean()*365:+.2%}/anno annualizzato contro "
|
||||
f"{xs[~flat].mean()*365:+.2%} negli altri")
|
||||
|
||||
# ---------------------------------------------------------------- (2) iso-nozionale
|
||||
sez("(2) MIX A ISO-NOZIONALE — la lente INGENUA (w di XSR01 tolto al libro)")
|
||||
print(f" {'w XSR01':>8} {'drift/a':>9} {'vol/a':>8} {'Sharpe':>8} {'maxDD':>9} "
|
||||
f"{'perpetua':>10} {'MURO':>13} {'d muro':>10}")
|
||||
print(" " + "-" * 88)
|
||||
base_perp = perpetua(lb, aliq, patr)
|
||||
base_muro = muro_da(base_perp, prelievo)
|
||||
iso_n = {}
|
||||
for w in PESI:
|
||||
m = (1 - w) * lb + w * xs
|
||||
p = perpetua(m, aliq, patr)
|
||||
mu = muro_da(p, prelievo)
|
||||
iso_n[w] = dict(perp=p, muro=mu, drift=m.mean()*365, vol=m.std()*ANN, sh=_sh(m), dd=_dd(m))
|
||||
dmu = "n/d" if not np.isfinite(mu) or not np.isfinite(base_muro) else f"{mu/base_muro-1:+.1%}"
|
||||
print(f" {w:>7.0%} {m.mean()*365:>8.2%} {m.std()*ANN:>7.2%} {_sh(m):>8.2f} {_dd(m):>8.1%} "
|
||||
f"{p:>9.2%} ${mu:>12,.0f} {dmu:>10}")
|
||||
|
||||
# ---------------------------------------------------------------- (3) iso-rischio
|
||||
sez("(3) MIX A ISO-RISCHIO (M6) — la lente OBBLIGATORIA per un diversificatore a basso CAGR")
|
||||
print(" Ogni mix e' ri-scalato di k = vol(libro)/vol(mix) cosi' che la vol sia IDENTICA a")
|
||||
print(" quella del libro da solo. E' l'unico confronto che non regala al mix il merito di")
|
||||
print(" essere semplicemente piu' piccolo (M6, e null del de-levering M5).")
|
||||
vol_b = lb.std()
|
||||
print(f"\n {'w XSR01':>8} {'k richiesto':>12} {'drift/a':>9} {'vol/a':>8} {'Sharpe':>8} "
|
||||
f"{'maxDD':>9} {'perpetua':>10} {'MURO':>13} {'d muro':>10}")
|
||||
print(" " + "-" * 96)
|
||||
iso_r = {}
|
||||
for w in PESI:
|
||||
m = (1 - w) * lb + w * xs
|
||||
k = vol_b / m.std()
|
||||
mk = m * k
|
||||
p = perpetua(mk, aliq, patr)
|
||||
mu = muro_da(p, prelievo)
|
||||
iso_r[w] = dict(k=k, perp=p, muro=mu, drift=mk.mean()*365, sh=_sh(mk), dd=_dd(mk))
|
||||
dmu = "n/d" if not np.isfinite(mu) or not np.isfinite(base_muro) else f"{mu/base_muro-1:+.1%}"
|
||||
print(f" {w:>7.0%} {k:>11.3f}x {mk.mean()*365:>8.2%} {mk.std()*ANN:>7.2%} {_sh(mk):>8.2f} "
|
||||
f"{_dd(mk):>8.1%} {p:>9.2%} ${mu:>12,.0f} {dmu:>10}")
|
||||
print("\n ⚠️ Il k di questa colonna e' ARITMETICA, non una configurazione: in `config/live.json`")
|
||||
print(" NON esiste una chiave di scala (§3), e il gradino di leva NON e' autorizzato oggi")
|
||||
print(" (1,50x BOCCIATO, k max difendibile 1,40). Un k > 1,40 in questa tabella e' un")
|
||||
print(" numero che il libro **non puo' eseguire**.")
|
||||
|
||||
# M8: l'argmax di questa griglia sta al BORDO (il muro scende monotonamente in w) -> non e'
|
||||
# una decisione, e' il bordo. Il peso che si porta avanti e' il migliore DENTRO il k
|
||||
# autorizzato (<= 1,40, decisione operatore 23/08); il bordo resta come nota.
|
||||
bordo = max((w for w in PESI if w > 0), key=lambda w: -iso_r[w]["muro"])
|
||||
ammessi = [w for w in PESI if w > 0 and iso_r[w]["k"] <= 1.40]
|
||||
best_w = max(ammessi, key=lambda w: -iso_r[w]["muro"]) if ammessi else min(
|
||||
w for w in PESI if w > 0)
|
||||
print(f"\n ⚠️ M8 — l'argmax di questa griglia e' al BORDO (w={bordo:.0%}, k={iso_r[bordo]['k']:.2f}x):")
|
||||
print(f" il muro scende MONOTONAMENTE in w, quindi la griglia non indica un ottimo, dice")
|
||||
print(f" 'il piu' possibile'. Cio' che si sta comprando in quella direzione e' il k, non XSR01.")
|
||||
print(f" => peso portato avanti = il migliore col k AUTORIZZATO (<=1,40x): XSR01 {best_w:.0%} "
|
||||
f"(k={iso_r[best_w]['k']:.2f}x)")
|
||||
|
||||
# ------------------------------------------------------- (3b) da dove viene il guadagno?
|
||||
sez("(3b) IL NULL — il guadagno a iso-rischio e' di XSR01, o di 'una qualunque cosa scorrelata'?")
|
||||
print(" A iso-vol vale `drift = Sharpe x vol_libro`: la colonna 'drift' della sezione (3) E' la")
|
||||
print(" colonna 'Sharpe' ri-etichettata. Quindi tutto il guadagno di muro e' un guadagno di")
|
||||
print(" SHARPE da diversificazione — e va chiesto se e' XSR01 o la sua sola statistica.")
|
||||
print(" Tre sostituti, stessa w, stessa ri-scalatura a iso-vol del libro:")
|
||||
print(" N1 XSR01 MESCOLATO — stessi rendimenti in ordine casuale: stesso drift, stessa vol,")
|
||||
print(" nessuna relazione col libro giorno per giorno (null M14).")
|
||||
print(" N2 RUMORE a drift di XSR01 — gaussiano, stessa media e stessa vol, zero struttura.")
|
||||
print(" N3 RUMORE a drift ZERO — stessa vol, media 0: isola quanta parte e' pura varianza.")
|
||||
rng = np.random.default_rng(20260825)
|
||||
print(f"\n {'sostituto':<26} {'drift/a':>9} {'Sharpe':>8} {'perpetua':>10} {'MURO':>13} {'d muro':>10}")
|
||||
print(" " + "-" * 80)
|
||||
w0 = best_w
|
||||
def _riga(nome, sub):
|
||||
m = (1 - w0) * lb + w0 * sub
|
||||
m = m * (vol_b / m.std())
|
||||
pp = perpetua(m, aliq, patr)
|
||||
mm = muro_da(pp, prelievo)
|
||||
print(f" {nome:<26} {m.mean()*365:>8.2%} {_sh(m):>8.2f} {pp:>9.2%} ${mm:>12,.0f} "
|
||||
f"{mm/base_muro-1:>9.1%}")
|
||||
return mm
|
||||
m_vero = iso_r[w0]["muro"]
|
||||
print(f" {'XSR01 (vero)':<26} {iso_r[w0]['drift']:>8.2%} {iso_r[w0]['sh']:>8.2f} "
|
||||
f"{iso_r[w0]['perp']:>9.2%} ${m_vero:>12,.0f} {m_vero/base_muro-1:>9.1%}")
|
||||
mescolati = [_riga(f"N1 mescolato (seme {i})", rng.permutation(xs)) for i in range(3)]
|
||||
n2 = [_riga(f"N2 rumore, drift XSR01 ({i})", rng.normal(xs.mean(), xs.std(), len(xs)))
|
||||
for i in range(3)]
|
||||
n3 = [_riga(f"N3 rumore, drift ZERO ({i})", rng.normal(0.0, xs.std(), len(xs)))
|
||||
for i in range(3)]
|
||||
# N4: la gamba che nessuno ha messo sul tavolo — un rendimento SENZA rischio e senza venue.
|
||||
# Se il merito di XSR01 e' 'un drift scorrelato a bassa vol', allora un conto remunerato
|
||||
# (T-bill / USDC lending) lo fornisce con vol ~0, corr 0 e nessun pavimento d'esecuzione.
|
||||
n4 = {y: _riga(f"N4 conto remunerato {y:.1%}", np.full(len(xs), y / 365.0))
|
||||
for y in (0.020, 0.0372, 0.040)}
|
||||
print(f"\n LETTURA (calcolata, non asserita):")
|
||||
print(f" XSR01 vero ${m_vero:>10,.0f}")
|
||||
print(f" N1 mescolato, mediana ${np.median(mescolati):>10,.0f} "
|
||||
f"-> il MECCANISMO vale {abs(m_vero-np.median(mescolati))/base_muro:.1%} di muro")
|
||||
print(f" N2 rumore, mediana ${np.median(n2):>10,.0f} "
|
||||
f"-> (media, vol) da sole bastano: {'SI' if np.median(n2) <= m_vero else 'NO'}")
|
||||
print(f" N3 drift zero, mediana${np.median(n3):>10,.0f} "
|
||||
f"-> la sola riduzione di varianza paga: "
|
||||
f"{'SI' if np.median(n3) < base_muro else 'NO, il muro SALE'}")
|
||||
print(f" N4 conto remunerato 4,0% ${n4[0.040]:>8,.0f} "
|
||||
f"-> {'MEGLIO' if n4[0.040] <= m_vero else 'peggio'} di XSR01, "
|
||||
f"a vol ZERO e senza secondo venue")
|
||||
print(f"\n drift di XSR01 sulla finestra comune: {xs.mean()*365:+.2%}/anno · SE {se_drift(xs):.2%} "
|
||||
f"-> t = {xs.mean()*365/se_drift(xs):+.2f}")
|
||||
|
||||
# ---------------------------------------------------------------- (4) gate dei pesi
|
||||
sez("(4) `weights_tilt_null` — il gate obbligatorio per OGNI proposta di cambio pesi (§8.5)")
|
||||
print(f" proposta testata: XSR01 {best_w:.0%} (il miglior peso a k autorizzato, sezione 3).")
|
||||
print(f" ⚠️ hold-out del gate = 2025-01-01: su questa finestra la gamba in-sample e' UN SOLO")
|
||||
print(f" anno (2024). Il gate gira, ma con questa risoluzione va letto come indizio.")
|
||||
cols = {"LIBRO": pd.Series(lb, index=ix), "XSR01": pd.Series(xs, index=ix)}
|
||||
try:
|
||||
g = weights_tilt_null(cols,
|
||||
{"LIBRO": 1.0, "XSR01": 0.0}, # corrente: XSR01 fuori dal book
|
||||
{"LIBRO": 1.0 - best_w, "XSR01": best_w},
|
||||
floor=0.0, n=500, k_seen=len(PESI))
|
||||
print(f" delta_insample {g['delta_insample']:+.3f} · delta_hold {g['delta_hold']:+.3f} "
|
||||
f"· pctl_hold {g['pctl_hold']:.1f} · soglia best-of-k {g['bestofk_pctl']:.1f}")
|
||||
print(f" tilt casuali che battono CURRENT sull'hold-out: {g['frac_random_beat_hold']:.0%}")
|
||||
print(f" => gate_pass = {g['gate_pass']}")
|
||||
except Exception as e:
|
||||
g = None
|
||||
print(f" NON ESEGUIBILE su questa finestra: {type(e).__name__}: {e}")
|
||||
|
||||
# ---------------------------------------------------------------- (5) risoluzione
|
||||
sez("(5) LA RISOLUZIONE — la differenza misurata e' piu' grande del rumore che la misura?")
|
||||
se_b = se_drift(lb)
|
||||
m_best = (1 - best_w) * lb + best_w * xs
|
||||
m_best = m_best * (vol_b / m_best.std())
|
||||
se_m = se_drift(m_best)
|
||||
d_drift = m_best.mean() * 365 - lb.mean() * 365
|
||||
se_pair = se_drift(m_best - lb)
|
||||
print(f" drift libro {lb.mean()*365:>8.2%} (SE {se_b:.2%})")
|
||||
print(f" drift mix {best_w:.0%} {m_best.mean()*365:>8.2%} (SE {se_m:.2%}) iso-rischio")
|
||||
print(f" DIFFERENZA appaiata (M7: si misura la differenza, non si differenziano le mediane):")
|
||||
print(f" {d_drift:+.2%}/anno · SE della differenza {se_pair:.2%} -> "
|
||||
f"t = {d_drift/se_pair if se_pair > 0 else float('nan'):+.2f}")
|
||||
p1 = perpetua(m_best, aliq, patr, seed=SEED_WALL)
|
||||
p2 = perpetua(m_best, aliq, patr, seed=SEED_WALL + 1)
|
||||
p3 = perpetua(m_best, aliq, patr, seed=SEED_WALL + 2)
|
||||
print(f"\n risoluzione Monte Carlo del muro (M23), stesso mix a tre semi diversi:")
|
||||
print(f" perpetua {p1:.3%} / {p2:.3%} / {p3:.3%} -> spread {max(p1,p2,p3)-min(p1,p2,p3):.3%}")
|
||||
print(f" in muro: ${muro_da(p1, prelievo):,.0f} / ${muro_da(p2, prelievo):,.0f} / "
|
||||
f"${muro_da(p3, prelievo):,.0f}")
|
||||
|
||||
# ---------------------------------------------------------------- (6) il vincolo vero
|
||||
sez("(6) IL VINCOLO CHE NON E' NELLE TABELLE — si puo' COMPRARE questo mix?")
|
||||
eq = 2066.88
|
||||
print(f" capitale reale oggi: ${eq:,.0f} (Deribit). XSR01 gira su **Hyperliquid**: e' un")
|
||||
print(f" SECONDO CONTO, quindi il suo peso e' capitale che ESCE da Deribit, non che si aggiunge.")
|
||||
print(f"\n {'w XSR01':>8} {'$ su Hyperliquid':>18} {'ticket mediano/gamba':>22} {'eseguibile?':>14}")
|
||||
print(" " + "-" * 68)
|
||||
TICKET_MED = 3.33 # XSR-REPRO 22/08, misurato: mediano $3,33 · medio $5,98 · 63% sotto $5
|
||||
C_STAR = 15_000.0 # pavimento vero del venue misurato (non i ~$3.000 su cui e' tarato il gate)
|
||||
for w in (0.10, 0.20, 0.30, 0.50):
|
||||
cap_x = eq * w
|
||||
tk = TICKET_MED * cap_x / 5_000.0 # il ticket misurato era a taglia $5.000
|
||||
print(f" {w:>7.0%} ${cap_x:>17,.0f} ${tk:>21,.2f} "
|
||||
f"{'NO' if cap_x < C_STAR else 'forse':>14}")
|
||||
print(f"\n E IL COSTO DI ESECUZIONE MANGIA PROPRIO LA COSA CHE SI STA COMPRANDO.")
|
||||
print(f" Il gate 23/10 chiede 'haircut a $5.000 <= 40%'. Il drift lordo di XSR01 su questa")
|
||||
print(f" finestra e' {xs.mean()*365:.2%}/anno: e' quello, e solo quello, cio' che il null dice")
|
||||
print(f" di star comprando. Al netto dell'haircut:")
|
||||
for h in (0.0, 0.20, 0.40, 0.60):
|
||||
netto = xs.mean() * 365 * (1 - h)
|
||||
print(f" haircut {h:>4.0%} -> {netto:>6.2%}/anno netti "
|
||||
f"{'sopra' if netto > 0.040 else 'SOTTO'} un conto al 4,0% · "
|
||||
f"{'sopra' if netto > 0.020 else 'SOTTO'} un conto al 2,0%")
|
||||
h_be = 1 - 0.020 / (xs.mean() * 365)
|
||||
print(f" pareggio con un conto al 2,0%: haircut {h_be:.0%}. Con un conto al 4,0%: "
|
||||
f"NON pareggia nemmeno a haircut ZERO.")
|
||||
print(f" ⚠️ e l'haircut pubblicato ($14,41 di ticket) e' ~2,4x ottimista e senza script"
|
||||
f" che lo riproduca (XSR-REPRO 22/08).")
|
||||
print(f"\n C* misurato del venue: ${C_STAR:,.0f}-20.000 (il gate 23/10 e' tarato su ~$3.000, §5.3).")
|
||||
print(f" Capitale che serve per dare a XSR01 il {best_w:.0%} restando sopra C*: "
|
||||
f"${C_STAR/best_w:,.0f} sul conto totale.")
|
||||
|
||||
# ---------------------------------------------------------------- verdetto
|
||||
sez("VERDETTO — calcolato a runtime, non scritto a mano (N11)")
|
||||
mu_n = iso_n[best_w]["muro"]
|
||||
mu_r = iso_r[best_w]["muro"]
|
||||
print(f" base (libro solo, finestra comune): perpetua {base_perp:.2%} · muro ${base_muro:,.0f}")
|
||||
print(f" a ISO-NOZIONALE, XSR01 {best_w:.0%}: muro ${mu_n:,.0f} ({mu_n/base_muro-1:+.1%})")
|
||||
print(f" a ISO-RISCHIO, XSR01 {best_w:.0%}: muro ${mu_r:,.0f} ({mu_r/base_muro-1:+.1%}) "
|
||||
f"[k = {iso_r[best_w]['k']:.2f}x]")
|
||||
aiuta_n = mu_n < base_muro
|
||||
aiuta_r = mu_r < base_muro
|
||||
risolto = abs(d_drift) > 2 * se_pair
|
||||
eseguibile = eq * best_w >= C_STAR
|
||||
k_ok = iso_r[best_w]["k"] <= 1.40
|
||||
print(f"\n (a) abbassa il muro a iso-nozionale? {'SI' if aiuta_n else 'NO'}")
|
||||
print(f" (b) abbassa il muro a iso-rischio? {'SI' if aiuta_r else 'NO'}")
|
||||
print(f" (c) la differenza e' RISOLTA (|d| > 2 SE)? {'SI' if risolto else 'NO'}")
|
||||
print(f" (d) il k richiesto e' autorizzato (<= 1,40)? {'SI' if k_ok else 'NO'}")
|
||||
print(f" (e) e' ESEGUIBILE al capitale di oggi? {'SI' if eseguibile else 'NO'}")
|
||||
if g is not None:
|
||||
print(f" (f) `weights_tilt_null` passa? {'SI' if g['gate_pass'] else 'NO'}")
|
||||
cond = [aiuta_r, risolto, k_ok, eseguibile] + ([g["gate_pass"]] if g is not None else [])
|
||||
print(f" (g) batte un CONTO REMUNERATO al 4,0% a pari peso? "
|
||||
f"{'SI' if m_vero < n4[0.040] else 'NO'} "
|
||||
f"(XSR01 ${m_vero:,.0f} vs conto ${n4[0.040]:,.0f})")
|
||||
cond.append(m_vero < n4[0.040])
|
||||
print(f"\n => XSR01 sotto la lente RENDITA: {sum(cond)}/{len(cond)} condizioni.")
|
||||
if not eseguibile:
|
||||
print(f" => Il vincolo BINDING non e' il rendimento: e' il CAPITALE. Sotto ${C_STAR/best_w:,.0f}")
|
||||
print(f" totali la domanda 'serve XSR01?' non ha una risposta comprabile.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,111 @@
|
||||
"""r0826_skh_band_drift.py — si guarda SOTTO il test rosso §5.9 prima di toccare la banda.
|
||||
|
||||
IL FATTO. `test_t1_canonical_riproduce_il_backtest_ufficiale` fallisce dal ~24-25/08:
|
||||
Sharpe hold-out SKH01 `canonical` = 1,9223 contro la banda cablata `1.3 < h < 1.9`
|
||||
(scritta il 25/07 sull'audit del 02/07: ~1,64). Il codice non e' cambiato; `data/raw/`
|
||||
e' gitignored e il cron lo ricostruisce ogni notte -> la finestra si allunga e il numero
|
||||
deriva. Lezione 07/08: un test che fallisce senza che il codice sia cambiato sta
|
||||
segnalando QUALCOSA — si misura cosa, non si allarga la banda a occhio.
|
||||
|
||||
LA MISURA. Stesso codice di produzione (`r0726_skh_onbook.skh_series`), stesso `sh3`,
|
||||
ma col feed TAGLIATO a date crescenti: se il numero alla data dell'audit riproduce
|
||||
~1,64 e la curva sale in modo regolare con le barre aggiunte, la deriva e' CRESCITA
|
||||
DELLA FINESTRA (hold-out 2025+ che si allunga con settimane favorevoli), non un dato
|
||||
corrotto ne' un codice cambiato. Se invece il taglio all'epoca NON riproduce il numero
|
||||
dell'epoca, il problema e' nel DATO RICOSTRUITO e la banda e' l'ultima delle questioni.
|
||||
|
||||
COSA MI ASPETTO PRIMA DI MISURARE (M12): riproduzione al taglio 02/07 dentro ±0,05;
|
||||
curva monotona o quasi verso 1,92 (ago 2026 e' stato un rally forte, BTC +21%/30g,
|
||||
e SKH01 era long ETH da meta' agosto).
|
||||
|
||||
uv run python scripts/research/r0826_skh_band_drift.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
for p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"):
|
||||
sys.path.insert(0, str(p))
|
||||
|
||||
import r0702_anchor_skh01 as r02 # noqa: E402
|
||||
import r0726_skh_onbook as ob # noqa: E402
|
||||
|
||||
TAGLI = ("2026-07-02", # la data dell'audit che ha scritto ~1,64
|
||||
"2026-07-25", # il giorno in cui la banda e' stata cablata nel test
|
||||
"2026-08-01", "2026-08-08", "2026-08-15", "2026-08-22",
|
||||
None) # oggi: deve riprodurre l'1,9223 del test rosso
|
||||
|
||||
_GET5M_VERO = r02.get5m.__wrapped__ # la funzione nuda, senza lru_cache
|
||||
|
||||
|
||||
def _taglia(cut_ms: int | None):
|
||||
"""Rimpiazza get5m con una versione tagliata e svuota TUTTE le cache a valle."""
|
||||
if cut_ms is None:
|
||||
def g(asset: str):
|
||||
return _GET5M_VERO(asset)
|
||||
else:
|
||||
def g(asset: str):
|
||||
df = _GET5M_VERO(asset)
|
||||
return df[df["timestamp"] < cut_ms].reset_index(drop=True)
|
||||
r02.get5m = g
|
||||
# ⚠️ run_asset non usa lru_cache: memoizza in un DICT di modulo (`_CACHE`) — svuotare
|
||||
# solo le lru_cache serviva a niente e la prima stesura di questo script ha misurato
|
||||
# SETTE volte la stessa finestra (tutte le righe identiche). Si svuota il dict.
|
||||
r02._CACHE.clear()
|
||||
r02.skh_port.cache_clear()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 96)
|
||||
print(" SKH01 canonical — Sharpe (full / in-sample / HOLD-OUT) in funzione della finestra dati")
|
||||
print(" hold-out = barre >= 2025-01-01: si ALLUNGA ogni notte col cron")
|
||||
print("=" * 96)
|
||||
print(f" {'taglio':>12} {'barre HO (g)':>13} {'full':>7} {'in-s':>7} {'HOLD-OUT':>9} "
|
||||
f"{'maxDD':>8} nota")
|
||||
print(" " + "-" * 90)
|
||||
esiti = {}
|
||||
for cut in TAGLI:
|
||||
cut_ms = None if cut is None else int(pd.Timestamp(cut, tz="UTC").value // 10**6)
|
||||
_taglia(cut_ms)
|
||||
s, _ = ob.skh_series(0, "canonical")
|
||||
full, is_, hold, dd = ob.sh3(s)
|
||||
n_ho = int((s.index >= r02.HOLDOUT).sum())
|
||||
nota = ""
|
||||
if cut == "2026-07-02":
|
||||
nota = "<- deve riprodurre ~1,64 (audit 02/07)"
|
||||
elif cut == "2026-07-25":
|
||||
nota = "<- il giorno della banda 1.3<h<1.9"
|
||||
elif cut is None:
|
||||
nota = "<- deve riprodurre l'1,9223 del test rosso"
|
||||
lab = cut or "oggi"
|
||||
esiti[lab] = hold
|
||||
print(f" {lab:>12} {n_ho:>13} {full:>7.3f} {is_:>7.3f} {hold:>9.4f} {dd:>7.1%} {nota}")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(" VERDETTO (runtime)")
|
||||
print("=" * 96)
|
||||
rip_audit = abs(esiti["2026-07-02"] - 1.64) < 0.05
|
||||
rip_oggi = abs(esiti["oggi"] - 1.9223) < 0.01
|
||||
serie = [esiti[k] for k in esiti]
|
||||
mono = all(b >= a - 0.03 for a, b in zip(serie, serie[1:]))
|
||||
print(f" taglio 02/07 riproduce l'audit (~1,64)? {'SI' if rip_audit else 'NO'} "
|
||||
f"({esiti['2026-07-02']:.4f})")
|
||||
print(f" nessun taglio riproduce -> dato cambiato: "
|
||||
f"{'no, il dato regge' if rip_audit else 'SI - INDAGARE IL FEED'}")
|
||||
print(f" oggi riproduce il numero del test rosso? {'SI' if rip_oggi else 'NO'} "
|
||||
f"({esiti['oggi']:.4f})")
|
||||
print(f" la curva sale con la finestra (quasi-monot.)? {'SI' if mono else 'NO'}")
|
||||
if rip_audit and rip_oggi:
|
||||
print("\n => La deriva e' CRESCITA DELLA FINESTRA, non corruzione ne' codice: il test")
|
||||
print(" confronta un numero che si muove col feed contro una banda scritta su una")
|
||||
print(" finestra congelata. La riparazione giusta e' ancorare il test alla finestra")
|
||||
print(" su cui la banda fu scritta (taglio fisso), non allargare la banda — una")
|
||||
print(" banda che insegue il feed non riproduce niente (N11).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,100 @@
|
||||
"""r0826_usde_scenari.py — USDE come collaterale su Deribit: cosa compra, cosa rischia, sui NOSTRI numeri.
|
||||
|
||||
DOMANDA (operatore, 2026-08-26): *"analizziamo usde"* — dopo la chiusura della pista USDC
|
||||
(Italia esclusa da MiCA: i reward USDC li paga Coinbase, che li ha cessati nell'EEA dal 12/2024).
|
||||
|
||||
FATTI VERIFICATI SUL VENUE (API pubblica, 2026-08-26 — N10):
|
||||
- `public/get_currencies`: USDE `apr: 4.0` (era "fino a 9%" al lancio 03/2025: il tasso e'
|
||||
l'APR settimanale di Ethena meno il 5% di fee Deribit, e comprime coi funding);
|
||||
- spot `USDE_USDC` attivo, spread osservato ~3 bps (0.9999/1.0002), fee spot USDE = 0;
|
||||
- indice `usde_usd` ESISTE: multi-exchange, mediana con clamp ±0,5% per fonte — NON e' il
|
||||
book interno (la causa del $0,65 su Binance il 10/10/2025 fu l'oracle sul PROPRIO book
|
||||
da $8M; sugli altri venue USDe quoto' ~$0,99 e i riscatti funzionarono);
|
||||
- haircut cross-collateral USDE: **10%** (USDC 0%) -> il 90% del valore fa margine;
|
||||
- reward GIORNALIERI (~12:00 UTC) sul minimo di equity USDE della finestra, snapshot ogni
|
||||
5 min, voce nel Transaction Log -> falsificazione in 24-72h, non in un mese;
|
||||
- eligibilita' PER RESIDENZA, lista non pubblica -> stessa trappola dell'USDC: si verifica
|
||||
SUL CONTO, non sui documenti (lezione di stamattina).
|
||||
|
||||
RISCHI DICHIARATI (non modellabili con precisione — si dichiarano, non si inventano p):
|
||||
R1 emittente: USDe = dollaro sintetico (basis trade: staking + short perp). Fallimento
|
||||
Ethena = perdita fino al 100% della quota. p NON stimabile — stessa classe del rischio
|
||||
venue accettato il 26/07, ma SI SOMMA a quello (N4: si prezza a parte).
|
||||
R2 marcatura in crash: l'indice mediano protegge dal caso-Binance, ma in un crash
|
||||
sistemico il mark puo' scendere qualche punto proprio quando il libro e' lungo (P10:
|
||||
conta l'accoppiamento, non la marginale).
|
||||
R3 tasso: 9% -> 4% in 17 mesi; nei regimi di funding negativo prolungato -> ~0.
|
||||
R4 regolatorio EU: Ethena GmbH liquidata dopo il no di BaFin; USDe non e' un EMT MiCA
|
||||
(per questo puo' pagare rendimento dove USDC non puo'), ma il perimetro puo' cambiare.
|
||||
NOTA a favore, da misurare se si procede: il rendimento di USDe sale coi funding positivi,
|
||||
cioe' ESATTAMENTE quando il nostro libro (long) li paga (-2,16%/anno misurato): la quota
|
||||
USDE farebbe da parziale sconto sulla tassa di funding, con correlazione POSITIVA.
|
||||
|
||||
uv run python scripts/research/r0826_usde_scenari.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
APR_NETTO = 0.04 # apr dall'API oggi (gia' al netto del 5% Deribit)
|
||||
SPREAD_RT = 0.0003 # ~3 bps round-trip osservati sul book (fee spot = 0)
|
||||
HAIRCUT = 0.10
|
||||
CAPITALI = (2_065.0, 5_065.0, 20_000.0)
|
||||
QUOTE = (0.25, 0.50, 1.00) # frazione dell'equity convertita in USDE
|
||||
DEPEG = (0.01, 0.035, 0.10, 1.00) # scenari di marcatura/perdita sulla quota USDE
|
||||
LEVA_CAP = 1.0 # tetto di leva lorda del libro (config)
|
||||
LEVA_MAX_REALIZZATA = 0.52
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(" USDE su Deribit — aritmetica sui numeri del conto (apr 4,0% · haircut 10% · spread 3 bps)")
|
||||
print("=" * 100)
|
||||
|
||||
print("\n (1) COSA RENDE, per capitale e quota convertita (netto conversione, primo anno)")
|
||||
print(f" {'equity':>9} {'quota USDE':>11} {'$ in USDE':>10} {'$/anno':>8} "
|
||||
f"{'giorni di spread':>17} {'margine usabile':>16}")
|
||||
print(" " + "-" * 78)
|
||||
for cap in CAPITALI:
|
||||
for q in QUOTE:
|
||||
usde = cap * q
|
||||
resa = usde * APR_NETTO
|
||||
giorni_be = SPREAD_RT / APR_NETTO * 365
|
||||
margine = (cap - usde * HAIRCUT) / cap
|
||||
print(f" ${cap:>8,.0f} {q:>10.0%} ${usde:>9,.0f} ${resa:>7,.0f} "
|
||||
f"{giorni_be:>16.1f}g {margine:>15.1%}")
|
||||
print(f"\n la conversione si ripaga in ~{SPREAD_RT/APR_NETTO*365:.0f} giorni di rendimento;")
|
||||
print(f" il margine usabile resta sopra il necessario: leva max realizzata "
|
||||
f"{LEVA_MAX_REALIZZATA:.2f}x su tetto {LEVA_CAP:.2f}x -> l'haircut 10% oggi non morde")
|
||||
print(f" (morderebbe a leva > {1/(1-0):.2f}x solo con quota 100%: 0,90x usabile)")
|
||||
|
||||
print("\n (2) COSA COSTA UNO SCENARIO AVVERSO sulla quota USDE (equity $5.065)")
|
||||
cap = 5_065.0
|
||||
print(f" {'scenario':>26} {'quota 25%':>12} {'quota 50%':>12} {'quota 100%':>12} "
|
||||
f"{'mesi di resa persi (50%)':>25}")
|
||||
print(" " + "-" * 92)
|
||||
nomi = {0.01: "mark -1% (stress indice)", 0.035: "mark -3,5% (crash forte)",
|
||||
0.10: "-10% (depeg parziale)", 1.00: "-100% (fallimento Ethena)"}
|
||||
for d in DEPEG:
|
||||
riga = []
|
||||
for q in QUOTE:
|
||||
perdita = cap * q * d
|
||||
riga.append(f"${perdita:>10,.0f}")
|
||||
mesi = d / APR_NETTO * 12
|
||||
mesi_s = f"{mesi:,.0f} mesi" if mesi < 240 else f"{mesi/12:,.0f} ANNI"
|
||||
print(f" {nomi[d]:>26} {riga[0]:>12} {riga[1]:>12} {riga[2]:>12} {mesi_s:>25}")
|
||||
print("\n la riga che decide: il -100% non si recupera MAI col rendimento (25 anni di apr")
|
||||
print(" al 4%); percio' la QUOTA e' l'unica leva di controllo vera (N4), non il tasso.")
|
||||
|
||||
print("\n (3) VERDETTO (runtime)")
|
||||
resa50 = 5_065.0 * 0.50 * APR_NETTO
|
||||
print(f" a $5.065 e quota 50%: ~${resa50:,.0f}/anno — utile ma non decisivo; il valore vero")
|
||||
print(f" arriva col capitale (a $20k e 50%: ${20_000*0.5*APR_NETTO:,.0f}/anno).")
|
||||
print(" PRIMA di qualunque allocazione: il TEST DI ELIGIBILITA' sul conto —")
|
||||
print(" convertire ~$500 USDC->USDE (costo ~$0,08 di spread), tenerli 3 giorni,")
|
||||
print(" guardare il Transaction Log alle ~12:00 UTC: reward giornaliero atteso ~$0,05/g.")
|
||||
print(" Regola dichiarata PRIMA: >=1 voce reward in 3 giorni -> idoneo, si apre la")
|
||||
print(" decisione di quota (con gate); 0 voci -> non idoneo, si riconverte e la pista")
|
||||
print(" si chiude come l'USDC. Costo totale del test: < $0,20.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,139 @@
|
||||
"""r0826_xsr_haircut.py — l'haircut di eseguibilita' di XSR01 a $5.000, MISURATO con uno script.
|
||||
|
||||
PERCHE' ESISTE. La seconda gamba del gate XSR01 (23/10) e' "haircut a $5.000 <= 40%", ma:
|
||||
(a) il numero pubblicato all'ammissione ($14,41 di ticket/gamba) NON ha uno script committato
|
||||
che lo riproduca ed e' ~2,4x ottimista (XSR-REPRO 22/08: mediano vero $3,33/gamba);
|
||||
(b) il libro REAL-$5000 del monitor gira con pavimento $5, ma il pavimento VERO misurato del
|
||||
venue e' $10 (HL-EXEC 22/08) — e a $10 si esegue ~1 ordine su 5;
|
||||
(c) il paradosso dello strumento: con POCHI ordini eseguiti il libro reale resta vicino al
|
||||
modellato nei periodi calmi -> l'haircut misurato dal monitor puo' sembrare piccolo
|
||||
proprio quando l'eseguibilita' e' pessima. La frazione di ordini eseguiti va letta
|
||||
ACCANTO all'haircut, mai al suo posto (P7: uno "zero" si legge accanto alla quota di
|
||||
campione utilizzabile).
|
||||
|
||||
COSA FA. Replay dei libri XSR01 col codice di PRODUZIONE (`paper_xsr._step`, nessuna
|
||||
reimplementazione — il metodo dell'audit 22/08), capitale $5.000, pavimenti $5 e $10, su due
|
||||
finestre: FULL (tutte le barre chiuse, ~2,6 anni: la risoluzione) e FORWARD (dal forward-day 0
|
||||
del gate, 2026-07-25: cio' che il gate legge). Sole barre CHIUSE (paper_guard).
|
||||
|
||||
E' lo script che il gate del 23/10 cita come strumento DECISIVO per la gamba haircut
|
||||
(modifica dichiarata del 2026-08-26, diario `2026-08-26-xsr01-gate-riscritto.md`).
|
||||
|
||||
uv run python scripts/research/r0826_xsr_haircut.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live.paper_guard import MS_1D, nuove_chiuse # noqa: E402
|
||||
|
||||
CAP = 5_000.0
|
||||
PAVIMENTI = (5.0, 10.0) # $5 = il pavimento del monitor · $10 = il pavimento VERO (HL-EXEC)
|
||||
ANN = float(np.sqrt(365.0))
|
||||
|
||||
|
||||
def _px():
|
||||
spec = importlib.util.spec_from_file_location("_hx_paper_xsr",
|
||||
ROOT / "scripts" / "live" / "paper_xsr.py")
|
||||
m = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(m)
|
||||
return m
|
||||
|
||||
|
||||
def _sh(r: np.ndarray) -> float:
|
||||
return float(r.mean() / r.std() * ANN) if len(r) > 1 and r.std() > 0 else float("nan")
|
||||
|
||||
|
||||
def replay(px, idx: list[int], W, R, rb, n_gambe: int, min_order: float | None) -> dict:
|
||||
bk = px._book(CAP, n_gambe)
|
||||
net = []
|
||||
for i in idx:
|
||||
net.append(px._step(bk, W[i], R[i], float(rb[i]), min_order))
|
||||
net = np.asarray(net)
|
||||
tot = float(np.prod(1 + net) - 1)
|
||||
n_ordini = bk["n_fill"] + bk["n_skip"]
|
||||
return dict(net=net, tot=tot, sh=_sh(net),
|
||||
frazione_eseguita=(bk["n_fill"] / n_ordini if n_ordini else float("nan")))
|
||||
|
||||
|
||||
def misura(px, idx: list[int], W, R, rb, n_gambe: int) -> dict:
|
||||
"""Per una finestra: MODELED + un libro reale per pavimento, con l'haircut del gate."""
|
||||
out = dict(modeled=replay(px, idx, W, R, rb, n_gambe, None))
|
||||
tot_m = out["modeled"]["tot"]
|
||||
for f in PAVIMENTI:
|
||||
r = replay(px, idx, W, R, rb, n_gambe, f)
|
||||
r["haircut"] = (tot_m - r["tot"]) / abs(tot_m) if tot_m != 0 else float("nan")
|
||||
out[f] = r
|
||||
return out
|
||||
|
||||
|
||||
def ticket_stats(W, idx: list[int]) -> dict:
|
||||
"""Taglia degli ordini per gamba a $5.000: |Δw| x capitale a ogni ribilancio."""
|
||||
t = []
|
||||
prev = np.zeros(W.shape[1])
|
||||
for i in idx:
|
||||
w = np.nan_to_num(W[i])
|
||||
d = np.abs(w - prev) * CAP
|
||||
t.extend(d[d > 0].tolist())
|
||||
prev = w
|
||||
t = np.asarray(t)
|
||||
return dict(mediano=float(np.median(t)), medio=float(np.mean(t)),
|
||||
sotto5=float((t < 5.0).mean()), sotto10=float((t < 10.0).mean()), n=len(t))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 96)
|
||||
print(f" XSR01 — haircut di eseguibilita' a ${CAP:,.0f}, replay col codice di produzione")
|
||||
print("=" * 96)
|
||||
px = _px()
|
||||
ts, dt, W, R, rb, syms = px.build_panel()
|
||||
st = json.loads((ROOT / "data" / "paper_xsr" / "state.json").read_text())
|
||||
chiuse = nuove_chiuse(ts, 0, MS_1D)
|
||||
fwd = [i for i in chiuse if ts[i] > st["start_ts"]]
|
||||
Wv = np.nan_to_num(np.asarray(W, float))
|
||||
Rv = np.nan_to_num(np.asarray(R, float))
|
||||
|
||||
tk = ticket_stats(Wv, chiuse)
|
||||
print(f"\n TICKET PER GAMBA a ${CAP:,.0f} (riproduce/sostituisce il '$14,41' senza script):")
|
||||
print(f" mediano ${tk['mediano']:.2f} · medio ${tk['medio']:.2f} · "
|
||||
f"sotto $5: {tk['sotto5']:.0%} · sotto $10: {tk['sotto10']:.0%} "
|
||||
f"({tk['n']:,} ordini su {len(chiuse)} barre)")
|
||||
|
||||
for nome, idx in (("FULL (tutte le barre chiuse)", chiuse), ("FORWARD (dal 2026-07-25)", fwd)):
|
||||
if len(idx) < 5:
|
||||
print(f"\n {nome}: {len(idx)} barre — troppo corta, nessun numero")
|
||||
continue
|
||||
m = misura(px, idx, Wv, Rv, rb, len(syms))
|
||||
print(f"\n {nome} — {len(idx)} barre "
|
||||
f"({pd.Timestamp(int(ts[idx[0]]), unit='ms', tz='UTC').date()} -> "
|
||||
f"{pd.Timestamp(int(ts[idx[-1]]), unit='ms', tz='UTC').date()})")
|
||||
print(f" {'libro':<18} {'tot':>9} {'Sharpe':>8} {'haircut':>9} {'ordini eseguiti':>17}")
|
||||
print(" " + "-" * 66)
|
||||
print(f" {'MODELED':<18} {m['modeled']['tot']:>8.2%} {m['modeled']['sh']:>8.2f} "
|
||||
f"{'—':>9} {'—':>17}")
|
||||
for f in PAVIMENTI:
|
||||
r = m[f]
|
||||
print(f" {'REAL floor $%.0f' % f:<18} {r['tot']:>8.2%} {r['sh']:>8.2f} "
|
||||
f"{r['haircut']:>8.1%} {r['frazione_eseguita']:>16.0%}")
|
||||
if nome.startswith("FORWARD"):
|
||||
h10 = m[10.0]["haircut"]
|
||||
fe10 = m[10.0]["frazione_eseguita"]
|
||||
print(f"\n VERDETTO gate (gamba haircut, pavimento VERO $10): "
|
||||
f"{h10:+.1%} contro guardia 40%"
|
||||
f" -> {'SFONDA (RITIRO a prescindere)' if abs(h10) > 0.40 else 'dentro la guardia'}")
|
||||
print(f" ⚠️ da leggere ACCANTO: ordini eseguiti {fe10:.0%} — un haircut piccolo "
|
||||
f"con pochi ordini\n eseguiti non e' eseguibilita', e' un libro che non "
|
||||
f"si muove (P7).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0828_gtaa_band_median_phase.py — criterio (C): la banda giudicata sulla MEDIANA delle 5 fasi.
|
||||
|
||||
⚠️ DA LEGGERE PRIMA DI CITARE QUALUNQUE NUMERO DI QUI.
|
||||
Questo criterio e' stato scelto **DOPO** aver visto la tabella delle fasi di
|
||||
`r0828_gtaa_band_phase.py`, che mostra il criterio a fase singola cambiare verdetto da una notte
|
||||
all'altra. Sceglierne uno nuovo guardando l'esito del vecchio e' selezione, esattamente come
|
||||
scegliere una cella guardando l'hold-out. Quindi:
|
||||
|
||||
· questo script NON valida la proposta del 27/07 e NON puo' essere citato come sua conferma;
|
||||
· l'unica domanda che puo' porre onestamente e' quella sullo STRUMENTO — «un criterio costruito
|
||||
cosi' avrebbe risoluzione e potenza?» — che si risponde senza guardare se la cella che vince
|
||||
e' quella che ci piace;
|
||||
· se la risposta e' si', il criterio va poi DICHIARATO e la validazione RIFATTA da capo su di
|
||||
esso, con la sua data. Il verdetto di oggi sulla banda resta *non decidibile*.
|
||||
|
||||
L'IDEA. Il difetto misurato in `r0828_gtaa_band_phase.py` e' che `gtaa._gated_returns` fasa il
|
||||
ribilanciamento sulla POSIZIONE nell'array (`i % every == 0`), e la prima barra di SPY scivola di
|
||||
un giorno ogni notte perche' IB serve una finestra rotolante di 30 anni. La fase canonica quindi
|
||||
cambia da sola. La mediana sulle 5 fasi toglie di mezzo la componente di FASE — e' la stessa
|
||||
mossa che il progetto fa gia' in `r0726_loo_deluck` (M7: su ancore appaiate la statistica e' la
|
||||
mediana). Resta la componente di DATO: le barre perse. La domanda e' se basti.
|
||||
|
||||
I CRITERI, DICHIARATI QUI PRIMA DI GUARDARE I NUMERI. Il pavimento resta **0.01**, quello che il
|
||||
test si diede il 07/08: non lo si ritocca dopo aver visto l'esito, o si sta tarando il metro sul
|
||||
risultato. Il criterio (C) ha risoluzione e potenza se e solo se, sulla statistica mediana:
|
||||
(i) la banda scelta al buio e' LA STESSA in tutte le NOTTI notti simulate;
|
||||
(ii) il margine sulla seconda resta >= 0.01 in tutte;
|
||||
(iii) POTENZA — la banda scelta sull'hold-out e' DIVERSA da quella scelta al buio. Se
|
||||
coincidono, il gate lo passerebbe anche una proposta selezionata sull'hold-out, e non
|
||||
distingue niente (controllo positivo, come il 07/08).
|
||||
E una lettura di contorno che vale piu' del verdetto: la mediana di 5 fasi non e' una mediana di
|
||||
5 osservazioni. Si stampa la correlazione media fra le serie di fase e l'N_eff che ne segue
|
||||
(§2 del CLAUDE.md: «positivo in N/N ancore NON e' N osservazioni»).
|
||||
|
||||
uv run python scripts/research/r0828_gtaa_band_median_phase.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
||||
|
||||
import r0727_gtaa_band_gate as BG # noqa: E402
|
||||
import src.portfolio.gtaa as G # noqa: E402
|
||||
|
||||
NOTTI = 10
|
||||
MARGINE_MINIMO = 0.01 # invariato dal 07/08: il metro non si ritocca sul risultato
|
||||
EVERY = BG.PROPOSTA[0]
|
||||
FASI = tuple(range(EVERY))
|
||||
PROPOSTA_BANDA = BG.PROPOSTA[1]
|
||||
ROTOLANTE = ("SPY",) # l'unica gamba al muro dei 30 anni (misurato in r0828_gtaa_band_phase)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def _serie(sym: str, taglio: int):
|
||||
"""(prezzi, esposizione) di una gamba, tagliata in testa di `taglio` barre. In cache: e' il
|
||||
pezzo caro (l'esposizione e' un doppio ciclo su 30 anni) e non dipende da banda ne' fase."""
|
||||
close = G._close(sym)
|
||||
if taglio:
|
||||
close = close.iloc[taglio:]
|
||||
ex = np.nan_to_num(np.asarray(G._exposure(close).values, float))
|
||||
return close, ex
|
||||
|
||||
|
||||
def _leg(sym: str, cap_leg: float, band_usd: float, ph: int, taglio: int) -> pd.Series:
|
||||
close, ex = _serie(sym, taglio)
|
||||
px = close.values.astype(float)
|
||||
ret = np.zeros(len(px))
|
||||
ret[1:] = px[1:] / px[:-1] - 1.0
|
||||
held = np.empty(len(ex))
|
||||
comm = np.zeros(len(ex))
|
||||
cur = 0.0
|
||||
for i in range(len(ex)):
|
||||
if i % EVERY == ph:
|
||||
notional = abs(ex[i] - cur) * cap_leg
|
||||
if notional >= max(band_usd, G.IB_MIN_TRADE_USD):
|
||||
comm[i] = G.ib_commission(notional, px[i]) / cap_leg
|
||||
cur = ex[i]
|
||||
held[i] = cur
|
||||
pos = np.zeros(len(held))
|
||||
pos[1:] = held[:-1]
|
||||
net = pos * ret - comm
|
||||
net[0] = 0.0
|
||||
return pd.Series(net, index=close.index)
|
||||
|
||||
|
||||
def cella(frac: float, ph: int, taglio: int) -> pd.Series:
|
||||
cap_leg = BG.CAP_REF / len(G.EQ_UNIVERSE)
|
||||
band = frac * BG.CAP_REF / len(G.EQ_UNIVERSE)
|
||||
cols = {a: _leg(a, cap_leg, band,
|
||||
ph if a in ROTOLANTE else 0,
|
||||
taglio if a in ROTOLANTE else 0)
|
||||
for a in G.EQ_UNIVERSE}
|
||||
return pd.concat(cols, axis=1, sort=True).sort_index().mean(axis=1, skipna=True).dropna()
|
||||
|
||||
|
||||
def sh(frac: float, ph: int, taglio: int, finestra: str) -> float:
|
||||
s = cella(frac, ph, taglio)
|
||||
s = s.loc[: BG.HOLDOUT] if finestra == "in" else s.loc[BG.HOLDOUT:]
|
||||
return BG.met(s)["sharpe"]
|
||||
|
||||
|
||||
def mediana_fasi(frac: float, taglio: int, finestra: str = "in") -> float:
|
||||
return float(np.median([sh(frac, ph, taglio, finestra) for ph in FASI]))
|
||||
|
||||
|
||||
def scelta(valori: dict) -> tuple[float, float]:
|
||||
best = max(valori, key=valori.get)
|
||||
return best, valori[best] - max(v for f, v in valori.items() if f != best)
|
||||
|
||||
|
||||
def sanity() -> bool:
|
||||
mia = cella(PROPOSTA_BANDA, 0, 0)
|
||||
vera = BG.run_cell(EVERY, PROPOSTA_BANDA, BG.CAP_REF)
|
||||
j = pd.concat({"m": mia, "v": vera}, axis=1, join="outer")
|
||||
d = float((j["m"] - j["v"]).abs().max())
|
||||
ok = bool(np.isfinite(d) and d < 1e-12 and len(j) == len(vera))
|
||||
print(f"[SANITY] replica (fase 0, taglio 0) vs produzione: max|Δ| = {d:.2e} -> {'OK' if ok else 'DIVERGE'}")
|
||||
return ok
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print(__doc__.split("\n\n")[0])
|
||||
print("\n⚠️ criterio scelto DOPO aver visto l'esito del precedente: misura lo STRUMENTO, "
|
||||
"non valida la banda.\n")
|
||||
if not sanity():
|
||||
return 2
|
||||
|
||||
# ---------------------------------------------------------------- contorno: quante osservazioni
|
||||
print("=" * 100)
|
||||
print("CONTORNO — la mediana di 5 fasi, quante osservazioni sono davvero?")
|
||||
print("=" * 100)
|
||||
serie = {ph: cella(PROPOSTA_BANDA, ph, 0).loc[: BG.HOLDOUT] for ph in FASI}
|
||||
M = pd.concat(serie, axis=1, join="inner").dropna()
|
||||
C = M.corr().values
|
||||
rho = float((C.sum() - len(FASI)) / (len(FASI) * (len(FASI) - 1)))
|
||||
n_eff = len(FASI) / (1.0 + (len(FASI) - 1) * rho)
|
||||
print(f" correlazione media fra le 5 serie di fase (banda {PROPOSTA_BANDA:.0%}, in-sample): {rho:.3f}")
|
||||
print(f" N_eff = {len(FASI)} / (1 + {len(FASI)-1}·ρ) = {n_eff:.2f}")
|
||||
print(" -> la mediana media su ~2 osservazioni indipendenti, non su 5: toglie la fase,")
|
||||
print(" non compra precisione. Va citata come robustezza alla fase, mai come campione.")
|
||||
|
||||
# ---------------------------------------------------------------- (i) e (ii)
|
||||
print("\n" + "=" * 100)
|
||||
print(f"(C) LA MEDIANA DELLE 5 FASI — banda scelta al buio, su {NOTTI} notti di finestra rotolante")
|
||||
print("=" * 100)
|
||||
spy = G._close("SPY")
|
||||
righe, righe_singola = [], []
|
||||
for k in range(NOTTI):
|
||||
med = {f: mediana_fasi(f, k) for f in BG.FRAC_GRID}
|
||||
b, m = scelta(med)
|
||||
righe.append(dict(notte=k, inizio_SPY=str(spy.index[k].date()), scelta=f"{b:.0%}",
|
||||
margine=round(m, 4), **{f"{f:.0%}": round(med[f], 4) for f in BG.FRAC_GRID}))
|
||||
sing = {f: sh(f, 0, k, "in") for f in BG.FRAC_GRID}
|
||||
bs, ms = scelta(sing)
|
||||
righe_singola.append(dict(notte=k, scelta=f"{bs:.0%}", margine=round(ms, 4)))
|
||||
D = pd.DataFrame(righe)
|
||||
pd.set_option("display.width", 250)
|
||||
print(D.to_string(index=False))
|
||||
|
||||
scelte = set(D.scelta)
|
||||
quota = float((D.scelta == f"{PROPOSTA_BANDA:.0%}").mean())
|
||||
print(f"\n celle distinte scelte: {sorted(scelte)} | la proposta ({PROPOSTA_BANDA:.0%}) vince {quota:.0%} delle notti")
|
||||
print(f" margine: min {D.margine.min():.4f} mediana {D.margine.median():.4f} max {D.margine.max():.4f}")
|
||||
print(f" notti sotto il pavimento {MARGINE_MINIMO}: {int((D.margine < MARGINE_MINIMO).sum())}/{NOTTI}")
|
||||
|
||||
S = pd.DataFrame(righe_singola)
|
||||
print("\n confronto diretto col criterio a FASE SINGOLA (quello che fallisce oggi):")
|
||||
print(f" fase singola : celle {sorted(set(S.scelta))} "
|
||||
f"| sotto pavimento {int((S.margine < MARGINE_MINIMO).sum())}/{NOTTI} "
|
||||
f"| margine min {S.margine.min():.4f}")
|
||||
print(f" mediana fasi : celle {sorted(scelte)} "
|
||||
f"| sotto pavimento {int((D.margine < MARGINE_MINIMO).sum())}/{NOTTI} "
|
||||
f"| margine min {D.margine.min():.4f}")
|
||||
esc_s = {f: 0.0 for f in BG.FRAC_GRID}
|
||||
for f in BG.FRAC_GRID:
|
||||
vs = [sh(f, 0, k, "in") for k in range(NOTTI)]
|
||||
vm = [D[f"{f:.0%}"].iloc[k] for k in range(NOTTI)]
|
||||
esc_s[f] = (max(vs) - min(vs), max(vm) - min(vm))
|
||||
print("\n escursione dello Sharpe in-sample su 10 notti, per banda (fase singola -> mediana):")
|
||||
for f in BG.FRAC_GRID:
|
||||
a, b = esc_s[f]
|
||||
print(f" banda {f:>4.0%}: {a:.4f} -> {b:.4f} ({'−' if b < a else '+'}{abs(b-a)/a*100:4.0f}%)")
|
||||
|
||||
# ---------------------------------------------------------------- (iii) potenza
|
||||
print("\n" + "=" * 100)
|
||||
print("(iii) POTENZA — chi guardasse l'HOLD-OUT sceglierebbe la stessa banda?")
|
||||
print("=" * 100)
|
||||
med_in = {f: mediana_fasi(f, 0, "in") for f in BG.FRAC_GRID}
|
||||
med_oos = {f: mediana_fasi(f, 0, "oos") for f in BG.FRAC_GRID}
|
||||
b_in, m_in = scelta(med_in)
|
||||
b_oos, m_oos = scelta(med_oos)
|
||||
T = pd.DataFrame([dict(banda=f"{f:.0%}", med_in_sample=round(med_in[f], 4),
|
||||
med_hold_out=round(med_oos[f], 4)) for f in BG.FRAC_GRID])
|
||||
print(T.to_string(index=False))
|
||||
print(f"\n scelta al buio (pre-2015): {b_in:.0%} (margine {m_in:.4f})")
|
||||
print(f" scelta sull'hold-out : {b_oos:.0%} (margine {m_oos:.4f})")
|
||||
potenza = b_in != b_oos
|
||||
print(f" -> {'DIVERSE: il gate ha potenza' if potenza else 'UGUALI: il gate NON ha potenza'}")
|
||||
|
||||
# ---------------------------------------------------------------- verdetto
|
||||
print("\n" + "=" * 100)
|
||||
print("VERDETTO — coi criteri dichiarati in testa, calcolati adesso")
|
||||
print("=" * 100)
|
||||
stabile = len(scelte) == 1
|
||||
sopra = bool(D.margine.min() >= MARGINE_MINIMO)
|
||||
print(f" (i) scelta stabile su {NOTTI} notti : {'SI' if stabile else 'NO'} — {sorted(scelte)}")
|
||||
print(f" (ii) margine sempre >= {MARGINE_MINIMO} : {'SI' if sopra else 'NO'} — minimo {D.margine.min():.4f}")
|
||||
print(f" (iii) potenza (in ≠ hold-out) : {'SI' if potenza else 'NO'}")
|
||||
if stabile and sopra and potenza:
|
||||
print("\n -> (C) e' uno strumento utilizzabile. NON e' una validazione della banda:")
|
||||
print(" va DICHIARATO come criterio, con la sua data, e la validazione va RIFATTA su di esso.")
|
||||
return 0
|
||||
manca = [n for n, v in (("i", stabile), ("ii", sopra), ("iii", potenza)) if not v]
|
||||
print(f"\n -> (C) NON basta: cade su ({'), ('.join(manca)}).")
|
||||
print(" La mediana toglie la FASE ma non le barre perse: se il verdetto si muove ancora,")
|
||||
print(" l'instabilita' e' nel DATO, e nessun criterio la aggira. Si ripara la fonte.")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0828_gtaa_band_phase.py — il gate (A) della banda GTAA01 fallisce di nuovo: e' la proposta, o e' ANCORA il gate?
|
||||
|
||||
CONTESTO. Il 27/07 fu proposta la banda al 25% della gamba. Il gate (A) — SELEZIONE IN-SAMPLE —
|
||||
la valido' due volte, con due criteri diversi, e li ha rotti entrambi senza che nessuno toccasse
|
||||
il codice:
|
||||
|
||||
· 27/07 criterio `rank_in <= rank_oos`. Rotto il 07/08. Motivo (r0807_gtaa_gate_resolution):
|
||||
lo passa ~meta' della griglia PER COSTRUZIONE, e sulla proposta decideva su 0.00116
|
||||
di Sharpe. Una moneta. RITIRATO.
|
||||
· 07/08 criterio sostitutivo: «a cadenza di produzione, la banda scelta guardando SOLO il
|
||||
pre-2015 e' la proposta». Passava col 25% e un margine di +0.0151 sulla seconda —
|
||||
13x il criterio precedente. Rotto il 28/08: al buio esce il 60%, margine 0.0074,
|
||||
SOTTO il pavimento di 0.01 che il test stesso si era dato.
|
||||
|
||||
Il codice e' fermo (`git log src/portfolio/gtaa.py scripts/research/r0727_gtaa_band_gate.py`
|
||||
non ha commit dopo il 07/08). Quindi si e' mosso il DATO. Questo script chiede, per la terza
|
||||
volta e sullo stesso parametro, se il criterio misura cio' che dichiara.
|
||||
|
||||
LE DUE COSE CHE SI SONO TROVATE
|
||||
-------------------------------
|
||||
(0) **LA FINESTRA IN-SAMPLE NON E' FERMA.** IB ritorna una finestra ROTOLANTE di 30 anni. SPY e'
|
||||
l'unica gamba al muro (quotato 1993, storia servita dal 1996), e nel `logs/cron_daily.log`
|
||||
la sua data d'inizio avanza di un giorno di borsa OGNI NOTTE: 1996-07-02 il 24/06,
|
||||
1996-09-04 oggi. Il pre-2015 — che tutto il progetto tratta come una finestra congelata —
|
||||
perde il suo giorno piu' vecchio ogni notte. (QQQ e IWM colpiranno lo stesso muro nel
|
||||
2029-2030: non e' un difetto di SPY, e' il muro che avanza.)
|
||||
|
||||
(1) **L'AMPLIFICATORE E' LA FASE.** `gtaa._gated_returns` ribilancia su `i % every == 0`: l'indice
|
||||
e' la POSIZIONE NELL'ARRAY, quindi la fase e' ancorata alla prima barra del file. Se la prima
|
||||
barra scivola di un giorno, si ri-fasa ogni decisione di ribilanciamento di trent'anni di
|
||||
storia. Il progetto sa gia' che la fase conta — `r0726_loo_deluck.gtaa01_at(ph)` la tratta
|
||||
come un'ancora a 5 valori e ci de-lucka sopra — ma la assumeva FISSA alla fase canonica 0.
|
||||
Non lo e': **la fase canonica cambia da sola ogni notte**.
|
||||
|
||||
I DUE ESPERIMENTI, in quest'ordine
|
||||
----------------------------------
|
||||
(A) **LE NOTTI.** Si taglia la testa di SPY di k=0..N-1 barre, che e' esattamente cio' che la
|
||||
finestra rotolante fa da sola in N notti, e si guarda la banda scelta al buio.
|
||||
(B) **LA FASE PURA.** A dato FISSO, si sposta la sola fase di ribilanciamento di SPY (ph=0..4).
|
||||
Serve ad attribuire: se il verdetto si muove anche qui, l'imputato e' la fase, non le barre
|
||||
perse. Col controllo su TUTTE le gambe accanto, che dice quanto sarebbe peggio quando anche
|
||||
QQQ e IWM arriveranno al muro.
|
||||
|
||||
CRITERIO DEL VERDETTO, dichiarato PRIMA (M13: un criterio si misura sulla sua RISOLUZIONE prima
|
||||
che sul suo esito). Il gate (A) ha risoluzione se e solo se:
|
||||
(i) la banda scelta al buio e' LA STESSA in tutte le notti e in tutte le fasi, e
|
||||
(ii) il margine sulla seconda resta sopra MARGINE_MINIMO in tutte.
|
||||
Se (i) o (ii) cadono, il criterio non distingue la proposta da una cella qualunque, e va ritirato
|
||||
come il suo predecessore — si congela il MOTIVO, non l'esito.
|
||||
|
||||
uv run python scripts/research/r0828_gtaa_band_phase.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
|
||||
|
||||
import r0727_gtaa_band_gate as BG # noqa: E402
|
||||
import src.portfolio.gtaa as G # noqa: E402
|
||||
|
||||
NOTTI = 10 # notti di finestra rotolante da simulare
|
||||
MARGINE_MINIMO = 0.01 # il pavimento che il test si e' dato il 07/08, non ritoccato qui
|
||||
MURO_IB_ANNI = 30.0 # finestra massima servita da IB: chi la tocca rotola
|
||||
PROPOSTA_BANDA = BG.PROPOSTA[1]
|
||||
|
||||
# Tabella PUBBLICATA il 07/08 (docs/diary/2026-08-07-gate-gtaa-e-storia-troncata.md, sezione (2)).
|
||||
# Serve a misurare di quanto si e' mossa una finestra che NON puo' acquisire dati nuovi.
|
||||
SH_IN_0807 = {0.00: 0.5396, 0.05: 0.5714, 0.10: 0.5956, 0.25: 0.6398, 0.40: 0.6247, 0.60: 0.5874}
|
||||
|
||||
|
||||
def _leg(sym: str, cap_leg: float, band_usd: float, every: int, ph: int,
|
||||
taglio: int = 0) -> pd.Series:
|
||||
"""`gtaa._gated_returns` con due manopole: la FASE (`ph`) e il TAGLIO in testa alla serie.
|
||||
|
||||
Copia dichiarata, non riuso: la produzione non espone ne' l'una ne' l'altra. La replica a
|
||||
(ph=0, taglio=0) deve tornare bit-exact quella di produzione — lo verifica `sanity()`.
|
||||
"""
|
||||
close = G._close(sym)
|
||||
if taglio:
|
||||
close = close.iloc[taglio:]
|
||||
ex = np.nan_to_num(np.asarray(G._exposure(close).values, float))
|
||||
px = close.values.astype(float)
|
||||
ret = np.zeros(len(px))
|
||||
ret[1:] = px[1:] / px[:-1] - 1.0
|
||||
held = np.empty(len(ex))
|
||||
comm = np.zeros(len(ex))
|
||||
cur = 0.0
|
||||
for i in range(len(ex)):
|
||||
if i % every == ph:
|
||||
notional = abs(ex[i] - cur) * cap_leg
|
||||
if notional >= max(band_usd, G.IB_MIN_TRADE_USD):
|
||||
comm[i] = G.ib_commission(notional, px[i]) / cap_leg
|
||||
cur = ex[i]
|
||||
held[i] = cur
|
||||
pos = np.zeros(len(held))
|
||||
pos[1:] = held[:-1]
|
||||
net = pos * ret - comm
|
||||
net[0] = 0.0
|
||||
return pd.Series(net, index=close.index)
|
||||
|
||||
|
||||
def cella(frac: float, ph: int = 0, taglio: int = 0, tutte: bool = False) -> pd.Series:
|
||||
"""La cella (cadenza di produzione, banda `frac`). `ph`/`taglio` si applicano alla sola SPY
|
||||
salvo `tutte`: e' SPY l'unica gamba al muro dei 30 anni, e il resto della griglia e' fermo."""
|
||||
cap_leg = BG.CAP_REF / len(G.EQ_UNIVERSE)
|
||||
band = frac * BG.CAP_REF / len(G.EQ_UNIVERSE)
|
||||
cols = {a: _leg(a, cap_leg, band, BG.PROPOSTA[0],
|
||||
ph if (tutte or a == "SPY") else 0,
|
||||
taglio if (tutte or a == "SPY") else 0)
|
||||
for a in G.EQ_UNIVERSE}
|
||||
return pd.concat(cols, axis=1, sort=True).sort_index().mean(axis=1, skipna=True).dropna()
|
||||
|
||||
|
||||
def riga(ph: int = 0, taglio: int = 0, tutte: bool = False) -> dict:
|
||||
sh = {f: BG.met(cella(f, ph, taglio, tutte).loc[: BG.HOLDOUT])["sharpe"] for f in BG.FRAC_GRID}
|
||||
best = max(sh, key=sh.get)
|
||||
secondo = max(v for f, v in sh.items() if f != best)
|
||||
return dict(scelta=best, margine=sh[best] - secondo, sh=sh)
|
||||
|
||||
|
||||
def sanity() -> bool:
|
||||
"""La replica a (fase 0, taglio 0) DEVE essere quella di produzione. Senza questo, ogni
|
||||
numero sotto misura la mia copia invece dello sleeve che gira."""
|
||||
mia = cella(PROPOSTA_BANDA, 0, 0)
|
||||
vera = BG.run_cell(BG.PROPOSTA[0], PROPOSTA_BANDA, BG.CAP_REF)
|
||||
j = pd.concat({"m": mia, "v": vera}, axis=1, join="outer")
|
||||
delta = float((j["m"] - j["v"]).abs().max())
|
||||
ok = bool(np.isfinite(delta) and delta < 1e-12 and len(j) == len(vera))
|
||||
print(f"[SANITY] replica (fase 0, taglio 0) vs produzione: max|Δ| = {delta:.2e} "
|
||||
f"barre {len(j)} vs {len(vera)} -> {'OK' if ok else 'DIVERGE'}")
|
||||
return ok
|
||||
|
||||
|
||||
def storia() -> pd.DataFrame:
|
||||
"""(0) Quali gambe sono al muro dei 30 anni — cioe' quali hanno una finestra ROTOLANTE."""
|
||||
rows = []
|
||||
for a in G.EQ_UNIVERSE:
|
||||
s = G._close(a)
|
||||
anni = (s.index[-1] - s.index[0]).days / 365.25
|
||||
rows.append(dict(gamba=a, dal=str(s.index[0].date()), barre=len(s), anni=round(anni, 1),
|
||||
al_muro="SI — rotola" if anni >= MURO_IB_ANNI - 0.15 else "no",
|
||||
anni_al_muro=round(MURO_IB_ANNI - anni, 1)))
|
||||
return pd.DataFrame(rows)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print(__doc__.split("\n\n")[0])
|
||||
print()
|
||||
if not sanity():
|
||||
print("!! la replica non riproduce la produzione: i numeri sotto non sono confrontabili")
|
||||
return 2
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print("(0) LA FINESTRA E' ROTOLANTE — quali gambe sono al muro dei 30 anni di IB")
|
||||
print("=" * 96)
|
||||
H = storia()
|
||||
print(H.to_string(index=False))
|
||||
rotolanti = list(H[H.al_muro.str.startswith("SI")].gamba)
|
||||
print(f"\n gambe con finestra rotolante: {rotolanti or 'nessuna'} — per queste, il pre-2015 "
|
||||
"perde il giorno piu' vecchio ogni notte.")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(" quanto si e' mossa una finestra che NON puo' acquisire dati nuovi (07/08 -> oggi)")
|
||||
print("=" * 96)
|
||||
oggi = riga(0, 0)["sh"]
|
||||
D = pd.DataFrame([dict(banda=f"{f:.0%}", sh_in_0807=SH_IN_0807[f], sh_in_oggi=round(oggi[f], 4),
|
||||
delta=round(oggi[f] - SH_IN_0807[f], 4)) for f in BG.FRAC_GRID])
|
||||
print(D.to_string(index=False))
|
||||
print(f"\n spostamento massimo in valore assoluto: {D.delta.abs().max():.4f} di Sharpe, "
|
||||
f"su una finestra CHIUSA (pre-2015) e a codice fermo.")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(f"(A) LE NOTTI — banda scelta al buio in {NOTTI} notti consecutive di finestra rotolante")
|
||||
print("=" * 96)
|
||||
spy = G._close("SPY")
|
||||
A = []
|
||||
for k in range(NOTTI):
|
||||
r = riga(0, k)
|
||||
A.append(dict(notte=k, inizio_SPY=str(spy.index[k].date()), scelta=f"{r['scelta']:.0%}",
|
||||
margine=round(r["margine"], 4),
|
||||
**{f"{f:.0%}": round(r["sh"][f], 4) for f in BG.FRAC_GRID}))
|
||||
DA = pd.DataFrame(A)
|
||||
print(DA.to_string(index=False))
|
||||
scelte_A = set(DA.scelta)
|
||||
quota_proposta = float((DA.scelta == f"{PROPOSTA_BANDA:.0%}").mean())
|
||||
print(f"\n celle distinte scelte: {sorted(scelte_A)} "
|
||||
f"| la proposta ({PROPOSTA_BANDA:.0%}) vince {quota_proposta:.0%} delle notti")
|
||||
print(f" margine: min {DA.margine.min():.4f} mediana {DA.margine.median():.4f} "
|
||||
f"max {DA.margine.max():.4f} (pavimento {MARGINE_MINIMO})")
|
||||
print(f" notti sotto il pavimento: {int((DA.margine < MARGINE_MINIMO).sum())}/{NOTTI}")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print("(B) LA FASE PURA — dato FISSO, si muove solo la fase di ribilanciamento")
|
||||
print("=" * 96)
|
||||
for tutte, eti in ((False, f"fase mossa solo su {rotolanti or ['SPY']} (cio' che succede DAVVERO oggi)"),
|
||||
(True, "fase mossa su TUTTE le gambe (controllo: quando anche QQQ e IWM saranno al muro)")):
|
||||
B = []
|
||||
for ph in range(BG.PROPOSTA[0]):
|
||||
r = riga(ph, 0, tutte)
|
||||
B.append(dict(fase=ph, scelta=f"{r['scelta']:.0%}", margine=round(r["margine"], 4),
|
||||
**{f"{f:.0%}": round(r["sh"][f], 4) for f in BG.FRAC_GRID}))
|
||||
DB = pd.DataFrame(B)
|
||||
print(f"\n {eti}")
|
||||
print(DB.to_string(index=False))
|
||||
print(f" celle distinte: {sorted(set(DB.scelta))} | margine min {DB.margine.min():.4f}")
|
||||
if not tutte:
|
||||
scelte_B, marg_B = set(DB.scelta), DB.margine
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print("VERDETTO — coi criteri dichiarati in testa allo script, calcolati adesso")
|
||||
print("=" * 96)
|
||||
stabile = len(scelte_A) == 1 and len(scelte_B) == 1
|
||||
sopra = bool(DA.margine.min() >= MARGINE_MINIMO and marg_B.min() >= MARGINE_MINIMO)
|
||||
print(f" (i) scelta stabile su notti e fasi : {'SI' if stabile else 'NO'} "
|
||||
f"— notti {sorted(scelte_A)}, fasi {sorted(scelte_B)}")
|
||||
print(f" (ii) margine sempre >= {MARGINE_MINIMO} : {'SI' if sopra else 'NO'} "
|
||||
f"— minimo osservato {min(DA.margine.min(), marg_B.min()):.4f}")
|
||||
if stabile and sopra:
|
||||
print("\n -> il gate (A) HA risoluzione: il fallimento del test e' un fatto sulla PROPOSTA.")
|
||||
return 0
|
||||
print("\n -> il gate (A) NON ha risoluzione. Il verdetto non e' una proprieta' della banda:")
|
||||
print(" e' una proprieta' della notte in cui lo si legge. Come il criterio a ranghi")
|
||||
print(" ritirato il 07/08, va ritirato — congelando il MOTIVO, non l'esito.")
|
||||
print(f" La banda della proposta ({PROPOSTA_BANDA:.0%}) resta la scelta al buio in "
|
||||
f"{quota_proposta:.0%} delle notti: ne' confermata ne' smentita, NON DECIDIBILE cosi'.")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0828_senza_gtaa.py — cosa cambia nel portafoglio di RICERCA se si toglie GTAA01.
|
||||
|
||||
PERCHE'. Il 28/08 si e' misurato che il gate (A) della banda GTAA01 non ha risoluzione, e che
|
||||
lo sleeve gira su una finestra che rotola da sola (r0828_gtaa_band_phase). Prima di decidere
|
||||
cosa fare del gate conviene sapere quanto pesa lo sleeve: se togliendolo non cambia niente, la
|
||||
domanda sul gate e' accademica.
|
||||
|
||||
⚠️ QUESTO NON E' IL LIBRO LIVE. Il book che gira e' TP01+SKH01 75/25 su Deribit: GTAA01 non c'e'.
|
||||
Qui si guarda il portafoglio a 5 sleeve di `sleeves.active_sleeves` (33/15/12/20/20), che e' la
|
||||
serie su cui stanno quasi tutti i numeri di portafoglio del progetto.
|
||||
|
||||
LE DUE LENTI, ed e' la seconda quella che decide (M6: un diversificatore a BASSO CAGR si giudica
|
||||
a iso-rischio, mai a iso-nozionale; M5: ogni claim "meno drawdown" si testa a iso-rischio prima
|
||||
di crederci, o si sta comprando de-levering e chiamandolo diversificazione):
|
||||
· ISO-NOZIONALE — si toglie GTAA01 e si ridistribuisce il suo 20% agli altri quattro in
|
||||
proporzione. E' cio' che succede materialmente.
|
||||
· ISO-RISCHIO — la stessa serie, riscalata alla volatilita' del portafoglio a 5. Risponde alla
|
||||
domanda giusta: a parita' di rischio preso, quanto rende in piu' o in meno?
|
||||
|
||||
Si stampano FULL e HOLD-OUT (la data e' quella del progetto, `portfolio.HOLDOUT`), il per-anno
|
||||
(M9: un contributo si scompone per anno prima di crederci) e la correlazione di GTAA01 col resto.
|
||||
Verdetto calcolato a runtime coi criteri dichiarati qui sotto.
|
||||
|
||||
CRITERIO, dichiarato prima: GTAA01 "porta qualcosa" se, A ISO-RISCHIO, toglierlo peggiora lo
|
||||
Sharpe FULL di almeno SOGLIA_SHARPE **e** l'hold-out non lo smentisce. Sotto quella soglia il
|
||||
contributo e' dentro lo spread fra le tre baseline che il progetto gia' si porta dietro (0.12 di
|
||||
Sharpe, §2 del CLAUDE.md), quindi non e' distinguibile dalla scelta della lente.
|
||||
|
||||
uv run python scripts/research/r0828_senza_gtaa.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.portfolio.portfolio import HOLDOUT, combine_outer, metrics, to_daily, yearly # noqa: E402
|
||||
from src.portfolio.sleeves import active_sleeves # noqa: E402
|
||||
|
||||
SOGLIA_SHARPE = 0.12 # spread fra le tre baseline "libro 75/25" (§2): sotto, non si vede
|
||||
FUORI = "GTAA01_eq_trend"
|
||||
|
||||
|
||||
def serie_sleeve() -> tuple[dict, dict]:
|
||||
cols, w = {}, {}
|
||||
for s in active_sleeves():
|
||||
cols[s.name] = s.daily().dropna()
|
||||
w[s.name] = float(s.weight)
|
||||
return cols, w
|
||||
|
||||
|
||||
def senza(w: dict, fuori: str) -> dict:
|
||||
"""I pesi degli altri, rinormalizzati: il 20% di GTAA01 va agli altri in proporzione."""
|
||||
resto = {k: v for k, v in w.items() if k != fuori}
|
||||
tot = sum(resto.values())
|
||||
return {k: v / tot for k, v in resto.items()}
|
||||
|
||||
|
||||
def riga(nome: str, m: dict) -> str:
|
||||
return (f" {nome:<22} Sh {m['sharpe']:>5.2f} CAGR {m['cagr']*100:>+6.1f}% "
|
||||
f"maxDD {m['maxdd']*100:>5.1f}% vol {m.get('vol', float('nan'))*100:>5.1f}% n {m['n']}")
|
||||
|
||||
|
||||
def con_vol(s: pd.Series) -> dict:
|
||||
m = metrics(s)
|
||||
m["vol"] = float(np.asarray(s.dropna().values, float).std() * np.sqrt(365.0))
|
||||
return m
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print(__doc__.split("\n\n")[0])
|
||||
cols, w5 = serie_sleeve()
|
||||
if FUORI not in cols:
|
||||
print(f"!! {FUORI} non e' fra gli sleeve attivi: niente da togliere")
|
||||
return 2
|
||||
w4 = senza(w5, FUORI)
|
||||
cols4 = {k: v for k, v in cols.items() if k != FUORI}
|
||||
|
||||
print(f"\n pesi CON : " + " ".join(f"{k.split('_')[0]} {v:.0%}" for k, v in w5.items()))
|
||||
print(f" pesi SENZA: " + " ".join(f"{k.split('_')[0]} {v:.0%}" for k, v in w4.items()))
|
||||
|
||||
con = combine_outer(cols, w5)
|
||||
sen = combine_outer(cols4, w4)
|
||||
# allineate sullo STESSO calendario: senza questo si confronterebbero due finestre diverse
|
||||
J = pd.concat({"con": con, "senza": sen}, axis=1, join="inner").dropna()
|
||||
con, sen = J["con"], J["senza"]
|
||||
|
||||
m_con, m_sen = con_vol(con), con_vol(sen)
|
||||
k = m_con["vol"] / m_sen["vol"] if m_sen["vol"] > 0 else 1.0
|
||||
sen_iso = sen * k
|
||||
m_iso = con_vol(sen_iso)
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(f" FULL ({con.index[0].date()} -> {con.index[-1].date()})")
|
||||
print("=" * 96)
|
||||
print(riga("CON GTAA01", m_con))
|
||||
print(riga("SENZA (iso-nozionale)", m_sen))
|
||||
print(riga(f"SENZA (iso-rischio ×{k:.2f})", m_iso))
|
||||
d_full = m_con["sharpe"] - m_iso["sharpe"]
|
||||
print(f"\n Δ Sharpe a iso-rischio (con − senza): {d_full:+.3f} "
|
||||
f"[soglia dichiarata {SOGLIA_SHARPE}]")
|
||||
print(f" Δ maxDD a iso-rischio: {(m_con['maxdd']-m_iso['maxdd'])*100:+.2f} pp")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(f" HOLD-OUT ({HOLDOUT.date()}+)")
|
||||
print("=" * 96)
|
||||
hc, hs = con[con.index >= HOLDOUT], sen[sen.index >= HOLDOUT]
|
||||
mhc, mhs = con_vol(hc), con_vol(hs)
|
||||
kh = mhc["vol"] / mhs["vol"] if mhs["vol"] > 0 else 1.0
|
||||
mhi = con_vol(hs * kh)
|
||||
print(riga("CON GTAA01", mhc))
|
||||
print(riga(f"SENZA (iso-rischio ×{kh:.2f})", mhi))
|
||||
d_hold = mhc["sharpe"] - mhi["sharpe"]
|
||||
print(f"\n Δ Sharpe a iso-rischio: {d_hold:+.3f}")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(" PER ANNO — un contributo positivo si scompone prima di crederci (M9)")
|
||||
print("=" * 96)
|
||||
yc, yi = yearly(con), yearly(sen * k)
|
||||
print(f" {'anno':<6} {'CON ret':>9} {'SENZA ret':>10} {'Δ ret':>8} {'CON DD':>7} {'SENZA DD':>9}")
|
||||
pos = 0
|
||||
for a in sorted(set(yc) & set(yi)):
|
||||
d = yc[a]["ret"] - yi[a]["ret"]
|
||||
pos += d > 0
|
||||
print(f" {a:<6} {yc[a]['ret']*100:>+8.1f}% {yi[a]['ret']*100:>+9.1f}% {d*100:>+7.1f}pp "
|
||||
f"{yc[a]['dd']*100:>6.1f}% {yi[a]['dd']*100:>8.1f}%")
|
||||
n_anni = len(set(yc) & set(yi))
|
||||
print(f"\n anni in cui GTAA01 aiuta: {pos}/{n_anni}")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(" CORRELAZIONE — quanto e' davvero 'altro' rispetto al resto")
|
||||
print("=" * 96)
|
||||
g = cols[FUORI]
|
||||
altro = combine_outer(cols4, w4)
|
||||
Jg = pd.concat({"g": g, "altro": altro}, axis=1, join="inner").dropna()
|
||||
print(f" GTAA01 vs resto del portafoglio: corr {Jg['g'].corr(Jg['altro']):+.3f} "
|
||||
f"({len(Jg)} giorni in comune)")
|
||||
print(f" GTAA01 standalone: " + riga("", con_vol(g)).strip())
|
||||
|
||||
# ---------------------------------------------------------------- la banda di FASE sul Δ
|
||||
print("\n" + "=" * 96)
|
||||
print(" LA FASCIA DI FASE SUL Δ — il Δ e' una misura, o una proprieta' della notte?")
|
||||
print("=" * 96)
|
||||
print(" GTAA01 ribilancia su `i % 5` a partire dalla PRIMA barra del file, e la prima barra")
|
||||
print(" di SPY scivola ogni notte (finestra IB rotolante a 30 anni: r0828_gtaa_band_phase).")
|
||||
print(" Quindi il Δ qui sopra va letto accanto alla sua escursione sulle 5 fasi.")
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
try:
|
||||
from r0726_loo_deluck import gtaa01_at # replica ESATTA dello sleeve, per fase
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f" non misurabile qui: {type(e).__name__}: {e}")
|
||||
gtaa01_at = None
|
||||
if gtaa01_at is not None:
|
||||
base = gtaa01_at(0)
|
||||
j0 = pd.concat({"a": base, "b": cols[FUORI]}, axis=1, join="inner").dropna()
|
||||
d0 = float((j0["a"] - j0["b"]).abs().max())
|
||||
print(f"\n [SANITY] fase 0 == sleeve di produzione: max|Δ| = {d0:.2e} "
|
||||
f"-> {'OK' if d0 < 1e-12 else 'DIVERGE (i numeri sotto non valgono)'}")
|
||||
deltas = []
|
||||
for ph in range(5):
|
||||
c = dict(cols); c[FUORI] = gtaa01_at(ph)
|
||||
cc = combine_outer(c, w5)
|
||||
Jp = pd.concat({"con": cc, "senza": sen}, axis=1, join="inner").dropna()
|
||||
mc = con_vol(Jp["con"])
|
||||
ms = con_vol(Jp["senza"])
|
||||
kk = mc["vol"] / ms["vol"] if ms["vol"] > 0 else 1.0
|
||||
d = mc["sharpe"] - con_vol(Jp["senza"] * kk)["sharpe"]
|
||||
deltas.append(d)
|
||||
print(f" fase {ph}: Sharpe CON {mc['sharpe']:.3f} Δ a iso-rischio {d:+.3f}"
|
||||
+ (" <- la fase che gira stanotte" if ph == 0 else ""))
|
||||
lo, hi = min(deltas), max(deltas)
|
||||
print(f"\n Δ su 5 fasi: da {lo:+.3f} a {hi:+.3f} (ampiezza {hi-lo:.3f}, "
|
||||
f"mediana {float(np.median(deltas)):+.3f})")
|
||||
sopra = sum(d >= SOGLIA_SHARPE for d in deltas)
|
||||
print(f" fasi in cui il Δ supera la soglia {SOGLIA_SHARPE}: {sopra}/5")
|
||||
if 0 < sopra < 5:
|
||||
print(" ⚠️ IL VERDETTO DIPENDE DALLA FASE: non e' una proprieta' di GTAA01,")
|
||||
print(" e' una proprieta' della notte in cui lo si legge.")
|
||||
|
||||
print("\n" + "=" * 96)
|
||||
print(" VERDETTO — criterio dichiarato in testa, calcolato adesso")
|
||||
print("=" * 96)
|
||||
porta = d_full >= SOGLIA_SHARPE and d_hold > -SOGLIA_SHARPE
|
||||
print(f" Δ Sharpe FULL a iso-rischio {d_full:+.3f} >= {SOGLIA_SHARPE}? "
|
||||
f"{'SI' if d_full >= SOGLIA_SHARPE else 'NO'}")
|
||||
print(f" l'hold-out non lo smentisce (Δ {d_hold:+.3f} > {-SOGLIA_SHARPE})? "
|
||||
f"{'SI' if d_hold > -SOGLIA_SHARPE else 'NO'}")
|
||||
if porta:
|
||||
print("\n -> GTAA01 PORTA: toglierlo costa piu' dello spread fra le baseline.")
|
||||
return 0
|
||||
print("\n -> il contributo di GTAA01 NON si distingue dalla scelta della lente.")
|
||||
print(" Non e' 'GTAA01 e' inutile': e' 'con questi dati non si vede'. E cio' che")
|
||||
print(" non si vede non giustifica di tenere in piedi un gate che non decide.")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,303 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0830_usde_quota.py — la decisione di QUOTA USDE, dovuta lunedi' 31/08. SOLA LETTURA.
|
||||
|
||||
COSA DECIDE E COSA NO. Il gate USDE-01 e' CHIUSO IDONEO (28/08): la pista e' aperta. Resta
|
||||
da scegliere *quanto* del conto tenere in USDE. Questo script NON sceglie: mette accanto ai
|
||||
criteri candidati la quota che ciascuno implica e il suo prezzo, perche' la scelta e'
|
||||
dell'operatore (N4: il rischio emittente non e' diversificabile dagli sleeve).
|
||||
|
||||
IL RISULTATO STRUTTURALE, DICHIARATO PRIMA DI GUARDARE I NUMERI (N6/P6). Il valore atteso
|
||||
e' LINEARE in q: EV(q) = q*E*(apr - hazard*...). Un massimo lineare sta sempre in un ANGOLO
|
||||
(0% o 100%). ⇒ **l'EV non puo' scegliere una quota interna**: puo' solo dire se il segno e'
|
||||
positivo. La quota interna nasce SOLO da un criterio sulla CODA, e quel criterio va
|
||||
dichiarato, non ottimizzato (M8: un argmax dentro un plateau non e' una decisione).
|
||||
|
||||
Quindi la struttura e':
|
||||
(A) il rendimento osservato, CON LA SUA BANDA e il suo n — un pagamento non e' un tasso;
|
||||
(B) l'hazard di PAREGGIO: sopra quello ogni quota e' EV-negativa. Criterio senza criterio;
|
||||
(C) il rischio MARGINALE, dato che il 100%-Deribit e' gia' stato accettato il 26/07;
|
||||
(D) i vincoli operativi: il haircut morde? il depeg costa churn?
|
||||
(E) il menu dei criteri di coda, ciascuno con la quota che implica e cosa costa in resa.
|
||||
|
||||
LENTI (N8: quando le leve hanno ordini di grandezza diversi, dirlo). La resa si legge in DUE
|
||||
monete e NON vanno sommate: (1) CASSA, gli euro veri all'anno; (2) DRIFT-EQUIVALENTE, via
|
||||
l'equivalenza registrata in CLAUDE.md 100 EUR/mese == +4,07%/anno di drift, che e' una
|
||||
capitalizzazione a 10 anni, non un flusso.
|
||||
|
||||
SEMPLIFICAZIONE DICHIARATA: EUR e USD trattati 1:1. A ~1,08 sposta i "mesi di versamento" di
|
||||
~8% (su 2,0 mesi = 0,16 mesi): non cambia nessuna soglia di questa tabella.
|
||||
|
||||
uv run python scripts/research/r0830_usde_quota.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from src.live import usde as U # noqa: E402
|
||||
|
||||
# P1: il tasso puntuale si DERIVA dal sorvegliante che gira, non si ridichiara qui. Il bootstrap
|
||||
# (banda) resta locale perche' usde_watch non lo fa — e i due denominatori NON coincidono:
|
||||
# rendimento() divide per il TEMPO VERO fra le letture, questo per il NUMERO di finestre.
|
||||
# La divergenza si stampa invece di essere appianata (P12).
|
||||
_spec = importlib.util.spec_from_file_location(
|
||||
"usde_watch", ROOT / "scripts" / "live" / "usde_watch.py")
|
||||
_uw = importlib.util.module_from_spec(_spec)
|
||||
sys.modules["usde_watch"] = _uw
|
||||
_spec.loader.exec_module(_uw)
|
||||
|
||||
WATCH = ROOT / "data" / "live" / "usde_watch.jsonl"
|
||||
|
||||
# --- stato del conto, letto dal venue il 2026-08-30T17:05Z e CONGELATO qui per riproducibilita' ---
|
||||
EQ_USDC = 1563.66151017
|
||||
AVAIL_USDC = 1552.28862113 # -> margine in uso = equity - available
|
||||
EQ_USDE = 500.175691
|
||||
GROSS_NOTIONAL = 355.327065 + 213.317387
|
||||
|
||||
# --- framework di rischio EREDITATO dalla decisione 26/07, non re-inventato ---
|
||||
# "100% Deribit fino a $20k: accettato P(perso tutto) 10/18/34/64% a p=0,5/1/2/5%"
|
||||
# riprodotto esattamente da P = 1-(1-p)^T con T=20. Il test lo verifica.
|
||||
ORIZZONTE_ANNI = 20
|
||||
P_DERIBIT = [0.005, 0.01, 0.02, 0.05]
|
||||
|
||||
# --- piano di versamento: la leva CERTA (N1). Baseline dichiarata in CLAUDE.md ---
|
||||
VERSAMENTO_BASE = 250.0 # EUR/mese, piano corrente
|
||||
VERSAMENTO_ALT = 500.0 # EUR/mese, l'alternativa gia' misurata
|
||||
EUR_PER_100_DRIFT = 4.07 / 100.0 # 100 EUR/mese == +4,07%/anno di drift
|
||||
|
||||
# --- ancore del libro, dalla banda onesta di CLAUDE.md §2 ---
|
||||
MAXDD_BOOK_HI = 0.0794 # maxDD FULL book, estremo alto della banda [5,99-7,94]
|
||||
MARGINE_PERP = 0.02 # tasso di margine iniziale osservato (verificato sotto)
|
||||
|
||||
SPREAD_BPS = 3.0 # spot USDE_USDC, osservato sul venue il 26/08 (fee 0)
|
||||
N_BOOT = 200_000 # M23: la risoluzione del MC si dichiara
|
||||
RNG = np.random.default_rng(20260830)
|
||||
|
||||
|
||||
def finestre(records: list[dict]) -> list[float]:
|
||||
"""I reward per finestra, in USDE. Uno zero E' un'osservazione (P5), non un buco."""
|
||||
return [float(r["reward_stimato"]) for r in records
|
||||
if r.get("reward_stimato") is not None and not r.get("con_trade")]
|
||||
|
||||
|
||||
def apr_banda(rew: list[float], base: float) -> dict:
|
||||
"""APR puntuale e banda bootstrap sulle finestre. Con n piccolo la banda E' il risultato."""
|
||||
n = len(rew)
|
||||
if n == 0:
|
||||
return dict(apr=None, lo=None, hi=None, n=0)
|
||||
a = np.array(rew, dtype=float)
|
||||
punt = a.mean() / base * 365.0
|
||||
draws = RNG.choice(a, size=(N_BOOT, n), replace=True).mean(axis=1) / base * 365.0
|
||||
lo, hi = np.percentile(draws, [2.5, 97.5])
|
||||
return dict(apr=punt, lo=float(lo), hi=float(hi), n=n,
|
||||
per_finestra=[x / base * 365.0 for x in rew])
|
||||
|
||||
|
||||
def p_rovina(p: float, anni: int = ORIZZONTE_ANNI) -> float:
|
||||
return 1.0 - (1.0 - p) ** anni
|
||||
|
||||
|
||||
def main() -> int:
|
||||
recs = [json.loads(l) for l in WATCH.read_text().splitlines() if l.strip()]
|
||||
c = U.cfg()
|
||||
px = float(recs[-1]["px"])
|
||||
usde_usd, _ = U.valuta(EQ_USDE, px)
|
||||
E = EQ_USDC + usde_usd
|
||||
q0 = usde_usd / E
|
||||
|
||||
rew = finestre(recs)
|
||||
R = apr_banda(rew, EQ_USDE)
|
||||
UFF = _uw.rendimento(recs) # il numero che gira, derivato
|
||||
R["apr_uff"] = UFF["apr"]
|
||||
R["giorni_uff"] = UFF["giorni"]
|
||||
|
||||
print("=" * 79)
|
||||
print(" ANALISI QUOTA USDE — decisione dovuta 2026-08-31")
|
||||
print(f" config: {c['fonte']} · stato conto congelato al 2026-08-30T17:05Z")
|
||||
print("=" * 79)
|
||||
|
||||
# ---------------------------------------------------------------- (A) il rendimento
|
||||
print("\n(A) IL RENDIMENTO, COL SUO n")
|
||||
print(f" finestre misurabili : {R['n']} (pagate {sum(1 for x in rew if x > 0)})")
|
||||
print(" per finestra : " + ", ".join(f"{x*100:.2f}%" for x in R["per_finestra"]))
|
||||
print(f" APR di usde_watch : {R['apr_uff']*100:.2f}% annuo"
|
||||
f" (denominatore = tempo vero, {R['giorni_uff']:.2f} giorni) <- IL NUMERO CHE GIRA")
|
||||
print(f" APR per-finestra : {R['apr']*100:.2f}% annuo"
|
||||
f" (denominatore = n finestre, {R['n']}.00 giorni)")
|
||||
print(f" divergenza : {abs(R['apr_uff']-R['apr'])*100:.2f} punti — i due"
|
||||
f" denominatori differiscono di {R['n']-R['giorni_uff']:.2f} giorni. Dichiarata (P12),")
|
||||
print(" non appianata: sotto si usa il puntuale di usde_watch.")
|
||||
print(f" banda bootstrap 95% : [{R['lo']*100:.2f}% - {R['hi']*100:.2f}%]"
|
||||
f" (n={R['n']}, {N_BOOT:,} ricampionamenti)")
|
||||
APR = R["apr_uff"]
|
||||
larghezza = R["hi"] - R["lo"]
|
||||
print(f" ⚠️ la banda e' larga {larghezza*100:.2f} punti su un livello di "
|
||||
f"{R['apr']*100:.2f}% (per-finestra): a n={R['n']} il tasso NON e' misurato, e' abbozzato.")
|
||||
|
||||
# ---------------------------------------------------------------- (B) hazard di pareggio
|
||||
print("\n(B) L'HAZARD DI PAREGGIO — l'unico criterio che non richiede un criterio")
|
||||
print(f" Perdita totale non recuperabile, resa {APR*100:.2f}%/anno.")
|
||||
print(f" ⇒ hazard annuo di pareggio = APR = {APR*100:.2f}%/anno")
|
||||
print(f" (banda: {R['lo']*100:.2f}% - {R['hi']*100:.2f}%)")
|
||||
print(f" ⇒ recupero di una perdita totale con la sola resa: {1/APR:.1f} anni"
|
||||
f" — INDIPENDENTE dalla quota.")
|
||||
print(f" Letto sul verso della decisione: la quota e' EV-positiva SE E SOLO SE si crede")
|
||||
print(f" che Ethena abbia meno del {APR*100:.1f}% annuo di probabilita' di evento")
|
||||
print(f" catastrofico. Quel numero e' ASSUNTO, non stimato — come il p di Deribit (26/07).")
|
||||
|
||||
# ---------------------------------------------------------------- (C) rischio marginale
|
||||
print("\n(C) IL RISCHIO MARGINALE, dato il 100%-Deribit gia' accettato (26/07)")
|
||||
print(f" Framework ereditato: P = 1-(1-p)^{ORIZZONTE_ANNI}. Riproduzione della riga 26/07:")
|
||||
riga = " p annuo ".ljust(14) + "".join(f"{p*100:>7.1f}%" for p in P_DERIBIT)
|
||||
print(riga)
|
||||
print(" P(perso tutto)".ljust(14) + "".join(f"{p_rovina(p)*100:>7.0f}%" for p in P_DERIBIT))
|
||||
print(" atteso 26/07 " + "".join(f"{v:>7}" for v in ("10%", "18%", "34%", "64%")))
|
||||
ok_repro = all(abs(p_rovina(p) * 100 - v) < 1.5
|
||||
for p, v in zip(P_DERIBIT, (10, 18, 34, 64)))
|
||||
print(f" riproduzione : {'OK' if ok_repro else 'DIVERGE'} (tolleranza 1,5 punti)")
|
||||
print(" ⇒ L'USDE fa perdere soldi SOLO nel mondo in cui Ethena salta E Deribit sopravvive.")
|
||||
print(" Nel mondo in cui salta Deribit, USDC e USDE si perdono insieme: la quota e'")
|
||||
print(" irrilevante. Il rischio AGGIUNTO dalla quota e' quindi di SECONDO ordine")
|
||||
print(" rispetto a quello gia' accettato — ma e' un TERZO strato sullo STESSO conto,")
|
||||
print(" e N4 dice che un rischio di venue si compra con un secondo CONTO, non")
|
||||
print(" aumentando l'esposizione al primo. Le due letture non si annullano: la prima")
|
||||
print(" dice che la quota costa poco, la seconda che non e' li' che si compra sicurezza.")
|
||||
|
||||
# ---------------------------------------------------------------- (D) vincoli operativi
|
||||
print("\n(D) I VINCOLI OPERATIVI — il haircut morde? il depeg costa?")
|
||||
margine_uso = EQ_USDC - AVAIL_USDC
|
||||
tasso_margine = margine_uso / GROSS_NOTIONAL
|
||||
print(f" nozionale lordo : ${GROSS_NOTIONAL:,.2f}")
|
||||
print(f" margine in uso : ${margine_uso:,.2f} ⇒ tasso {tasso_margine*100:.2f}%")
|
||||
print(f" tetto del libro : leva lorda 1,00x ⇒ nozionale max ${E:,.0f}"
|
||||
f" ⇒ margine max ~${E*tasso_margine:,.0f}")
|
||||
print(f" haircut USDE {c['haircut']:.0%} : il {1-c['haircut']:.0%} dell'USDE fa margine")
|
||||
print("\n quota USDC USDE margine utilizzabile serve al tetto morde?")
|
||||
binding = []
|
||||
for q in (q0, 0.25, 0.50, 0.75, 0.90):
|
||||
usde_q, usdc_q = E * q, E * (1 - q)
|
||||
util = usdc_q + usde_q * (1 - c["haircut"])
|
||||
serve = E * tasso_margine
|
||||
morde = util < serve
|
||||
binding.append(morde)
|
||||
print(f" {q:5.1%} ${usdc_q:9,.0f} ${usde_q:9,.0f} ${util:14,.0f}"
|
||||
f" ${serve:12,.0f} {'SI' if morde else 'no'}")
|
||||
print(f" ⇒ il haircut NON morde a nessuna quota testata: il libro gira al"
|
||||
f" {tasso_margine*100:.1f}% di margine,")
|
||||
print(" tre ordini di grandezza sotto il collaterale. D5: buco quantificato e innocuo.")
|
||||
depeg_eq = (1.0 - c["depeg_crit"])
|
||||
print(f"\n churn da depeg: a soglia critica {c['depeg_crit']} l'equity cala di"
|
||||
f" {depeg_eq:.0%}*q,")
|
||||
for q in (q0, 0.50):
|
||||
print(f" a quota {q:.1%} ⇒ equity {depeg_eq*q*100:.2f}% ⇒ target di posizione"
|
||||
f" -{depeg_eq*q*100:.2f}% (~${E*depeg_eq*q*1.0:,.0f} di nozionale da girare)")
|
||||
print(" ⇒ il libro de-risca mentre l'USDE e' a sconto e ri-carica al ritorno del peg:")
|
||||
print(" compra alto e vende basso, ma per importi di secondo ordine. Non e' il vincolo.")
|
||||
|
||||
# ---------------------------------------------------------------- (E) il menu dei criteri
|
||||
print("\n(E) IL MENU DEI CRITERI DI CODA — ciascuno con la quota che implica e il suo prezzo")
|
||||
print(" (l'EV e' lineare in q: NON produce un ottimo interno. Questi si', ma sono")
|
||||
print(" scelte di tolleranza, non misure.)")
|
||||
criteri = [
|
||||
("C0 status quo, nessuna decisione",
|
||||
q0, "la quota di oggi, arrivata li' per la taglia del test di eligibilita'"),
|
||||
("C1 <= maxDD del libro (7,94%)",
|
||||
MAXDD_BOOK_HI, "un evento emittente non deve superare cio' che la strategia gia' fa"),
|
||||
("C2 <= 6 mesi di versamento",
|
||||
6 * VERSAMENTO_BASE / E, f"a {VERSAMENTO_BASE:.0f} EUR/mese: il piano arretra <= mezzo anno"),
|
||||
("C3 <= 12 mesi di versamento",
|
||||
12 * VERSAMENTO_BASE / E, f"a {VERSAMENTO_BASE:.0f} EUR/mese: il piano arretra <= un anno"),
|
||||
("C4 tetto di ALLERTA di config",
|
||||
c["quota_max_frac"], "non e' un criterio: e' la soglia oltre cui il watch grida"),
|
||||
]
|
||||
print(f"\n {'criterio':38s} {'quota':>7s} {'USDE $':>9s} {'resa $/a':>9s}"
|
||||
f" {'drift bk':>9s} {'== EUR/m':>9s} {'perdita':>9s} {'mesi vers':>9s}")
|
||||
for nome, q, _ in criteri:
|
||||
q = min(max(q, 0.0), 1.0)
|
||||
usde_q = E * q
|
||||
resa = usde_q * APR
|
||||
drift = q * APR
|
||||
eur_mese = drift / EUR_PER_100_DRIFT * 100.0
|
||||
mesi = usde_q / VERSAMENTO_BASE
|
||||
print(f" {nome:38s} {q:6.1%} {usde_q:9,.0f} {resa:9,.2f}"
|
||||
f" {drift*100:8.2f}% {eur_mese:9,.0f} {usde_q:9,.0f} {mesi:9.1f}")
|
||||
print("\n note sulle colonne (N8: due monete, non si sommano):")
|
||||
print(" · 'resa $/a' = CASSA vera all'anno, alla taglia di oggi")
|
||||
print(" · '== EUR/m' = quanto varrebbe in VERSAMENTO l'equivalente di drift, lente 10 anni")
|
||||
print(" · 'mesi vers' = quanti mesi di bonifico brucia una perdita TOTALE")
|
||||
|
||||
# ---------------------------------------------------------------- (F) attesa e reversibilita'
|
||||
print("\n(F) COSA COSTA ASPETTARE — N10: una data si giustifica col COSTO della misura,")
|
||||
print(" non con la sua difficolta' percepita.")
|
||||
print(f" La conversione e' su spot {U.SPOT}: fee 0, spread osservato ~{SPREAD_BPS:.0f} bps")
|
||||
print(f" (26/08) ⇒ andata+ritorno ~{2*SPREAD_BPS:.0f} bps. La decisione e' REVERSIBILE"
|
||||
" a costo quasi nullo:")
|
||||
print(" non e' una porta a senso unico, e va decisa come tale.")
|
||||
print(f"\n {'attendere':>10s} {'finestre':>9s} {'costo andata/ritorno':>21s}"
|
||||
f" {'resa persa':>11s} {'costo totale':>13s}")
|
||||
for sett, q_target in ((0, 0.50), (2, 0.50), (4, 0.50), (8, 0.50)):
|
||||
incremento = E * q_target - usde_usd
|
||||
giorni = sett * 7
|
||||
persa = incremento * APR * giorni / 365.0
|
||||
rt = incremento * 2 * SPREAD_BPS / 10_000.0
|
||||
n_fin = R["n"] + giorni
|
||||
print(f" {sett:>7d} sett {n_fin:>9d} {rt:>20,.2f}$ {persa:>10,.2f}$"
|
||||
f" {rt+persa:>12,.2f}$")
|
||||
print(f"\n ⇒ portare la quota da {q0:.1%} a 50% fra QUATTRO settimane invece che domani")
|
||||
inc = E * 0.50 - usde_usd
|
||||
costo_4 = inc * APR * 28 / 365.0
|
||||
print(f" costa ${costo_4:,.2f} di resa non incassata, e compra"
|
||||
f" {R['n']+28}/{R['n']} = {(R['n']+28)/R['n']:.0f}x le finestre.")
|
||||
print(" A n=32 la banda si stringe di ~sqrt(8) ≈ 2,8x: da ~3,4 punti a ~1,2.")
|
||||
print(f" ${costo_4:,.2f} per sapere se il tasso e' 1,1% o 4,5% e' il miglior rapporto")
|
||||
print(" misura/costo che il progetto abbia incontrato (cfr. i due gate d'attesa da mesi")
|
||||
print(" eliminati in una sera perche' nessuno aveva contato i secondi, N10).")
|
||||
|
||||
# ---------------------------------------------------------------- verdetto a runtime (N11)
|
||||
print("\n" + "=" * 79)
|
||||
print(" VERDETTO calcolato a runtime")
|
||||
print("=" * 79)
|
||||
eur_mese_q0 = q0 * APR / EUR_PER_100_DRIFT * 100.0
|
||||
cassa_q0 = usde_usd * APR
|
||||
mesi_q0 = usde_usd / VERSAMENTO_BASE
|
||||
inc50 = E * 0.50 - usde_usd
|
||||
costo_attesa_4s = inc50 * APR * 28 / 365.0
|
||||
verdetti = []
|
||||
if R["n"] < 8:
|
||||
verdetti.append(f"RISOLUZIONE INSUFFICIENTE PER ALZARE: n={R['n']} finestre, banda"
|
||||
f" [{R['lo']*100:.2f}%-{R['hi']*100:.2f}%] larga {larghezza*100:.1f} punti."
|
||||
f" Alzare la quota ora e' comprare un tasso che non e' ancora misurato.")
|
||||
if not any(binding):
|
||||
verdetti.append("NESSUN VINCOLO OPERATIVO: il haircut non morde a nessuna quota"
|
||||
" testata; il vincolo non e' il margine.")
|
||||
if cassa_q0 < VERSAMENTO_BASE:
|
||||
verdetti.append(f"SCALA: la resa alla quota di oggi vale ${cassa_q0:,.0f}/anno,"
|
||||
f" cioe' meno di UN mese di versamento ({VERSAMENTO_BASE:.0f})."
|
||||
f" La leva binding resta il bonifico (N1/N3), non la quota.")
|
||||
if MAXDD_BOOK_HI < q0:
|
||||
verdetti.append(f"ASIMMETRIA: gia' alla quota di oggi ({q0:.1%}) l'evento emittente"
|
||||
f" vale {q0/MAXDD_BOOK_HI:.1f}x il maxDD dell'intero libro"
|
||||
f" ({MAXDD_BOOK_HI:.2%}), che e' il rischio che il progetto"
|
||||
f" ha passato due anni a limare.")
|
||||
verdetti.append(
|
||||
f"REVERSIBILE E QUASI GRATIS DA RIMANDARE: alzare a 50% fra 4 settimane invece che"
|
||||
f" domani costa ${costo_attesa_4s:,.2f} di resa e {2*SPREAD_BPS:.0f} bps di andata/ritorno,"
|
||||
f" e porta il campione da {R['n']} a {R['n']+28} finestre.")
|
||||
for v in verdetti:
|
||||
print(f" · {v}")
|
||||
print(f"\n Quota di oggi {q0:.1%} = ${usde_usd:,.0f} · resa ${cassa_q0:,.0f}/anno"
|
||||
f" · == {eur_mese_q0:,.0f} EUR/mese di drift · {mesi_q0:.1f} mesi di bonifico a rischio")
|
||||
print(" La SCELTA resta dell'operatore: questo script mette il prezzo accanto a ogni")
|
||||
print(" criterio, non ne sceglie uno (N4, e la colonna 'cosa la riapre' della §3).")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0831_margini_conto.py — riconcilia la pagina margini del conto. Riproduce due numeri.
|
||||
|
||||
PERCHE'. Due domande erano rimaste aperte e nessuna era rispondibile dal gateway (debito #11:
|
||||
`account_summary` filtrato a 8 campi, niente initial_margin, `extended` ignorato):
|
||||
|
||||
1. l'HAIRCUT sull'USDe: la KB ufficiale dice 5%, `config/live.json` diceva 10% "verificato sul
|
||||
venue il 26/08" senza traccia di come;
|
||||
2. quale MODELLO DI MARGINE e' attivo — che nessuno aveva mai chiesto.
|
||||
|
||||
Lo screenshot della pagina `Standard Margin` (2026-08-31 07:50:30 UTC, fornito dall'operatore) le
|
||||
chiude entrambe. Qui la riconciliazione, perche' un numero pubblicato deve avere uno script che lo
|
||||
riproduce (N11) e perche' l'haircut non si legge nella pagina: si RICAVA per differenza fra due
|
||||
righe, ed e' un conto che va mostrato invece che asserito.
|
||||
|
||||
uv run python scripts/research/r0831_margini_conto.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
# --- dalla schermata (2026-08-31 07:50:30 UTC) ---
|
||||
SEG_USDC = 1400.86 # S:SM (ATTIVO) -> riga USDC, Available Balance. USDE NON e' in tabella.
|
||||
CROSS_XSM = 2011.73 # X:SM -> riga CROSS, Available Balance
|
||||
PX_INDICE = 0.9998 # prezzo mostrato in cima alla pagina
|
||||
|
||||
# --- dal conto, letti via gateway alle ~07:55 dello stesso giorno ---
|
||||
USDC_EQ = 1412.281074
|
||||
USDE_EQ = 643.175691
|
||||
IM_MIS = 11.5530 # riservato in USDC = equity - available (misurato)
|
||||
ETH_DUST_USD = 0.000078 * 2500 # riga ETH della schermata, ~$0.20
|
||||
|
||||
TOLL = 2.0 # $: sotto questo scarto due ipotesi non si distinguono
|
||||
|
||||
|
||||
def haircut_implicito(cross: float, usdc: float, eth_dust: float, im: float,
|
||||
usde: float, px: float) -> tuple[float, float]:
|
||||
"""PURA. -> (contributo dell'USDE al cross in USD, haircut implicito).
|
||||
|
||||
L'haircut non e' scritto da nessuna parte nella pagina: si ricava perche' il CROSS conta
|
||||
l'USDE scontato e il SEGREGATO non lo conta affatto. La differenza fra le due righe, tolto
|
||||
cio' che e' comune, E' il contributo dell'USDE."""
|
||||
contributo = cross - (usdc + eth_dust - im)
|
||||
return contributo, 1.0 - contributo / (usde * px)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("=" * 78)
|
||||
print(" PAGINA MARGINI — riconciliazione (screenshot 2026-08-31 07:50:30Z)")
|
||||
print("=" * 78)
|
||||
|
||||
atteso_seg = USDC_EQ - IM_MIS
|
||||
print(f"\n [1] S:SM, il modello ATTIVO — solo il silo USDC")
|
||||
print(f" USDC equity {USDC_EQ:,.2f} - IM {IM_MIS:,.2f} = {atteso_seg:,.2f}"
|
||||
f" schermata {SEG_USDC:,.2f} scarto ${abs(atteso_seg-SEG_USDC):.2f}")
|
||||
print(f" → l'USDE non compare in questa tabella: nel modello attivo NON fa margine.")
|
||||
|
||||
contrib, h = haircut_implicito(CROSS_XSM, USDC_EQ, ETH_DUST_USD, IM_MIS, USDE_EQ, PX_INDICE)
|
||||
print(f"\n [2] X:SM — l'haircut si RICAVA per differenza")
|
||||
print(f" contributo USDE al cross = ${contrib:,.2f} su ${USDE_EQ*PX_INDICE:,.2f} di valore")
|
||||
print(f" → haircut implicito {h:.4%}")
|
||||
for cand, nome in ((0.05, "KB ufficiale"), (0.10, "config al 30/08")):
|
||||
att = USDE_EQ * PX_INDICE * (1 - cand)
|
||||
ok = abs(att - contrib) < TOLL
|
||||
print(f" {cand:.0%} ({nome:16s}) → atteso ${att:,.2f}, scarto ${abs(att-contrib):6.2f}"
|
||||
f" {'TORNA' if ok else 'NON torna'}")
|
||||
|
||||
verdetto_h = "5%" if abs(USDE_EQ*PX_INDICE*0.95 - contrib) < TOLL else "indeterminato"
|
||||
delta_modelli = CROSS_XSM - SEG_USDC
|
||||
print(f"\n VERDETTO (calcolato a runtime, N11):")
|
||||
print(f" haircut USDe = {verdetto_h} → `config/live.json` diceva 10%: SBAGLIATO")
|
||||
print(f" modello attivo = SEGREGATO (S:SM) → l'USDE non e' collaterale del book")
|
||||
print(f" passare a X:SM aggiungerebbe ${delta_modelli:,.2f} di margine utilizzabile")
|
||||
print(f"\n{'=' * 78}")
|
||||
live()
|
||||
return 0
|
||||
|
||||
|
||||
# --- 2026-08-31 08:34Z: X:SM e' ATTIVO. La pagina non serve piu': si RICOSTRUISCE. --------------
|
||||
#
|
||||
# L'operatore ha incollato la riga CROSS a X:SM attivo: Available $2.010,81, IM 2,15%, MM 0,37%.
|
||||
# Il gateway, letto allo stesso minuto, dice `available_funds` = 2010.82299383 — **scarto $0,01**.
|
||||
# ⇒ la riga CROSS della pagina E' `available_funds`, che leggiamo da soli. Lo screenshot che ieri
|
||||
# era l'unica fonte per l'haircut e per il modello attivo NON e' piu' necessario a nulla.
|
||||
PAG_AVAIL, PAG_IM_PCT, PAG_MM_PCT = 2010.81, 0.0215, 0.0037 # incollati dall'operatore, 08:34Z
|
||||
GW_AVAIL, GW_MB = 2010.82299383, 2055.07348077 # gateway, stesso minuto
|
||||
GW_USDC_EQ, GW_USDE = 1400.70809113, 654.175691
|
||||
XPM_AVAIL = 1935.14 # riga X:PM della schermata delle 07:50 (proiezione, modello inattivo)
|
||||
|
||||
|
||||
def im_di_posizione(avail: float, usdc_eq: float, usde: float, px: float, h: float,
|
||||
dust: float = ETH_DUST_USD) -> float:
|
||||
"""PURA. -> IM di POSIZIONE in USD, ripulita dall'haircut.
|
||||
|
||||
Inversa esatta della riga CROSS: available = usdc_eq + (1-h)*usde*px + dust - IM.
|
||||
Serve perche' la percentuale stampata dalla pagina NON e' questa (vedi live())."""
|
||||
return usdc_eq + (1 - h) * usde * px + dust - avail
|
||||
|
||||
|
||||
def live() -> None:
|
||||
print(" X:SM ATTIVO (2026-08-31 08:34Z) — la pagina si ricostruisce dal gateway")
|
||||
print("=" * 78)
|
||||
ric = GW_USDC_EQ + 0.95 * GW_USDE + ETH_DUST_USD - IM_MIS
|
||||
print(f"\n [3] la riga CROSS, ricostruita con haircut 5%")
|
||||
print(f" {GW_USDC_EQ:,.2f} + 0,95x{GW_USDE:,.3f} + {ETH_DUST_USD:.2f} dust"
|
||||
f" - {IM_MIS:.3f} IM = ${ric:,.2f}")
|
||||
print(f" pagina ${PAG_AVAIL:,.2f} · gateway ${GW_AVAIL:,.2f} → scarto ${abs(ric-PAG_AVAIL):.4f}")
|
||||
for h in (0.05, 0.10):
|
||||
im = im_di_posizione(GW_AVAIL, GW_USDC_EQ, GW_USDE, 1.0, h)
|
||||
print(f" haircut {h:5.0%} → IM implicita ${im:9,.2f}"
|
||||
f" {'coerente con gli $11,55 misurati' if im > 0 else 'IMPOSSIBILE: IM negativa'}")
|
||||
print(" → terza conferma del 5%, ed e' la prima ESATTA. Il 10% non e' improbabile:"
|
||||
"\n e' aritmeticamente impossibile.")
|
||||
|
||||
riserv = GW_MB - GW_AVAIL
|
||||
hc = 0.05 * GW_USDE
|
||||
print(f"\n [4] 🚨 l'\"IM %\" della pagina NON e' il margine delle posizioni")
|
||||
print(f" (margin_balance - available)/margin_balance = {riserv/GW_MB:.4%}"
|
||||
f" → la pagina stampa {PAG_IM_PCT:.2%}")
|
||||
print(f" scomposta: haircut USDE ${hc:,.2f} ({hc/GW_MB:.3%})"
|
||||
f" + IM vera ${IM_MIS:,.2f} ({IM_MIS/GW_MB:.3%})")
|
||||
print(f" ⇒ il {hc/riserv:.0%} di quella percentuale e' HAIRCUT, non rischio.")
|
||||
print(f" E' salita da 2,13% a 2,15% perche' abbiamo COMPRATO COLLATERALE A RENDIMENTO.")
|
||||
print(f" La MM invece e' pulita: {PAG_MM_PCT:.2%} = ${PAG_MM_PCT*GW_MB:,.2f}, e non puo'"
|
||||
f" contenere\n l'haircut (${hc:,.2f} da solo la renderebbe negativa):"
|
||||
" le due colonne hanno basi DIVERSE.")
|
||||
|
||||
print(f"\n [5] X:PM — il confronto rifatto sulla misura pulita")
|
||||
print(f" La KB da' USDe al 5% sotto ENTRAMBI i modelli: l'haircut si CANCELLA nel confronto.")
|
||||
for nome, av in (("X:SM", CROSS_XSM), ("X:PM", XPM_AVAIL)):
|
||||
im = im_di_posizione(av, USDC_EQ, USDE_EQ, PX_INDICE, 0.05)
|
||||
print(f" {nome}: available ${av:,.2f} → IM di POSIZIONE ${im:6,.2f}")
|
||||
im_sm = im_di_posizione(CROSS_XSM, USDC_EQ, USDE_EQ, PX_INDICE, 0.05)
|
||||
im_pm = im_di_posizione(XPM_AVAIL, USDC_EQ, USDE_EQ, PX_INDICE, 0.05)
|
||||
print(f" → PM chiede {im_pm/im_sm:.1f}x il margine iniziale sulle STESSE due posizioni,"
|
||||
f"\n e {0.0343/PAG_MM_PCT:.1f}x la MM. Due misure pulite, stesso verso: SCARTATO confermato.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,716 @@
|
||||
"""COLLAR01 — hold BTC (long/short) con COLLAR di opzioni a <=15 giorni.
|
||||
|
||||
DOMANDA (posta dall'operatore il 2026-09-01):
|
||||
"crea una strategia di hold BTC in long o short con copertura con options (max 15gg)"
|
||||
+ due precisazioni in corso d'opera:
|
||||
"opzione deve essere al max di 15gg" -> vincolo sulla SCADENZA, non sul solo holding
|
||||
"voglio ridurre la vincita, ma bloccare la perdita" -> NON e' una put protettiva: e' un COLLAR
|
||||
(pavimento comprato, tetto venduto per finanziarlo).
|
||||
"l'entrata deve essere gestita da una strategia confortata da indicatori (es. forte bull)"
|
||||
"dalle opzioni dobbiamo uscire prima del termine (tra 50% e 75% del tempo)" -> `exit_frac`.
|
||||
|
||||
PERCHE' QUESTO FILONE NON E' §46 (TAIL-HEDGE, REFUTATO il 2026-08-23).
|
||||
§46 compro' put deep-OTM sul LIBRO (TP01+SKH01) e fu refutato *prima del prezzo*: il maxDD
|
||||
saliva in 162/162 celle perche' **il beta del libro al sottostante e' +0,076** — "non si
|
||||
assicura un libro che nei crash e' gia' quasi piatto".
|
||||
Qui il sottostante e' un **hold di BTC nudo, beta 1,0 per costruzione**: il motivo che uccise
|
||||
§46 NON si applica, ed e' l'unica ragione per cui questo filone puo' essere riaperto
|
||||
(CLAUDE.md: "chi la riapre deve battere il motivo, non ripetere la misura").
|
||||
§46 dichiara inoltre di NON aver provato la copertura dinamica e di aver misurato la sola
|
||||
statica: anche quella porta e' aperta, ma qui non la uso — il collar e' statico a griglia.
|
||||
|
||||
COSA E' GIA' NOTO E VIENE RIUSATO, NON RIMISURATO:
|
||||
- metriche e i due null di scala (`k_for_same_dd`, `k_for_same_drift`) da r0823_tail_hedge;
|
||||
- listino fee opzioni Deribit (taker 0,03% del sottostante, cap 12,5% del premio, consegna
|
||||
0,015%) dallo stesso;
|
||||
- la catena reale via `cblib.load_chain()` (archivio 2026-05-01 + raccolta propria 2026-07-30).
|
||||
|
||||
LE DUE LENTI, ed e' una scelta dichiarata:
|
||||
(A) LENTE LUNGA (decide): 2021-03-24 -> oggi, la finestra del DVOL — ~5,4 anni. Opzioni
|
||||
prezzate a modello (BS, r=0) con DVOL come ancora ATM, CORRETTA da skew e struttura a
|
||||
termine MISURATI SULLA CATENA VERA, e spread f misurato per ala. E' l'impianto di §46.
|
||||
(B) LENTE REALE (controlla il segno, NON decide): 2026-05-01 -> oggi, solo quote a due lati.
|
||||
123 giorni = ~8 cicli a 15 giorni: sotto-potenziata per costruzione, e per giunta su una
|
||||
finestra in cui BTC e' salito da ~64,7k a ~77,5k, che e' AVVERSA a un collar (il tetto si
|
||||
paga proprio li'). Si legge il segno, mai la taglia.
|
||||
|
||||
M5 (null del de-levering) E' IL PRIMO TEST, NON L'ULTIMO: ogni claim "meno drawdown" si misura
|
||||
contro "tieni semplicemente meno BTC" a iso-maxDD. Se il collar non batte quello, non e'
|
||||
protezione: e' una ri-scalatura pagata cara.
|
||||
C9 e' il secondo: "una regola d'uscita che scatta piu' spesso sui vincenti che sui perdenti non e'
|
||||
protezione, e' troncatura" — qui il tetto e' letteralmente quella regola.
|
||||
|
||||
NESSUN ORDINE. Libro, pesi, cron, config INVARIATI.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy.stats import norm
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import cblib # noqa: E402
|
||||
from src.data.downloader import load_data # noqa: E402
|
||||
|
||||
DPY = 365.25
|
||||
|
||||
# ---- listino Deribit opzioni (identico a r0823_tail_hedge, non ridichiarato a mano) -----------
|
||||
OPT_TAKER_UNDERLYING = 0.0003
|
||||
OPT_FEE_CAP_FRAC = 0.125
|
||||
OPT_DELIVERY_UNDERLYING = 0.00015
|
||||
|
||||
# ---- GRIGLIA DICHIARATA PRIMA DI GUARDARE I RISULTATI -----------------------------------------
|
||||
G_DPUT = (0.10, 0.20, 0.30) # |delta| della put comprata = il PAVIMENTO
|
||||
G_DCALL = (0.10, 0.20, 0.30) # delta della call venduta = il TETTO
|
||||
G_TENOR = (7, 14) # giorni a scadenza: <=15, vincolo dell'operatore
|
||||
N_CELLS = len(G_DPUT) * len(G_DCALL) * len(G_TENOR)
|
||||
|
||||
# varianti dichiarate a parte (non nella griglia, per non gonfiare i trial)
|
||||
V_ZEROCOST = "zero-cost" # tetto scelto per finanziare esattamente il pavimento
|
||||
|
||||
ATTESE = """
|
||||
ATTESE A PRIORI — scritte e stampate PRIMA di misurare (M12: un follow-up contiene una previsione).
|
||||
A1 Il maxDD SCENDE davvero, a differenza di §46: il beta e' 1,0 e il pavimento morde.
|
||||
(se A1 fallisse, il filone cade come §46 e per lo stesso motivo)
|
||||
A2 Il drift scende PIU' del maxDD, perche' il rendimento di BTC vive nella coda destra e il
|
||||
tetto la taglia. Previsione: rapporto Δdrift/ΔmaxDD > 1.
|
||||
A3 Il collar PERDE contro il null del de-levering a iso-maxDD in piu' della meta' delle celle.
|
||||
E' la previsione centrale del filone.
|
||||
A4 Lo skew rende il collar delta-simmetrico un DEBITO netto (put piu' cara della call).
|
||||
[gia' misurato prima di scrivere: +31% / +19% / +12% a |d| 0,10 / 0,20 / 0,30 -> CONFERMATA]
|
||||
A5 Il tick morde MENO che in §46: quella era la famiglia USDC (tick 5 USDC); la catena che
|
||||
raccogliamo e' 100% inverse, con tick in BTC.
|
||||
A6 Nella lente reale (BTC +20% in 123 giorni) il collar sembrera' pessimo: finestra avversa,
|
||||
non evidenza. Va letta come segno, non come taglia.
|
||||
A7 Il verso SHORT sara' peggiore del LONG: il drift di BTC e' positivo e lo short lo paga tutto,
|
||||
col collar che ne cappa il solo rimbalzo favorevole.
|
||||
"""
|
||||
|
||||
|
||||
# ==================================================================== 0. METRICHE E NULL (da §46)
|
||||
|
||||
def _sh(x) -> float:
|
||||
r = np.asarray(pd.Series(x).dropna().values, float)
|
||||
return float(np.mean(r) / np.std(r) * math.sqrt(DPY)) if len(r) > 2 and np.std(r) > 0 else 0.0
|
||||
|
||||
|
||||
def _dd(x) -> float:
|
||||
r = np.asarray(pd.Series(x).dropna().values, float)
|
||||
eq = np.cumprod(1.0 + r)
|
||||
pk = np.maximum.accumulate(eq)
|
||||
return float(np.max((pk - eq) / pk)) if len(eq) else 0.0
|
||||
|
||||
|
||||
def _drift(x) -> float:
|
||||
r = np.asarray(pd.Series(x).dropna().values, float)
|
||||
if len(r) < 2:
|
||||
return 0.0
|
||||
tot = float(np.prod(1.0 + r))
|
||||
return -1.0 if tot <= 0 else float(tot ** (DPY / len(r)) - 1.0)
|
||||
|
||||
|
||||
def k_for_same_dd(base: np.ndarray, target_dd: float) -> float:
|
||||
"""La scala costante che porta il maxDD del NUDO esattamente a quello del coperto (bisezione)."""
|
||||
lo, hi = 1e-4, 1.0
|
||||
if _dd(hi * base) <= target_dd:
|
||||
return 1.0
|
||||
for _ in range(60):
|
||||
mid = 0.5 * (lo + hi)
|
||||
if _dd(mid * base) > target_dd:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid
|
||||
return 0.5 * (lo + hi)
|
||||
|
||||
|
||||
# ==================================================================== 1. CALIBRAZIONE DALLA CATENA
|
||||
|
||||
_BANDS = (0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.40, 0.50)
|
||||
|
||||
|
||||
def calibra_catena() -> dict:
|
||||
"""Skew, struttura a termine e spread f: MISURATI sulla catena vera, mai assunti.
|
||||
|
||||
Restituisce funzioni di correzione applicate al DVOL (che e' un ATM a 30 giorni) per ottenere
|
||||
l'IV di una gamba a delta e scadenza dati. Senza queste il modello prezzerebbe la put e la
|
||||
call allo STESSO vol: l'errore andrebbe tutto a favore del collar (A4).
|
||||
"""
|
||||
df = cblib.load_chain()
|
||||
b = df[df["asset"].eq("BTC")].copy()
|
||||
b["exp"] = pd.to_datetime(b["exp"], utc=True)
|
||||
b["ts"] = pd.to_datetime(b["ts"], utc=True)
|
||||
b["dte"] = (b["exp"] - b["ts"]).dt.total_seconds() / 86400.0
|
||||
q = b[(b["dte"] > 1) & (b["dte"] <= 15) & b["bid"].gt(0) & b["ask"].gt(0)
|
||||
& b["iv"].notna() & b["delta"].notna()].copy()
|
||||
q["ad"] = q["delta"].abs()
|
||||
|
||||
atm = q[q["ad"].between(0.45, 0.55)].groupby(["ts", "exp"])["iv"].mean().rename("iv_atm")
|
||||
q = q.join(atm, on=["ts", "exp"])
|
||||
q = q[q["iv_atm"].notna() & q["iv_atm"].gt(0)]
|
||||
|
||||
skew = {}
|
||||
for t, nm in (("P", "put"), ("C", "call")):
|
||||
xs, ys = [], []
|
||||
for d in _BANDS:
|
||||
lo, hi = d * 0.85, d * 1.15
|
||||
s = q[q["option_type"].eq(t) & q["ad"].between(lo, hi)]
|
||||
if len(s) >= 200:
|
||||
xs.append(d)
|
||||
ys.append(float((s["iv"] / s["iv_atm"]).median()))
|
||||
skew[nm] = (np.array(xs), np.array(ys))
|
||||
|
||||
# struttura a termine: IV ATM a DTE t contro il DVOL 30g dello stesso istante
|
||||
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 D−1
|
||||
a = q[q["ad"].between(0.45, 0.55)].copy()
|
||||
a["giorno"] = a["ts"].dt.floor("D")
|
||||
dvd = dv.copy()
|
||||
dvd.index = pd.to_datetime(dvd.index, utc=True).floor("D")
|
||||
dvd = dvd.groupby(level=0).last()
|
||||
a = a.join(dvd.rename("dvol"), on="giorno")
|
||||
a = a[a["dvol"].notna() & a["dvol"].gt(0)]
|
||||
a["ratio"] = (a["iv"] / 100.0) / (a["dvol"] / 100.0) if a["iv"].median() > 3 else a["iv"] / (a["dvol"] / 100.0)
|
||||
ts_x, ts_y = [], []
|
||||
for lo, hi in ((1, 4), (4, 8), (8, 12), (12, 15)):
|
||||
s = a[a["dte"].between(lo, hi)]
|
||||
if len(s) >= 200:
|
||||
ts_x.append(0.5 * (lo + hi))
|
||||
ts_y.append(float(s["ratio"].median()))
|
||||
|
||||
# spread relativo per ala e per banda di delta (mezza forchetta su mid) = il costo di f
|
||||
spread = {}
|
||||
for t, nm in (("P", "put"), ("C", "call")):
|
||||
xs, ys = [], []
|
||||
for d in _BANDS:
|
||||
lo, hi = d * 0.85, d * 1.15
|
||||
s = q[q["option_type"].eq(t) & q["ad"].between(lo, hi)]
|
||||
s = s[s["mid"].gt(0)]
|
||||
if len(s) >= 200:
|
||||
xs.append(d)
|
||||
ys.append(float((((s["ask"] - s["bid"]) / 2.0) / s["mid"]).median()))
|
||||
spread[nm] = (np.array(xs), np.array(ys))
|
||||
|
||||
return dict(skew=skew, term=(np.array(ts_x), np.array(ts_y)), spread=spread,
|
||||
n_quote=len(q), giorni=int(q["ts"].dt.date.nunique()),
|
||||
span=(q["ts"].min(), q["ts"].max()))
|
||||
|
||||
|
||||
def _interp(tab, x):
|
||||
xs, ys = tab
|
||||
if len(xs) == 0:
|
||||
return 1.0
|
||||
return float(np.interp(x, xs, ys))
|
||||
|
||||
|
||||
# ==================================================================== 2. PREZZATORE
|
||||
|
||||
def _d1(S, K, T, sig):
|
||||
return (math.log(S / K) + 0.5 * sig * sig * T) / (sig * math.sqrt(T))
|
||||
|
||||
|
||||
def bs_call(S, K, T, sig):
|
||||
if T <= 0 or sig <= 0:
|
||||
return max(S - K, 0.0)
|
||||
d1 = _d1(S, K, T, sig)
|
||||
return S * norm.cdf(d1) - K * norm.cdf(d1 - sig * math.sqrt(T))
|
||||
|
||||
|
||||
def bs_put(S, K, T, sig):
|
||||
if T <= 0 or sig <= 0:
|
||||
return max(K - S, 0.0)
|
||||
d1 = _d1(S, K, T, sig)
|
||||
return K * norm.cdf(-(d1 - sig * math.sqrt(T))) - S * norm.cdf(-d1)
|
||||
|
||||
|
||||
def strike_da_delta(S, T, sig_atm, cal, delta, tipo):
|
||||
"""Strike che realizza |delta| dato, risolvendo la circolarita' skew<->delta per punto fisso.
|
||||
|
||||
L'IV dipende dal delta (skew) e il delta dipende dallo strike che dipende dall'IV: due
|
||||
iterazioni bastano (verificato: la terza muove lo strike di <0,1%).
|
||||
"""
|
||||
sig = sig_atm * _interp(cal["skew"][tipo], delta)
|
||||
K = S
|
||||
for _ in range(3):
|
||||
z = norm.ppf(delta) if tipo == "call" else -norm.ppf(delta)
|
||||
K = S * math.exp(0.5 * sig * sig * T - z * sig * math.sqrt(T))
|
||||
sig = sig_atm * _interp(cal["skew"][tipo], delta)
|
||||
return K, sig
|
||||
|
||||
|
||||
def _fee(prem, S):
|
||||
return min(OPT_TAKER_UNDERLYING * S, OPT_FEE_CAP_FRAC * prem) if prem > 0 else 0.0
|
||||
|
||||
|
||||
# ==================================================================== 3. IL MOTORE DEL COLLAR
|
||||
|
||||
def serie_giornaliera() -> pd.DataFrame:
|
||||
"""BTC giornaliero + DVOL, sulla finestra in cui ESISTONO entrambi (2021-03 in poi)."""
|
||||
d = load_data("BTC", "1h")
|
||||
idx = pd.DatetimeIndex(pd.to_datetime(d["datetime"], utc=True))
|
||||
px = pd.Series(d["close"].values.astype(float), index=idx).resample("1D").last()
|
||||
dv = pd.read_parquet(ROOT / "data" / "raw" / "dvol_btc.parquet")
|
||||
iv = pd.Series(dv["close"].values.astype(float) / 100.0,
|
||||
index=pd.to_datetime(dv["timestamp"], unit="ms", utc=True)).resample("1D").last()
|
||||
P = pd.concat({"px": px, "iv": iv}, axis=1, join="inner").dropna()
|
||||
return P
|
||||
|
||||
|
||||
SPOT_FEE_SIDE = 0.00035 # Deribit taker reale dal 2026-08-01 (3,50 bps/lato), §8
|
||||
|
||||
|
||||
def gate_trend(P: pd.DataFrame, forte: bool) -> np.ndarray:
|
||||
"""L'ENTRATA, governata da indicatori — e presa dal progetto, non inventata qui.
|
||||
|
||||
`trend_portfolio.tsmom_blend` media tre `np.sign()` sugli orizzonti (30, 90, 180) giorni,
|
||||
quindi assume SOLO i valori {-1, -1/3, +1/3, +1} (il bucket 2/3 non esiste: e' un errore gia'
|
||||
corretto in §57). Questo da' a "forte bull" una definizione che non aggiunge NEMMENO UN
|
||||
parametro nuovo:
|
||||
forte=True -> |blend| == 1 : tutti e tre gli orizzonti concordi ("forte bull/bear")
|
||||
forte=False -> |blend| >= 1/3: almeno la maggioranza (confronto dichiarato)
|
||||
Il segno del blend da' la DIREZIONE (long o short), come chiesto.
|
||||
"""
|
||||
from src.strategies.trend_portfolio import tsmom_blend
|
||||
c = P["px"].values.astype(float)
|
||||
b = tsmom_blend(c, (30, 90, 180))
|
||||
d = np.sign(b)
|
||||
d[np.abs(b) < (0.99 if forte else 0.32)] = 0.0
|
||||
return np.concatenate([[0.0], d[:-1]]) # ESEGUIBILE: si entra il giorno DOPO il segnale
|
||||
|
||||
|
||||
def collar(P: pd.DataFrame, cal: dict, *, dput: float, dcall: float, tenor: int,
|
||||
dirs: np.ndarray, zerocost: bool = False, senza_opzioni: bool = False,
|
||||
prem_scale: float = 1.0, solo_pavimento: bool = False,
|
||||
exit_frac: float = 1.0) -> dict:
|
||||
"""Hold di BTC gated dal trend, coperto da un collar rollato ogni `tenor` giorni (<=15).
|
||||
|
||||
direzione +1 = LONG BTC -> compra PUT (pavimento), vende CALL (tetto)
|
||||
direzione -1 = SHORT BTC -> compra CALL (pavimento), vende PUT (tetto)
|
||||
`exit_frac` < 1 esce dalle opzioni PRIMA della scadenza (0,50-0,75 = richiesta dell'operatore
|
||||
del 2026-09-01). Non e' un dettaglio di rollo: cambia il segno di due cose opposte.
|
||||
- sulla gamba COMPRATA (il pavimento) uscire presto RECUPERA valore temporale: il theta di una
|
||||
long accelera verso la scadenza, e §46 aveva gia' misurato che *"f si paga solo sulla parte
|
||||
di valore che converge a intrinseco, quindi un roll anticipato non lo paga"*;
|
||||
- sulla gamba VENDUTA (il tetto) uscire presto RINUNCIA al theta piu' veloce, cioe' proprio
|
||||
alla parte che in questo filone stampava il premio di varianza.
|
||||
Il prezzo di entrambe e' che si paga lo SPREAD in uscita su due gambe, invece di regolare a
|
||||
intrinseco. Chi lo attiva compra meno short-vol e piu' protezione onesta: e' misurabile, ed e'
|
||||
misurato nella sezione dedicata.
|
||||
|
||||
`prem_scale` e `solo_pavimento` esistono per i CONTROLLI (M15): con premio 0 e solo il
|
||||
pavimento la struttura e' un pranzo gratis e DEVE vincere; con premio x10 DEVE perdere ovunque.
|
||||
Un apparato che non riconosce il pranzo gratis e' guasto, e §46 lo scopri' a proprie spese.
|
||||
|
||||
`senza_opzioni=True` produce il MEDESIMO gated hold senza collar: e' la BASE del null M5, e
|
||||
dev'essere generata dallo stesso motore o il confronto misurerebbe anche le differenze di
|
||||
contabilita' (P1: il null deriva dal codice sorvegliato, non lo ridichiara).
|
||||
|
||||
Marcatura GIORNALIERA a modello: il pavimento vincola il PAYOFF A SCADENZA, non il mark
|
||||
intermedio. Senza marcatura giornaliera il maxDD del collar sarebbe misurato solo ai roll e la
|
||||
protezione sembrerebbe migliore di quanto e'.
|
||||
"""
|
||||
S = P["px"].values.astype(float)
|
||||
A = P["iv"].values.astype(float)
|
||||
n = len(S)
|
||||
if n < tenor + 5:
|
||||
return {}
|
||||
|
||||
tipo_di = {+1: ("put", "call"), -1: ("call", "put")}
|
||||
nav = np.full(n, np.nan)
|
||||
V = 1.0 # capitale normalizzato
|
||||
cash, q, nopt = 1.0, 0.0, 0.0
|
||||
Kl = Ks = np.nan
|
||||
dc_vivo = 0.20 # delta del tetto VIVO: serve allo spread d'uscita anticipata
|
||||
scad = -1
|
||||
roll_at = -1 # quando si ESCE (<= scad): con exit_frac<1 e' PRIMA della scadenza
|
||||
cur = 0
|
||||
cicli = []
|
||||
dc_scelti = [] # delta del tetto effettivamente usato a ogni apertura
|
||||
apre = 0
|
||||
|
||||
def _mark(K, T, sig_atm, tipo, S_t):
|
||||
if T <= 0 or math.isnan(K):
|
||||
return 0.0 if math.isnan(K) else (max(K - S_t, 0.0) if tipo == "put" else max(S_t - K, 0.0))
|
||||
sig = sig_atm * _interp(cal["term"], max(T * DPY, 1.0))
|
||||
d1 = _d1(S_t, K, T, sig)
|
||||
dl = norm.cdf(d1) if tipo == "call" else 1.0 - norm.cdf(d1)
|
||||
sig *= _interp(cal["skew"][tipo], min(max(dl, 0.02), 0.60))
|
||||
return bs_put(S_t, K, T, sig) if tipo == "put" else bs_call(S_t, K, T, sig)
|
||||
|
||||
def _chiudi(i, S_t, anche_spot=True):
|
||||
"""Regola le opzioni vive; liquida lo spot SOLO se richiesto.
|
||||
|
||||
⚠️ Il flag esiste per un difetto che falserebbe tutto: al ROLL delle opzioni lo spot non
|
||||
ha motivo di muoversi. Chiuderlo e riaprirlo pagherebbe 2x3,5 bps ogni `tenor` giorni
|
||||
(a 7 giorni: ~3,6%/anno) di fee che nella realta' non si pagano.
|
||||
"""
|
||||
nonlocal cash, q, nopt, Kl, Ks, cur
|
||||
if nopt > 0 and not senza_opzioni:
|
||||
tl, ts = tipo_di[cur]
|
||||
T_rem = max(scad - i, 0) / DPY
|
||||
vl = _mark(Kl, T_rem, A[i], tl, S_t)
|
||||
vs = 0.0 if solo_pavimento else _mark(Ks, T_rem, A[i], ts, S_t)
|
||||
if T_rem > 0: # smontaggio anticipato: pago lo spread in USCITA
|
||||
vl *= 1.0 - _interp(cal["spread"][tl], dput)
|
||||
vs *= 1.0 + _interp(cal["spread"][ts], dc_vivo)
|
||||
else:
|
||||
for x in (vl, vs):
|
||||
if x > 0:
|
||||
cash -= nopt * OPT_DELIVERY_UNDERLYING * S_t
|
||||
cash += nopt * (vl - vs)
|
||||
nopt, Kl, Ks = 0.0, np.nan, np.nan
|
||||
if anche_spot and q != 0.0:
|
||||
cash += q * S_t - abs(q) * S_t * SPOT_FEE_SIDE
|
||||
q = 0.0
|
||||
|
||||
for i in range(n):
|
||||
S_t = S[i]
|
||||
d = int(dirs[i])
|
||||
fine_ciclo = (i >= roll_at) and cur != 0 and not senza_opzioni
|
||||
solo_roll = fine_ciclo and d == cur
|
||||
if d != cur or fine_ciclo:
|
||||
if cur != 0:
|
||||
if nopt > 0 and i >= scad:
|
||||
tl, ts = tipo_di[cur]
|
||||
il_ = max(Kl - S_t, 0.0) if tl == "put" else max(S_t - Kl, 0.0)
|
||||
is_ = 0.0 if solo_pavimento else (
|
||||
max(Ks - S_t, 0.0) if ts == "put" else max(S_t - Ks, 0.0))
|
||||
cicli.append(dict(i0=apre, i1=i, S0=S[apre], S1=S_t, Kl=Kl, Ks=Ks,
|
||||
dirn=cur, cap=is_ > 0, floor=il_ > 0,
|
||||
scaduto=bool(fine_ciclo)))
|
||||
_chiudi(i, S_t, anche_spot=not solo_roll)
|
||||
if not solo_roll:
|
||||
V = cash
|
||||
if not solo_roll:
|
||||
cur = d
|
||||
if cur != 0:
|
||||
if not solo_roll:
|
||||
q = cur * V / S_t
|
||||
cash = V - q * S_t - abs(q) * S_t * SPOT_FEE_SIDE
|
||||
apre, scad = i, i + tenor
|
||||
roll_at = i + max(1, int(round(exit_frac * tenor)))
|
||||
if not senza_opzioni:
|
||||
tl, ts = tipo_di[cur]
|
||||
T = tenor / DPY
|
||||
Kl, sl = strike_da_delta(S_t, T, A[i], cal, dput, tl)
|
||||
pl = (bs_put if tl == "put" else bs_call)(S_t, Kl, T, sl)
|
||||
pl *= 1.0 + _interp(cal["spread"][tl], dput) # compro all'ASK
|
||||
pl *= prem_scale
|
||||
if zerocost:
|
||||
lo, hi = 0.02, 0.60
|
||||
for _ in range(40):
|
||||
md = 0.5 * (lo + hi)
|
||||
Kx, sx = strike_da_delta(S_t, T, A[i], cal, md, ts)
|
||||
px_ = (bs_put if ts == "put" else bs_call)(S_t, Kx, T, sx)
|
||||
px_ *= 1.0 - _interp(cal["spread"][ts], md) # vendo al BID
|
||||
# il premio CRESCE col delta: se incasso meno di quanto pago,
|
||||
# il tetto va avvicinato (delta PIU' ALTO) -> lo = md.
|
||||
if px_ < pl:
|
||||
lo = md
|
||||
else:
|
||||
hi = md
|
||||
dc_use = 0.5 * (lo + hi)
|
||||
else:
|
||||
dc_use = dcall
|
||||
dc_vivo = dc_use
|
||||
dc_scelti.append(float(dc_use))
|
||||
Ks, ss = strike_da_delta(S_t, T, A[i], cal, dc_use, ts)
|
||||
ps = (bs_put if ts == "put" else bs_call)(S_t, Ks, T, ss)
|
||||
ps *= (1.0 - _interp(cal["spread"][ts], dc_use)) * prem_scale
|
||||
if solo_pavimento:
|
||||
ps, Ks = 0.0, np.inf if ts == "call" else 0.0
|
||||
nopt = abs(q)
|
||||
cash += nopt * (ps - pl) - nopt * (_fee(pl, S_t) + _fee(ps, S_t))
|
||||
vl = vs = 0.0
|
||||
if nopt > 0 and not senza_opzioni:
|
||||
tl, ts = tipo_di[cur]
|
||||
T_rem = max(scad - i, 0) / DPY
|
||||
vl = _mark(Kl, T_rem, A[i], tl, S_t)
|
||||
vs = 0.0 if solo_pavimento else _mark(Ks, T_rem, A[i], ts, S_t)
|
||||
nav[i] = cash + q * S_t + nopt * (vl - vs)
|
||||
|
||||
v = pd.Series(nav, index=P.index).dropna()
|
||||
r = (v / v.shift(1) - 1.0).dropna()
|
||||
return dict(ret=r, nav=v, cicli=cicli, dc_scelti=dc_scelti)
|
||||
|
||||
|
||||
# ==================================================================== 4. MAIN
|
||||
|
||||
def _riga(nome, r) -> dict:
|
||||
return dict(nome=nome, sh=_sh(r), dd=_dd(r), drift=_drift(r))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 96)
|
||||
print(" COLLAR01 — hold BTC gated dal trend, coperto da collar di opzioni a <=15 giorni")
|
||||
print("=" * 96)
|
||||
print(ATTESE)
|
||||
|
||||
cal = calibra_catena()
|
||||
print(f" CALIBRAZIONE dalla catena VERA: {cal['n_quote']:,} quote a due lati su "
|
||||
f"{cal['giorni']} giorni, {cal['span'][0].date()} -> {cal['span'][1].date()}")
|
||||
for nm in ("put", "call"):
|
||||
xs, ys = cal["skew"][nm]
|
||||
print(f" skew {nm:5s} IV/ATM : " + " ".join(f"d{x:.2f}={y:.3f}" for x, y in zip(xs, ys)))
|
||||
xs, ys = cal["term"]
|
||||
print(f" termine IV/DVOL30 : " + " ".join(f"{x:.0f}g={y:.3f}" for x, y in zip(xs, ys)))
|
||||
for nm in ("put", "call"):
|
||||
xs, ys = cal["spread"][nm]
|
||||
print(f" spread {nm:5s} (mezza forchetta/mid): "
|
||||
+ " ".join(f"d{x:.2f}={y:.3f}" for x, y in zip(xs, ys)))
|
||||
|
||||
P = serie_giornaliera()
|
||||
print(f"\n LENTE LUNGA: {P.index[0].date()} -> {P.index[-1].date()} "
|
||||
f"({len(P)} giorni, {len(P)/DPY:.2f} anni)")
|
||||
|
||||
bh = (P["px"] / P["px"].shift(1) - 1.0).dropna()
|
||||
print(f" BTC buy&hold nudo: Sharpe {_sh(bh):.3f} · maxDD {100*_dd(bh):.2f}% · "
|
||||
f"drift {100*_drift(bh):+.2f}%/a")
|
||||
|
||||
righe = []
|
||||
for forte in (True, False):
|
||||
dirs = gate_trend(P, forte)
|
||||
nome_g = "FORTE (3/3 orizzonti)" if forte else "LARGO (>=2/3)"
|
||||
quota = 100.0 * np.mean(dirs != 0)
|
||||
qlong = 100.0 * np.mean(dirs > 0)
|
||||
base = collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)
|
||||
rb = base["ret"]
|
||||
print(f"\n ── GATE {nome_g}: a mercato {quota:.1f}% dei giorni "
|
||||
f"(long {qlong:.1f}%, short {quota-qlong:.1f}%)")
|
||||
print(f" BASE gated SENZA opzioni: Sharpe {_sh(rb):.3f} · maxDD {100*_dd(rb):.2f}% · "
|
||||
f"drift {100*_drift(rb):+.2f}%/a ← e' il NULL da battere (M5)")
|
||||
for tenor in G_TENOR:
|
||||
for dp in G_DPUT:
|
||||
for dc in G_DCALL:
|
||||
out = collar(P, cal, dput=dp, dcall=dc, tenor=tenor, dirs=dirs)
|
||||
if not out:
|
||||
continue
|
||||
r = out["ret"]
|
||||
dd_c, dr_c = _dd(r), _drift(r)
|
||||
k = k_for_same_dd(rb.values, dd_c)
|
||||
dr_null = _drift(k * rb.values)
|
||||
righe.append(dict(gate=nome_g, forte=forte, tenor=tenor, dp=dp, dc=dc,
|
||||
sh=_sh(r), dd=dd_c, drift=dr_c, k=k, drift_null=dr_null,
|
||||
vince=(dr_c > dr_null) and (dd_c < _dd(rb)),
|
||||
dd_scende=dd_c < _dd(rb), cicli=out["cicli"], ret=r))
|
||||
# varianti zero-cost, dichiarate FUORI griglia
|
||||
for tenor in G_TENOR:
|
||||
for dp in G_DPUT:
|
||||
out = collar(P, cal, dput=dp, dcall=np.nan, tenor=tenor, dirs=dirs, zerocost=True)
|
||||
if not out:
|
||||
continue
|
||||
r = out["ret"]
|
||||
dd_c, dr_c = _dd(r), _drift(r)
|
||||
k = k_for_same_dd(rb.values, dd_c)
|
||||
righe.append(dict(gate=nome_g, forte=forte, tenor=tenor, dp=dp, dc="zc",
|
||||
sh=_sh(r), dd=dd_c, drift=dr_c, k=k,
|
||||
drift_null=_drift(k * rb.values),
|
||||
vince=(dr_c > _drift(k * rb.values)) and (dd_c < _dd(rb)),
|
||||
dd_scende=dd_c < _dd(rb), cicli=out["cicli"], ret=r))
|
||||
R = pd.DataFrame([x for x in righe if x["gate"] == nome_g])
|
||||
print(f"\n {'ten':>4} {'dput':>5} {'dcall':>6} {'Sharpe':>7} {'maxDD':>7} "
|
||||
f"{'drift':>8} {'k null':>7} {'drift null':>10} esito")
|
||||
for _, x in R.iterrows():
|
||||
dcs = x["dc"] if isinstance(x["dc"], str) else f"{x['dc']:.2f}"
|
||||
print(f" {x['tenor']:>4} {x['dp']:>5.2f} {dcs:>6} {x['sh']:>7.3f} "
|
||||
f"{100*x['dd']:>6.2f}% {100*x['drift']:>7.2f}% {x['k']:>7.3f} "
|
||||
f"{100*x['drift_null']:>9.2f}% "
|
||||
f"{'VINCE' if x['vince'] else ('dd↓ ma perde' if x['dd_scende'] else 'dd↑')}")
|
||||
|
||||
R = pd.DataFrame(righe)
|
||||
n_dd = int(R["dd_scende"].sum())
|
||||
n_win = int(R["vince"].sum())
|
||||
print("\n" + "=" * 96)
|
||||
print(f" A1 maxDD SCENDE in {n_dd}/{len(R)} celle "
|
||||
f"({'CONFERMATA' if n_dd > len(R)//2 else 'REFUTATA'})")
|
||||
print(f" A3 il collar BATTE il de-levering in {n_win}/{len(R)} celle "
|
||||
f"({'REFUTATA (il collar vince)' if n_win > len(R)//2 else 'CONFERMATA (il collar perde)'})")
|
||||
|
||||
# ---- A2: rapporto Δdrift/ΔmaxDD ----------------------------------------------------------
|
||||
for forte in (True, False):
|
||||
sub = R[R["forte"] == forte]
|
||||
if not len(sub):
|
||||
continue
|
||||
dirs = gate_trend(P, forte)
|
||||
rb = collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)["ret"]
|
||||
d0, g0 = _dd(rb), _drift(rb)
|
||||
rap = ((g0 - sub["drift"]) / np.maximum(d0 - sub["dd"], 1e-9)).median()
|
||||
print(f" A2 gate {'FORTE' if forte else 'LARGO'}: Δdrift/ΔmaxDD mediano {rap:.2f} "
|
||||
f"({'CONFERMATA' if rap > 1 else 'REFUTATA'}: >1 = il tetto costa piu' di quanto il "
|
||||
f"pavimento renda)")
|
||||
|
||||
# ---- C9: il tetto scatta piu' sui vincenti o sui perdenti? -------------------------------
|
||||
best = R.sort_values("sh", ascending=False).iloc[0]
|
||||
cic = pd.DataFrame(best["cicli"])
|
||||
if len(cic):
|
||||
# ⚠️ "vincente" dipende dalla DIREZIONE: per un ciclo SHORT un prezzo che sale e' una
|
||||
# PERDITA. Usare S1>S0 come proxy di vittoria e' il difetto che dava "pavimento 0,0%".
|
||||
cic["vinc"] = cic["dirn"] * (cic["S1"] - cic["S0"]) > 0
|
||||
cap_w = 100.0 * cic[cic["vinc"]]["cap"].mean()
|
||||
flo_l = 100.0 * cic[~cic["vinc"]]["floor"].mean()
|
||||
print(f"\n C9 cella migliore (gate {best['gate']}, {best['tenor']}g, dput {best['dp']}, "
|
||||
f"dcall {best['dc']}), {len(cic)} cicli:")
|
||||
print(f" il TETTO taglia nel {cap_w:.1f}% dei cicli VINCENTI · "
|
||||
f"il PAVIMENTO para nel {flo_l:.1f}% dei cicli PERDENTI")
|
||||
print(f" -> {'TRONCATURA' if cap_w > flo_l else 'PROTEZIONE'} "
|
||||
f"(C9: se scatta piu' sui vincenti che sui perdenti, non e' protezione)")
|
||||
|
||||
# ---- M1: il confronto che decide se merita uno slot --------------------------------------
|
||||
try:
|
||||
from src.portfolio.sleeves import _tp01_returns
|
||||
tp = _tp01_returns()
|
||||
tp.index = pd.to_datetime(tp.index, utc=True)
|
||||
tpd = (1.0 + tp).resample("1D").prod() - 1.0
|
||||
j = pd.concat({"c": best["ret"], "tp": tpd}, axis=1, join="inner").dropna()
|
||||
print(f"\n M1 vs TP01 sulla finestra comune ({len(j)} giorni): "
|
||||
f"collar Sharpe {_sh(j['c']):.3f} · TP01 {_sh(j['tp']):.3f} · "
|
||||
f"corr {j['c'].corr(j['tp']):+.3f}")
|
||||
for w in (0.10, 0.25):
|
||||
mix = (1 - w) * j["tp"] + w * j["c"]
|
||||
print(f" TP01 + {100*w:.0f}% collar: Sharpe {_sh(mix):.3f} "
|
||||
f"({_sh(mix)-_sh(j['tp']):+.3f}) · maxDD {100*_dd(mix):.2f}% "
|
||||
f"({100*(_dd(mix)-_dd(j['tp'])):+.2f}pp)")
|
||||
except Exception as e: # pragma: no cover
|
||||
print(f"\n M1 non calcolato: {type(e).__name__}: {e}")
|
||||
|
||||
# ---- ESTENSIONE AL BORDO (M4: si riapre la FAMIGLIA, M8: un argmax sul bordo non decide) --
|
||||
print("\n" + "=" * 96)
|
||||
print(" ESTENSIONE DICHIARATA — le celle vincenti stanno tutte sul BORDO (dput min, dcall max).")
|
||||
print(" Sondo dove punta la pendenza. Il limite dput->0 / dcall->0,50 e' la COVERED CALL pura:")
|
||||
print(" nessun pavimento, tetto ATM. Trial aggiunti: 2 gate x 2 tenor x 3 x 3 = 36 (dichiarati).")
|
||||
ext = []
|
||||
for forte in (True, False):
|
||||
dirs = gate_trend(P, forte)
|
||||
rb = collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)["ret"]
|
||||
for tenor in (7, 14):
|
||||
for dp in (0.02, 0.05, 0.10):
|
||||
for dc in (0.30, 0.40, 0.50):
|
||||
r = collar(P, cal, dput=dp, dcall=dc, tenor=tenor, dirs=dirs)["ret"]
|
||||
k = k_for_same_dd(rb.values, _dd(r))
|
||||
ext.append(dict(forte=forte, tenor=tenor, dp=dp, dc=dc, sh=_sh(r),
|
||||
dd=_dd(r), drift=_drift(r),
|
||||
vince=(_drift(r) > _drift(k * rb.values)) and (_dd(r) < _dd(rb))))
|
||||
E = pd.DataFrame(ext)
|
||||
top = E.sort_values("sh", ascending=False).iloc[0]
|
||||
print(f" -> VINCE in {int(E['vince'].sum())}/{len(E)} celle, e il massimo e' NELL'ANGOLO: "
|
||||
f"gate {'FORTE' if top['forte'] else 'LARGO'} {top['tenor']}g dput {top['dp']:.2f} "
|
||||
f"dcall {top['dc']:.2f} -> Sharpe {top['sh']:.3f}, maxDD {100*top['dd']:.2f}%, "
|
||||
f"drift {100*top['drift']:+.2f}%/a")
|
||||
print(" ⚠️ Il pavimento non e' cio' che vince: vince il TETTO. La pendenza porta FUORI da cio'")
|
||||
print(" che l'operatore ha chiesto ('bloccare la perdita') e DENTRO lo short-vol.")
|
||||
|
||||
# ---- LA DIAGNOSTICA CHE DECIDE: quanto dell'edge e' il PREMIO DI VARIANZA? ----------------
|
||||
lr = np.log(P["px"] / P["px"].shift(1))
|
||||
print("\n RIPREZZATURA ALLA VOLATILITA' REALIZZATA — diagnostica con LOOK-AHEAD DICHIARATO,")
|
||||
print(" non e' una strategia: e' il valore equo ex-post. Se l'edge sparisce, era IV-RV.")
|
||||
for h in (7, 14):
|
||||
fw = (lr[::-1].rolling(h).std()[::-1] * math.sqrt(DPY)).bfill().ffill()
|
||||
j = pd.concat({"iv": P["iv"], "fw": fw}, axis=1).dropna()
|
||||
print(f" DVOL / RV-forward a {h}g: mediana {(j['iv']/j['fw']).median():.3f} · "
|
||||
f"DVOL sopra nel {100*(j['iv']>j['fw']).mean():.1f}% dei giorni")
|
||||
for tenor in (7, 14):
|
||||
fw = (lr[::-1].rolling(tenor).std()[::-1] * math.sqrt(DPY)).bfill().ffill()
|
||||
Pf = P.copy()
|
||||
Pf["iv"] = fw.reindex(P.index).values
|
||||
for forte in (True, False):
|
||||
dirs = gate_trend(P, forte)
|
||||
rb = collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)["ret"]
|
||||
for dp, dc in ((0.02, 0.50), (0.10, 0.30)):
|
||||
a = collar(P, cal, dput=dp, dcall=dc, tenor=tenor, dirs=dirs)["ret"]
|
||||
b = collar(Pf, cal, dput=dp, dcall=dc, tenor=tenor, dirs=dirs)["ret"]
|
||||
ka, kb = k_for_same_dd(rb.values, _dd(a)), k_for_same_dd(rb.values, _dd(b))
|
||||
wa = _drift(a) > _drift(ka * rb.values) and _dd(a) < _dd(rb)
|
||||
wb = _drift(b) > _drift(kb * rb.values) and _dd(b) < _dd(rb)
|
||||
print(f" {'FORTE' if forte else 'LARGO'} {tenor:>2}g dput {dp:.2f} dcall {dc:.2f}: "
|
||||
f"a DVOL Sh {_sh(a):6.3f} drift {100*_drift(a):+7.2f}% {'VINCE' if wa else 'perde'}"
|
||||
f" | a RV Sh {_sh(b):6.3f} drift {100*_drift(b):+7.2f}% {'VINCE' if wb else 'perde'}"
|
||||
f" | VRP {100*(_drift(a)-_drift(b)):+6.2f} pp")
|
||||
|
||||
# ---- USCITA ANTICIPATA (richiesta dell'operatore: fra il 50% e il 75% del tempo) ---------
|
||||
print("\n" + "=" * 96)
|
||||
print(" USCITA ANTICIPATA DALLE OPZIONI — exit_frac 0,50 / 0,625 / 0,75 contro la scadenza.")
|
||||
print(" §46 misuro' che 'f si paga solo sulla parte di valore che converge a intrinseco,")
|
||||
print(" quindi un roll anticipato NON lo paga'. ⚠️ Vale per una copertura SOLO LONG: in un")
|
||||
print(" collar c'e' una gamba VENDUTA da RICOMPRARE, e uscire prima paga f esattamente sulla")
|
||||
print(" parte che a scadenza si sarebbe regolata gratis. L'asimmetria si INVERTE.")
|
||||
lr2 = np.log(P["px"] / P["px"].shift(1))
|
||||
for forte in (True, False):
|
||||
dirs = gate_trend(P, forte)
|
||||
rb = collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)["ret"]
|
||||
print(f"\n gate {'FORTE' if forte else 'LARGO'} (base drift {100*_drift(rb):+.2f}%/a):")
|
||||
for dp, dc in ((0.10, 0.30), (0.20, 0.20), (0.02, 0.50)):
|
||||
for ef in (1.00, 0.75, 0.625, 0.50):
|
||||
a = collar(P, cal, dput=dp, dcall=dc, tenor=14, dirs=dirs, exit_frac=ef)["ret"]
|
||||
hh = max(1, int(round(ef * 14)))
|
||||
fw = (lr2[::-1].rolling(hh).std()[::-1] * math.sqrt(DPY)).bfill().ffill()
|
||||
Pf = P.copy()
|
||||
Pf["iv"] = fw.reindex(P.index).values
|
||||
b = collar(Pf, cal, dput=dp, dcall=dc, tenor=14, dirs=dirs, exit_frac=ef)["ret"]
|
||||
k = k_for_same_dd(rb.values, _dd(a))
|
||||
w = (_drift(a) > _drift(k * rb.values)) and (_dd(a) < _dd(rb))
|
||||
print(f" d{dp:.2f}/{dc:.2f} exit {ef:.3f}: Sh {_sh(a):6.3f} DD {100*_dd(a):6.2f}% "
|
||||
f"drift {100*_drift(a):+7.2f}% null {100*_drift(k*rb.values):+6.2f}% "
|
||||
f"{'VINCE' if w else 'perde'} | a RV drift {100*_drift(b):+7.2f}% "
|
||||
f"VRP {100*(_drift(a)-_drift(b)):+6.2f}pp")
|
||||
|
||||
# ---- CONTROLLI DELL'APPARATO (M15) — un rilevatore che non li passa non misura nulla ----
|
||||
print("\n CONTROLLI DELL'APPARATO:")
|
||||
dirs = gate_trend(P, True)
|
||||
rb = collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)["ret"]
|
||||
esiti = []
|
||||
g = collar(P, cal, dput=0.20, dcall=0.20, tenor=14, dirs=dirs,
|
||||
prem_scale=0.0, solo_pavimento=True)["ret"]
|
||||
ok_g = (_dd(g) < _dd(rb)) and (_drift(g) > _drift(rb))
|
||||
esiti.append(ok_g)
|
||||
print(f" pranzo gratis (pavimento a premio ZERO): maxDD {100*_dd(rb):.2f}%->{100*_dd(g):.2f}% "
|
||||
f"drift {100*_drift(rb):+.2f}%->{100*_drift(g):+.2f}% {'OK' if ok_g else 'FALLITO'}")
|
||||
c = collar(P, cal, dput=0.20, dcall=0.20, tenor=14, dirs=dirs, prem_scale=10.0)["ret"]
|
||||
ok_c = _drift(c) < _drift(rb)
|
||||
esiti.append(ok_c)
|
||||
print(f" premio x10 (deve perdere): drift {100*_drift(c):+.2f}%/a "
|
||||
f"{'OK' if ok_c else 'FALLITO'}")
|
||||
zc = collar(P, cal, dput=0.20, dcall=np.nan, tenor=14, dirs=dirs, zerocost=True)
|
||||
prem_res = np.nan
|
||||
ok_z = bool(len(zc.get("cicli", [])) > 0) and np.isfinite(_drift(zc["ret"]))
|
||||
esiti.append(ok_z)
|
||||
print(f" zero-cost costruibile e finito: drift {100*_drift(zc['ret']):+.2f}%/a "
|
||||
f"{'OK' if ok_z else 'FALLITO'}")
|
||||
if not all(esiti):
|
||||
print(" 🚨 APPARATO GUASTO: il verdetto sotto NON e' leggibile (§46: un controllo "
|
||||
"positivo rotto dichiara guasto l'apparato)")
|
||||
|
||||
# ---- VERDETTO A RUNTIME (N11) ------------------------------------------------------------
|
||||
print("\n" + "=" * 96)
|
||||
# il verdetto deve tenere conto della diagnostica VRP: il bordo vince, ma di che cosa?
|
||||
dirs_f = gate_trend(P, True)
|
||||
rb_f = collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs_f, senza_opzioni=True)["ret"]
|
||||
fw7 = (np.log(P["px"] / P["px"].shift(1))[::-1].rolling(7).std()[::-1]
|
||||
* math.sqrt(DPY)).bfill().ffill()
|
||||
Pf7 = P.copy()
|
||||
Pf7["iv"] = fw7.reindex(P.index).values
|
||||
ang_dvol = collar(P, cal, dput=0.02, dcall=0.50, tenor=7, dirs=dirs_f)["ret"]
|
||||
ang_rv = collar(Pf7, cal, dput=0.02, dcall=0.50, tenor=7, dirs=dirs_f)["ret"]
|
||||
quota_vrp = (_drift(ang_dvol) - _drift(ang_rv)) / max(_drift(ang_dvol), 1e-9)
|
||||
|
||||
if n_dd == 0:
|
||||
v = "IL PAVIMENTO NON RIDUCE IL maxDD — cade come §46 e per lo stesso motivo"
|
||||
elif n_win <= len(R) // 8:
|
||||
v = (f"IL PAVIMENTO FUNZIONA (a differenza di §46: maxDD scende in {n_dd}/{len(R)}) MA IL "
|
||||
f"TETTO LO PAGA TROPPO: solo {n_win}/{len(R)} celle battono il de-levering. "
|
||||
f"E cio' che vince sul bordo NON e' il pavimento: e' il premio di varianza "
|
||||
f"({100*quota_vrp:.0f}% del drift dell'angolo sparisce riprezzando alla vol vera) "
|
||||
f"— cioe' il meccanismo di VRP01, non una scoperta, e §3 lo blocca in deploy")
|
||||
else:
|
||||
v = f"REGGE IL PRIMO GATE: {n_win}/{len(R)} celle battono il de-levering — serve M2/DSR"
|
||||
print(f" VERDETTO (calcolato a runtime): {v}")
|
||||
print("=" * 96)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,263 @@
|
||||
"""PAVIMENTO-LEVA — il pavimento come LICENZA DI TAGLIA, non come sconto sul drawdown.
|
||||
|
||||
DOMANDA (operatore, 2026-09-01): "possiamo usare quanto conosciamo del pavimento per studiare
|
||||
una strategia?"
|
||||
|
||||
COSA SAPPIAMO DEL PAVIMENTO (COLLAR01, §71, stesso giorno):
|
||||
- FUNZIONA: con beta 1,0 il maxDD scende in 36/48 celle (§46 lo negava per un libro a beta 0,076);
|
||||
- a premio ZERO vale moltissimo: maxDD 51,83% -> 36,76%, drift +10,04% -> +35,69% (il pranzo gratis);
|
||||
- cio' che uccise il collar fu il TETTO, non il pavimento (C9: il tetto tagliava il 46% dei cicli
|
||||
vincenti, il pavimento parava il 6,7% dei perdenti);
|
||||
- la put e' l'ala CARA (skew: IV_put/ATM 1,25 a d0,10) e il DVOL sta il 32% sopra la RV: comprare
|
||||
il pavimento significa pagare il premio di varianza. Qui il prezzo a DVOL e' REALISTICO (e'
|
||||
cio' che il mercato chiede): M29 corregge chi VENDE a modello, non chi compra.
|
||||
|
||||
L'INVERSIONE. COLLAR01 chiese "quanto DD mi risparmia il pavimento a taglia fissa?" e perse contro il
|
||||
de-levering. La domanda speculare e' "quanta TAGLIA mi autorizza il pavimento a DD fisso?". Sembra
|
||||
la stessa (a iso-DD e' un solo confronto), ma non lo e' per due ragioni misurabili:
|
||||
(1) il de-levering riduce taglia e rischio IN PROPORZIONE; un pavimento cambia la FORMA — se la
|
||||
forma taglia la coda sinistra piu' di quanto costi, scalare in su la struttura protetta puo'
|
||||
battere la base nuda alla stessa DD. E' l'unica cosa che il de-levering NON puo' comprare;
|
||||
(2) il tetto di leva del progetto NON e' fissato dallo Sharpe ma dal COSTO DI UN EPISODIO di
|
||||
disaster-SL: n_asset * frac * scala * sl_pct <= 0,50 => k <= 1,67x (GATE SCALA-01, §4.5).
|
||||
Il disaster-SL e' ROTOLANTE e non limita la perdita (peggior DD dall'ingresso misurato
|
||||
-60,6%). Una put limita la perdita PER COSTRUZIONE — dentro la sua finestra.
|
||||
|
||||
ATTESE A PRIORI (scritte prima di misurare):
|
||||
B1 A premio reale il pavimento LICENZIA taglia (k_f > 1) ma il drift della struttura scalata
|
||||
resta SOTTO quello della base nuda: il premio costa piu' di quanto la licenza renda. Previsione
|
||||
centrale: il pavimento perde il test della licenza nella maggioranza delle celle.
|
||||
B2 La riduzione di DD viene dai crolli VELOCI (dentro una finestra di opzione), non dai declini
|
||||
LENTI: in un grind di settimane la put scade OTM ogni volta e si paga premio E perdita.
|
||||
Il maxDD di BTC 2021-2026 e' il 2022, che fu un grind => il pavimento lo protegge poco.
|
||||
B3 La "licenza" del disaster-SL e' di CARTA: l'invariante limita UN episodio, la put limita UNA
|
||||
finestra, e il vero massimo su finestre consecutive NON e' limitato. Sostituire sl_pct con la
|
||||
distanza del pavimento nell'invariante sarebbe un errore — e va scritto ora, prima che
|
||||
qualcuno lo proponga.
|
||||
B4 Il pavimento gated su IV a buon mercato (DVOL/RV basso) migliora ma non basta a passare.
|
||||
|
||||
NESSUN ORDINE. Libro, pesi, cron, config INVARIATI.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0901_btc_collar as C # noqa: E402 — il motore, riusato e non riscritto
|
||||
|
||||
DPY = C.DPY
|
||||
|
||||
G_DPUT = (0.05, 0.10, 0.20, 0.30)
|
||||
G_TENOR = (7, 14)
|
||||
|
||||
|
||||
def k_up_for_same_dd(r: np.ndarray, target_dd: float, kmax: float = 6.0) -> float:
|
||||
"""La scala >=1 che porta il maxDD della struttura PROTETTA esattamente a quello della base NUDA.
|
||||
E' il null M5 letto al contrario: invece di ridurre la base al DD del protetto, si ALZA il
|
||||
protetto al DD della base. maxDD(k*r) e' crescente in k: bisezione."""
|
||||
if C._dd(r) >= target_dd:
|
||||
return 1.0
|
||||
lo, hi = 1.0, kmax
|
||||
if C._dd(hi * r) < target_dd:
|
||||
return kmax
|
||||
for _ in range(60):
|
||||
mid = 0.5 * (lo + hi)
|
||||
if C._dd(mid * r) > target_dd:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid
|
||||
return 0.5 * (lo + hi)
|
||||
|
||||
|
||||
def peggior_finestra(nav: pd.Series, giorni: int) -> float:
|
||||
"""La peggior perdita su una finestra di `giorni` (quella che UNA put puo' coprire)."""
|
||||
r = nav / nav.shift(giorni) - 1.0
|
||||
return float(r.min())
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 96)
|
||||
print(" PAVIMENTO-LEVA — il pavimento come licenza di taglia")
|
||||
print("=" * 96)
|
||||
print(__doc__.split("ATTESE A PRIORI")[1].split("NESSUN ORDINE")[0])
|
||||
|
||||
cal = C.calibra_catena()
|
||||
P = C.serie_giornaliera()
|
||||
print(f" LENTE LUNGA {P.index[0].date()} -> {P.index[-1].date()} ({len(P)/DPY:.2f} anni). "
|
||||
f"Prezzo a DVOL x skew x termine misurati sulla catena (realistico per chi COMPRA).")
|
||||
|
||||
righe = []
|
||||
for forte in (True, False):
|
||||
dirs = C.gate_trend(P, forte)
|
||||
nm = "FORTE" if forte else "LARGO"
|
||||
base = C.collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)
|
||||
rb, nb = base["ret"], base["nav"]
|
||||
dd_b, dr_b, sh_b = C._dd(rb), C._drift(rb), C._sh(rb)
|
||||
print(f"\n ── gate {nm}: BASE nuda Sharpe {sh_b:.3f} · maxDD {100*dd_b:.2f}% · drift {100*dr_b:+.2f}%/a"
|
||||
f" · peggior 7g {100*peggior_finestra(nb,7):.1f}% · peggior 14g {100*peggior_finestra(nb,14):.1f}%")
|
||||
print(f" {'ten':>4}{'dput':>6}{'Sharpe':>8}{'maxDD':>8}{'drift':>9} | "
|
||||
f"{'k_f':>6}{'drift@k_f':>11}{'vs base':>9} | {'k null':>7}{'drift null':>11}{'vs':>4} | "
|
||||
f"{'pegg7':>7}{'pegg14':>7}{'pav.para':>9}")
|
||||
for tenor in G_TENOR:
|
||||
for dp in G_DPUT:
|
||||
o = C.collar(P, cal, dput=dp, dcall=0.2, tenor=tenor, dirs=dirs, solo_pavimento=True)
|
||||
r, nav = o["ret"], o["nav"]
|
||||
dd, dr, sh = C._dd(r), C._drift(r), C._sh(r)
|
||||
# (a) LICENZA: alzo il protetto fino al DD della base
|
||||
kf = k_up_for_same_dd(r.values, dd_b)
|
||||
dr_kf = C._drift(kf * r.values)
|
||||
lic = dr_kf > dr_b
|
||||
# (b) NULL M5 classico: abbasso la base fino al DD del protetto
|
||||
kn = C.k_for_same_dd(rb.values, dd)
|
||||
dr_n = C._drift(kn * rb.values)
|
||||
m5 = dr > dr_n and dd < dd_b
|
||||
cic = pd.DataFrame(o["cicli"])
|
||||
para = 100.0 * cic["floor"].mean() if len(cic) else float("nan")
|
||||
righe.append(dict(gate=nm, forte=forte, tenor=tenor, dp=dp, sh=sh, dd=dd, drift=dr,
|
||||
kf=kf, drift_kf=dr_kf, licenza=lic, kn=kn, drift_null=dr_n, m5=m5,
|
||||
pegg7=peggior_finestra(nav, 7), pegg14=peggior_finestra(nav, 14),
|
||||
para=para, ret=r, nav=nav))
|
||||
print(f" {tenor:>4}{dp:>6.2f}{sh:>8.3f}{100*dd:>7.2f}%{100*dr:>8.2f}% | "
|
||||
f"{kf:>6.3f}{100*dr_kf:>10.2f}%{('VINCE' if lic else 'perde'):>9} | "
|
||||
f"{kn:>7.3f}{100*dr_n:>10.2f}%{('V' if m5 else 'p'):>4} | "
|
||||
f"{100*peggior_finestra(nav,7):>6.1f}%{100*peggior_finestra(nav,14):>6.1f}%{para:>8.1f}%")
|
||||
|
||||
R = pd.DataFrame(righe)
|
||||
n_lic = int(R["licenza"].sum())
|
||||
print("\n" + "=" * 96)
|
||||
print(f" B1 il pavimento a premio reale VINCE il test della LICENZA in {n_lic}/{len(R)} celle "
|
||||
f"({'REFUTATA' if n_lic > len(R)//2 else 'CONFERMATA'})")
|
||||
print(f" (e il null M5 classico in {int(R['m5'].sum())}/{len(R)}: le due letture "
|
||||
f"{'concordano' if abs(n_lic-int(R['m5'].sum()))<=2 else 'DIVERGONO — va capito perche'}) ")
|
||||
|
||||
# ---- B2: da dove viene la riduzione di DD? crolli veloci o grind? ------------------------
|
||||
print("\n B2 MECCANISMO — la put copre UNA finestra: protegge i crolli veloci, non i grind.")
|
||||
for forte in (True, False):
|
||||
dirs = C.gate_trend(P, forte)
|
||||
nm = "FORTE" if forte else "LARGO"
|
||||
base = C.collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)
|
||||
nb = base["nav"]
|
||||
best = R[(R["forte"] == forte)].sort_values("sh", ascending=False).iloc[0]
|
||||
nf = best["nav"]
|
||||
# il maxDD: quando, e quanto e' durato
|
||||
for lbl, nav in (("base", nb), ("pavimento", nf)):
|
||||
pk = nav.cummax()
|
||||
ddser = nav / pk - 1.0
|
||||
i_min = ddser.idxmin()
|
||||
i_pk = nav[:i_min].idxmax()
|
||||
durata = (i_min - i_pk).days
|
||||
print(f" {nm} {lbl:>9}: maxDD {100*ddser.min():.2f}% da {i_pk.date()} a {i_min.date()} "
|
||||
f"— {durata} giorni ({'GRIND' if durata > 3*best['tenor'] else 'crollo dentro ~1 finestra'})")
|
||||
# perdita "dentro finestra" contro "fra finestre": quanto del DD e' coperto dalla put
|
||||
pegg_b = peggior_finestra(nb, best["tenor"])
|
||||
pegg_f = peggior_finestra(nf, best["tenor"])
|
||||
print(f" {nm} peggior finestra di {best['tenor']}g: base {100*pegg_b:.1f}% -> "
|
||||
f"pavimento {100*pegg_f:.1f}% (la put fa QUESTO); il maxDD invece "
|
||||
f"{100*C._dd(nb/nb.shift(1)-1):.1f}% -> {100*best['dd']:.1f}% (i grind restano)")
|
||||
|
||||
# ---- B3: l'invariante del disaster-SL con un pavimento: licenza di carta ------------------
|
||||
print("\n B3 L'INVARIANTE DEL DISASTER-SL con un pavimento — licenza di CARTA.")
|
||||
frac, sl = 0.50, 0.30
|
||||
k_inv = 0.50 / (2 * frac * sl)
|
||||
print(f" oggi: n_asset 2 x frac {frac} x scala x sl {sl} <= 0,50 => scala <= {k_inv:.2f}x")
|
||||
for forte in (True,):
|
||||
best = R[R["forte"] == forte].sort_values("sh", ascending=False).iloc[0]
|
||||
cic = pd.DataFrame(best["cicli"] if "cicli" in best else [])
|
||||
dirs = C.gate_trend(P, forte)
|
||||
o = C.collar(P, cal, dput=best["dp"], dcall=0.2, tenor=best["tenor"], dirs=dirs, solo_pavimento=True)
|
||||
cic = pd.DataFrame(o["cicli"])
|
||||
# distanza mediana del pavimento dallo spot all'apertura
|
||||
cic["dist"] = (cic["S0"] - cic["Kl"]) / cic["S0"] * cic["dirn"]
|
||||
dist = float(cic["dist"].abs().median())
|
||||
k_carta = 0.50 / (2 * frac * dist)
|
||||
print(f" con la put a d{best['dp']:.2f}/{best['tenor']}g il pavimento sta a {100*dist:.1f}% "
|
||||
f"dallo spot: sostituire sl con la distanza darebbe scala <= {k_carta:.2f}x ({k_carta/k_inv:.1f}x in piu')")
|
||||
# ma la perdita su finestre CONSECUTIVE non e' limitata: la misuro
|
||||
nf = best["nav"]
|
||||
for n_fin in (1, 2, 4, 8):
|
||||
g = n_fin * best["tenor"]
|
||||
print(f" peggior perdita su {n_fin} finestre consecutive ({g:>3}g): "
|
||||
f"{100*peggior_finestra(nf, g):.1f}% "
|
||||
f"{'<= una finestra: la put copre' if n_fin == 1 else '-> la put NON copre: e'' un grind'}")
|
||||
print(f" ⇒ la licenza {k_carta:.2f}x vale per UN crollo dentro UNA finestra; su un grind di "
|
||||
f"{8*best['tenor']} giorni la perdita e' {100*abs(peggior_finestra(nf, 8*best['tenor'])):.0f}%, "
|
||||
f"e a {k_carta:.1f}x farebbe {100*k_carta*abs(peggior_finestra(nf, 8*best['tenor'])):.0f}% dell'equity.")
|
||||
|
||||
# ---- B4: pavimento GATED su IV a buon mercato -------------------------------------------
|
||||
print("\n B4 PAVIMENTO GATED sull'IV — compro la put solo quando il premio di varianza e' sottile.")
|
||||
print(" (§46: 'nessuna copertura dinamica gated su regime e'' stata provata' — qui lo e'.)")
|
||||
lr = np.log(P["px"] / P["px"].shift(1))
|
||||
rv30 = (lr.rolling(30).std() * math.sqrt(DPY))
|
||||
ratio = (P["iv"] / rv30) # DVOL / RV trailing: quanto e' cara la vol OGGI
|
||||
pct = ratio.rolling(365, min_periods=90).rank(pct=True)
|
||||
for forte in (True, False):
|
||||
dirs = C.gate_trend(P, forte)
|
||||
nm = "FORTE" if forte else "LARGO"
|
||||
base = C.collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)
|
||||
rb = base["ret"]; dd_b, dr_b = C._dd(rb), C._drift(rb)
|
||||
best = R[R["forte"] == forte].sort_values("sh", ascending=False).iloc[0]
|
||||
for soglia in (0.25, 0.50):
|
||||
# NOTA: il gate di IV si applica alla PUT, non alla posizione. Lo simulo con due
|
||||
# strutture (protetta / nuda) e le incollo giorno per giorno sul segnale di IV, pagando
|
||||
# lo spread alle transizioni tramite il motore stesso (ricalcolo con dirs modulati).
|
||||
economico = (pct.shift(1) <= soglia).reindex(P.index).fillna(False).values
|
||||
r_prot = best["ret"].reindex(P.index)
|
||||
r_nuda = rb.reindex(P.index)
|
||||
r_mix = pd.Series(np.where(economico, r_prot, r_nuda), index=P.index).dropna()
|
||||
dd, dr = C._dd(r_mix), C._drift(r_mix)
|
||||
kf = k_up_for_same_dd(r_mix.values, dd_b)
|
||||
dr_kf = C._drift(kf * r_mix.values)
|
||||
quota = 100.0 * np.mean(economico[np.asarray(dirs) != 0]) if np.any(np.asarray(dirs) != 0) else 0.0
|
||||
print(f" {nm} put ON se DVOL/RV sotto il {100*soglia:.0f}° pctl (ON il {quota:.0f}% dei giorni a mercato): "
|
||||
f"Sh {C._sh(r_mix):.3f} DD {100*dd:.2f}% drift {100*dr:+.2f}% | licenza k_f {kf:.3f} "
|
||||
f"-> drift {100*dr_kf:+.2f}% vs base {100*dr_b:+.2f}% {'VINCE' if dr_kf > dr_b else 'perde'}")
|
||||
print(" ⚠️ approssimazione dichiarata: l'incollatura ignora lo spread delle transizioni ON/OFF "
|
||||
"della put (a favore del gated). Se perde COSI', perde a maggior ragione.")
|
||||
|
||||
# ---- IL PREMIO DI PAREGGIO: quanto dovrebbe costare la put perche' il pavimento protegga? --
|
||||
print("\n PREMIO DI PAREGGIO — a quale frazione del premio REALE il pavimento pareggia il maxDD nudo.")
|
||||
print(" (Il DVOL sta 1,32x sopra la RV-forward: un prezzo 'equo' sarebbe ~76% del reale.)")
|
||||
pareggi = []
|
||||
for forte in (True, False):
|
||||
dirs = C.gate_trend(P, forte)
|
||||
nm = "FORTE" if forte else "LARGO"
|
||||
base = C.collar(P, cal, dput=0.2, dcall=0.2, tenor=7, dirs=dirs, senza_opzioni=True)["ret"]
|
||||
ddb = C._dd(base)
|
||||
for tenor, dp in ((14, 0.05), (14, 0.10), (7, 0.10), (14, 0.20)):
|
||||
lo, hi = 0.0, 1.0
|
||||
for _ in range(25):
|
||||
md = 0.5 * (lo + hi)
|
||||
r = C.collar(P, cal, dput=dp, dcall=0.2, tenor=tenor, dirs=dirs,
|
||||
solo_pavimento=True, prem_scale=md)["ret"]
|
||||
if C._dd(r) < ddb:
|
||||
lo = md
|
||||
else:
|
||||
hi = md
|
||||
pareggi.append(0.5 * (lo + hi))
|
||||
print(f" {nm} d{dp:.2f}/{tenor}g: pareggio del maxDD a **{100*0.5*(lo+hi):.0f}%** del premio reale")
|
||||
print(f" ⇒ banda {100*min(pareggi):.0f}-{100*max(pareggi):.0f}%: il mercato dovrebbe scontare la put del "
|
||||
f"{100*(1-max(pareggi)):.0f}-{100*(1-min(pareggi)):.0f}% perche' il pavimento INIZI a proteggere.")
|
||||
|
||||
# ---- VERDETTO A RUNTIME -----------------------------------------------------------------
|
||||
print("\n" + "=" * 96)
|
||||
if n_lic == 0:
|
||||
v = ("IL PAVIMENTO NON LICENZIA TAGLIA A PREMIO REALE: 0 celle. Cio' che il de-levering non "
|
||||
"puo' comprare, la put lo vende a un prezzo che nessuna cella ripaga")
|
||||
elif n_lic <= len(R) // 4:
|
||||
v = f"MARGINALE: {n_lic}/{len(R)} celle — dentro il rumore di griglia, e sul bordo"
|
||||
else:
|
||||
v = f"IL PAVIMENTO LICENZIA TAGLIA in {n_lic}/{len(R)} celle — serve M2/DSR e il null di permutazione"
|
||||
print(f" VERDETTO (calcolato a runtime): {v}")
|
||||
print("=" * 96)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user