registro: chiusi §58-69 — dodici verdetti, 0 candidati, e due appoggi tolti a decisioni gia' prese

I dodici filoni della sesta ondata erano rimasti `_in corso_`: la sessione si e'
chiusa dopo che gli agenti avevano scritto gli script e prima che i verdetti
fossero consolidati, e i loro messaggi finali sono persi. I numeri qui NON
vengono da quei messaggi: vengono dalla riesecuzione dei dodici script
(07:38-08:06 UTC, sequenziale, log in logs/r0823b/ che e' gitignored).
E' il motivo per cui l'ondata non e' andata persa: ogni script calcola il
proprio verdetto a runtime.

Cosa tocca decisioni gia' prese:
- §67 il gate pre-registrato XSR01 del 23/10 legge un monitor tarato sul
  pavimento del venue sbagliato (C* $15-20k, non ~$3k)
- §64 la raccomandazione di §51 (raccogliere la catena USDC) non e'
  giustificata dalla ragione che porta: le due superfici sono la stessa
- §60 la politica MISTO scelta il 25/07 non e' piu' l'ottimo (oggi MISTO-A)
- §63 domanda fiscale NUOVA, diversa da quella aperta il 07/08

Il risultato piu' grande e' di §58: l'obiettivo del progetto ha DUE definizioni
operative in uso che danno 33,9% contro 0,33% sulla stessa domanda, e non era
mai stato detto quale si stesse ottimizzando.

r0823b_quasi_passati.py (§68) terminava con IndexError: Griglia.combo()
indicizzava con self.idx (2720 giorni) un sottoinsieme di 958 righe. Corretto
col parametro idx esplicito piu' un controllo di lunghezza; la
rinormalizzazione e' riga per riga, quindi i valori sono quelli dell'intento
dell'autore. La correzione e' del coordinatore, non dell'autore, ed e'
dichiarata nel registro.

Libro, pesi, cron, config INVARIATI. Nessun ordine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-08-23 12:40:30 +00:00
parent cdec81c5bc
commit 8c0b5f97ee
13 changed files with 10798 additions and 12 deletions
+59 -12
View File
@@ -723,18 +723,18 @@ stessi hanno nominato senza poterli eseguire**. Stesso contratto di consegna, st
| 55 | COSTO-CAPITALE | i muri sono calcolati a costo d'esecuzione COSTANTE, ma il costo cresce col capitale: dove sta il muro vero? | **NON MORDE** — il muro si sposta di **+0,6%** (dentro la risoluzione MC), la saturazione sta **30x sopra** ($9,5-10M), e 📌 **alla taglia di oggi il costo mancante ha segno NEGATIVO** |
| 56 | XS-AMPIEZZA | su un universo povero (11 gambe), si recupera ampiezza EFFETTIVA senza aggiungere gambe? | **NO — e la caduta NON era ampiezza (solo il 14%)**: e' **selettivita'** (recuperabile ma sotto l'MDE) **piu' un IC che su quelle 11 gambe vale ZERO**. 🚨 E `k=5` **non e' invariante di scala** |
| 57 | CRITICO | l'ondata ha prodotto qualcosa che avvicina i 50 EUR/giorno — e i suoi numeri di testa reggono a una verifica indipendente? | **NO al mandato, SI' alle repliche** — 6 numeri di testa riprodotti (4 muri al dollaro, funding 2,1597%, dShFULL 0,4502); ma 🚨 **il muro $313k e' una MEDIANA con banda [$187k, $1,14M]** e a 2 SE il traguardo non esiste, e 🚨 **«positivo in N/N ancore» vale ~2 osservazioni**, con la banda **4,3x piu' stretta dell'IC**. La somma di TUTTI i lead dell'ondata vale **+0,036 EUR/giorno** |
| 58 | OBIETTIVO-DIRETTO | ottimizzare DIRETTAMENTE per `P(>=50 EUR/g entro H)` invece che per Sharpe/J/E[ricchezza]: l'ottimo e' un altro? | _in corso_ |
| 59 | SCALA-PROP | l'aritmetica diretta: quanti conti funded, a che leva, per fare 50 EUR/g — e quale vincolo morde per primo? | _in corso_ |
| 60 | LADDER-REDO | la politica multi-conto «MISTO» fu scelta con numeri poi corretti di 16x: e' ancora l'ottimo? | _in corso_ |
| 61 | RETRY | quando ricomprare un biglietto dopo un bust, se il valore sta nel NUMERO di tentativi? | _in corso_ |
| 62 | SPLIT-DEPOSITO | «tutto o niente» non e' una politica: qual e' la frazione ottima del versamento verso i biglietti? | _in corso_ |
| 63 | FISCO-PAYOUT | i payout di una prop firm non sono plusvalenze cripto: a quale aliquota il canale smette di convenire? | _in corso_ |
| 64 | USDC-SURFACE | la superficie opzioni USDC-lineare non e' mai stata misurata: e' negoziabile a qualunque capitale? | _in corso_ |
| 65 | CURVA-DATATI | i 30 futures scaduti sono stati usati solo per il livello del carry: la FORMA della curva dice qualcosa? | _in corso_ |
| 66 | CROSS-VENUE | lo scarto Deribit-Hyperliquid e' sorvegliato come guasto e mai guardato come segnale | _in corso_ |
| 67 | REGIME-CAPITALE | tutto e' stato screenato a $600: cosa e' stato scartato PER TAGLIA che vivrebbe a $100k? | _in corso_ |
| 68 | QUASI-PASSATI | decine di candidati morti per 0,002 di DSR: insieme passano, o e' data-dredging? | _in corso_ |
| 69 | SCETTICO-CONVERSIONE | se il canale funded fosse presentato oggi come candidato-sleeve, passerebbe i gate di questo progetto? | _in corso_ |
| 58 | OBIETTIVO-DIRETTO | ottimizzare DIRETTAMENTE per `P(>=50 EUR/g entro H)` invece che per Sharpe/J/E[ricchezza]: l'ottimo e' un altro? | **L'OTTIMO E' UN ALTRO, E LA DOMANDA HA DUE RISPOSTE** — 🚨 l'obiettivo del progetto ha **due definizioni operative in uso** che rispondono in modo opposto alla stessa domanda: a **FLUSSO** (la lente del canale funded) il massimo a 36 mesi e' **33,9%**, a **RICCHEZZA SOSTENIBILE** (la lente dei muri e del piano) e' **0,33%**. E l'ottimo di `P(>=50/g)` **NON e' l'ottimo di J**: 37,3% contro 13,9% alla leva argmax-J. Il libro di oggi a 36 mesi: mediana **0 EUR/g** di flusso (non preleva), capitale mediano $22.467 = **3,59 EUR/g** di rendita perpetua. 316 trial, 1500 percorsi |
| 59 | SCALA-PROP | l'aritmetica diretta: quanti conti funded, a che leva, per fare 50 EUR/g — e quale vincolo morde per primo? | **NON AVVICINA** — a regime **13,36 EUR/g per conto da $100k** ⇒ servono **4 conti** ($400.000 di nozionale = i cap di 2 firm, $2.316 di biglietti). Ma 🚨 **`P(>=50/g)` SATURA all'87%**: *il canale non e' una rendita, e' una scommessa che si puo' ingrandire ma non rendere affidabile*. Da EUR 635: `P(4 conti contemporanei in 36 mesi)` **32,6%**, **19,31 EUR/g attesi contro 1,78 mediani**. 178 celle |
| 60 | LADDER-REDO | la politica multi-conto «MISTO» fu scelta con numeri poi corretti di 16x: e' ancora l'ottimo? | 🚨 **NO — il vincitore del 25/07 non e' piu' l'ottimo**: oggi **MISTO-A `P(>=50/g)` 9,27%** contro **MISTO 6,60%** (288 celle, 369 valutazioni MC). Ordine odierno MISTO-A > CONC-A > MISTO > CONC-DIV > SPARSO > CONC-2SL. **Mediana 0,00 EUR/g in tutte e sei le politiche**, P(zero) 53,7-80,9% |
| 61 | RETRY | quando ricomprare un biglietto dopo un bust, se il valore sta nel NUMERO di tentativi? | **NON MORDE — e il fatto trovato e' un altro:** la politica di ri-acquisto **cablata** (compra subito, il biglietto piu' grande, in tutti e 6 gli slot, con qualunque soldo) **non era mai stata dichiarata come scelta**, ed e' la piu' aggressiva della famiglia — *da li' escono TUTTI i numeri del canale funded*. Mappata la famiglia (7 regole di tempo x 2 di taglia): **il massimo di famiglia E' il cablato** (4,0%), spread 4,05pp; il prezzo della prudenza e' 4,05pp di bersaglio per 2,05pp di rovina. 📌 **«SOLO DAI PAYOUT» non vincola nulla a $654**: dopo il primo biglietto restano $75, sotto il biglietto piu' economico ⇒ ogni ri-acquisto viene GIA' dai payout. 📌 **Ottimizzare la politica vale +0,00 EUR/g; versare EUR 500/mese ne vale +15,75** |
| 62 | SPLIT-DEPOSITO | «tutto o niente» non e' una politica: qual e' la frazione ottima del versamento verso i biglietti? | **LA CURVA ESISTE MA COMPRA LA METRICA SBAGLIATA** — fra phi=0 e phi=1 (36 mesi, EUR 500/m): **+16,3 pp** di `P(>=50/g)` a **FLUSSO** (risoluzione MC 1,0 pp ⇒ segnale) ma **0,0 pp** su quella a **RENDITA**, e **+12,4 pp** di `P(finire sotto il versato)`. Mediane **3,58 / 12,90 / 26,05 EUR/g** a phi 0 / 0,5 / 1, contro un bersaglio di 50. Nessuna cella selezionata (famiglia dichiarata nel docstring, tutte riportate) |
| 63 | FISCO-PAYOUT | i payout di una prop firm non sono plusvalenze cripto: a quale aliquota il canale smette di convenire? | 🚨 **IL FORFAIT 33% E' LA CASELLA SBAGLIATA** — sul funded il trader **non possiede il conto ne' le posizioni**: non c'e' realizzo di cripto-attivita', e `c-sexies` (33%) e `c-quater` (26%) — le due caselle su cui il progetto discute per il LIBRO — sono **entrambe FUORI CATEGORIA** qui. Le fonti professionali convergono su **LAVORO AUTONOMO**; **nessuna prassi ufficiale esiste**. ✅ La conclusione di §37 («il biglietto batte il versare» a EUR 500/mese) **REGGE su tutta la banda plausibile**. Costo della questione: cassa mediana $44.112 → $34.448 (22%), `P(>=50/g)` 21,0% → 4,7%, **8,21 EUR/g** fra i due estremi. 📌 **Al 33% di oggi l'aliquota NON e' cio' che tiene lontano il bersaglio** (17,77 EUR/g mediani, P 14,5%): lo tiene lontano la distribuzione. **Domanda NUOVA al commercialista**, diversa da quella aperta il 07/08 |
| 64 | USDC-SURFACE | la superficie opzioni USDC-lineare non e' mai stata misurata: e' negoziabile a qualunque capitale? | 🚨 **LE DUE SUPERFICI SONO LA STESSA SUPERFICIE**, a strike appaiati e su **entrambi** gli assi: prezzo equo identico per costruzione (una sola superficie di mark al venue), mezzo-spread per gamba **Δ 0,21 / 0,38 pp** (soglia 1pp) e `f_venue` di struttura **Δ +0,005 / 0,011** (soglia 0,05). 38 chiamate, 0 errori, 0 risposte 429. ⇒ **la raccomandazione di §51 (raccogliere la catena USDC, +18% di chiamate/giro) NON e' giustificata dalla ragione che porta**: il `f` di VRP01 misurato sull'inverse non e' «il numero della famiglia sbagliata», e' **lo stesso numero**. Nessuna struttura proposta, **0,00 EUR/g** |
| 65 | CURVA-DATATI | i 30 futures scaduti sono stati usati solo per il livello del carry: la FORMA della curva dice qualcosa? | **LA FORMA E' UN GRADO DI LIBERTA' NUOVO (scorrelato dal funding) E NON PREDICE NULLA: 0 candidati su 4 rami.** A1 CONFERMATA (livello = funding travestito, corr **+0,71 BTC / +0,68 ETH**); **A2 CONFERMATA e piu' forte dell'atteso** (slope/curvatura portano informazione che il funding non ha); ramo A direzionale `earns_slot_honest=False` (**DSR 0,108**, marginale **HEDGE**); ramo D (gate su TP01) dShFULL mediano su 24 ore **0,072**; ramo B (butterfly) **sotto l'MDE per costruzione**; ramo C nessun controllo positivo passa ⇒ non decisivo. Il roll costa **0,49%/anno = lo stesso ordine del premio**. `implausible_sharpe` FALSE. **0,00 EUR/g** |
| 66 | CROSS-VENUE | lo scarto Deribit-Hyperliquid e' sorvegliato come guasto e mai guardato come segnale | **SCARTATO — manca un fattore 6,5x.** Su 8 celle (2 asset x 4 TF) **2 hanno |bD| sopra la propria MDE** (BTC 15m, ETH 15m); miglior rapporto segnale/muro fra le celle MISURATE **0,153** (1,08 bps attesi a 1 sd contro **7,04 bps** di costo su UN venue; **16,2x** per l'arbitraggio a due venue, 17,4 bps). 📌 Il massimo incondizionato (0,739, BTC 1d) **non e' piccolo, e' NON MISURATO** (|bD| 0,76 contro MDE 2,30) — citarlo sarebbe scegliere la cella piu' rumorosa perche' e' la piu' alta. E ogni numero e' **ottimistico tre volte**: a 1 sd invece che al valore tipico, col segno di bD preso dagli stessi dati, e **ignorando che per entrare si attraversano gli stessi due book che generano lo scarto***si paga il rumore che si vuole incassare*. **ZERO** |
| 67 | REGIME-CAPITALE | tutto e' stato screenato a $600: cosa e' stato scartato PER TAGLIA che vivrebbe a $100k? | 🚨 **IL MURO DI TAGLIA NON NASCONDEVA NIENTE** — a $100.000 nessun archiviato passa tutti i gate, **e non perche' li abbia ricontrollati: i gate pubblicati erano GIA' calcolati sulla serie MODELLATA, che e' il limite a capitale infinito**. Censimento **14 voci**, **12 con un rifiuto ANCHE sull'edge**; delle 2 in cui la taglia era l'unico motivo **nessuna e' una strategia** (XS01, gia' sbloccato e fermo per la decisione di venue; GTAA01, fermo per il PRIIPs). Muri di taglia caduti **5 su 14, tutti nell'ondata 22-23/08, tutti nella stessa direzione**. Misurati qui per la prima volta: STATARB-MULTI C* **$2.000-7.500**. ⚠️ **Cio' che TOGLIE e' reale: il gate XSR01 del 23/10 legge un monitor tarato sul pavimento del venue SBAGLIATO** — C* vero **$15.000-20.000**, contro i ~$3.000 che HL-EXEC riportava con un criterio piu' permissivo. **+0,000 EUR/g** |
| 68 | QUASI-PASSATI | decine di candidati morti per 0,002 di DSR: insieme passano, o e' data-dredging? | **NON PROMUOVIBILE, E IL MOTIVO E' IL DSR CHE LI AVEVA FERMATI** — il paniere dei quasi-passati fa **FULL 1,822 / IS 1,948 / HOLD 1,323**, maxDD **8,34%**, ampiezza effettiva **4,67/5**, ed e' al **100,0° pctl** contro i panieri di scartati netti (98,5° FULL / 91,9° HOLD contro celle casuali). Ma 🚨 **DSR 0,828-1,000 secondo la PARTIZIONE dei trial***lo stesso difetto misurato il 22/08 da tre agenti, qui applicato al paniere che dovrebbe superarlo***dipende da UNA gamba** (PREVDAY, la piu' costosa da togliere) e almeno una e' **STAT-MODE** (XSR01 + VRP-TENORE). Il paniere e' a 5 gambe solo nel **35,2%** dei giorni |
| 69 | SCETTICO-CONVERSIONE | se il canale funded fosse presentato oggi come candidato-sleeve, passerebbe i gate di questo progetto? | **REPLICA e poi attacca il pilastro.** Replica: `P(>=50/g)` **3,4%** contro il **2,6%** pubblicato, P(zero) 38,1% contro 40,3% ⇒ *la macchina e' quella*. 🚨 **Il pilastro:** SE del drift di XS01 = **9,6%/anno** ⇒ a **1 SE** il vantaggio del canale passa da **+0,076 a +0,034** di J e `P(>=50/g)` da 3,4% a **1,6%**. 🚨 **La morte-firm:** evidenza pubblica **12-19%/anno** contro il **10% assunto** (l'EV regge — break-even >55% — ma P scende a 2,9%). 🚨 **Il gate mancante:** max-J dal **puro rumore** su 360 celle **0,069** contro **0,303** osservato (eccesso +0,234). In EUR/g mediani a 36 mesi da EUR 600: canale **1,79** al drift misurato, **0,11** a 1 SE, libro LIVE **0,35**, obiettivo **50,00**. **Raccomanda di non cambiare nulla** |
| — | **NOTIFIER** (audit di fatto) | 🚨 **PUNTO SINGOLO DI GUASTO nella rete di sicurezza** | il `send()` di Telegram e' **un solo tentativo da 10s, senza retry, con l'esito MAI registrato** — e lo stato `alerted=True` viene scritto **prima** dell'invio: un 🚨 di `venue_watch` perso e' perso **per l'intero episodio**. Tasso di fallimento **misurato 6,9%** (2/29, IC95 [1,9%, 22,0%]) |
| 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` |
| 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente |
@@ -3683,3 +3683,50 @@ ricerca**. **Il suo valore e' DIFENSIVO ed e' reale: ha impedito decisioni sbagl
domanda posta ha risposta negativa, e la 56ª ondata avra' la stessa risposta della 55ª.
**Nulla di sepolto sotto uno «SCARTATO» merita di essere riesumato**: il candidato piu' forte non
promosso (PREVDAY) vale +0,014 €/g de-luckato e gira la cella sbagliata.
---
## 58-69 — SESTA ONDATA (12 filoni) — verdetti RECUPERATI RIESEGUENDO GLI SCRIPT
⚠️ **PROVENIENZA DEI NUMERI, dichiarata perche' e' diversa da quella di ogni altra sezione di questo
registro.** I dodici filoni furono aperti alle 04:24 del 23/08; gli agenti scrissero gli script fra le
04:39 e le 04:44 e la sessione si chiuse **prima** che i verdetti fossero consolidati qui — le dodici
righe della tabella restarono `_in corso_` e i messaggi finali degli agenti sono persi.
**I verdetti sopra NON vengono da quei messaggi: vengono da una riesecuzione dei dodici script**
(23/08, 07:38:50-08:06:46 UTC, sequenziale), i cui log stanno in `logs/r0823b/`**che e' gitignored**,
quindi *questa tabella e' l'unica copia versionata di quei numeri*. Ogni script calcola il proprio
verdetto a runtime, quindi il recupero e' una **rigenerazione**, non una ricostruzione a memoria.
📌 **La regola gia' scritta il 26/07 vale anche per un'ondata: un numero pubblicato deve avere uno
script committato che lo riproduce.** Qui e' stata la ragione per cui l'ondata non e' andata persa.
🚨 **UN FILONE E' MORTO SU UN BUG E LA CORREZIONE E' MIA (coordinatore), non dell'autore.**
`r0823b_quasi_passati.py` (§68) terminava con `IndexError: Boolean index has wrong length: 958 instead
of 2720`: `Griglia.combo()` indicizzava sempre con `self.idx` (l'unione, 2720 giorni) mentre il call
site le passava un **sottoinsieme di righe** (i 958 giorni con 5/5 gambe vive). Corretto aggiungendo il
parametro `idx` esplicito **con un controllo di lunghezza** e passando `common` al call site. La
rinormalizzazione dei pesi in `combo` e' **riga per riga**, quindi i valori dei giorni interessati sono
identici a quelli dell'intento dell'autore: e' una correzione di **contabilita' dell'indice**, non di
calcolo. **Il verdetto di §68 va letto sapendo che gira su codice toccato dal coordinatore.**
### Cosa tocca decisioni gia' prese (e non e' nella colonna «verdetto»)
| § | cosa cambia | dove stava scritto |
|---|---|---|
| **67** | 🚨 il **gate pre-registrato XSR01 del 23/10** legge un monitor tarato sul pavimento del venue **sbagliato**: C* vero **$15.000-20.000**, non i ~$3.000 riportati | il gate dice *«deploy solo se haircut a $5.000 <= 40%»* — la soglia di capitale su cui poggia e' misurata su un criterio piu' permissivo |
| **64** | la raccomandazione di §51 (**raccogliere la catena USDC**, +18% di chiamate/giro) **non e' giustificata dalla ragione che porta** | §51 la motivava col `f` di VRP01 «misurato sulla famiglia sbagliata»; le due superfici sono la stessa |
| **60** | la politica multi-conto **MISTO**, scelta il 25/07, **non e' piu' l'ottimo**: oggi vince **MISTO-A** | CLAUDE.md, bullet «LA VIA: i 600 euro come BIGLIETTO» |
| **63** | **domanda fiscale NUOVA** (payout di prop firm = lavoro autonomo?), **diversa** da quella aperta il 07/08 sui derivati | il forfait 33% e' applicato al canale funded in ogni simulazione dal 25/07 |
### La riga dell'ondata
**Dodici filoni, zero candidati.** Nessuno avvicina i 50 EUR/giorno, e la sintesi involontaria e' di
§61: **ottimizzare la politica di ri-acquisto dei biglietti vale +0,00 EUR/g, versare EUR 500/mese ne
vale +15,75** — rapporto che il filone stampa da solo. §58 aggiunge il pezzo che mancava a cinque
ondate di misure: **l'obiettivo del progetto ha due definizioni operative in uso** (flusso e ricchezza
sostenibile) **che danno 33,9% contro 0,33% sulla stessa domanda**, e finora non era mai stato detto
quale delle due si stesse ottimizzando. Il valore dell'ondata, come della quinta, e' **difensivo**:
§67 e §64 tolgono due appoggi a decisioni gia' prese, §69 toglie precisione a un numero gia'
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**.
+969
View File
@@ -0,0 +1,969 @@
#!/usr/bin/env python
"""r0823b_crossvenue.py — §66 CROSS-VENUE: lo scarto Deribit-vs-Hyperliquid come SEGNALE.
LA DOMANDA
==========
Il progetto usa due venue — **Deribit** (dove esegue) e **Hyperliquid** (dove vive l'universo di
XS01) — e li ha sempre trattati come *fonti di dato indipendenti da certificare*, mai come *due
mercati fra cui puo' esistere una relazione*. `venue_watch` misura lo scarto Deribit-vs-consenso
**per rilevare un guasto** (soglia 100 bps, 4h, segno costante; Deribit sta a ~3 bps dal consenso
in mediana su 8 anni). Quello stesso scarto non e' mai stato guardato come SEGNALE.
Tre domande, in ordine di plausibilita' DECRESCENTE:
(a) lo scarto REVERTE, e a che orizzonte?
(b) uno dei due venue GUIDA (lead-lag)?
(c) lo scarto e' un GATE DI REGIME utile a TP01/SKH01?
ATTESE DICHIARATE PRIMA DI MISURARE (metodo, punto 1)
=====================================================
A1. **Lo scarto e' quasi tutto FATTORE COMUNE.** Due perp sullo stesso sottostante: mi aspetto
corr dei rendimenti > 0,99 e varianza idiosincratica < 0,1% della comune. (Regola del 22/08:
*una divergenza fra due fonti di prezzo si scompone in COMUNE e IDIOSINCRATICA prima di
giudicarla — la prima da' sempre la risposta rassicurante.*)
A2. **Taglia attesa dello scarto: 3-9 bps** in mediana — stesso ordine dei 3 bps Deribit-consenso
e dei 4-9 bps HL-vs-Binance della certificazione di `fetch_hyperliquid`.
A3. **(a) REVERSIONE: SI', ma con mezza-vita <= 1 barra**, cioe' la firma del RUMORE DI
OSSERVAZIONE (bid-ask bounce su due book distinti), non di una dislocazione arbitrabile.
Discriminante dichiarato prima: la regressione **SALTANDO UNA BARRA** (entrare a t+1, non a t).
Se il coefficiente crolla -> era rumore; se sopravvive -> era dislocazione.
A4. **(b) LEAD-LAG: nessuno rilevabile a >= 5 minuti.** Il lead-lag fra perp liquidi arbitrati vive
a scala sub-secondo. Picco della cross-correlazione atteso a lag 0.
A5. **(c) GATE: NO.** Prior bassissimo — il progetto ha gia' refutato 5+ gate come «TP01
travestito» o «ridondante col trend», e un residuo di microstruttura non ha ragione di
predire il regime di trend. Inoltre la finestra comune (HL 1d dal 2024-01) e' **interamente
post-hold-out**, dove `marginal_vs_tp01` non puo' dare ADDS per costruzione (lezione §12).
A6. **MURO DI NEGOZIABILITA': il segnale sara' sotto il costo.** Dichiarato PRIMA di guardare i
rendimenti, e stampato PRIMA nell'output.
A7. **STAT-MODE, non deploy.** Una gamba su Hyperliquid non e' eseguibile con questo conto
(Deribit, ~$635) senza aprire un secondo venue, e c'e' la **decisione dell'operatore del
26/07** che tiene tutto su Deribit fino a $20k. Questo filone si giudica su *«esiste il
fenomeno?»*, non su *«e' deployabile?»*.
PRIOR ART (non si ripete): il progetto ha GIA' testato un premio cross-venue con una barriera
STRUTTURALE vera (kimchi Upbit/USD, controlli sui capitali, `r0724_premium_wave` +
`r0724_kimchi_skeptic`, 2026-07-24): EARNS_SLOT=True al marginal scorer ma DSR 0,891, nessun
plateau, lag +1g lo azzera -> SCARTATO come parameter-luck. Deribit-HL **non ha alcuna barriera**
(stessa valuta di conto, nessun controllo sui capitali, arbitraggio libero) -> il prior e' PEGGIORE
di quello, non migliore.
REPLICHE PRIMA DI PRODURRE NUMERI NUOVI (metodo, punto 2)
=========================================================
R1 `venue_watch`: |scarto| mediano Deribit-vs-consenso(coinbase+bitstamp) ~ **3 bps** su 8 anni.
R2 SLIP-AUDIT (§ ondata 22/08): base perp INVERSE vs LINEARE USDC = **BTC -0,03 bps,
ETH -0,27 bps**. Serve a due cose: replicare un numero pubblicato E chiudere in anticipo la
4a occorrenza dello schema `fee_watch` (misurare su una configurazione diversa da quella che
gira: il feed certificato e' l'INVERSE, il libro trada il LINEARE).
R3 certificazione `fetch_hyperliquid`: HL 1d vs Binance **4-9 bps** sui 19 major.
DATI
====
* Deribit BTC/ETH: feed certificato su disco (1h, 2018-08/2019-03 -> oggi).
* HL 1d: `data/raw/hl_{btc,eth}_1d.parquet`, certificato, **dal 2024-01-01 (~2,6 anni)**.
* HL 5m/15m/1h: **NON su disco** -> presi dall'endpoint pubblico `candleSnapshot`
(tokenless, GET/POST in lettura) e **certificati qui** (flat, volume, n. trade, gap,
duplicati, cross-venue vs Coinbase USD dalla cache di `venue_tripwire`).
⚠️ L'endpoint tiene **~5000 candele** per intervallo, punto: 5m = ~17 giorni,
15m = ~52 giorni, 1h = ~209 giorni. Non c'e' modo pubblico di andare piu' indietro.
* ⚠️ **Mai USDT come ancora** (regola del progetto): la referenza terza e' Coinbase USD.
MDE (metodo, punto 4) — dichiarato prima
========================================
* Sharpe, finestra 1h (0,57 anni): 1,96/sqrt(0,57) = **2,59** -> nessuna affermazione di
STRATEGIA e' possibile su questa finestra. Si misura il FENOMENO, non un edge.
* Sharpe, finestra 1d (2,6 anni): 1,96/sqrt(2,6) = **1,22**.
* Correlazione/AR su n barre: 1,96/sqrt(n) -> a n~5000 = **0,028**. QUI la potenza c'e'.
Cioe': *reversione e lead-lag sono misurabili; un edge no.* Sono due frasi diverse.
uv run python scripts/research/r0823b_crossvenue.py [--refresh]
"""
from __future__ import annotations
import pickle
import sys
import time
import datetime as dt
from pathlib import Path
import numpy as np
import pandas as pd
import requests
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from src.data.downloader import load_data # noqa: E402
CACHE = ROOT / "data" / "_cache" / "r0823b_crossvenue.pkl"
REF_CACHE = ROOT / "data" / "_cache" / "venue_ref_1h.pkl"
HL_INFO = "https://api.hyperliquid.xyz/info"
DERIBIT_CHART = "https://www.deribit.com/api/v2/public/get_tradingview_chart_data"
ASSETS = ("BTC", "ETH")
TF_MS = {"5m": 300_000, "15m": 900_000, "1h": 3_600_000, "1d": 86_400_000}
BARS_PER_YEAR = {"5m": 365.25 * 288, "15m": 365.25 * 96, "1h": 365.25 * 24, "1d": 365.25}
# --- costi MISURATI dal progetto (non assunti) ---------------------------------------------
DER_TAKER_BPS = 3.50 # fee_watch + SLIP-AUDIT sul nastro (16/16 fill esatti) dal 2026-08-01
HL_TAKER_BPS = 4.50 # HL-EXEC (due fonti indipendenti coincidono)
HL_HALF_SPREAD_BPS = 0.70 # HL-EXEC, 19 major
DER_TICK_USDC = {"BTC": 0.1, "ETH": 0.01} # lineare USDC dopo il cambio del 2026-08-18
# --- numeri pubblicati da replicare ---------------------------------------------------------
PUB_VENUEWATCH_BPS = 3.0
PUB_BASE_INV_LIN = {"BTC": -0.03, "ETH": -0.27}
PUB_HL_BINANCE_BPS = (4.0, 9.0)
# ===========================================================================================
# rete: gentile, e fuori dalle finestre del cron live
# ===========================================================================================
def wait_ok_minute() -> None:
"""Il cron live gira ai minuti :05-:10 (cron_book) e :24-:30 (collettore catena).
Nessuna richiesta di ricerca deve trovarsi nella stessa finestra."""
while True:
m = dt.datetime.now(dt.timezone.utc).minute
if not (5 <= m <= 10 or 24 <= m <= 30):
return
time.sleep(20)
def hl_candles(coin: str, interval: str) -> pd.DataFrame:
"""`candleSnapshot` pubblico. Ritorna al massimo ~5000 candele, sempre le PIU' RECENTI:
startTime piu' indietro NON allunga la storia (verificato: 2024 -> lista vuota)."""
wait_ok_minute()
now = int(time.time() * 1000)
start = now - 6000 * TF_MS[interval]
r = requests.post(HL_INFO, json={"type": "candleSnapshot",
"req": {"coin": coin, "interval": interval,
"startTime": start, "endTime": now}}, timeout=40)
r.raise_for_status()
d = r.json()
if not d:
return pd.DataFrame()
df = pd.DataFrame([{"ts": int(x["t"]), "open": float(x["o"]), "high": float(x["h"]),
"low": float(x["l"]), "close": float(x["c"]), "volume": float(x["v"]),
"ntrades": int(x["n"])} for x in d])
return df.drop_duplicates("ts").sort_values("ts").reset_index(drop=True)
def deribit_chart(instrument: str, resolution: str, start_ms: int, end_ms: int) -> pd.DataFrame:
wait_ok_minute()
r = requests.get(DERIBIT_CHART, params=dict(instrument_name=instrument, resolution=resolution,
start_timestamp=start_ms, end_timestamp=end_ms),
timeout=40)
r.raise_for_status()
res = r.json().get("result", {})
if res.get("status") == "no_data" or not res.get("ticks"):
return pd.DataFrame()
return pd.DataFrame({"ts": res["ticks"], "close": res["close"],
"volume": res["volume"]}).drop_duplicates("ts").sort_values("ts")
def load_cache(refresh: bool) -> dict:
if CACHE.exists() and not refresh:
return pickle.loads(CACHE.read_bytes())
return {}
def save_cache(c: dict) -> None:
CACHE.parent.mkdir(parents=True, exist_ok=True)
CACHE.write_bytes(pickle.dumps(c))
# ===========================================================================================
# nucleo statistico (niente statsmodels nell'ambiente -> OLS + Newey-West a mano)
# ===========================================================================================
def ols_hac(y: np.ndarray, X: np.ndarray, lags: int | None = None):
"""OLS con covarianza Newey-West. X DEVE gia' contenere la costante."""
y = np.asarray(y, float)
X = np.asarray(X, float)
n, k = X.shape
XtXi = np.linalg.pinv(X.T @ X)
b = XtXi @ (X.T @ y)
e = y - X @ b
if lags is None:
lags = max(1, int(np.floor(4 * (n / 100.0) ** (2.0 / 9.0))))
Xe = X * e[:, None]
S = Xe.T @ Xe
for L in range(1, lags + 1):
w = 1.0 - L / (lags + 1.0)
G = Xe[L:].T @ Xe[:-L]
S = S + w * (G + G.T)
V = XtXi @ S @ XtXi
se = np.sqrt(np.maximum(np.diag(V), 0.0))
r2 = 1.0 - e.var() / y.var() if y.var() > 0 else np.nan
return dict(b=b, se=se, t=b / np.where(se > 0, se, np.nan), V=V, e=e, n=n, r2=r2, lags=lags)
def wald_block(fit: dict, idx: list[int]) -> tuple[float, float]:
"""Wald chi2 su un blocco di coefficienti (HAC). Ritorna (chi2, p)."""
from scipy import stats
b = fit["b"][idx]
V = fit["V"][np.ix_(idx, idx)]
try:
w = float(b @ np.linalg.pinv(V) @ b)
except Exception:
return np.nan, np.nan
return w, float(stats.chi2.sf(w, len(idx)))
def ar1(x: np.ndarray) -> dict:
"""AR(1) sul livello: x[t+1] = a + phi x[t]. Mezza-vita in barre."""
x = np.asarray(x, float)
y, X = x[1:], np.column_stack([np.ones(len(x) - 1), x[:-1]])
f = ols_hac(y, X)
phi = float(f["b"][1])
hl = float(np.log(0.5) / np.log(phi)) if 0 < phi < 1 else (0.0 if phi <= 0 else np.inf)
return dict(phi=phi, se=float(f["se"][1]), t=float(f["t"][1]), half_life=hl, n=f["n"])
def ccf(a: np.ndarray, b: np.ndarray, lags: range) -> dict:
"""corr(a[t], b[t-L]). L>0 => b GUIDA a (b di ieri spiega a di oggi)."""
a = np.asarray(a, float)
b = np.asarray(b, float)
out = {}
for L in lags:
if L >= 0:
x, y = a[L:], b[:len(b) - L] if L else b
else:
x, y = a[:len(a) + L], b[-L:]
m = np.isfinite(x) & np.isfinite(y)
out[L] = float(np.corrcoef(x[m], y[m])[0, 1]) if m.sum() > 30 else np.nan
return out
def bp(x) -> float:
return float(x)
# ===========================================================================================
# costruzione delle coppie allineate
# ===========================================================================================
def deribit_series(asset: str, tf: str) -> pd.Series:
"""Feed certificato, chiuse. Indice = timestamp di APERTURA della barra (ms)."""
d = load_data(asset, "1h" if tf in ("1h", "1d") else tf)
s = pd.Series(d["close"].to_numpy(float),
index=pd.to_datetime(d["timestamp"], unit="ms", utc=True))
if tf == "1d":
s = s.resample("1D").last()
return s.dropna()
def hl_series(cache: dict, asset: str, tf: str) -> pd.DataFrame:
if tf == "1d":
h = pd.read_parquet(ROOT / "data" / "raw" / f"hl_{asset.lower()}_1d.parquet")
h.index = pd.to_datetime(h["timestamp"], unit="ms", utc=True)
h["ntrades"] = np.nan
return h[["open", "high", "low", "close", "volume", "ntrades"]]
key = ("hl", asset, tf)
if key not in cache:
cache[key] = hl_candles(asset, tf)
time.sleep(0.7)
df = cache[key].copy()
df.index = pd.to_datetime(df["ts"], unit="ms", utc=True)
return df[["open", "high", "low", "close", "volume", "ntrades"]]
def pair(cache: dict, asset: str, tf: str) -> pd.DataFrame:
"""Coppia allineata sull'APERTURA della barra: le chiuse cadono allo stesso istante."""
d = deribit_series(asset, tf)
h = hl_series(cache, asset, tf)
# NB: `ntrades` NON entra qui. A 1d e' una colonna tutta-NaN (il parquet certificato non la
# ha) e i chiamanti fanno `.dropna()`: includerla svuotava la coppia 1d **in silenzio**
# (n=0 stampato come "picco a -2 -> sfasamento"). Errore commesso e corretto in sessione:
# un dropna su una colonna che non serve e' un filtro invisibile.
j = pd.concat({"der": d, "hl": h["close"], "vol_hl": h["volume"]},
axis=1, join="inner").dropna(subset=["der", "hl"])
j["s_bps"] = 1e4 * np.log(j["hl"] / j["der"])
j["r_der"] = np.log(j["der"]).diff()
j["r_hl"] = np.log(j["hl"]).diff()
return j
# ===========================================================================================
# 0 — REPLICHE
# ===========================================================================================
def repliche(cache: dict) -> None:
print("\n" + "=" * 100)
print(" [0] REPLICHE — un numero gia' pubblicato PRIMA di produrne uno nuovo")
print("=" * 100)
# R1 — venue_watch: Deribit vs consenso (coinbase + bitstamp), orario, 8 anni
ok = REF_CACHE.exists()
print(f"\n R1 venue_watch |scarto| mediano Deribit-vs-consenso — pubblicato ~{PUB_VENUEWATCH_BPS:.0f} bps su 8 anni")
if not ok:
print(" cache referenze assente -> NON replicato (non rifaccio 8 anni di fetch)")
else:
refs = pickle.loads(REF_CACHE.read_bytes())
for a in ASSETS:
d = load_data(a, "1h")
der = pd.Series(d["close"].to_numpy(float), index=d["timestamp"].to_numpy())
cols = {"der": der}
for eid in ("coinbase", "bitstamp"):
r = refs.get((a, eid))
if r is not None and len(r):
cols[eid] = pd.Series(np.asarray(r, float), index=np.asarray(r.index))
m = pd.concat(cols, axis=1, join="outer")
m = m[m["der"].notna()]
R = m[[c for c in m.columns if c != "der"]]
cons = R.median(axis=1, skipna=True)
spread = (R.max(axis=1) - R.min(axis=1)) / cons * 1e4
usable = (R.notna().sum(axis=1) >= 2) & (spread.fillna(np.inf) <= 100.0)
bps = ((m["der"] - cons) / cons * 1e4)[usable & cons.notna()]
print(f" {a}: n={len(bps):,} |scarto| mediano = {bps.abs().median():.2f} bps"
f" (p95 {bps.abs().quantile(.95):.1f})")
print(" -> REPLICATO: l'ordine di grandezza dei 3 bps e' confermato su entrambi gli asset.")
# R2 — base INVERSE vs LINEARE USDC (chiude in anticipo lo schema `fee_watch`)
print(f"\n R2 base perp INVERSE vs LINEARE USDC — pubblicato BTC {PUB_BASE_INV_LIN['BTC']:+.2f} / "
f"ETH {PUB_BASE_INV_LIN['ETH']:+.2f} bps")
for a in ASSETS:
key = ("der_lin", a)
if key not in cache:
now = int(time.time() * 1000)
cache[key] = deribit_chart(f"{a}_USDC-PERPETUAL", "5", now - 17 * 86_400_000, now)
time.sleep(0.7)
lin = cache[key]
if not len(lin):
print(f" {a}: nessun dato lineare -> non replicato")
continue
L = pd.Series(np.asarray(lin["close"], float),
index=pd.to_datetime(np.asarray(lin["ts"]), unit="ms", utc=True))
I = deribit_series(a, "5m")
j = pd.concat({"inv": I, "lin": L}, axis=1, join="inner").dropna()
b = 1e4 * np.log(j["inv"] / j["lin"])
print(f" {a}: n={len(j):,} barre 5m base(inv-lin) mediana = {b.median():+.3f} bps "
f"(media {b.mean():+.3f}, sd {b.std():.2f})")
print(" -> ⚠️ REPLICA PARZIALE: l'ORDINE DI GRANDEZZA (sotto il mezzo bps) e' confermato, il")
print(" SEGNO no (+0,24/+0,27 contro -0,03/-0,27). Non e' una contraddizione: il numero")
print(" pubblicato viene da **18 fill** di luglio, questo da 4.849 barre 5m di agosto —")
print(" due stimandi diversi. Si cita l'ordine di grandezza, non il segno.")
print(" -> 📌 E il fatto che conta e' un ALTRO: la **sd** di quella base (2,7 bps BTC / 4,0")
print(" ETH) e' dello stesso ordine dello scarto cross-venue mediano a 5m (2,8/3,1 bps).")
print(" Cioe': **quale CONTRATTO Deribit si guarda sposta la misura quanto il VENUE**.")
print(f"\n R3 certificazione fetch_hyperliquid: HL 1d vs Binance {PUB_HL_BINANCE_BPS[0]:.0f}-"
f"{PUB_HL_BINANCE_BPS[1]:.0f} bps sui 19 major — non ri-misurato qui (userebbe USDT come")
print(" ancora, che la regola del progetto vieta). Citato come contesto della TAGLIA attesa.")
# ===========================================================================================
# 1 — CERTIFICAZIONE del dato NUOVO (HL intraday)
# ===========================================================================================
def certifica_hl(cache: dict) -> dict:
print("\n" + "=" * 100)
print(" [1] CERTIFICAZIONE del dato HL intraday (NUOVO: non era su disco)")
print("=" * 100)
refs = pickle.loads(REF_CACHE.read_bytes()) if REF_CACHE.exists() else {}
print(f"\n {'asset':<5}{'tf':<5}{'barre':>7}{'da':>13}{'a':>13}{'gg':>6}"
f"{'flat%':>7}{'vol0%':>7}{'ntr_med':>9}{'gap':>5}{'dup':>5}{'vsCB_bps':>10}")
info = {}
for a in ASSETS:
for tf in ("5m", "15m", "1h"):
h = hl_series(cache, a, tf)
n = len(h)
flat = float((h["high"] == h["low"]).mean() * 100)
vol0 = float((h["volume"] <= 0).mean() * 100)
ntr = float(np.nanmedian(h["ntrades"])) if h["ntrades"].notna().any() else np.nan
# epoca ESPLICITA in ms: `.view("int64")` su indici tz-aware e' la trappola
# codificata il 01/07 (scala sbagliata senza errore).
ms = ((h.index - pd.Timestamp("1970-01-01", tz="UTC")) // pd.Timedelta("1ms")).to_numpy()
dts = np.diff(ms)
gap = int((dts != TF_MS[tf]).sum())
dup = int(h.index.duplicated().sum())
# cross-venue vs Coinbase USD (mai USDT), solo dove la cache copre
vs = np.nan
r = refs.get((a, "coinbase"))
if r is not None and len(r) and tf == "1h":
cb = pd.Series(np.asarray(r, float),
index=pd.to_datetime(np.asarray(r.index), unit="ms", utc=True))
j = pd.concat({"hl": h["close"], "cb": cb}, axis=1, join="inner").dropna()
if len(j) > 100:
vs = float((1e4 * np.log(j["hl"] / j["cb"])).abs().median())
gg = (h.index[-1] - h.index[0]).total_seconds() / 86400
print(f" {a:<5}{tf:<5}{n:>7,}{h.index[0].strftime('%Y-%m-%d'):>13}"
f"{h.index[-1].strftime('%Y-%m-%d'):>13}{gg:>6.0f}{flat:>7.2f}{vol0:>7.2f}"
f"{ntr:>9.0f}{gap:>5}{dup:>5}" + (f"{vs:>10.2f}" if np.isfinite(vs) else f"{'-':>10}"))
info[(a, tf)] = dict(n=n, days=gg, flat=flat, vol0=vol0, gap=gap, dup=dup, vs_cb=vs)
print("\n ⚠️ Il cross-venue vs Coinbase e' calcolabile solo a 1h (la cache di venue_tripwire e'")
print(" oraria e si ferma al 2026-07-24): a 5m/15m il dato HL e' certificato su liquidita' e")
print(" integrita', NON su un terzo venue. Dichiarato, non nascosto.")
print(" ⚠️ HL vs Coinbase e' un confronto PERP-vs-SPOT: contiene la base, non solo il rumore.")
return info
# ===========================================================================================
# 2 — VERIFICA DELLE CONVENZIONI COL LAG (il progetto ha appena pagato uno sfasamento di 1h)
# ===========================================================================================
def verifica_lag(cache: dict) -> None:
print("\n" + "=" * 100)
print(" [2] CONVENZIONI DI ETICHETTATURA, verificate COL LAG (non credute)")
print("=" * 100)
print(" Deribit: indice = APERTURA della barra (feed tradingview). HL candleSnapshot: 't' =")
print(" apertura, 'T' = t + durata - 1 ms (verificato sul campo). Allineando sull'apertura,")
print(" le CHIUSE cadono allo stesso istante. Prova: la corr dei rendimenti deve avere il")
print(" MASSIMO a lag 0. (Lezione §39: due serie dello stesso progetto possono avere")
print(" convenzioni diverse — corr 0,996 al lag +1 e ~0 al lag 0.)")
print(f"\n {'asset':<5}{'tf':<5}{'n':>7}" + "".join(f"{f'lag{L:+d}':>9}" for L in range(-2, 3)) + " verdetto")
for a in ASSETS:
for tf in ("5m", "15m", "1h", "1d"):
j = pair(cache, a, tf).dropna()
c = ccf(j["r_der"].to_numpy(), j["r_hl"].to_numpy(), range(-2, 3))
best = max(c, key=lambda k: (c[k] if np.isfinite(c[k]) else -9))
ok = "OK (picco a 0)" if best == 0 else f"!! PICCO A {best:+d} — sfasamento"
print(f" {a:<5}{tf:<5}{len(j):>7,}" + "".join(f"{c[L]:>9.4f}" for L in range(-2, 3)) + f" {ok}")
# ===========================================================================================
# 3 — SCOMPOSIZIONE COMUNE / IDIOSINCRATICA (controllo obbligatorio)
# ===========================================================================================
def scomposizione(cache: dict) -> dict:
print("\n" + "=" * 100)
print(" [3] SCOMPOSIZIONE COMUNE / IDIOSINCRATICA — il controllo contro l'auto-inganno")
print("=" * 100)
print(" r_der e r_hl si scompongono in comune c=(r_der+r_hl)/2 e idio d=(r_hl-r_der)=Ds.")
print(" La quota idiosincratica e' TUTTO cio' su cui una strategia cross-venue puo' vivere.")
print(f"\n {'asset':<5}{'tf':<5}{'n':>7}{'corr(r)':>9}{'sd_com%':>10}{'sd_idio%':>10}"
f"{'var_idio/var_tot':>18}{'|s| med bps':>13}{'sd(s) bps':>11}{'|s| p99':>9}")
out = {}
for a in ASSETS:
for tf in ("5m", "15m", "1h", "1d"):
j = pair(cache, a, tf).dropna()
rd, rh = j["r_der"].to_numpy(), j["r_hl"].to_numpy()
com, idio = (rd + rh) / 2, (rh - rd)
corr = float(np.corrcoef(rd, rh)[0, 1])
vt = float(np.var(com) + np.var(idio) / 4) # var(r_der) ~ var(com)+var(idio)/4
share = float(np.var(idio) / 4) / vt if vt > 0 else np.nan
s = j["s_bps"]
print(f" {a:<5}{tf:<5}{len(j):>7,}{corr:>9.5f}{np.std(com)*100:>10.4f}"
f"{np.std(idio)*100:>10.4f}{share:>18.6f}{s.abs().median():>13.2f}"
f"{s.std():>11.2f}{s.abs().quantile(.99):>9.1f}")
out[(a, tf)] = dict(corr=corr, share=share, med=float(s.abs().median()), sd=float(s.std()))
print("\n 📌 La quota idiosincratica e' il tetto ASSOLUTO di qualunque cosa questo filone possa")
print(" produrre: tutto il resto e' lo stesso bitcoin visto da due finestre.")
# --- confronto a FINESTRA COMUNE: la sd(s) cresce col timeframe o con la FINESTRA? ---
print("\n ⚠️ Le tre finestre intraday NON sono la stessa finestra (5m=18gg, 15m=52gg, 1h=208gg):")
print(" un confronto fra timeframe letto cosi' misura il CALENDARIO, non il timeframe.")
print(" Ricalcolo tutto sugli STESSI 18 giorni (differenza a un grado di liberta' per volta).")
print(f"\n {'asset':<5}{'tf':<5}{'n(18gg)':>9}{'|s| med':>9}{'sd(s)':>8}{'phi':>8}{'HL barre':>10}"
f"{'HL ore':>9}")
for a in ASSETS:
for tf in ("5m", "15m", "1h"):
j = pair(cache, a, tf).dropna()
t0 = j.index[-1] - pd.Timedelta(days=18)
k = j[j.index >= t0]
if len(k) < 100:
continue
A = ar1(k["s_bps"].to_numpy())
ore = A["half_life"] * TF_MS[tf] / 3_600_000
print(f" {a:<5}{tf:<5}{len(k):>9,}{k['s_bps'].abs().median():>9.2f}{k['s_bps'].std():>8.2f}"
f"{A['phi']:>8.3f}{A['half_life']:>10.2f}{ore:>9.2f}")
# --- lento vs veloce: quanta parte dello scarto e' un livello che dura ore? ---
print("\n Scomposizione dello scarto in LENTO (media mobile 24h, causale) e VELOCE (residuo):")
print(f" {'asset':<5}{'tf':<5}{'var lento %':>13}{'var veloce %':>14} lettura")
for a in ASSETS:
for tf in ("5m", "15m", "1h"):
j = pair(cache, a, tf).dropna()
w = max(2, int(round(86_400_000 / TF_MS[tf])))
sl = j["s_bps"].rolling(w).mean()
fa = j["s_bps"] - sl
m = sl.notna()
vs, vf = float(sl[m].var()), float(fa[m].var())
tot = vs + vf
lett = "quasi tutto rumore veloce" if vf / tot > 0.6 else "componente LENTA dominante"
print(f" {a:<5}{tf:<5}{100*vs/tot:>13.1f}{100*vf/tot:>14.1f} {lett}")
return out
# ===========================================================================================
# 4 — MURO DI NEGOZIABILITA' (dichiarato PRIMA dei rendimenti)
# ===========================================================================================
def muro(cache: dict, dec: dict) -> dict:
print("\n" + "=" * 100)
print(" [4] MURO DI NEGOZIABILITA' — dichiarato PRIMA di guardare un solo rendimento")
print("=" * 100)
px = {a: float(deribit_series(a, "1h").iloc[-1]) for a in ASSETS}
soglie = {}
for a in ASSETS:
der_half = 0.5 * DER_TICK_USDC[a] / px[a] * 1e4
arb_rt = 2 * (DER_TAKER_BPS + HL_TAKER_BPS + der_half + HL_HALF_SPREAD_BPS)
dir_rt = 2 * (DER_TAKER_BPS + der_half)
soglie[a] = dict(der_half=der_half, arb=arb_rt, dirz=dir_rt)
print(f"\n {a} (ultimo {px[a]:,.0f}) — mezzo spread Deribit lineare = {der_half:.4f} bps (1 tick), "
f"HL = {HL_HALF_SPREAD_BPS:.2f} bps")
print(f" (i) ARBITRAGGIO cross-venue (4 gambe: apri D+HL, chiudi D+HL)"
f" = 2x({DER_TAKER_BPS:.2f}+{HL_TAKER_BPS:.2f}+{der_half:.3f}+{HL_HALF_SPREAD_BPS:.2f})"
f" = **{arb_rt:.2f} bps** di andata-ritorno")
print(f" (ii) DIREZIONALE su Deribit soltanto (2 gambe, HL usato solo come SEGNALE)"
f" = **{dir_rt:.2f} bps**")
for tf in ("5m", "15m", "1h", "1d"):
d = dec[(a, tf)]
print(f" {tf:<4} |scarto| mediano {d['med']:5.2f} bps sd {d['sd']:5.2f} -> "
f"serve {arb_rt/max(d['sd'],1e-9):5.1f} sd per pagare l'arb, "
f"{dir_rt/max(d['sd'],1e-9):4.1f} sd per pagare il direzionale")
print("\n 📌 SOGLIA DI NEGOZIABILITA' CONGELATA QUI: un segnale che promette meno di ~17 bps di")
print(" movimento atteso non e' arbitrabile, e meno di ~7 bps non e' nemmeno tradabile su un")
print(" venue solo. Il fondo di rumore dello scarto e' di 4-6 bps: **il muro sta sopra la")
print(" DEVIAZIONE STANDARD dell'intero fenomeno**, non sopra il suo valore tipico.")
return soglie
# ===========================================================================================
# 5 — (a) REVERSIONE, con il discriminante rumore-vs-dislocazione
# ===========================================================================================
def reversione(cache: dict, soglie: dict) -> dict:
print("\n" + "=" * 100)
print(" [5] (a) LO SCARTO REVERTE? — e la reversione e' RUMORE o DISLOCAZIONE?")
print("=" * 100)
print(" s(t) = 1e4*ln(p_HL/p_DER). AR(1) sul livello -> mezza-vita.")
print(" Poi la decomposizione che conta: r_DER(t+1) = a + bD*s(t) e r_HL(t+1) = a + bH*s(t).")
print(" Solo **bD** e' tradabile su Deribit; bH richiede la gamba HL (fuori perimetro).")
print(" DISCRIMINANTE dichiarato in A3: la stessa regressione SALTANDO UNA BARRA (r a t+2).")
print(" Se bD crolla -> era rumore di osservazione; se sopravvive -> dislocazione vera.")
print(" MDE(bD) = 1,96*sd(r)/(sd(s)*sqrt(n)): stampata accanto, perche' un coefficiente")
print(" sotto la propria MDE non e' 'piccolo', e' NON MISURATO.\n")
print(f" {'asset':<5}{'tf':<5}{'n':>7}{'phi':>7}{'HLbar':>7}{'HLore':>7}"
f"{'bD':>9}{'t':>6}{'MDE(bD)':>9}{'bD skip1':>10}{'t':>6}"
f"{'E|1sd| bps':>12}{'muro':>7}{'x muro':>8} verso")
out = {}
for a in ASSETS:
for tf in ("5m", "15m", "1h", "1d"):
j = pair(cache, a, tf).dropna()
s_ = j["s_bps"].to_numpy()
rd = j["r_der"].to_numpy() * 1e4 # bps
A = ar1(s_)
y, X = rd[1:], np.column_stack([np.ones(len(s_) - 1), s_[:-1]])
f1 = ols_hac(y, X)
y2, X2 = rd[2:], np.column_stack([np.ones(len(s_) - 2), s_[:-2]])
f2 = ols_hac(y2, X2)
bD, tD = float(f1["b"][1]), float(f1["t"][1])
bD2, tD2 = float(f2["b"][1]), float(f2["t"][1])
sd = float(np.std(s_))
n = len(s_) - 1
mde = 1.96 * float(np.std(rd)) / (sd * np.sqrt(n)) if sd > 0 else np.nan
mossa = abs(bD) * sd
w = soglie[a]["dirz"]
ore = A["half_life"] * TF_MS[tf] / 3_600_000
verso = "converge" if bD > 0 else "diverge"
if abs(bD) < mde:
verso += " (sotto MDE)"
print(f" {a:<5}{tf:<5}{len(j):>7,}{A['phi']:>7.3f}{A['half_life']:>7.2f}{ore:>7.1f}"
f"{bD:>9.4f}{tD:>6.1f}{mde:>9.4f}{bD2:>10.4f}{tD2:>6.1f}"
f"{mossa:>12.2f}{w:>7.2f}{mossa/w:>8.3f} {verso}")
out[(a, tf)] = dict(phi=A["phi"], hl=A["half_life"], bD=bD, tD=tD, bD2=bD2, tD2=tD2,
mossa=mossa, muro=w, sd=sd, mde=mde)
print("\n bH (la gamba HL) per completezza — NON eseguibile con questo conto."
" Identita' di controllo: bH - bD deve valere (phi-1).")
print(f" {'asset':<5}{'tf':<5}{'bH':>9}{'t':>7}{'bH-bD':>9}{'phi-1':>9}{'|dif|':>9}")
for a in ASSETS:
for tf in ("5m", "1h", "1d"):
j = pair(cache, a, tf).dropna()
s_ = j["s_bps"].to_numpy()
rh = j["r_hl"].to_numpy() * 1e4
f = ols_hac(rh[1:], np.column_stack([np.ones(len(s_) - 1), s_[:-1]]))
bH = float(f["b"][1])
dif = (bH - out[(a, tf)]["bD"]) - (out[(a, tf)]["phi"] - 1.0)
print(f" {a:<5}{tf:<5}{bH:>9.4f}{float(f['t'][1]):>7.1f}"
f"{bH - out[(a, tf)]['bD']:>9.4f}{out[(a, tf)]['phi'] - 1:>9.4f}{abs(dif):>9.2e}")
# --- il conto che decide: la strategia MIGLIORE POSSIBILE su questo segnale ---------------
print("\n LA STRATEGIA IN-SAMPLE-OTTIMA (massimamente generosa: il SEGNO di bD e' preso")
print(" dagli stessi dati, la posizione e' gia' scalata al meglio, niente ritardo di")
print(" esecuzione). pos(t) = clip(sgn(bD)*s(t)/sd(s), -1, +1); pnl = pos*r_DER(t+1);")
print(" costo = |Dpos| * (taker Deribit + mezzo spread) su OGNI barra.")
print(" In piu' la stessa strategia RITARDATA DI UNA BARRA (l'unica versione che non si")
print(" finanzia col proprio rumore di osservazione): se il lordo crolla li', il lordo")
print(" della prima colonna era bid-ask bounce, non informazione.")
print(f"\n {'asset':<5}{'tf':<5}{'barre/anno':>11}{'turnover/anno':>14}{'Sh LORDO':>10}"
f"{'Sh LORDO skip1':>16}{'costo %/anno':>14}{'Sh NETTO':>10}{'MDE Sh':>8}")
for a in ASSETS:
for tf in ("5m", "15m", "1h", "1d"):
j = pair(cache, a, tf).dropna()
s_ = j["s_bps"].to_numpy()
rd = j["r_der"].to_numpy()
sd = float(np.std(s_))
sgn = np.sign(out[(a, tf)]["bD"]) or 1.0
pos = np.clip(sgn * s_ / sd, -1, 1)
pnl_g = pos[:-1] * rd[1:]
pnl_s = pos[:-2] * rd[2:] # entrata ritardata di una barra
one_way = (DER_TAKER_BPS + soglie[a]["der_half"]) / 1e4
dpos = np.abs(np.diff(pos, prepend=pos[0]))[:-1]
pnl_n = pnl_g - dpos * one_way
bpy = BARS_PER_YEAR[tf]
anni = len(j) / bpy
def shp(x):
return float(np.mean(x) / np.std(x) * np.sqrt(bpy)) if np.std(x) > 0 else np.nan
print(f" {a:<5}{tf:<5}{bpy:>11,.0f}{dpos.mean()*bpy:>14,.0f}{shp(pnl_g):>10.2f}"
f"{shp(pnl_s):>16.2f}{dpos.mean()*one_way*bpy*100:>14.1f}"
f"{shp(pnl_n):>10.2f}{1.96/np.sqrt(anni):>8.2f}")
print("\n 📌 La colonna 'costo %/anno' NON e' una stima statistica: e' aritmetica "
"(turnover x listino).")
print(" La colonna 'Sh LORDO' e' invece sotto la propria MDE su ogni riga intraday: e' il")
print(" numero che NON si puo' rivendicare. Il verdetto poggia sulla prima, non sulla seconda.")
return out
# ===========================================================================================
# 6 — (b) LEAD-LAG: cross-correlazione + causalita' diretta + CONTROLLI
# ===========================================================================================
def leadlag(cache: dict) -> dict:
print("\n" + "=" * 100)
print(" [6] (b) UNO DEI DUE VENUE GUIDA? — cross-correlazione E test di causalita' diretto")
print("=" * 100)
print(" ccf(L) = corr(r_DER[t], r_HL[t-L]). L>0 => HL GUIDA Deribit. L<0 => il contrario.")
print(f"\n {'asset':<5}{'tf':<5}{'n':>7}" + "".join(f"{f'{L:+d}':>9}" for L in range(-3, 4)))
ll = {}
for a in ASSETS:
for tf in ("5m", "15m", "1h"):
j = pair(cache, a, tf).dropna()
c = ccf(j["r_der"].to_numpy(), j["r_hl"].to_numpy(), range(-3, 4))
print(f" {a:<5}{tf:<5}{len(j):>7,}" + "".join(f"{c[L]:>9.4f}" for L in range(-3, 4)))
ll[(a, tf)] = c
print(f"\n MDE sulla correlazione a n~5000: 1,96/sqrt(n) = {1.96/np.sqrt(5000):.4f}")
print("\n CAUSALITA' DIRETTA (Granger con HAC Newey-West, k=4 ritardi, Wald chi2 sul blocco).")
print(" 🚨 12 test in questa tabella -> soglia BONFERRONI alpha=0,05/12 = 0,0042, non 0,05.")
def _granger(j, k=4):
"""RIPARAMETRIZZATA: al posto dei ritardi di r_HL uso quelli di d = r_HL - r_DER.
⚠️ AUTOCORREZIONE: avevo scritto che la parametrizzazione ingenua produce coefficienti
gonfiati dalla collinearita' e che la loro somma "non e' una grandezza economica".
E' FALSO, e l'algebra lo dice in due righe: `span{r_D, r_H} == span{r_D, r_H-r_D}` con
`a_i = a'_i - c'_i` e **`c_i = c'_i`** — il coefficiente del blocco e' letteralmente lo
STESSO nelle due scritture, e infatti il controllo numerico sotto da' uno scarto di
2,7e-12. La riparametrizzazione compra solo un'assicurazione sul condizionamento (con
corr(r_D,r_H)=0,994) e la leggibilita' di `sum c` = **bps di Deribit per bps di
divergenza passata**. Cio' che ha davvero riparato la lettura non e' questa, e' la
colonna `sd null`: senza di lei una "sd predetta" e' in gran parte gradi di liberta'.
"""
rd, rh = j["r_der"].to_numpy() * 1e4, j["r_hl"].to_numpy() * 1e4
dd = rh - rd
n = len(rd) - k
if n < 200:
return dict(ok=False)
L = lambda x, i: x[k - i - 1: k - i - 1 + n] # noqa: E731
Xd = np.column_stack([np.ones(n)] + [L(rd, i) for i in range(k)] + [L(dd, i) for i in range(k)])
fd = ols_hac(rd[k:], Xd)
w1, p1 = wald_block(fd, list(range(1 + k, 1 + 2 * k)))
Xh = np.column_stack([np.ones(n)] + [L(rh, i) for i in range(k)] + [L(dd, i) for i in range(k)])
fh = ols_hac(rh[k:], Xh)
w2, p2 = wald_block(fh, list(range(1 + k, 1 + 2 * k)))
cb = fd["b"][1 + k: 1 + 2 * k]
contrib = Xd[:, 1 + k: 1 + 2 * k] @ cb # parte di r_DER predetta dalla DIVERGENZA
# controllo di equivalenza con la parametrizzazione ingenua (r_HL al posto di d)
Xn = np.column_stack([np.ones(n)] + [L(rd, i) for i in range(k)] + [L(rh, i) for i in range(k)])
fn = ols_hac(rd[k:], Xn)
wn, _ = wald_block(fn, list(range(1 + k, 1 + 2 * k)))
# null della sd predetta: con p regressori su n punti, il puro rumore produce gia'
# sd(fit) ~ sd(y)*sqrt(p/n). Senza questo confronto una 'sd predetta' e' un artefatto
# di gradi di liberta' — e a n=429 con 8 regressori sarebbe quasi tutto artefatto.
sd_null = float(np.std(rd[k:])) * np.sqrt(k / n)
return dict(ok=True, w1=w1, p1=p1, w2=w2, p2=p2, sum_c=float(cb.sum()),
sd_fit=float(np.std(contrib)), sd_null=sd_null, r2=float(fd["r2"]), n=n,
w_naive=wn, cnaive=float(fn["b"][1 + k: 1 + 2 * k].sum()))
def _lettura(p1, p2, alpha=0.05 / 12):
if not np.isfinite(p1):
return "campione insufficiente"
if p1 > alpha and p2 > alpha:
return "nessuna direzione"
if p1 <= alpha and p2 > alpha:
return "HL guida"
if p2 <= alpha and p1 > alpha:
return "DER guida"
return "reciproca"
gs = []
for etichetta, giorni, tfs in (("FINESTRA PIENA di ciascun tf (18 / 52 / 208 gg)", None,
("5m", "15m", "1h")),
("STESSI 52 giorni (isola il tf dalla finestra)", 52, ("15m", "1h")),
("STESSI 18 giorni (idem, al limite del campione)", 18,
("5m", "15m", "1h"))):
print(f"\n -- {etichetta} --")
print(f" {'asset':<5}{'tf':<5}{'n':>7}{'div->DER chi2':>15}{'p':>9}{'div->HL chi2':>14}{'p':>9}"
f"{'sum c':>8}{'sd pred':>9}{'sd null':>9}{'muro':>7} lettura (Bonferroni)")
for a in ASSETS:
for tf in tfs:
j = pair(cache, a, tf).dropna()
if giorni is not None:
j = j[j.index >= j.index[-1] - pd.Timedelta(days=giorni)]
g = _granger(j)
if not g.get("ok"):
print(f" {a:<5}{tf:<5}{len(j):>7,} campione insufficiente")
continue
w = 2 * (DER_TAKER_BPS + 0.5 * DER_TICK_USDC[a] / float(j['der'].iloc[-1]) * 1e4)
print(f" {a:<5}{tf:<5}{g['n']:>7,}{g['w1']:>15.1f}{g['p1']:>9.4f}{g['w2']:>14.1f}"
f"{g['p2']:>9.4f}{g['sum_c']:>8.3f}{g['sd_fit']:>9.3f}{g['sd_null']:>9.3f}"
f"{w:>7.2f} {_lettura(g['p1'], g['p2'])}")
if giorni is None:
gs.append((a, tf, etichetta, g))
print("\n 📌 Se HL guidasse davvero Deribit, l'effetto sarebbe MASSIMO alla risoluzione piu'")
print(" fine e si diluirebbe salendo. Un effetto che compare SOLO a 15m e sparisce sia a 5m")
print(" sia a 1h non e' un lead-lag: e' una finestra.")
if gs:
alpha = 0.05 / 12
passa = [x for x in gs if x[3]["p1"] <= alpha or x[3]["p2"] <= alpha]
print(f"\n 🚨 La colonna che DECIDE non e' il p-value, e' 'sd pred' — e va letta accanto a")
print(f" 'sd null' (con 4 regressori di blocco su n punti il PURO RUMORE produce gia'")
print(f" sd(y)*sqrt(4/n) di 'predizione').")
for a_, tf_, _, g in sorted(passa, key=lambda x: -x[3]["sd_fit"]):
w = 2 * DER_TAKER_BPS
print(f" · {a_} {tf_} (p={min(g['p1'], g['p2']):.4f}, passa Bonferroni): sd pred "
f"**{g['sd_fit']:.2f} bps** contro {g['sd_null']:.2f} di rumore e {w:.1f} bps di"
f" costo -> manca **{w/max(g['sd_fit'],1e-9):.1f}x**")
if not passa:
print(" · nessuna cella passa Bonferroni sulla finestra piena.")
mx = max(gs, key=lambda x: x[3]["sd_fit"])
g = mx[3]
print(f" Il massimo assoluto di 'sd pred' e' {g['sd_fit']:.2f} bps ({mx[0]} {mx[1]}), ma li'")
print(f" il rumore da solo ne fa {g['sd_null']:.2f} e il test NON e' significativo"
f" (p={min(g['p1'], g['p2']):.4f}).")
# controllo di equivalenza fra le due parametrizzazioni
if gs:
dif = max(abs(g[3]["w1"] - g[3]["w_naive"]) / max(g[3]["w1"], 1e-9) for g in gs)
print(f"\n ✅ Controllo: le due parametrizzazioni (ritardi di r_HL vs ritardi della")
print(f" divergenza) danno lo STESSO chi2 — scarto relativo massimo {dif:.2e}. La")
print(f" riparametrizzazione non cambia il test, cambia solo la LEGGIBILITA' dei")
print(f" coefficienti (somma ingenua fino a {max(abs(g[3]['cnaive']) for g in gs):.2f}, "
f"somma leggibile {max(abs(g[3]['sum_c']) for g in gs):.3f}).")
# ---- CONTROLLI: un rilevatore che non trova nulla e' indistinguibile da uno rotto ----
print("\n CONTROLLI OBBLIGATORI (metodo, punto 3) — sugli STESSI stimatori:")
rng = np.random.default_rng(823)
n = 5000
r = rng.normal(0, 0.001, n) # prezzo comune
pD = np.exp(np.cumsum(r))
# PC1 lead-lag: HL = Deribit RITARDATO di 1 barra + rumore -> Deribit GUIDA (picco a lag -1)
pH = np.concatenate([[pD[0]], pD[:-1]]) * np.exp(rng.normal(0, 5e-5, n))
rd_, rh_ = np.diff(np.log(pD)) * 1e4, np.diff(np.log(pH)) * 1e4
c = ccf(rd_, rh_, range(-3, 4))
best = max(c, key=lambda k_: c[k_])
print(f" PC1 HL = Deribit ritardato di 1 barra -> picco atteso a lag -1; "
f"MISURATO {best:+d} (ccf {c[best]:.3f}) {'OK' if best == -1 else 'ROTTO'}")
# PC2 reversione: spread AR(1) noto phi=0.7 -> mezza-vita attesa 1.94 barre
phi_true = 0.7
e = rng.normal(0, 3.0, n)
s_syn = np.zeros(n)
for i in range(1, n):
s_syn[i] = phi_true * s_syn[i - 1] + e[i]
A = ar1(s_syn)
hl_att = np.log(0.5) / np.log(phi_true)
print(f" PC2 spread AR(1) phi={phi_true} (mezza-vita {hl_att:.2f}) -> "
f"MISURATO phi {A['phi']:.3f}, mezza-vita {A['half_life']:.2f} "
f"{'OK' if abs(A['phi'] - phi_true) < 0.05 else 'ROTTO'}")
# NC: due random walk indipendenti -> nessun lead-lag, nessuna reversione
p1_ = np.exp(np.cumsum(rng.normal(0, 0.001, n)))
p2_ = np.exp(np.cumsum(rng.normal(0, 0.001, n)))
c0 = ccf(np.diff(np.log(p1_)) * 1e4, np.diff(np.log(p2_)) * 1e4, range(-3, 4))
mx = max(abs(v) for v in c0.values())
print(f" NC due random walk indipendenti -> max |ccf| = {mx:.4f} "
f"(soglia rumore {1.96/np.sqrt(n):.4f}) {'OK' if mx < 3*1.96/np.sqrt(n) else 'ROTTO'}")
return ll
# ===========================================================================================
# 7 — (c) GATE DI REGIME
# ===========================================================================================
def gate(cache: dict) -> None:
print("\n" + "=" * 100)
print(" [7] (c) LO SCARTO E' UN GATE DI REGIME PER TP01/SKH01?")
print("=" * 100)
j = {a: pair(cache, a, "1d").dropna() for a in ASSETS}
n = min(len(j[a]) for a in ASSETS)
anni = n / 365.25
mde_sh = 1.96 / np.sqrt(anni)
mde_c = 1.96 / np.sqrt(n)
print(f"\n Finestra comune: {n} giorni = {anni:.2f} anni (HL 1d parte dal 2024-01-01).")
print(f" MDE dichiarato PRIMA: Sharpe {mde_sh:.2f} · correlazione {mde_c:.3f}.")
print(" ⚠️ La finestra e' INTERAMENTE dentro l'hold-out 2025-26 + il 2024 -> `marginal_vs_tp01`")
print(" non puo' dare ADDS per costruzione (lezione §12): non lo uso, misuro il")
print(" CONTENUTO PREDITTIVO direttamente, dove la potenza c'e'.\n")
print(" 🚨 12 correlazioni in questa tabella -> soglia BONFERRONI |t| > 2,86, non 1,96.\n")
print(f" {'asset':<5}{'feature -> bersaglio (t+1)':<42}{'corr':>8}{'t(HAC)':>9}{'MDE':>8} esito")
for a in ASSETS:
d = j[a]
s_ = d["s_bps"].to_numpy()
r = d["r_der"].to_numpy() * 1e4
absr = np.abs(r)
a20 = pd.Series(np.abs(s_)).rolling(20).mean().to_numpy() # causale
feats = {"s(t) firmato": s_, "|s(t)|": np.abs(s_), "media20 |s| (causale)": a20}
targs = {"rendimento": r, "|rendimento| (vol)": absr}
for fn, fv in feats.items():
for tn, tv in targs.items():
x, y = fv[:-1], tv[1:]
m = np.isfinite(x) & np.isfinite(y)
if m.sum() < 100:
continue
c = float(np.corrcoef(x[m], y[m])[0, 1])
f = ols_hac(y[m], np.column_stack([np.ones(m.sum()), x[m]]))
t = float(f["t"][1])
if abs(t) > 2.86:
ok = "SEGNALE (regge Bonferroni)"
elif abs(c) > mde_c:
ok = "sopra MDE ma NON regge Bonferroni"
else:
ok = "sotto MDE"
print(f" {a:<5}{fn + ' -> ' + tn:<42}{c:>8.4f}{t:>9.2f}{mde_c:>8.3f} {ok}")
# ------------------------------------------------------------------ gate vero su TP01
print("\n GATE VERO su TP01 (baseline daily di altlib), finestra comune.")
print(" ⚠️ ERRORE MIO, COMMESSO E CORRETTO IN SESSIONE: la prima stesura usava come null")
print(" il **de-levering** (base x k a pari vol). Su un gate quel null e' DEGENERE —")
print(" `sh(k*base) === sh(base)`, e infatti stampava dSharpe identico nelle due colonne.")
print(" E' esattamente il caso avvertito in §12(d). Il null giusto per un GATE e' un")
print(" **gate CASUALE con lo stesso ciclo di servizio**: spegne gli stessi giorni di")
print(" mercato, ma scelti a caso. Cosi' si misura se il gate spegne i giorni GIUSTI.")
try:
from scripts.research.alt import altlib as al
tp = al.tp01_baseline_daily()
except Exception as ex: # pragma: no cover
print(f" (baseline TP01 non disponibile: {ex})")
return
d = j["BTC"]
idx = d.index.tz_convert("UTC").normalize()
sabs = pd.Series(np.abs(d["s_bps"].to_numpy()), index=idx).rolling(20).mean()
tpi = tp.copy()
tpi.index = (pd.DatetimeIndex(tpi.index).tz_localize("UTC")
if pd.DatetimeIndex(tpi.index).tz is None else pd.DatetimeIndex(tpi.index))
tpi.index = tpi.index.normalize()
m = pd.concat({"tp": tpi, "g": sabs}, axis=1, join="inner").dropna()
if len(m) < 200:
print(f" finestra comune troppo corta ({len(m)}) -> non misurabile")
return
thr = m["g"].expanding(60).median().shift(1) # causale, espandente
base = m["tp"].to_numpy()
def sh(x):
x = np.asarray(x, float)
return float(np.mean(x) / np.std(x) * np.sqrt(365.25)) if np.std(x) > 0 else np.nan
rng = np.random.default_rng(66)
print(f"\n {'gate':<34}{'on%':>6}{'Sh':>8}{'null casuale (mediana)':>24}{'pctl':>7}"
f"{'IC95 del null':>22} esito")
for nome, on in (("CALMO acceso (|s| bassa = on)", (m["g"].shift(1) <= thr)),
("AGITATO acceso (|s| alta = on)", (m["g"].shift(1) > thr))):
onv = on.astype(float).fillna(1.0).to_numpy()
gated = base * onv
duty = float(np.nanmean(onv))
nulls = []
for _ in range(2000):
# ROTAZIONE CIRCOLARE, non permutazione: conserva il ciclo di servizio **e** la
# struttura a run del gate (un gate vero e' fatto di periodi, non di giorni sparsi),
# randomizzando solo l'allineamento col rendimento. Null piu' severo e piu' onesto.
perm = np.roll(onv, int(rng.integers(1, len(onv))))
nulls.append(sh(base * perm))
nulls = np.array(nulls)
nulls = nulls[np.isfinite(nulls)]
pct = float((nulls < sh(gated)).mean() * 100)
lo, hi = np.percentile(nulls, [2.5, 97.5])
esito = ("il gate spegne i giorni GIUSTI" if pct > 97.5
else ("il gate spegne i giorni SBAGLIATI" if pct < 2.5 else "indistinguibile dal caso"))
print(f" {nome:<34}{duty*100:>6.0f}{sh(gated):>8.3f}{np.median(nulls):>24.3f}{pct:>7.1f}"
f"{f'[{lo:+.3f}, {hi:+.3f}]':>22} {esito}")
print(f"\n Sharpe di TP01 SENZA gate sulla stessa finestra: {sh(base):+.3f} (n={len(m)})")
print(" 📌 I due gate sono SPECULARI: se uno spegne i giorni sbagliati, l'altro spegne")
print(" quelli giusti — ma sceglierlo DOPO aver visto la tabella e' selezione, e va")
print(" detto. Cio' che si puo' concludere senza barare e' il FATTO: i giorni a")
print(" |scarto| alto NON sono i giorni da evitare per TP01, sono l'opposto.")
# ===========================================================================================
# 8 — rinforzo indipendente della taratura di venue_watch
# ===========================================================================================
def rinforzo(cache: dict) -> None:
print("\n" + "=" * 100)
print(" [8] SOTTOPRODOTTO — HL come QUARTA referenza per `venue_watch` (soglia 100 bps / 4h)")
print("=" * 100)
from src.live.venue_watch import PERSIST_HOURS, THRESHOLD_BPS
print(f" Soglia in produzione: {THRESHOLD_BPS:.0f} bps persistenti {PERSIST_HOURS}h a segno costante.")
print(f"\n {'asset':<5}{'tf':<5}{'n':>7}{'|s|med':>8}{'|s|p99':>9}{'|s|max':>9}"
f"{'>100bps':>9}{'episodi 4h':>12}")
for a in ASSETS:
for tf in ("1h", "1d"):
d = pair(cache, a, tf).dropna()
s = d["s_bps"]
over = (s.abs() > THRESHOLD_BPS).to_numpy()
sg = np.sign(s.to_numpy())
ep, run, cs = 0, 0, 0
for i in range(len(over)):
if over[i] and (run == 0 or sg[i] == cs):
run = run + 1 if run else 1
cs = sg[i]
if run == PERSIST_HOURS:
ep += 1
else:
run = 1 if over[i] else 0
cs = sg[i] if over[i] else 0
print(f" {a:<5}{tf:<5}{len(s):>7,}{s.abs().median():>8.2f}{s.abs().quantile(.99):>9.1f}"
f"{s.abs().max():>9.1f}{int(over.sum()):>9}{ep:>12}")
print("\n ⚠️ HL NON e' una referenza USD indipendente e non va aggiunta a `venue_watch` senza")
print(" verificare la composizione del suo oracolo: se e' costruito da prezzi CEX che")
print(" includono coppie USDT, aggiungerla farebbe rientrare l'USDT dalla finestra —")
print(" esattamente cio' che la regola «mai USDT» vieta. Qui e' usata come MISURA, non")
print(" come referenza di produzione.")
print(" 📌 Cio' che invece e' un fatto utile: HL e' l'unico candidato-referenza con piu' di un")
print(" mese di storia recuperabile (2,6 anni a 1d, contro il MESE di Kraken) — ma a 1d, e")
print(" `venue_watch` decide a 1h. Non ripara la taratura degli 8 anni; puo' fare da")
print(" conferma LENTA.")
# ===========================================================================================
def main() -> None:
refresh = "--refresh" in sys.argv
print("=" * 100)
print(" §66 CROSS-VENUE — lo scarto Deribit-vs-Hyperliquid come SEGNALE (STAT-MODE)")
print("=" * 100)
print(" 🚨 STAT-MODE DICHIARATO IN TESTA: una gamba su Hyperliquid non e' eseguibile con questo")
print(" conto (Deribit, ~$635) senza aprire un secondo venue, e la decisione dell'operatore")
print(" del 26/07 tiene tutto su Deribit fino a $20k. Il filone si giudica su «esiste il")
print(" fenomeno?», non su «e' deployabile?».")
cache = load_cache(refresh)
try:
repliche(cache)
certifica_hl(cache)
verifica_lag(cache)
dec = scomposizione(cache)
soglie = muro(cache, dec)
rev = reversione(cache, soglie)
leadlag(cache)
gate(cache)
rinforzo(cache)
finally:
save_cache(cache)
print("\n" + "=" * 100)
print(" VERDETTO")
print("=" * 100)
kall = max(rev, key=lambda k: rev[k]["mossa"] / rev[k]["muro"])
misurate = {k: v for k, v in rev.items() if abs(v["bD"]) > v["mde"]}
print(f" Su 8 celle (2 asset x 4 timeframe) **{len(misurate)} hanno |bD| sopra la propria MDE**:"
f" {', '.join(f'{k[0]} {k[1]}' for k in misurate) or 'nessuna'}.")
if misurate:
kb = max(misurate, key=lambda k: misurate[k]["mossa"] / misurate[k]["muro"])
v = misurate[kb]
arb = soglie[kb[0]]["arb"]
print(f" Miglior rapporto segnale/muro fra le celle MISURATE: **{v['mossa']/v['muro']:.3f}** "
f"({kb[0]} {kb[1]}: {v['mossa']:.2f} bps attesi a 1 sd contro {v['muro']:.2f} bps di costo"
f" su UN venue) -> manca un fattore **{v['muro']/v['mossa']:.1f}x**; per l'arbitraggio a"
f" due venue ({arb:.1f} bps) manca **{arb/v['mossa']:.1f}x**.")
va = rev[kall]
print(f" Il massimo INCONDIZIONATO sarebbe {va['mossa']/va['muro']:.3f} ({kall[0]} {kall[1]}), ma"
f" li' |bD|={abs(va['bD']):.2f} contro MDE {va['mde']:.2f}: **non e' piccolo, e' NON MISURATO**"
f" — citarlo sarebbe scegliere la cella piu' rumorosa perche' e' la piu' alta.")
print(" E ogni numero qui e' OTTIMISTICO tre volte: e' a 1 sd di scarto (non al valore tipico),")
print(" il segno di bD e' preso dagli stessi dati, e IGNORA che per entrare bisogna attraversare")
print(" gli stessi due book che generano lo scarto — cioe' si paga il rumore che si vuole incassare.")
print("\n In EUR/giorno: ZERO. Nessuna configurazione di questo filone e' negoziabile, quindi il")
print(" contributo alla rendita e' esattamente 0,00 EUR/giorno, oggi e a ogni capitale fino a")
print(" quando il costo per giro non scende sotto il fondo di rumore dei due book.")
print("=" * 100)
if __name__ == "__main__":
main()
+896
View File
@@ -0,0 +1,896 @@
#!/usr/bin/env python3
"""
r0823b_curva_datati.py — filone §65 CURVA-DATATI (ondata 2026-08-23)
===========================================================================
LA DOMANDA
===========================================================================
L'ondata 1 (§13 BASIS-CALENDAR) ha ricostruito un dataset che il progetto non
aveva: **30 futures trimestrali SCADUTI** di Deribit (BTC 240.150 barre orarie /
ETH 233.334, dal 2018-09), **col deleveraging 2022 dentro** — la coda che a CC01
mancava per costruzione.
Finora e' stato usato per UNA cosa sola: il **livello** del carry (§13 lo ha
scartato come edge; §39 lo ha scartato come veicolo di TP01). **La FORMA della
curva — pendenza e curvatura — non e' mai stata guardata.**
===========================================================================
ATTESE DICHIARATE **PRIMA** DI MISURARE (regola 1 del metodo)
===========================================================================
A1 Il LIVELLO della curva e' funding travestito. §39 ha gia' misurato che il
basis dei datati E' il funding del perp (+7,33% implicito contro +6,48%
realizzato). Mi aspetto |corr(livello, funding)| ALTA (>0,5).
-> se confermata, il ramo "livello" e' chiuso in partenza.
A2 La FORMA (pendenza, curvatura) e' un grado di liberta' NUOVO: mi aspetto
corr col funding BASSA. E' l'unica ragione per cui questo filone esiste.
A3 Nessun ramo produce un candidato. Il filone funding e' chiuso su QUATTRO
lati (carry cross-sectional, price-clock, time-series, affollamento) e §13
ha gia' esaurito il primo ordine. Mi aspetto SCARTATO su tutti i rami.
A4 Il 2022 e' l'unico regime di stress del campione: mi aspetto che qualunque
segnale di curva abbia li' il suo unico anno buono (o il suo unico anno
cattivo) -> **scomposizione per anno OBBLIGATORIA prima di credere a
qualsiasi aggregato** (lezione SOL del 22/08: 24/24 ancore positive che
erano un anno solo).
A5 Il roll trimestrale costa. §39: SKH01 sui datati fa 48-57 roll-equivalenti
l'anno contro 6,6-8,4 di TP01 -> lo stesso mezzo spread gli costa 7 volte
tanto. Mi aspetto che il netto sia molto peggiore del lordo su qualunque
cosa ribilanci spesso.
A6 MDE: 7,4 anni -> MDE sullo Sharpe ~= 1,96/sqrt(7,4) = **0,72**. Un ramo
DELTA-NEUTRO (§13: SE del rendimento annuo 1,79pp, vol 4,9%) non e'
rispondibile; un ramo DIREZIONALE (vol 50-80%) lo e' in linea di principio.
Esito di ciascuna: riportato in fondo, confermata/refutata.
===========================================================================
COME E' COSTRUITA LA CURVA — e perche' COSI'
===========================================================================
Deribit lista SOLO trimestrali (piu' settimanali/mensili che qui non usiamo):
a ogni ora sono vivi 2-5 contratti (**4 nel 73% delle ore**). Il problema e' che
la scadenza del PRIMO oscilla fra 0 e 91 giorni (dente di sega deterministico).
Interpolare a maturita' costante richiede estrapolazione nel 41-70% delle ore
(misurato). Quindi la curva e' costruita in **spazio FORWARD**, che non ha il
problema:
b1 = ln(F1/S) * 365,25/dte1 carry spot->primo (denominatore
che si annulla a scadenza: rumoroso)
fwd12 = ln(F2/F1) * 365,25/(dte2-dte1) forward carry, finestra ~91g FISSA
fwd23, fwd34 idem
slope = fwd23 - fwd12 curvatura = fwd34 - 2*fwd23 + fwd12
Ogni forward carry e' misurato su una finestra di ~91 giorni SEMPRE, quindi non
c'e' denominatore che si annulla e non c'e' dente di sega nell'annualizzazione.
Cio' che resta di sega (la finestra del fwd12 PARTE a dte1, che oscilla) e' una
proprieta' vera della curva, e viene comunque controllata con un PLACEBO
esplicito (un segnale che e' funzione SOLA di dte1).
📌 Tutto in LOG del rapporto dei prezzi, mai medie aritmetiche di differenze:
la media aritmetica contiene (varA - varB)/2, che su questi dati vale
1,48-2,60%/anno = 2-4x l'effetto cercato (regola gia' pagata, §39).
===========================================================================
IL LAG — verificato, non assunto (§39: corr 0,996 al lag +1)
===========================================================================
`get_tradingview_chart_data` etichetta la barra all'INIZIO: il close della barra
etichettata T e' il prezzo a T+1h. `get_funding_rate_history` etichetta
l'indice all'ISTANTE T. Appaiarli senza lag sfasa di un'ora.
Il lag NON e' assunto: e' scelto misurando la sd del basis perp-vs-indice a
k = -2..+2 e la convergenza a scadenza. Vedi §0.
===========================================================================
DISCIPLINA
===========================================================================
* SOLA LETTURA. Nessuna rete: tutto da `data/raw/fut_deribit/` (persistito
dall'ondata 1). Nessun ordine, nessuna chiave.
* Nessun file di produzione toccato.
* Famiglia DICHIARATA PRIMA e contata AL RIALZO: 86 backtest + 5 regressioni.
Il deflated-Sharpe e' riportato sia per RAMO sia sull'UNIONE, perche' la
stessa griglia da' verdetti diversi a seconda di come la si partiziona (§10,
§24): la partizione e' un secondo posto dove barare e' indolore e invisibile.
USO
nice -n 19 timeout 900 uv run python scripts/research/r0823b_curva_datati.py
"""
from __future__ import annotations
import sys
import warnings
from pathlib import Path
import numpy as np
import pandas as pd
from scipy import stats
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research" / "alt"))
import altlib as al # noqa: E402
from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio # noqa: E402
warnings.filterwarnings("ignore", category=RuntimeWarning)
DATA = PROJECT_ROOT / "data" / "raw" / "fut_deribit"
MONTHS = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN",
"JUL", "AUG", "SEP", "OCT", "NOV", "DEC"]
YR = 365.25
ASSETS = ("BTC", "ETH")
HOLDOUT = al.HOLDOUT
SIG_HOUR = 22 # ora di lettura della curva: nota a 23:00 UTC, un'ora
# PRIMA della chiusura della barra giornaliera -> causale
# con margine. La sensibilita' all'ora e' misurata in §6.
# ---- famiglia DICHIARATA PRIMA (contata al rialzo) ------------------------
VARS_A = ("b1", "fwd12", "slope", "slope_o", "curv") # 5
ZWINS = (90, 180, 365) # 3
SIGNS = (+1, -1) # 2
MAPS = ("sgn", "lin") # 2 -> 60 celle
VARS_D = ("fwd12", "slope_o", "curv") # 3
PCTLS = (0.30, 0.50, 0.70) # 3
DIRS = (+1, -1) # 2 -> 18 celle
VARS_B = ("curv", "slope_o") # 2
ZWINS_B = (90, 365) # 2
SIGNS_B = (+1, -1) # 2 -> 8 celle
N_DECLARED_BT = 60 + 18 + 8 # 86 backtest
N_DECLARED_REG = 5 # 5 regressioni ramo C
FEE_SIDE = al.FEE_SIDE # 5 bps/lato: la convenzione dei backtest del progetto
def hr(t: str = "") -> None:
print("\n" + "=" * 92)
if t:
print(t)
print("=" * 92)
# ===========================================================================
# §0 DATO — caricamento, LAG verificato, certificazione
# ===========================================================================
def expiry_of(name: str) -> pd.Timestamp:
_, tag = name.split("-")
return pd.Timestamp(2000 + int(tag[5:]), MONTHS.index(tag[2:5]) + 1,
int(tag[:2]), 8, tz="UTC")
def load_contracts(asset: str):
closes, exps, vols = {}, {}, {}
for f in sorted(DATA.glob(f"{asset}-*.parquet")):
nm = f.stem
df = pd.read_parquet(f)
if len(df) < 200:
continue
s = df.set_index("ts")["close"].astype(float)
s = s[s > 0]
s.index = s.index.tz_convert("UTC")
v = df.set_index("ts")["volume"].astype(float)
v.index = v.index.tz_convert("UTC")
e = expiry_of(nm)
s = s[s.index < e - pd.Timedelta(hours=2)] # esclude l'ora di settlement
closes[nm], exps[nm], vols[nm] = s, e, v
return closes, exps, vols
def load_fund(asset: str) -> pd.DataFrame:
f = pd.read_parquet(DATA / f"fund_{asset}.parquet").set_index("ts").sort_index()
f.index = f.index.tz_convert("UTC")
return f
def lag_check(asset: str) -> int:
"""Sceglie il lag MISURANDOLO (§39: appaiare due serie dello stesso progetto con
convenzioni di etichettatura diverse e' un bug gia' pagato). Ritorna il lag in ore."""
fund = load_fund(asset)
idx = fund["index"].astype(float)
p = al.get(asset, "1h")
perp = pd.Series(p["close"].values.astype(float),
index=pd.DatetimeIndex(pd.to_datetime(p["datetime"], utc=True)))
out = {}
for k in (-2, -1, 0, 1, 2):
ii = idx.copy()
ii.index = ii.index - pd.Timedelta(hours=k)
j = pd.concat({"p": perp, "i": ii}, axis=1, join="inner").dropna()
b = np.log(j["p"] / j["i"]) * 1e4
out[k] = (len(j), float(b.median()), float(b.std()))
best = min(out, key=lambda k: out[k][2])
print(f" {asset}: basis perp-vs-indice per lag (sd = il criterio)")
for k, (n, m, s) in out.items():
mark = " <- SCELTO (sd minima)" if k == best else ""
print(f" lag {k:+d}h n={n:,} mediana {m:+7.2f} bps sd {s:8.2f} bps{mark}")
return best
def build_curve(asset: str, lag_h: int) -> pd.DataFrame:
closes, exps, vols = load_contracts(asset)
fund = load_fund(asset)
idx = fund["index"].astype(float)
f1h = fund["f1h"].astype(float)
wide = pd.DataFrame(closes).sort_index()
S = idx.reindex(wide.index + pd.Timedelta(hours=lag_h))
S.index = wide.index
keep = S.notna()
wide, S = wide[keep], S[keep]
DTE = pd.DataFrame({nm: (exps[nm] - wide.index).total_seconds() / 86400.0
for nm in wide.columns}, index=wide.index).where(wide.notna())
dt_v, px_v, sv = DTE.values, wide.values, S.values
n = len(wide)
out = np.full((n, 6), np.nan)
for i in range(n):
m = np.isfinite(dt_v[i]) & np.isfinite(px_v[i]) & (dt_v[i] > 0.5)
d, p = dt_v[i][m], px_v[i][m]
o = np.argsort(d)
d, p = d[o], p[o]
k = len(d)
out[i, 5] = k
if k >= 1:
out[i, 0] = d[0]
out[i, 1] = np.log(p[0] / sv[i]) * YR / d[0]
if k >= 2:
out[i, 2] = np.log(p[1] / p[0]) * YR / (d[1] - d[0])
if k >= 3:
out[i, 3] = np.log(p[2] / p[1]) * YR / (d[2] - d[1])
if k >= 4:
out[i, 4] = np.log(p[3] / p[2]) * YR / (d[3] - d[2])
C = pd.DataFrame(out, index=wide.index,
columns=["dte1", "b1", "fwd12", "fwd23", "fwd34", "nlive"])
C["slope"] = C["fwd23"] - C["fwd12"]
C["curv"] = C["fwd34"] - 2 * C["fwd23"] + C["fwd12"]
C["fund24"] = f1h.reindex(C.index).rolling(24).mean() * 24 * YR
C["S"] = S
return C
def causal_orth(y: pd.Series, x: pd.Series, min_obs: int = 180) -> pd.Series:
"""Residuo di y su x con beta stimato SOLO sul passato (media espandente, shift 1).
Se il beta usasse tutto il campione sarebbe look-ahead che `causality_ok` non vede."""
j = pd.concat({"y": y, "x": x}, axis=1)
v = j.notna().all(axis=1)
yy = j["y"].where(v)
xx = j["x"].where(v)
n = v.astype(float).cumsum().shift(1)
sx = xx.fillna(0).cumsum().shift(1)
sy = yy.fillna(0).cumsum().shift(1)
sxx = (xx.fillna(0) ** 2).cumsum().shift(1)
sxy = (xx.fillna(0) * yy.fillna(0)).cumsum().shift(1)
cov = sxy / n - (sx / n) * (sy / n)
var = sxx / n - (sx / n) ** 2
beta = (cov / var).where(n >= min_obs)
alpha = (sy / n) - beta * (sx / n)
return (yy - (alpha + beta * xx)).where(n >= min_obs)
def daily_curve(asset: str, C: pd.DataFrame, hour: int = SIG_HOUR) -> pd.DataFrame:
d = C[C.index.hour == hour].copy()
d.index = d.index.normalize()
d = d[~d.index.duplicated(keep="last")]
d["slope_o"] = causal_orth(d["slope"], d["fwd12"])
return d
# ===========================================================================
# SEGNALI E BACKTEST
# ===========================================================================
def zscore(s: pd.Series, w: int) -> pd.Series:
m = s.rolling(w, min_periods=max(30, w // 2)).mean()
sd = s.rolling(w, min_periods=max(30, w // 2)).std()
return ((s - m) / sd).replace([np.inf, -np.inf], np.nan)
def direction_from(z: pd.Series, sign: int, mp: str) -> pd.Series:
if mp == "sgn":
d = np.sign(z)
else:
d = np.clip(z / 2.0, -1.0, 1.0)
return (sign * d).fillna(0.0)
def _align_to_bars(df: pd.DataFrame, s: pd.Series, fill: float = 0.0) -> np.ndarray:
idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True))
return s.reindex(idx).fillna(fill).values.astype(float)
def _ms(idx: pd.DatetimeIndex) -> np.ndarray:
"""Epoca in MILLISECONDI, esplicita.
⚠️ `idx.view("int64")` NON e' sicuro: i parquet dei datati hanno risoluzione
`datetime64[ms]` e `al.get` `[ns]` -> la stessa riga di codice ritorna ms in un
posto e ns nell'altro, senza eccezione e senza NaN. E' la famiglia di bug
codificata il 01/07 (`.view("int64")` su indici tz-aware non-ns) e ripagata in
§35. Qui e' stata ripagata una TERZA volta, da me, e l'ha resa visibile un
`n=0` — non un numero sbagliato."""
return (pd.DatetimeIndex(idx).tz_convert("UTC").as_unit("ns")
.astype("int64") // 10 ** 6)
def expanding_pctl(s: pd.Series, min_obs: int = 180) -> pd.Series:
"""Percentile ESPANDENTE causale: quota delle osservazioni STRETTAMENTE
precedenti che stanno sotto quella corrente. O(n log n) con bisect."""
import bisect
vals, seen, out = s.values.astype(float), [], np.full(len(s), np.nan)
for i, v in enumerate(vals):
if np.isfinite(v):
if len(seen) >= min_obs:
out[i] = bisect.bisect_right(seen, v) / len(seen)
bisect.insort(seen, v)
return pd.Series(out, index=s.index)
def cell_daily_A(dcur: dict, var: str, w: int, sign: int, mp: str,
fee_side: float = FEE_SIDE) -> pd.Series:
"""Serie giornaliera netta 50/50 BTC+ETH del ramo A (stessa convenzione di
`altlib.candidate_daily`: eval_weights fa lo shift, non si puo' fare leak)."""
ser = {}
for a in ASSETS:
df = al.get(a, "1d")
d = dcur[a]
dirn = direction_from(zscore(d[var], w), sign, mp)
tgt = al.vol_target(_align_to_bars(df, dirn), df, target_vol=0.20,
vol_win_days=30, leverage_cap=2.0)
ev = al.eval_weights(df, tgt, fee_side=fee_side)
ser[a] = pd.Series(ev["net"], index=ev["idx"])
J = pd.concat(ser, axis=1, join="inner").fillna(0.0)
return al._to_daily(0.5 * J[ASSETS[0]] + 0.5 * J[ASSETS[1]])
def tp01_targets() -> dict:
tp = TrendPortfolio(**CANONICAL)
return {a: tp.target_series(al.get(a, "1d")) for a in ASSETS}
def cell_daily_D(dcur: dict, tgts: dict, var: str, pctl: float, dirn: int,
g_off: float = 0.0, fee_side: float = FEE_SIDE,
t0: pd.Timestamp | None = None) -> pd.Series:
"""TP01 moltiplicato per un gate di curva. Il percentile e' ESPANDENTE e causale."""
ser = {}
for a in ASSETS:
df = al.get(a, "1d")
d = dcur[a]
rk = expanding_pctl(d[var])
on = (rk >= pctl) if dirn > 0 else (rk <= pctl)
g = pd.Series(np.where(on.fillna(False), 1.0, g_off), index=d.index)
g[rk.isna()] = 1.0 # prima del warm-up: gate INATTIVO
# ⚠️ fill=1.0, NON 0.0: fuori dalla finestra della curva il gate non esiste,
# e riempire a zero spegnerebbe TP01 su meta' della sua storia. E' il difetto
# che il controllo d'identita' ha catturato (max|diff| 2,4e-02, non 0).
tgt = tgts[a] * _align_to_bars(df, g, fill=1.0)
ev = al.eval_weights(df, tgt, fee_side=fee_side)
ser[a] = pd.Series(ev["net"], index=ev["idx"])
J = pd.concat(ser, axis=1, join="inner").fillna(0.0)
out = al._to_daily(0.5 * J[ASSETS[0]] + 0.5 * J[ASSETS[1]])
return out[out.index >= t0] if t0 is not None else out
def beta_baseline(fee_side: float = FEE_SIDE) -> pd.Series:
"""LONG SEMPRE, vol-targeted 20% con lo stesso motore: e' il benchmark che il
ramo A deve battere. Senza di lui una cella 'quasi sempre lunga' su un campione
2019-2026 con due tori sembra un segnale e non lo e'."""
ser = {}
for a in ASSETS:
df = al.get(a, "1d")
tgt = al.vol_target(np.ones(len(df)), df, target_vol=0.20,
vol_win_days=30, leverage_cap=2.0)
ev = al.eval_weights(df, tgt, fee_side=fee_side)
ser[a] = pd.Series(ev["net"], index=ev["idx"])
J = pd.concat(ser, axis=1, join="inner").fillna(0.0)
return al._to_daily(0.5 * J[ASSETS[0]] + 0.5 * J[ASSETS[1]])
def mean_pos(dcur: dict, var: str, w: int, sign: int, mp: str) -> float:
"""Esposizione media della cella: se e' ~1 la cella e' un LONG travestito."""
v = []
for a in ASSETS:
df = al.get(a, "1d")
dirn = direction_from(zscore(dcur[a][var], w), sign, mp)
v.append(float(np.mean(_align_to_bars(df, dirn))))
return float(np.mean(v))
def sh(s) -> float:
return al._sh(s)
def dd(s) -> float:
return al._dd_ret(s)
def split(s: pd.Series):
return s[s.index < HOLDOUT], s[s.index >= HOLDOUT]
def per_year(s: pd.Series) -> dict:
return {int(y): round(sh(g), 2) for y, g in s.groupby(s.index.year) if len(g) > 60}
# ===========================================================================
def main() -> None:
print(__doc__.split("USO")[0])
# ---------------------------------------------------------------- §0
hr("§0 IL DATO — lag MISURATO (non assunto), curva, certificazione")
lags = {}
for a in ASSETS:
lags[a] = lag_check(a)
lag = lags["BTC"]
print(f"\n -> lag adottato: +{lag}h su ENTRAMBI gli asset "
f"(BTC {lags['BTC']:+d}, ETH {lags['ETH']:+d})")
curves, dcur = {}, {}
for a in ASSETS:
C = build_curve(a, lag)
curves[a] = C
dcur[a] = daily_curve(a, C)
nl = C["nlive"].value_counts().sort_index()
print(f"\n {a}: {len(C):,} ore {C.index.min().date()} .. {C.index.max().date()}"
f" ({(C.index.max()-C.index.min()).days/365.25:.1f} anni)")
print(f" contratti vivi/ora: " +
" ".join(f"{int(k)}:{v/len(C)*100:.0f}%" for k, v in nl.items()))
print(f" giorni con curva (ora {SIG_HOUR:02d}): {len(dcur[a]):,}")
print("\n CONVERGENZA A SCADENZA |ln(F/indice)| ultime 4h — col lag e senza")
for a in ASSETS:
closes, exps, _ = load_contracts(a)
idx = load_fund(a)["index"].astype(float)
for k in (0, lag):
v = []
for nm, s in closes.items():
tail = s[s.index >= exps[nm] - pd.Timedelta(hours=6)]
if not len(tail):
continue
ii = idx.reindex(tail.index + pd.Timedelta(hours=k))
x = np.abs(np.log(tail.values / ii.values)) * 1e4
x = x[np.isfinite(x)]
if len(x):
v.append(float(np.median(x)))
print(f" {a} lag +{k}h: mediana {np.median(v):5.1f} bps (n={len(v)})")
print(" ⚠️ §13 pubblico' 33-47 bps: era calcolato SENZA il lag (merge_asof 'nearest').")
print(" Correzione a un numero pubblicato: il dato e' migliore di come e' stato descritto.")
# ---------------------------------------------------------------- §1
hr("§1 LA FORMA DELLA CURVA — livello, pendenza, curvatura (mai guardate prima)")
print(" Tutte le grandezze sono carry ANNUALIZZATI in log; fwdXY = forward carry")
print(" su finestra ~91g FISSA (niente denominatore che si annulla).\n")
for a in ASSETS:
C = curves[a]
print(f" --- {a} ---")
print(" var n mediana p05 p95 sd")
for c in ("b1", "fwd12", "fwd23", "fwd34", "slope", "curv", "fund24"):
s = C[c].dropna()
print(f" {c:9s} {len(s):7,} {s.median()*100:8.2f}% {s.quantile(.05)*100:9.2f}% "
f"{s.quantile(.95)*100:9.2f}% {s.std()*100:8.2f}%")
# ---------------------------------------------------------------- §2
hr("§2 PRIOR A1/A2 — la curva e' funding travestito? (si misura PRIMA di backtestare)")
print(" Attesa dichiarata: LIVELLO si (|corr|>0,5), FORMA no.\n")
corr_tab = {}
for a in ASSETS:
d = dcur[a]
print(f" --- {a} (giornaliero, n={len(d):,}) ---")
for c in ("b1", "fwd12", "fwd23", "fwd34", "slope", "slope_o", "curv"):
j = d[[c, "fund24"]].dropna()
r = float(j[c].corr(j["fund24"]))
corr_tab[(a, c)] = r
tag = " <- LIVELLO: funding travestito" if abs(r) > 0.5 else ""
print(f" corr({c:8s}, funding24h) = {r:+.3f} n={len(j):,}{tag}")
j = d[["slope", "fwd12"]].dropna()
print(f" [contorno] corr(slope, livello) = {j['slope'].corr(j['fwd12']):+.3f}"
f" -> la pendenza GREZZA e' in gran parte il livello col segno rovesciato")
print("\n CONTROLLO POSITIVO del rilevatore di correlazione (un rilevatore che non trova")
print(" nulla e' indistinguibile da uno rotto): rumore bianco vs funding, e funding vs se stesso")
rng = np.random.default_rng(20260823)
d = dcur["BTC"]
noise = pd.Series(rng.normal(size=len(d)), index=d.index)
j = pd.concat({"n": noise, "f": d["fund24"]}, axis=1).dropna()
print(f" corr(rumore, funding) = {j['n'].corr(j['f']):+.3f} (atteso ~0)")
print(f" corr(funding, funding) = {1.0:+.3f} (atteso +1)")
print("\n 📌 ESITO: A1 CONFERMATA (b1 +0,6 su entrambi), A2 CONFERMATA — e in modo piu'")
print(" forte dell'atteso: la pendenza ORTOGONALIZZATA al livello e la curvatura")
print(" sono praticamente scorrelate dal funding. **La forma e' un grado di")
print(" liberta' nuovo: il ramo non e' chiuso in partenza.**")
# ---------------------------------------------------------------- §3
hr("§3 PRE-TEST DI REGRESSIONE — e il suo CONTROLLO POSITIVO, che FALLISCE")
print(" Regressione dei rendimenti spot futuri sulla variabile di curva (z-unit).")
print(" t corretto grossolanamente per sovrapposizione: t_naive/sqrt(h).\n")
reg_rows = []
for a in ASSETS:
d = dcur[a]
lp = np.log(d["S"])
print(f" --- {a} ---")
print(" var h=5g h=21g h=63g")
for v in list(VARS_A) + ["mom90(CTRL+)"]:
if v.startswith("mom90"):
x0 = lp - lp.shift(90)
else:
x0 = d[v]
row = f" {v:11s}"
for h in (5, 21, 63):
fw = lp.shift(-h) - lp
jj = pd.concat([x0, fw], axis=1).dropna()
jj.columns = ["x", "y"]
if len(jj) < 100 or jj["x"].std() == 0:
row += " n/a "
continue
xz = (jj["x"] - jj["x"].mean()) / jj["x"].std()
sl, ic, rr, pv, se = stats.linregress(xz, jj["y"])
t = sl / se / np.sqrt(h)
row += f" {sl*100:+6.2f}%(t{t:+5.2f})"
if not v.startswith("mom90"):
reg_rows.append(dict(asset=a, var=v, h=h, beta=sl, t=t))
print(row)
print("\n 🚨 IL CONTROLLO POSITIVO NON PASSA: il momentum a 90 giorni — il segnale che")
print(" REGGE il 75% del libro live (TP01, Sharpe FULL 1,31 = t~3,6 su 7,4 anni) —")
print(" in questa lente da |t| 0,4-1,4. **La regressione predittiva univariata su")
print(" rendimenti crypto NON HA POTENZA a questo campione**: un t basso qui non e'")
print(" evidenza di assenza. -> il ramo si giudica sui BACKTEST (§4-§5), non su questi t.")
print(" (Le regressioni restano a registro: 5 variabili x 3 orizzonti x 2 asset.)")
# ---------------------------------------------------------------- §4
hr("§4 RAMO A — la curva come segnale DIREZIONALE (60 celle dichiarate)")
B = al.tp01_baseline_daily()
print(f" ✅ REPLICA prima di produrre numeri nuovi: TP01 baseline 50/50")
print(f" Sharpe FULL {sh(B):.3f} (pubblicato ~1,30) HOLD {sh(B[B.index>=HOLDOUT]):.3f}")
print(f" ⚠️ l'hold-out pubblicato e' ~0,31-0,44 a seconda del VINTAGE di data/raw/")
print(f" (gitignored, riscritto ogni notte): la terza cifra non e' confrontabile.\n")
BB = beta_baseline()
bi, bh = split(BB)
print(f" BENCHMARK BETA (long sempre, vol-target 20%, stesso motore):")
print(f" Sharpe IS {sh(bi):+.3f} FULL {sh(BB):+.3f} HOLD {sh(bh):+.3f}"
f" maxDD {dd(BB)*100:.1f}%")
print(" ⚠️ e' IL benchmark del ramo A: su 2019-2026 (due tori) una cella che sta")
print(" long quasi sempre eredita questo Sharpe senza contenere informazione.\n")
rowsA = []
for var in VARS_A:
for w in ZWINS:
for sg in SIGNS:
for mp in MAPS:
s = cell_daily_A(dcur, var, w, sg, mp)
ins, hol = split(s)
rowsA.append(dict(var=var, w=w, sign=sg, map=mp, daily=s,
is_sh=sh(ins) if len(ins) > 60 else np.nan,
full=sh(s), hold=sh(hol) if len(hol) > 60 else np.nan,
dd=dd(s)))
assert len(rowsA) == 60, len(rowsA)
print(f" celle valutate: {len(rowsA)} (dichiarate 60)")
validA = [r for r in rowsA if np.isfinite(r["is_sh"])]
chosenA = max(validA, key=lambda r: r["is_sh"])
print(f" distribuzione dei 60 Sharpe FULL: mediana {np.median([r['full'] for r in rowsA]):+.3f}"
f" min {min(r['full'] for r in rowsA):+.3f} max {max(r['full'] for r in rowsA):+.3f}")
print(f" frazione di celle con FULL>0: {np.mean([r['full']>0 for r in rowsA]):.0%}"
f" con HOLD>0: {np.mean([r['hold']>0 for r in rowsA]):.0%}")
print("\n CELLA SCELTA IN-SAMPLE-ONLY (nessuna occhiata all'hold-out):")
print(f" {chosenA['var']} w={chosenA['w']} sign={chosenA['sign']:+d} map={chosenA['map']}"
f" Sh IS {chosenA['is_sh']:+.3f} FULL {chosenA['full']:+.3f}"
f" HOLD {chosenA['hold']:+.3f} maxDD {chosenA['dd']*100:.1f}%")
best_hold = max(validA, key=lambda r: r["hold"])
print(f" (per contrasto, la cella a MASSIMO hold-out — che NON e' selezionabile —:")
print(f" {best_hold['var']} w={best_hold['w']} sign={best_hold['sign']:+d} "
f"map={best_hold['map']} IS {best_hold['is_sh']:+.3f} HOLD {best_hold['hold']:+.3f})")
print("\n top-8 per Sharpe IN-SAMPLE (pos = esposizione media: ~1 => LONG travestito)")
print(" var w sgn map ShIS ShFULL ShHOLD maxDD pos")
for r in sorted(validA, key=lambda r: r["is_sh"], reverse=True)[:8]:
mp_ = mean_pos(dcur, r["var"], r["w"], r["sign"], r["map"])
print(f" {r['var']:9s} {r['w']:4d} {r['sign']:+2d} {r['map']:4s}"
f" {r['is_sh']:+7.3f} {r['full']:+8.3f} {r['hold']:+7.3f} {r['dd']*100:7.1f}%"
f" {mp_:+6.2f}")
print(f" 🚨 NESSUNA delle 60 celle batte il benchmark beta ({sh(BB):+.3f} FULL): la")
print(f" migliore in-sample fa {chosenA['full']:+.3f}, la migliore in assoluto "
f"{max(r['full'] for r in rowsA):+.3f}.")
allA = [r["full"] for r in rowsA]
dsrA, sr0A = al.deflated_sharpe(chosenA["full"], allA, chosenA["daily"])
print(f"\n deflated-Sharpe (ramo A, N=60): {dsrA:.3f} "
f"(massimo atteso dal PURO RUMORE {sr0A:+.3f}) "
f"{'PASS' if dsrA >= 0.95 else 'FAIL'}")
mgA = al.marginal_vs_tp01(chosenA["daily"])
print(f" marginal_vs_tp01: {mgA['marginal_verdict']} corr {mgA['corr_full']:+.3f} "
f"beta {mgA['beta_to_tp01']:+.3f} alpha {mgA['alpha_ann']*100:+.2f}%/anno")
print(f" uplift w25 FULL {mgA['blends']['w25']['uplift_full']:+.3f} "
f"HOLD {mgA['blends']['w25']['uplift_hold']:+.3f} "
f"robust_oos {mgA['robust_oos']} has_insample_edge {mgA['has_insample_edge']} "
f"is_hedge {mgA['is_hedge']}")
earnsA = bool(mgA["marginal_verdict"] == "ADDS" and dsrA >= 0.95)
print(f" -> earns_slot_honest (ramo A) = {earnsA}")
print("\n PER ANNO della cella scelta (A4: un aggregato si scompone prima di crederci):")
py = per_year(chosenA["daily"])
print(" " + " ".join(f"{y}:{v:+.2f}" for y, v in py.items()))
pos = sum(1 for v in py.values() if v > 0)
print(f" anni positivi {pos}/{len(py)} 2022 (l'unico stress del campione): "
f"{py.get(2022, float('nan')):+.2f}")
print("\n PLACEBO DEL DENTE DI SEGA (il rischio strutturale di questo dataset):")
print(" un segnale che e' funzione SOLA di dte1 (giorni alla prima scadenza) non")
print(" contiene informazione di curva. Se battesse la cella scelta, il ramo A")
print(" starebbe misurando il calendario dei roll, non la forma della curva.")
dsaw = {a: dcur[a].assign(**{"__saw": dcur[a]["dte1"]}) for a in ASSETS}
for w in ZWINS:
s = cell_daily_A(dsaw, "__saw", w, +1, "lin")
s2 = cell_daily_A(dsaw, "__saw", w, -1, "lin")
print(f" dte1 w={w:3d}: Sh FULL {sh(s):+.3f} / {sh(s2):+.3f} (segno +/-)"
f" pos {mean_pos(dsaw, '__saw', w, +1, 'lin'):+.2f}")
print(" 🚨 IL PLACEBO BATTE OGNI CELLA DI CURVA — e la diagnosi e' il contrario di")
print(" quella che sembra. I due segni sono SPECCHI quasi esatti e l'esposizione")
print(" media e' ~+1: il placebo non sta misurando il dente di sega, sta stando")
print(f" LONG, e riproduce il benchmark beta ({sh(BB):+.3f}). Cio' che dimostra e'")
print(" piu' severo: **nessun segnale di curva batte 'stai lungo e basta'** su")
print(" questo campione. Un placebo va letto sulla sua ESPOSIZIONE prima che sul")
print(" suo Sharpe, o si conclude l'opposto.")
# ---------------------------------------------------------------- §5
hr("§5 RAMO D — la curva come GATE DI REGIME su TP01 (18 celle dichiarate)")
tgts = tp01_targets()
T0 = max(dcur[a].index.min() for a in ASSETS)
base_gate = cell_daily_D(dcur, tgts, "fwd12", 0.0, +1) # gate sempre acceso
print(f" ✅ CONTROLLO DI IDENTITA': gate sempre acceso vs TP01 baseline sulla finestra comune")
j = pd.concat({"g": base_gate, "b": B}, axis=1, join="inner").dropna()
print(f" max|diff| = {float((j['g']-j['b']).abs().max()):.2e} "
f"Sh {sh(j['g']):.4f} vs {sh(j['b']):.4f}")
ref = base_gate[base_gate.index >= T0]
rowsD = []
for var in VARS_D:
for p in PCTLS:
for dr in DIRS:
s = cell_daily_D(dcur, tgts, var, p, dr, t0=T0)
ins, hol = split(s)
rowsD.append(dict(var=var, p=p, dir=dr, daily=s,
is_sh=sh(ins), full=sh(s), hold=sh(hol), dd=dd(s)))
assert len(rowsD) == 18, len(rowsD)
chosenD = max(rowsD, key=lambda r: r["is_sh"])
ri, rh = split(ref)
print(f"\n riferimento (TP01 sulla finestra della curva): IS {sh(ri):+.3f} "
f"FULL {sh(ref):+.3f} HOLD {sh(rh):+.3f} maxDD {dd(ref)*100:.1f}%")
print(" celle (18): var pctl dir ShIS ShFULL ShHOLD maxDD dShFULL")
for r in sorted(rowsD, key=lambda r: r["is_sh"], reverse=True):
mark = " <- scelta IS" if r is chosenD else ""
print(f" {r['var']:9s} {r['p']:.2f} {r['dir']:+d} {r['is_sh']:+7.3f}"
f" {r['full']:+8.3f} {r['hold']:+7.3f} {r['dd']*100:7.1f}% "
f"{r['full']-sh(ref):+8.3f}{mark}")
allD = [r["full"] for r in rowsD]
dsrD, sr0D = al.deflated_sharpe(chosenD["full"], allD, chosenD["daily"])
print(f"\n deflated-Sharpe (ramo D, N=18): {dsrD:.3f} (rumore max atteso {sr0D:+.3f}) "
f"{'PASS' if dsrD >= 0.95 else 'FAIL'}")
print("\n 🚨 NULL DEL DE-LEVERING (7 occorrenze pagate dal progetto): un gate SPEGNE")
print(" esposizione, quindi va confrontato con un semplice k*TP01 a PARI maxDD.")
tgt_dd = chosenD["dd"]
lo, hi = 0.05, 1.0
for _ in range(40):
k = (lo + hi) / 2
if dd(k * ref) > tgt_dd:
hi = k
else:
lo = k
k = (lo + hi) / 2
print(f" gate scelto: maxDD {tgt_dd*100:.2f}% ShFULL {chosenD['full']:+.3f} "
f"ShHOLD {chosenD['hold']:+.3f}")
print(f" k*TP01 a pari maxDD (k={k:.3f}): maxDD {dd(k*ref)*100:.2f}% "
f"ShFULL {sh(k*ref):+.3f} ShHOLD {sh((k*ref)[(k*ref).index>=HOLDOUT]):+.3f}")
beats = chosenD["full"] > sh(k * ref) + 1e-9
print(f" -> il gate batte il de-levering puro? {beats}")
print(" (nota: lo Sharpe e' invariante alla scala, quindi il de-levering NON puo'")
print(" cambiare lo Sharpe: il confronto giusto e' 'a pari DD lo Sharpe migliora?'")
print(" -> qui la domanda si riduce a dShFULL > 0, e il null e' DEGENERE (§24 (d)).")
print(" Il fatto che conta e' che il gate cambia lo Sharpe, non che riduca il DD.")
print("\n PER ANNO della cella D scelta, contro TP01:")
pyD, pyR = per_year(chosenD["daily"]), per_year(ref)
for y in sorted(set(pyD) | set(pyR)):
print(f" {y}: gate {pyD.get(y, float('nan')):+6.2f} TP01 {pyR.get(y, float('nan')):+6.2f}"
f" d {pyD.get(y,np.nan)-pyR.get(y,np.nan):+6.2f}")
# ---------------------------------------------------------------- §6
hr("§6 SENSIBILITA' ALL'ORA DI LETTURA (robustezza alla SCELTA, non un IC — §57)")
print(" 24 ore di lettura della curva, stessa griglia giornaliera 00:00. Tutte causali.")
bandA, bandD = [], []
for h in range(24):
dc = {a: daily_curve(a, curves[a], hour=h) for a in ASSETS}
bandA.append(sh(cell_daily_A(dc, chosenA["var"], chosenA["w"],
chosenA["sign"], chosenA["map"])))
bandD.append(sh(cell_daily_D(dc, tgts, chosenD["var"], chosenD["p"],
chosenD["dir"], t0=T0)))
for nm, band, canon in (("A", bandA, chosenA["full"]), ("D", bandD, chosenD["full"])):
b = np.array(band)
print(f" ramo {nm}: canonica(h={SIG_HOUR}) {canon:+.3f} mediana {np.median(b):+.3f}"
f" p10 {np.percentile(b,10):+.3f} p90 {np.percentile(b,90):+.3f}"
f" pctl della canonica {float((b<=canon).mean())*100:.0f}°")
bD = np.array(bandD) - sh(ref)
print(f" ramo D, dShFULL vs TP01 su 24 ore: mediana {np.median(bD):+.3f}, "
f"positivo in {int((bD>0).sum())}/24 (⚠️ ~2 osservazioni indipendenti, §57)")
# ---------------------------------------------------------------- §7
hr("§7 RAMO B — la BUTTERFLY delta-neutra (8 celle) e il muro dell'MDE")
print(" F1 - 2*F2 + F3 a nozionale netto ZERO: e' esattamente la CURVATURA, quotata.")
print(" §13 aveva testato il calendar spread (F1 vs F2, 108 celle); la butterfly e'")
print(" il secondo ordine e non era mai stata guardata.\n")
fly = {}
for a in ASSETS:
C = curves[a]
closes, exps, _ = load_contracts(a)
wide = pd.DataFrame(closes).sort_index().reindex(C.index)
DTEa = pd.DataFrame({nm: (exps[nm] - wide.index).total_seconds() / 86400.0
for nm in wide.columns}, index=wide.index).where(wide.notna())
lr = np.log(wide).diff()
dv, lv = DTEa.values, lr.values
pv = wide.values
n = len(wide)
out = np.full(n, np.nan)
turn = np.zeros(n)
prev = None
for i in range(n):
m = np.isfinite(dv[i]) & np.isfinite(pv[i]) & (dv[i] > 0.5)
ordr = np.argsort(np.where(m, dv[i], np.inf))
sel = ordr[:3]
if not np.all(m[sel]) or len(sel) < 3:
prev = None
continue
wts = np.array([1.0, -2.0, 1.0])
if prev is not None and np.array_equal(prev, sel):
r = np.nansum(wts * lv[i][sel])
out[i] = r
else:
turn[i] = 4.0 # 4 gambe di nozionale unitario cambiate (1+2+1)
prev = sel
fly[a] = pd.Series(out, index=wide.index), pd.Series(turn, index=wide.index)
print(" rendimento LORDO della butterfly (nozionale lordo 4x, hold continuo):")
for a in ASSETS:
r, t = fly[a]
rd = r.resample("1D").sum().dropna()
yrs = (rd.index[-1] - rd.index[0]).days / YR
mu = float(rd.mean() * YR)
sd = float(rd.std() * np.sqrt(YR))
se = sd / np.sqrt(yrs)
n_roll = int((t > 0).sum())
print(f" {a}: drift {mu*100:+6.2f}%/anno vol {sd*100:5.2f}% Sharpe {mu/sd:+.2f}"
f" SE(drift) {se*100:.2f}pp roll {n_roll} in {yrs:.1f} anni")
print(f" 🚨 MDE: serve |drift| > {1.96*se*100:.2f}%/anno per distinguerlo da zero.")
rowsB = []
for var in VARS_B:
for w in ZWINS_B:
for sg in SIGNS_B:
ser, ser0 = {}, {}
for a in ASSETS:
r, t = fly[a]
d = dcur[a]
z = zscore(d[var], w)
pos = (sg * np.clip(z / 2, -1, 1)).fillna(0.0)
rd = r.resample("1D").sum()
td = t.resample("1D").sum()
idx = rd.index.intersection(pos.index)
p = pos.reindex(idx).shift(1).fillna(0.0)
gross = p * rd.reindex(idx)
cost = (p.abs() * td.reindex(idx).fillna(0.0) +
4.0 * p.diff().abs().fillna(0.0))
ser[a] = gross - FEE_SIDE * cost
ser0[a] = gross
J = pd.concat(ser, axis=1, join="inner").fillna(0.0)
J0 = pd.concat(ser0, axis=1, join="inner").fillna(0.0)
s = 0.5 * J[ASSETS[0]] + 0.5 * J[ASSETS[1]]
s0 = 0.5 * J0[ASSETS[0]] + 0.5 * J0[ASSETS[1]]
ins, hol = split(s)
rowsB.append(dict(var=var, w=w, sign=sg, daily=s, is_sh=sh(ins),
full=sh(s), hold=sh(hol), gross=sh(s0)))
assert len(rowsB) == 8, len(rowsB)
chosenB = max(rowsB, key=lambda r: r["is_sh"])
print("\n celle (8): var w sgn Sh LORDO ShIS ShFULL ShHOLD")
for r in sorted(rowsB, key=lambda r: r["is_sh"], reverse=True):
mark = " <- scelta IS" if r is chosenB else ""
print(f" {r['var']:9s} {r['w']:4d} {r['sign']:+d} {r['gross']:+9.3f}"
f" {r['is_sh']:+7.3f} {r['full']:+8.3f} {r['hold']:+7.3f}{mark}")
gmax = max(r["gross"] for r in rowsB)
print(f"\n 📌 il MIGLIOR lordo della famiglia e' {gmax:+.3f}: sotto l'MDE (Sharpe 0,72)")
print(" ANCHE a fee zero. Quindi NON e' morte-per-fee — la fee la seppellisce, ma")
print(" sotto non c'e' un edge lordo da salvare. (Distinzione che il progetto fa")
print(" sistematicamente: §42 TP01-LS, §7 CRT.)")
# ---------------------------------------------------------------- §8
hr("§8 RAMO C — la curva predice la VOLATILITA' futura? (5 regressioni)")
print(" RV30 futura su curva, CONTROLLANDO RV30 corrente e DVOL (che e' la previsione")
print(" di mercato). Senza i controlli si misura solo 'la vol e' persistente'.\n")
for a in ASSETS:
d = dcur[a]
lp = np.log(d["S"])
r = lp.diff()
rv = r.rolling(30).std() * np.sqrt(YR)
fut = rv.shift(-30)
ts = pd.DataFrame({"timestamp": _ms(d.index)})
dv = pd.Series(al.dvol(ts, a), index=d.index) / 100.0
assert dv.notna().sum() > 500 and 0.2 < float(dv.median()) < 1.5, (
"controllo positivo DVOL fallito: merge vuoto o fuori scala "
f"(n={int(dv.notna().sum())}, mediana={float(dv.median()):.3f})")
base = pd.concat({"y": fut, "rv": rv, "dv": dv}, axis=1).dropna()
print(f" --- {a} (n={len(base):,}, dal {base.index.min().date()}: DVOL parte 2021-03) ---")
for v in VARS_A:
jj = pd.concat([base, d[v].rename("x")], axis=1).dropna()
if len(jj) < 200:
print(f" {v:9s} n troppo piccolo")
continue
X = np.column_stack([np.ones(len(jj)), jj["rv"], jj["dv"],
(jj["x"] - jj["x"].mean()) / jj["x"].std()])
bb, *_ = np.linalg.lstsq(X, jj["y"].values, rcond=None)
res = jj["y"].values - X @ bb
s2 = res @ res / (len(jj) - 4)
se = np.sqrt(np.diag(s2 * np.linalg.pinv(X.T @ X)))[3]
print(f" {v:9s} beta {bb[3]*100:+6.2f}pp di vol per 1sd "
f"t {bb[3]/se:+6.2f} (naive) t/sqrt(30) {bb[3]/se/np.sqrt(30):+5.2f}")
print("\n ⚠️ stesso limite di §3: nessun controllo positivo passa a questa risoluzione,")
print(" quindi queste righe sono descrittive, non decisive.")
# ---------------------------------------------------------------- §9
hr("§9 DEFLATED-SHARPE SULL'UNIONE, e la sua dipendenza dalla PARTIZIONE (§10/§24)")
allBT = allA + allD + [r["full"] for r in rowsB]
print(f" backtest dichiarati: {N_DECLARED_BT}, valutati: {len(allBT)} "
f"(+{N_DECLARED_REG} regressioni ramo C)")
for nm, pool, ch in (("ramo A (60)", allA, chosenA), ("ramo D (18)", allD, chosenD),
("ramo B (8)", [r["full"] for r in rowsB], chosenB),
("UNIONE (86)", allBT, chosenA)):
dsr, sr0 = al.deflated_sharpe(ch["full"], pool, ch["daily"])
print(f" {nm:12s} N={len(pool):3d} sd(Sharpe di griglia) {np.std(pool, ddof=1):.3f}"
f" max atteso dal rumore {sr0:+.3f} DSR {dsr:.3f} "
f"{'PASS' if dsr >= 0.95 else 'FAIL'}")
print(" 📌 La stessa griglia da' verdetti diversi a seconda di come la si partiziona:")
print(" e' il secondo posto dove barare e' indolore e invisibile (dopo il conteggio).")
# ---------------------------------------------------------------- §10
hr("§10 ESEGUIBILITA' — il roll trimestrale, e il muro che nessun backtest supera")
lb = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/"
"b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/basis_cache/live_books.parquet")
if lb.exists():
d = pd.read_parquet(lb)
print(" spread bid-ask VIVI (fotografia del 22/08, non storia — va detto):")
for _, r in d.iterrows():
print(f" {r['instrument']:16s} spread {r['spread_bps']:6.2f} bps"
f" mezzo-spread {r['spread_bps']/2:5.2f} bps")
q = d[d.instrument.str.contains("-2")]
hs = float(q["spread_bps"].median()) / 2
else:
hs = 3.0
print(" (cache dei book non presente: mezzo-spread assunto 3,0 bps)")
print(f"\n costo per gamba = fee taker 3,50 bps + mezzo-spread mediano {hs:.2f} bps"
f" = {3.5+hs:.2f} bps")
print(f" un ROLL trimestrale = 2 gambe (chiudi F1, apri F2) = {2*(3.5+hs):.1f} bps,")
print(f" 4 roll/anno = {4*2*(3.5+hs):.0f} bps/anno = {4*2*(3.5+hs)/100:.2f}%/anno di drag")
print(" -> contro un premio a termine misurato da §13 a +0,85%/anno con IC95 che")
print(" CONTIENE LO ZERO. Il costo del roll da solo e' dello stesso ordine.")
print(f" ⚠️ A5: una butterfly ribilanciata ogni giorno paga 4 gambe per ribilancio.")
print(" Il ramo B sopra e' gia' al NETTO di questo, ed e' il motivo per cui il")
print(" suo lordo e il suo netto non si somigliano.")
# ---------------------------------------------------------------- §11
hr("§11 VERDETTO")
print(f" A1 (livello = funding travestito) -> CONFERMATA "
f"(corr b1/funding {corr_tab[('BTC','b1')]:+.2f} BTC, {corr_tab[('ETH','b1')]:+.2f} ETH)")
print(f" A2 (la FORMA e' un grado di liberta' nuovo) -> CONFERMATA e piu' forte "
f"dell'atteso (slope_o {corr_tab[('BTC','slope_o')]:+.2f}/"
f"{corr_tab[('ETH','slope_o')]:+.2f}, curv {corr_tab[('BTC','curv')]:+.2f}/"
f"{corr_tab[('ETH','curv')]:+.2f})")
print(f" A3 (nessun candidato) -> "
f"{'CONFERMATA' if not earnsA else 'REFUTATA'}")
print(f" A4 (2022 domina) -> vedi la scomposizione per anno")
print(f" A5 (il roll costa) -> {4*2*(3.5+hs)/100:.2f}%/anno, "
f"stesso ordine del premio")
print(f" A6 (MDE) -> ramo B non rispondibile, "
f"rami A/D si'")
print()
print(f" RAMO A direzionale : earns_slot_honest = {earnsA} "
f"(DSR {dsrA:.3f}, marginale {mgA['marginal_verdict']})")
print(f" RAMO D gate TP01 : dShFULL mediano su 24 ore {np.median(bD):+.3f}, "
f"DSR {dsrD:.3f}")
print(f" RAMO B butterfly : sotto l'MDE per costruzione")
print(f" RAMO C volatilita' : nessun controllo positivo passa -> non decisivo")
print()
isp = al.implausible_sharpe(chosenA["daily"])
print(f" implausible_sharpe sulla cella A scelta: implausible={isp['implausible']}"
f" motivi={isp['reasons'] or 'nessuno'} barre attive {isp['active_frac']:.0%}"
f" barre attive in perdita {isp['loss_frac']:.0%}")
print()
verdetto = ("LA FORMA DELLA CURVA E' UN GRADO DI LIBERTA' NUOVO (scorrelato dal funding) "
"E NON PREDICE NULLA: 0 candidati su 4 rami")
print(f" `{verdetto}`")
print()
print(" In EUR/giorno: questo filone NON avvicina i 50 EUR/giorno. Il miglior effetto")
print(" misurato (ramo D, dShFULL mediano di banda) e' dentro il rumore d'ancora del")
print(" libro stesso; a $635 di conto vale **0,00 EUR/giorno**. Versare 500 EUR/mese")
print(" resta la leva dominante misurata dal progetto.")
if __name__ == "__main__":
main()
+890
View File
@@ -0,0 +1,890 @@
"""r0823b_fisco_payout — §63 FISCO-PAYOUT: l'aliquota sui payout funded e' un FORFAIT mai
giustificato, e il canale funded non e' mai stato misurato contro di essa (2026-08-23).
IL BUCO CHE CHIUDE
------------------
Il progetto ha modellato con cura il fisco sull'accumulo del LIBRO PROPRIO (33% sulle plusvalenze
cripto, minusvalenze in carry 4 anni, 2 per mille patrimoniale) e il 07/08 ha misurato che
ignorarlo costava -30% a 10 anni. Sul canale FUNDED non ha modellato niente: `r0725_prop_ladder.py`
importa `TAX_RATE = 0.33` da `r0725_capcurve` e lo applica ai payout con una riga
net = gross * (1.0 - TAX_RATE) # r0725_prop_ladder:269
senza una giustificazione. Ed e' l'aliquota **sbagliata di CATEGORIA**, non solo di livello: su un
conto funded il trader **non possiede le posizioni** e **non possiede il conto**; cio' che riceve e'
un **compenso contrattuale** (profit share su un contratto di servizi), non una plusvalenza da
cripto-attivita' ex art. 67 c.1 lett. c-sexies TUIR. Il 33% e' l'aliquota di una cosa che qui non
succede.
Importa perche' §37 conclude che **il biglietto batte il versare** con EUR 500/mese (mediana
$39.820 contro $22.444). Se l'aliquota vera fosse molto piu' alta di 33%, quella conclusione
potrebbe ribaltarsi; se fosse piu' bassa (regime forfettario), il canale varrebbe di piu'.
QUESTO FILONE NON CONCLUDE QUALE SIA L'ALIQUOTA. Il progetto ha gia' una regola: una domanda
fiscale si **pone al commercialista**, non si risolve backtestando (07/08). Qui si fa l'unica cosa
che uno script puo' fare: **misurare quanto vale la risposta**, cioe' la sensibilita' delle
conclusioni del canale funded all'aliquota, e **formulare la domanda in modo che sia rispondibile**.
NON e' un parere fiscale.
LE ATTESE, REGISTRATE PRIMA DI MISURARE
---------------------------------------
A1. **L'EV del biglietto e' ESATTAMENTE lineare in (1-tau)** a livello di conto singolo: la soglia
di prelievo ($100 di quota trader) e' sul LORDO, quindi l'imposta non cambia quali eventi
accadono, solo quanto valgono. Previsione forte e falsificabile: misurato == analitico
**al bit**, non "entro il rumore MC".
A2. **A livello di SCALA DI CONTI la linearita' si ROMPE**, e nel verso sfavorevole: i payout netti
tornano in cassa e **ricomprano biglietti**, quindi l'imposta si compone. Mi aspetto che la
ricchezza terminale scenda **piu' che proporzionalmente** a (1-tau), e che il divario cresca
col deposito mensile (piu' cassa = piu' biglietti = piu' composizione).
A3. **L'EV del singolo biglietto resta positivo fino a un'aliquota assurda** (>70%), perche' il
prezzo ($579) e' piccolo rispetto a E[payout]. Quindi la gamba (c1) del `GATE PROP-01`
**non e' il criterio che morde**: mordera' il confronto con la strada-libro.
A4. **La soglia di ribaltamento cade DENTRO la banda plausibile** delle qualificazioni fiscali
(che va dal ~21% del forfettario al ~55-60% di IRPEF+addizionali+Gestione Separata) →
la domanda al commercialista diventa **BLOCCANTE**. Questa e' l'attesa che conta: se invece
la soglia cadesse fuori dalla banda, la domanda resterebbe interessante e non decisiva.
A5. **L'aliquota EFFETTIVA di uno scaglione progressivo, al livello di reddito che questo canale
produce, e' molto piu' bassa dell'aliquota MARGINALE del suo scaglione.** EUR 50/giorno sono
~EUR 18.250/anno: un'aliquota "43%" citata a mente sarebbe la marginale del terzo scaglione,
che a quel reddito non si tocca nemmeno. Mi aspetto che il modello progressivo, a reddito
preesistente ZERO, dia un'effettiva **sotto** il 33% che il progetto usa oggi — cioe' che il
forfait attuale sia **conservativo** proprio nello scenario che sembra il piu' minaccioso.
Ma con un reddito preesistente da lavoro dipendente il payout si somma **al margine** e
l'effettiva sale: **la stessa qualificazione da' due risposte diverse a seconda di chi la
riceve.** Questo, se confermato, e' il risultato trasferibile del filone.
CONVENZIONI DICHIARATE (ereditate e congelate da §37, nessuna scelta nuova)
---------------------------------------------------------------------------
* Cella d'ancora **MEDIANA** dei 23 offset x 3 fasi (non la canonica, che sta al 91 pctl), scelta
con lo stesso criterio di `r0822d_gate_c` §2 — **ricalcolata qui**, non copiata, e confrontata
con quella pubblicata.
* Conto funded: **W_A = TP01 38 / SKH01 38 / XS01 25**, XS01 = U13 Binance, finestra W-LONG 2021+,
de-luck sul drift x0,89, **funding DENTRO**, lente **ACCOPPIATA**.
* Conto proprio: libro **LIVE 75/25** a $654 con min-order $5, funding dentro, fisco sul modello
di `r0807_piano_netto` (aliquota sulla variazione ANNUA di valore, carry delle minusvalenze).
* Morte-firm 10%/anno, cap $200k/trader, split 80%, soglia di prelievo $100: assunzioni del
progetto, dichiarate.
* Bootstrap **CONDIVISO** fra tutte le aliquote e fra le due strade -> ogni confronto e' APPAIATO
e la differenza fra due aliquote e' molto meglio risolta del livello.
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823b_fisco_payout.py`
Rete: NESSUNA. Scritture: NESSUNA.
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import numpy as np
import pandas as pd
T0 = time.time()
ROOT = Path(__file__).resolve().parents[2]
for _p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"):
sys.path.insert(0, str(_p))
if "--no-net" not in sys.argv:
sys.argv.append("--no-net")
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata
import r0725_prop_ladder as pl # noqa: E402 regole firm
import r0822_prop_alloc as PA # noqa: E402 boot_idx, DELUCK
import r0822b_prop_recal as PR # noqa: E402 pannelli, obj_rg
import r0822b_xs01_oos as X # noqa: E402 U13
import r0822c_skh_anchor_prop as AP # noqa: E402 offset di SKH01
import r0822d_funding as FD # noqa: E402 funding
import r0822d_gate_c as GC # noqa: E402 LA MACCHINERIA DI §37
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
EURUSD = pl.EURUSD
SPLIT = pl.SPLIT
DELUCK = PA.DELUCK
W_A = GC.W_A
W_LIVE = GC.W_LIVE
OFFSETS = AP.OFFSETS
PHASES_SUB = AP.PHASES_SUB
N_LAD = GC.N_PATHS_LAD
N_TIX = GC.N_PATHS_TIX
SEED_LAD = GC.SEED_LAD
MIN_PAYOUT = GC.MIN_PAYOUT_TRADER
HORIZON_DAYS = GC.HORIZON_DAYS
PRICE_100K = 579.0
# Cella d'ancora MEDIANA dei 23 offset x 3 fasi: **stampata da `r0822d_gate_c` sullo stesso
# vintage di feed** (corsa del 23/08: "offset 30 min, fase 7, leva 0.75x"). E' un INPUT
# ereditato e congelato, come tutte le altre convenzioni di §37, non una scelta di questo
# filone. Ri-derivarla costa ~70 valutazioni della scala di conti: `--band` la ricalcola e la
# confronta con questa costante (fatto una volta; su una macchina scarica sono ~4 minuti).
CELL_M = (30, 7, 0.75)
BAND = "--band" in sys.argv
# numeri PUBBLICATI da riprodurre PRIMA di produrne di nuovi (§37 / §32)
PUB = dict(ev_sunk=1615.0, ev_at_pass=1919.0, ev_at_payout=1835.0,
p50g_fund=0.026, pzero_fund=0.403,
med_big_500=39820.0, med_lib_500=22444.0,
med_big_0=132.0, med_lib_0=787.0,
mean_big_500=46298.0, mean_lib_500=22694.0,
mean_big_0=9592.0, mean_lib_0=803.0)
# =============================================================================== 0. IL FISCO
# ATTENZIONE — QUESTA SEZIONE NON E' UN PARERE FISCALE.
# Ogni riga qui sotto e' una IPOTESI DI QUALIFICAZIONE con la sua fonte e il suo grado di
# affidabilita'. Il progetto ha gia' pagato una citazione normativa sbagliata cablata in 5 file
# (la "L.199/2025" che non era l'origine del 33%, corretta il 07/08): percio' cio' che NON e'
# stato verificato su fonte primaria e' etichettato come tale, e non c'e' un default nascosto.
#
# IL FATTO STRUTTURALE, che vale piu' di qualunque aliquota:
# su un conto funded il trader NON possiede il conto, NON possiede le posizioni, e non c'e'
# nessun "realizzo di cripto-attivita'". Riceve una quota di un risultato SIMULATO in base a un
# contratto. Percio' l'art. 67 c.1 lett. c-sexies (33%, cripto) e la lett. c-quater (26%,
# derivati) — le due caselle su cui il progetto sta discutendo per il LIBRO PROPRIO — sono
# entrambe **fuori categoria** per il canale funded. La discussione vera e' fra reddito
# diverso da "obbligazione di fare" (art. 67 c.1 lett. l), lavoro autonomo (abituale o
# occasionale) e reddito d'impresa. Cambia la CATEGORIA, non solo il numero.
# IRPEF 2026 — VERIFICATA in sessione (WebSearch 23/08): tre scaglioni 23% / 33% / 43%, con il
# SECONDO sceso da 35% a 33% per effetto della **L. 199/2025** (Legge di Bilancio 2026).
# ⚠ NOTA CHE EVITA UN EQUIVOCO CHE QUESTO PROGETTO HA GIA' PAGATO UNA VOLTA: la "L. 199/2025"
# e' la legge che il 07/08 fu tolta dai commenti come origine SBAGLIATA del 33% sulle cripto
# (quello viene dalla L. 207/2024 art. 1 c. 23-29). Qui la stessa legge ricompare LEGITTIMAMENTE,
# ma per un'altra cosa: il secondo scaglione IRPEF. **E il 33% IRPEF e il 33% cripto sono lo
# stesso numero per pura coincidenza** — confonderli renderebbe invisibile tutta la differenza
# di CATEGORIA che questo filone misura. Fonte: rassegna professionale (IPSOA/FiscoeTasse),
# non letta su Normattiva -> PROFESSIONALE, non primaria.
IRPEF_SCAGLIONI = ((28_000.0, 0.23), (50_000.0, 0.33), (float("inf"), 0.43))
IRPEF_2025 = ((28_000.0, 0.23), (50_000.0, 0.35), (float("inf"), 0.43)) # controllo di robustezza
ADDIZIONALI = 0.0203 # regionale minima 1,23% + comunale max 0,80% (fonte SECONDARIA)
INPS_GS_ALIQ = 0.2607 # Gestione Separata, non assicurati altrove
INPS_GS_QUOTA_PRESTATORE = 1.0 / 3.0
INPS_OCC_FRANCHIGIA = 5_000.0
FORFETTARIO_COEFF = 0.78 # coefficiente di redditivita' tipico dei servizi professionali
FORFETTARIO_ALIQ = 0.05 # 5% start-up (primi 5 anni), poi 15%
def irpef(imponibile: float, scaglioni=None) -> float:
"""IRPEF lorda a 3 scaglioni. Nessuna detrazione (conservativo: le detrazioni la abbassano)."""
t, prev = 0.0, 0.0
for soglia, aliq in (scaglioni or IRPEF_SCAGLIONI):
if imponibile <= prev:
break
t += (min(imponibile, soglia) - prev) * aliq
prev = soglia
return t
def eff_progressiva(lordo_annuo: float, altro_reddito: float, inps: str,
scaglioni=None, spese: float = 0.0) -> float:
"""Aliquota EFFETTIVA su `lordo_annuo` di payout che si SOMMA a `altro_reddito`.
`inps`: 'no' | 'occasionale' (franchigia 5k, quota prestatore 1/3) | 'piena' (tutta a carico).
Committente ESTERO -> nessuna ritenuta d'acconto, tutto in dichiarazione: qui e' irrilevante
per l'ALIQUOTA (cambia la cassa, non il dovuto) e va detto.
Il contributo previdenziale e' deducibile dall'imponibile IRPEF: modellato."""
if lordo_annuo <= 0:
return 0.0
# art. 71 c.2 TUIR (verificato sul testo, 23/08): i redditi di cui all'art. 67 c.1 lett. l)
# "sono costituiti dalla DIFFERENZA fra l'ammontare percepito nel periodo di imposta e le
# spese specificamente inerenti alla loro produzione" -> le quote di challenge sono un costo
# inerente e si DEDUCONO. Sotto un'imposta sostitutiva su reddito finanziario, no.
netto = max(0.0, lordo_annuo - spese)
if inps == "no":
contrib = 0.0
elif inps == "occasionale":
base = max(0.0, netto - INPS_OCC_FRANCHIGIA)
contrib = base * INPS_GS_ALIQ * INPS_GS_QUOTA_PRESTATORE
else:
contrib = netto * INPS_GS_ALIQ
imp_con = max(0.0, altro_reddito + netto - contrib)
imp_senza = max(0.0, altro_reddito)
d_irpef = irpef(imp_con, scaglioni) - irpef(imp_senza, scaglioni)
d_add = (imp_con - imp_senza) * ADDIZIONALI
return (contrib + d_irpef + d_add) / lordo_annuo
def eff_forfettario(lordo_annuo: float, aliq: float = FORFETTARIO_ALIQ,
coeff: float = FORFETTARIO_COEFF) -> float:
"""Regime forfettario: imponibile = lordo x coefficiente; imposta sostitutiva `aliq`;
Gestione Separata piena sull'imponibile (deducibile)."""
if lordo_annuo <= 0:
return 0.0
imponibile = lordo_annuo * coeff
contrib = imponibile * INPS_GS_ALIQ
imposta = max(0.0, imponibile - contrib) * aliq
return (contrib + imposta) / lordo_annuo
# ==================================================== 1. LA SCALA DI CONTI CON L'IMPOSTA APERTA
def ladder_tax(R: np.ndarray, G: np.ndarray, n_paths: int, lev: float, seed: int,
netfn, refund: bool = True, deposit_eur: float = 0.0,
start_eur: float = pl.START_EUR, min_payout: float = MIN_PAYOUT,
boot: np.ndarray | None = None, block: int = 20,
deluck: float = DELUCK, pay_per_year: int = 12) -> dict:
"""COPIA di `r0822d_gate_c.ladder` con **un solo grado di liberta' in piu'**: al posto della
riga `net = gross * (1 - TAX_RATE)` c'e' `netfn(gross, lordo_annuo_gia_incassato)`.
Tutto il resto — ordine delle chiamate al generatore compreso — e' identico, e §2.1 lo
verifica **bit-exact** contro l'originale con `netfn` piatta al 33%.
`pay_per_year` = payout per anno fiscale (12 prelievi da 30 giorni = 360 gg: l'accumulatore
annuale si azzera li'). Serve solo alle aliquote PROGRESSIVE, che dipendono dal lordo annuo.
L'imposta e' applicata **al momento del prelievo** con l'aliquota marginale sul cumulato
annuo: la somma sull'anno e' l'imposta progressiva esatta, ma la CASSA la vede prima che
a saldo. Differenza dichiarata, e conservativa (toglie liquidita' prima)."""
rng = np.random.default_rng(seed)
n_days = HORIZON_DAYS
L = len(R)
Rm = R.astype(float).reshape(-1, 1).copy()
Gm = G.astype(float).reshape(-1, 1).copy()
Rm = Rm - (1.0 - deluck) * Rm.mean(axis=0, keepdims=True)
if boot is None:
n_blocks = int(np.ceil(n_days / block))
starts = rng.integers(0, L - block, size=(n_paths, n_blocks))
idx = (starts[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days]
else:
idx = boot
n_slot = pl.MAX_CONCURRENT
sel = np.zeros(n_slot, dtype=int)
Rs = Rm[idx][:, :, sel] * lev
G_ = Gm[idx][:, :, sel] * lev
FIRMS = pl.FIRMS
cash = np.full(n_paths, start_eur * EURUSD)
phase = np.full((n_paths, n_slot), -1, np.int8)
eq = np.ones((n_paths, n_slot))
firm = np.full((n_paths, n_slot), -1, np.int8)
notional = np.zeros((n_paths, n_slot))
price_paid = np.zeros((n_paths, n_slot))
refunded = np.zeros((n_paths, n_slot), bool)
firm_alive = np.ones((n_paths, len(FIRMS)), bool)
cum_payout = np.zeros(n_paths)
cum_gross = np.zeros(n_paths)
ann_gross = np.zeros(n_paths)
spent_base = np.zeros(n_paths)
spent = np.zeros(n_paths)
got_back = np.zeros(n_paths)
deposited = np.zeros(n_paths)
n_payouts = np.zeros(n_paths, int)
n_pay_events = 0
p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0)
for t in range(n_days):
died = rng.random((n_paths, len(FIRMS))) < p_death_day
newly_dead = died & firm_alive
firm_alive &= ~died
if newly_dead.any():
for fi in range(len(FIRMS)):
kill = newly_dead[:, fi]
if kill.any():
hit = (firm == fi) & kill[:, None]
phase[hit] = -1
notional[hit] = 0.0
for k in range(n_slot):
free = phase[:, k] == -1
if not free.any():
continue
for fi, F in enumerate(FIRMS):
used = np.where(firm == fi, notional, 0.0).sum(axis=1)
for (size, price) in sorted(F["tickets"], key=lambda x: -x[0]):
buy = (free & firm_alive[:, fi] & (cash >= price)
& (used + size <= F["cap"]))
if not buy.any():
continue
cash[buy] -= price
spent[buy] += price
phase[buy, k] = 0
eq[buy, k] = 1.0
firm[buy, k] = fi
notional[buy, k] = size
price_paid[buy, k] = price
refunded[buy, k] = False
free = free & ~buy
if not free.any():
break
if not free.any():
break
r = Rs[:, t, :]
g = G_[:, t, :]
act = phase >= 0
eq_prev = eq
eq = np.where(act, eq * (1.0 + r), eq)
eq_low = np.where(act, eq_prev * (1.0 + r + g), eq)
r_low = r + g
for fi, F in enumerate(FIRMS):
s_ = act & (firm == fi)
if not s_.any():
continue
ev_t, ev_dd, ev_dl = F["ev"]
fu_ml, fu_dl = F["fu"]
in_ev = s_ & (phase == 0)
in_fu = s_ & (phase == 1)
bust = ((in_ev & ((eq_low < 1.0 - ev_dd) | (r_low < -ev_dl)))
| (in_fu & ((eq_low < 1.0 - fu_ml) | (r_low < -fu_dl))))
phase[bust] = -1
notional[bust] = 0.0
passed = in_ev & (eq >= 1.0 + ev_t) & ~bust
phase[passed] = 1
eq[passed] = 1.0
if (t + 1) % 30 == 0:
fu = phase == 1
pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout)
gain = np.where(pay, (eq - 1.0) * notional, 0.0)
gross = gain.sum(axis=1) * SPLIT
net = netfn(gross, ann_gross, spent - spent_base)
cum_payout += net
cum_gross += gross
ann_gross += gross
cash += net
n_payouts += (pay.sum(axis=1) > 0).astype(int)
eq = np.where(pay, 1.0, eq)
if refund:
back = pay & ~refunded
amt = np.where(back, price_paid, 0.0).sum(axis=1)
cash += amt
got_back += amt
refunded |= back
if deposit_eur:
cash += deposit_eur * EURUSD
deposited += deposit_eur * EURUSD
n_pay_events += 1
if n_pay_events % pay_per_year == 0:
ann_gross[:] = 0.0
spent_base = spent.copy()
eur_day = cum_payout / EURUSD / (n_days / 365.0) / 365.0
return dict(eur_day=eur_day, med=float(np.median(eur_day)),
p_ge10=float((eur_day >= 10.0).mean()),
p_target=float((eur_day >= pl.TARGET_EUR_DAY).mean()),
p_zero=float((cum_payout <= 0).mean()),
cash=cash, med_cash=float(np.median(cash)), mean_cash=float(cash.mean()),
p10_cash=float(np.percentile(cash, 10)),
p90_cash=float(np.percentile(cash, 90)),
gross=float(cum_gross.mean()), net=float(cum_payout.mean()),
spent=float(spent.mean()),
excess=cash - (start_eur * EURUSD + deposited),
mean_back=float(got_back.mean()), med_spent=float(np.median(spent)),
contributed=float(start_eur * EURUSD + deposited.mean()))
def net_flat(rate: float):
"""Imposta sostitutiva piatta su reddito FINANZIARIO: le quote di challenge NON sono
deducibili (una challenge persa non e' una minusvalenza da cripto-attivita').
Espressione IDENTICA a quella di `GC.ladder` -> bit-exact."""
def f(gross, ann, spese):
return gross * (1.0 - rate)
return f
def net_prog(altro_reddito: float, inps: str, scaglioni=None, deduci: bool = True):
"""Aliquota progressiva: marginale sul cumulato annuo, cosi' la somma sull'anno e' esatta.
`deduci` = le quote di challigne dell'anno si sottraggono dalla base (art. 71 c.2 TUIR)."""
def f(gross, ann, spese):
out = np.zeros_like(gross)
nz = gross > 0
if nz.any():
sp = np.asarray(spese, float) / EURUSD if deduci else np.zeros(len(gross))
for i in np.flatnonzero(nz):
a0, g = float(ann[i]) / EURUSD, float(gross[i]) / EURUSD
dovuto = (eff_progressiva(a0 + g, altro_reddito, inps, scaglioni, sp[i]) * (a0 + g)
- eff_progressiva(a0, altro_reddito, inps, scaglioni, sp[i]) * a0)
out[i] = float(gross[i]) * (1.0 - dovuto / g)
return out
return f
def net_forf(aliq: float = FORFETTARIO_ALIQ, coeff: float = FORFETTARIO_COEFF):
"""Forfettario: i costi NON si deducono per costruzione — li sostituisce il coefficiente."""
def f(gross, ann, spese):
out = np.zeros_like(gross)
nz = gross > 0
if nz.any():
for i in np.flatnonzero(nz):
a0, g = float(ann[i]) / EURUSD, float(gross[i]) / EURUSD
dovuto = (eff_forfettario(a0 + g, aliq, coeff) * (a0 + g)
- eff_forfettario(a0, aliq, coeff) * a0)
out[i] = float(gross[i]) * (1.0 - dovuto / g)
return out
return f
# ================================================================= 2. IL PANNELLO E LA CELLA
def hr(t: str = "") -> None:
print("\n" + "-" * 104)
if t:
print(" " + t)
print("-" * 104)
def build(verbose: bool = True) -> dict:
"""Ricostruisce ESATTAMENTE il contesto di `r0822d_gate_c` §0.2 + §2, cella d'ancora
MEDIANA compresa. Non e' copiata dal diario: e' ricalcolata e poi confrontata."""
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13 = Ob.reindex(p13.index)[X.U13]
h13 = Hb.reindex(p13.index)[X.U13]
l13 = Lb.reindex(p13.index)[X.U13]
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in PHASES_SUB}
xs0 = XSP[PHASES_SUB[0]]
cry_idx = pc.crypto_daily_tuples(1.0, 0.0).index
base = cry_idx.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
S_LONG = max(first13, pd.Timestamp("2021-01-01", tz="UTC"))
I = base[base >= S_LONG]
ev_b = PA.boot_idx(len(I), GC.EV_H, PA.N_PATHS, seed=GC.SEED_EV)
fu_b = PA.boot_idx(len(I), GC.FU_H, PA.N_PATHS, seed=GC.SEED_FU)
pc.DELUCK = DELUCK
rate = GC.funding_rate()
if rate is None:
raise SystemExit("cache funding assente: senza funding i numeri non sono quelli di §37")
xs_ann = GC.xs_funding_measure(verbose=False)["ann"]
if verbose:
print(f" W-LONG: {len(I)} giorni {I[0].date()} -> {I[-1].date()} | de-luck x{DELUCK}")
print(f" funding di XS01 misurato: {xs_ann*100:+.2f}%/anno (costo) — §37")
print(f" banda d'ancora: {len(OFFSETS)} offset x {len(PHASES_SUB)} fasi {PHASES_SUB} "
f"= {len(OFFSETS)*len(PHASES_SUB)} celle, ladder {N_LAD} percorsi ciascuna")
S = None
if BAND:
rows = []
t = time.time()
for off in OFFSETS:
fA = GC.fvec_for(W_A, I, rate, off, xs_ann)
for ph in PHASES_SUB:
R0, G0 = AP.rg_off(W_A, I, XSP[ph], off)
Rx = R0 - fA
j, lv, _ = GC.best_lev(Rx, G0, ev_b, fu_b)
lad = GC.ladder(Rx, G0, N_LAD, lv, SEED_LAD, refund=False)
rows.append(dict(off=off, ph=ph, J=j, lev=lv, p50=lad["p_target"],
pz=lad["p_zero"]))
S = pd.DataFrame(rows)
k = int((S["p50"] - S["p50"].median()).abs().idxmin())
OFF_M, PH_M, LEV_M = int(S.loc[k, "off"]), int(S.loc[k, "ph"]), float(S.loc[k, "lev"])
if verbose:
print(f" banda RI-DERIVATA ({time.time()-t:.0f}s) P(>=50/g) mediana "
f"{S['p50'].median():.1%} [{np.percentile(S['p50'],10):.1%}, "
f"{np.percentile(S['p50'],90):.1%}], P(zero) {S['pz'].median():.1%} "
f"(PUB §37 {PUB['p50g_fund']:.1%} / {PUB['pzero_fund']:.1%})")
print(f" cella ri-derivata {(OFF_M, PH_M, LEV_M)} contro la costante {CELL_M} -> "
f"{'COINCIDE' if (OFF_M, PH_M, LEV_M) == CELL_M else 'DIVERGE'}")
else:
OFF_M, PH_M, LEV_M = CELL_M
if verbose:
print(f" cella d'ancora MEDIANA ereditata da `r0822d_gate_c`: {CELL_M} "
f"(offset min, fase, leva). `--band` la ri-deriva dalle 69 celle.")
fM = GC.fvec_for(W_A, I, rate, OFF_M, xs_ann)
R_M0, G_M = AP.rg_off(W_A, I, XSP[PH_M], OFF_M)
R_M = R_M0 - fM
rb_full = FD.book_75_25(rate, alloc=654.0)
if rb_full.index.tz is None:
rb_full.index = rb_full.index.tz_localize("UTC")
rb = np.nan_to_num(rb_full.reindex(I).values.astype(float))
rngb = np.random.default_rng(51224)
nb = int(np.ceil(HORIZON_DAYS / 20))
st = rngb.integers(0, len(I) - 20, size=(N_LAD, nb))
BOOT = (st[:, :, None] + np.arange(20)[None, None, :]).reshape(N_LAD, -1)[:, :HORIZON_DAYS]
return dict(I=I, R_M=R_M, G_M=G_M, LEV_M=LEV_M, OFF_M=OFF_M, PH_M=PH_M,
rb=rb, BOOT=BOOT, band=S)
# ============================================================== 3. GLI SCENARI DI QUALIFICAZIONE
# Ogni riga e' un'IPOTESI, con la sua fonte e il suo grado. NESSUNA e' "la risposta".
# Ricerca documentale del 23/08 (15 fra ricerche e fetch): **nessuna fonte PRIMARIA italiana
# sulle prop firm** — nessuna circolare, risoluzione o interpello dell'Agenzia delle Entrate che
# qualifichi il profit share di un funded account. Due fonti professionali lo dichiarano
# espressamente ("mancano chiarimenti ufficiali"). Le fonti professionali convergono sull'area
# LAVORO AUTONOMO; l'ipotesi "reddito finanziario 26%" e' sostenuta da UNA sola fonte, la meno
# autorevole del corpus, che nello stesso paragrafo afferma una ritenuta alla fonte del 26% da
# parte della firm estera — quasi certamente falsa (un non residente senza stabile organizzazione
# non e' sostituto d'imposta ex d.P.R. 600/1973).
def scenari() -> list[dict]:
"""(etichetta, funzione netta, grado della fonte). Ordinate per aliquota effettiva crescente
a EUR 18.250 lordi/anno (= EUR 50/giorno, il bersaglio del progetto)."""
return [
dict(k="S0", lab="nessuna imposta (CONTROLLO, non uno scenario)",
fn=net_flat(0.0), src="controllo", tag="CTRL"),
dict(k="S1", lab="forfettario 5% start-up, coeff 67% (ATECO 82.99)",
fn=net_forf(0.05, 0.67), src="PROFESSIONALE (coeff.), ATECO non motivato", tag=""),
dict(k="S2", lab="forfettario 5% start-up, coeff 78% (ATECO 66.xx/74.90.99)",
fn=net_forf(0.05, 0.78), src="PROFESSIONALE (coeff.), ATECO non motivato", tag=""),
dict(k="S3", lab="sostitutiva 26% — art. 67 c-quater (redditi diversi finanz.)",
fn=net_flat(0.26), src="SECONDARIA: UNA fonte, non motivata", tag=""),
dict(k="S4", lab="forfettario 15% a regime, coeff 78%",
fn=net_forf(0.15, 0.78), src="PROFESSIONALE", tag=""),
dict(k="S5", lab="lav.aut. OCCASIONALE, INPS 1/3, nessun altro reddito",
fn=net_prog(0.0, "occasionale"), src="PROFESSIONALE: l'ipotesi piu' sostenuta",
tag="OCC"),
dict(k="S6", lab="sostitutiva 33% c-sexies = IL FORFAIT DEL PROGETTO OGGI",
fn=net_flat(0.33), src="aliquota primaria, CATEGORIA senza alcuna fonte",
tag="PROGETTO"),
dict(k="S7", lab="lav.aut. OCCASIONALE, INPS 1/3, altro reddito EUR 30.000",
fn=net_prog(30_000.0, "occasionale"), src="PROFESSIONALE + assunzione sul reddito",
tag="OCC30"),
dict(k="S8", lab="lav.aut. ABITUALE, IRPEF ord. + INPS PIENA, nessun altro reddito",
fn=net_prog(0.0, "piena"), src="PROFESSIONALE", tag=""),
dict(k="S9", lab="lav.aut. ABITUALE, IRPEF ord. + INPS PIENA, altro reddito EUR 30.000",
fn=net_prog(30_000.0, "piena"), src="il caso PEGGIORE documentabile", tag="PEGGIO"),
]
def aliq_eff(fn, lordo_eur: float, spese_eur: float = 0.0) -> float:
"""Aliquota effettiva implicita di una `netfn` su un lordo annuo in EUR, incassato in 12
rate mensili uguali (come fa la scala di conti), con `spese_eur` di quote nell'anno."""
ann = np.zeros(1)
tot_net = 0.0
rata = lordo_eur * EURUSD / 12.0
sp = np.array([spese_eur * EURUSD])
for _ in range(12):
g = np.array([rata])
tot_net += float(fn(g, ann, sp)[0])
ann = ann + g
lordo_usd = lordo_eur * EURUSD
return 1.0 - tot_net / lordo_usd if lordo_usd else 0.0
# ==================================================================================== 4. MAIN
TAU_GRID = (0.00, 0.20, 0.26, 0.33, 0.45, 0.60, 0.80, 1.00)
def cross(taus: list[float], vals: list[float], soglia: float) -> float | None:
"""Aliquota a cui la curva (decrescente) taglia `soglia`, per interpolazione lineare."""
for i in range(1, len(taus)):
a, b = vals[i - 1], vals[i]
if (a - soglia) * (b - soglia) <= 0 and a != b:
return taus[i - 1] + (taus[i] - taus[i - 1]) * (a - soglia) / (a - b)
return None
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
print("=" * 104)
print(" §63 FISCO-PAYOUT — l'aliquota sui payout funded e' un FORFAIT del 33% mai")
print(" giustificato, ed e' l'aliquota di una CATEGORIA che sul canale funded non esiste.")
print(" Non si conclude quale sia: si misura QUANTO VALE LA RISPOSTA. Non e' un parere fiscale.")
print("=" * 104)
print(" ATTESE REGISTRATE PRIMA: A1 l'EV del singolo biglietto e' ESATTAMENTE lineare in")
print(" (1-tau) [falsificabile al bit]; A2 la SCALA di conti NO, perche' i payout ricomprano")
print(" biglietti; A3 l'EV resta positivo oltre il 70% -> la gamba (c1) non e' cio' che morde;")
print(" A4 la soglia di ribaltamento cade DENTRO la banda plausibile -> domanda BLOCCANTE;")
print(" A5 l'effettiva di uno scaglione progressivo a EUR 18k/anno e' SOTTO il 33% del")
print(" forfait attuale, ma sale col reddito preesistente -> la stessa qualificazione da'")
print(" due risposte diverse a seconda di chi la riceve.")
hr("§0. IL CONTESTO DI §37, RICALCOLATO (non copiato) — e la cella d'ancora mediana")
B = build()
R_M, G_M, LEV_M, BOOT, rb = B["R_M"], B["G_M"], B["LEV_M"], B["BOOT"], B["rb"]
print(f" cella in uso: offset {B['OFF_M']} min, fase {B['PH_M']}, leva {LEV_M:.2f}x")
# ---------------------------------------------------------------- §1 dove entra l'imposta
hr("§1. DOVE ENTRA L'IMPOSTA — tre righe in tutto il canale funded, e una TRAPPOLA")
print(" `r0725_prop_ladder:269` net = gross * (1.0 - TAX_RATE)")
print(" `r0725_prop_coupled:414` net = gain.sum(axis=1) * SPLIT * (1.0 - TAX_RATE)")
print(" `r0822_prop_alloc:339` payout += ... * SPLIT * (1.0 - TAX_RATE)")
print(f" `TAX_RATE` = {GC.TAX_RATE:.2f}, importato PER VALORE da `r0725_capcurve`, dove il")
print(" commento cita la L.207/2024 — cioe' la norma sulle CRIPTO-ATTIVITA'. Il canale")
print(" funded ha ereditato l'aliquota del libro proprio senza che nessuno la giustificasse.")
print("\n ⚠ TRAPPOLA MISURATA (e' il motivo per cui questo filone NON fa uno sweep globale):")
_sav = GC.TAX_RATE
b33 = GC.book_road(rb, BOOT, pl.START_EUR, 500.0)
GC.TAX_RATE = 0.99
b99 = GC.book_road(rb, BOOT, pl.START_EUR, 500.0)
l99 = GC.ladder(R_M, G_M, N_LAD, LEV_M, SEED_LAD, refund=True, deposit_eur=500.0,
min_payout=MIN_PAYOUT, boot=BOOT)
GC.TAX_RATE = _sav
print(f" `GC.book_road` ha `tax: float = TAX_RATE` come DEFAULT, valutato all'import:")
print(f" portando `GC.TAX_RATE` da 0,33 a 0,99 la strada LIBRO NON si muove di un dollaro")
print(f" (mediana ${b33['med_cash']:,.0f} -> ${b99['med_cash']:,.0f}) mentre la strada")
print(f" BIGLIETTO crolla (mediana ${l99['med_cash']:,.0f}).")
print(" Chi facesse 'uno sweep sul fisco' patchando la costante globale tasserebbe UNA")
print(" SOLA delle due strade e leggerebbe un ribaltamento inesistente. Qui le due")
print(" aliquote sono DUE knob separati e dichiarati, che e' anche la verita' fiscale:")
print(" il libro proprio e il payout funded stanno in due categorie diverse.")
# ---------------------------------------------------------------- §2 controlli
hr("§2. CONTROLLI — un rilevatore che non trova nulla e' indistinguibile da uno rotto")
ref = GC.ladder(R_M, G_M, N_LAD, LEV_M, SEED_LAD, refund=True, deposit_eur=500.0,
min_payout=MIN_PAYOUT, boot=BOOT)
mine = ladder_tax(R_M, G_M, N_LAD, LEV_M, SEED_LAD, net_flat(GC.TAX_RATE), refund=True,
deposit_eur=500.0, min_payout=MIN_PAYOUT, boot=BOOT)
d = float(np.abs(ref["cash"] - mine["cash"]).max())
print(f" (a) BIT-EXACT: `ladder_tax` con aliquota piatta 33% contro `GC.ladder` (l'originale")
print(f" di §37), percorso per percorso: max|diff| = {d:.3e} -> "
f"{'IDENTICO' if d == 0.0 else 'DIVERGE — STOP'}")
if d != 0.0:
return
print(" (b) CONTROLLO POSITIVO: il knob morde — le righe 0% e 100% della griglia di §4")
print(" (a 100% la cassa non va a zero: restano i versamenti non spesi e i depositi")
print(" rimborsati. Un'imposta totale non confisca il capitale versato, e il modello")
print(" lo rispetta: e' il controllo che il knob colpisce il REDDITO e non il conto.)")
print(f" (c) L'aliquota effettiva delle netfn, controllata su un caso a mano:")
for lab, fn, atteso in (("piatta 26%", net_flat(0.26), 0.26),
("piatta 33%", net_flat(0.33), 0.33)):
got = aliq_eff(fn, 18_250.0)
print(f" {lab:>12}: misurata {got:.4f} attesa {atteso:.4f} "
f"{'OK' if abs(got-atteso) < 1e-12 else 'ERRORE'}")
# ---------------------------------------------------------------- §3 EV del biglietto
hr("§3. L'EV DEL BIGLIETTO CONTRO L'ALIQUOTA — e la REPLICA di §37 prima di ogni numero nuovo")
rows = []
for lev_ev in (0.50, 0.75, 1.00):
for lev_fu in (0.50, 0.75, 1.00):
r = GC.ticket_mc(R_M, G_M, lev_ev=lev_ev, lev_fu=lev_fu, notional=100_000.0)
rows.append(dict(lev_ev=lev_ev, lev_fu=lev_fu, **r))
T = pd.DataFrame(rows)
best = T.loc[T["e_payout"].idxmax()]
LEV_EV, LEV_FU = float(best["lev_ev"]), float(best["lev_fu"])
e33 = GC.ticket_mc(R_M, G_M, lev_ev=LEV_EV, lev_fu=LEV_FU, notional=100_000.0)
ev33 = GC.ev_three_ways(e33, PRICE_100K)
print(f" leve argmax E[payout]: eval {LEV_EV:.2f}x / funded {LEV_FU:.2f}x (come §37)")
print(f" {'convenzione':>26} {'oggi':>10} {'PUBBLICATO §37':>16} {'delta':>10}")
for k, lab, p in (("sunk", "(i) spesa persa", PUB["ev_sunk"]),
("at_pass", "(ii) rimborso al pass", PUB["ev_at_pass"]),
("at_payout", "(iii) rimborso al payout", PUB["ev_at_payout"])):
v = ev33[k]
print(f" {lab:>26} ${v:>+9,.0f} ${p:>+15,.0f} {v-p:>+10,.0f}")
print(" Lo scarto (~+3%) e' lo stesso vintage di feed di §37: +1 giorno di barre. La")
print(" macchineria e' quella. >>> REPLICA RIUSCITA 3/3.")
_sav = GC.TAX_RATE
GC.TAX_RATE = 0.0
e0 = GC.ticket_mc(R_M, G_M, lev_ev=LEV_EV, lev_fu=LEV_FU, notional=100_000.0)
GC.TAX_RATE = _sav
e_gross = e0["e_payout"]
print(f"\n E[payout] LORDO (aliquota 0): ${e_gross:,.0f}. P(pass) {e0['p_pass']:.1%}, "
f"P(payout) {e0['p_payout']:.1%} — IDENTICHE a quelle al 33% "
f"({'si' if (e0['p_pass'], e0['p_payout']) == (e33['p_pass'], e33['p_payout']) else 'NO'}): "
f"l'imposta non cambia QUALI eventi accadono.")
print(f"\n {'aliquota':>9} {'E[payout] netto':>16} {'atteso lineare':>16} {'|scarto|':>10} "
f"{'EV spesa persa':>16} {'EV al payout':>14}")
lin_ok = True
for tau in TAU_GRID:
GC.TAX_RATE = tau
rt = GC.ticket_mc(R_M, G_M, lev_ev=LEV_EV, lev_fu=LEV_FU, notional=100_000.0)
GC.TAX_RATE = _sav
pred = e_gross * (1.0 - tau)
lin_ok &= abs(rt["e_payout"] - pred) < 1e-9
e3 = GC.ev_three_ways(rt, PRICE_100K)
print(f" {tau:>8.0%} ${rt['e_payout']:>15,.1f} ${pred:>15,.1f} "
f"{abs(rt['e_payout']-pred):>10.2e} ${e3['sunk']:>+15,.0f} ${e3['at_payout']:>+13,.0f}")
tau_ev0 = 1.0 - PRICE_100K / e_gross
print(f"\n >>> ATTESA A1 ('lineare AL BIT'): "
f"{'CONFERMATA' if lin_ok else 'SMENTITA'} — scarto max < 1e-9 su tutta la griglia.")
print(" Meccanismo: la soglia di prelievo ($100 di quota trader) e' sul LORDO, quindi")
print(" l'imposta non sposta nessun evento. E' l'unico punto del canale dove il fisco")
print(" e' un semplice fattore moltiplicativo.")
print(f" >>> ATTESA A3: l'EV del biglietto (spesa persa) resta positivo fino a "
f"**tau = {tau_ev0:.1%}** -> {'CONFERMATA' if tau_ev0 > 0.70 else 'SMENTITA'}.")
print(" La gamba (c1) del `GATE PROP-01` NON e' cio' che il fisco puo' far cadere:")
print(" nessuna qualificazione plausibile arriva a quell'aliquota.")
# ---------------------------------------------------------------- §4 la scala di conti
hr("§4. LA SCALA DI CONTI — dove la linearita' si ROMPE (i payout ricomprano biglietti)")
print(" Confronto APPAIATO (stesso bootstrap `BOOT` a tutte le aliquote e in entrambe le")
print(" strade). 'atteso lineare' = il risultato che si avrebbe se l'imposta fosse solo un")
print(" fattore sul flusso, cioe' la mediana a tau=0 riscalata di (1-tau).")
lad = {}
for dep in (0.0, 500.0):
base = None
print(f"\n --- versamento EUR {dep:,.0f}/mese")
print(f" {'aliquota':>9} {'EUR/g med':>10} {'P(>=50/g)':>10} {'P(zero)':>9} "
f"{'cassa mediana':>14} {'media':>12} {'atteso lin. (med)':>18} {'scarto':>9}")
for tau in TAU_GRID:
r = ladder_tax(R_M, G_M, N_LAD, LEV_M, SEED_LAD, net_flat(tau), refund=True,
deposit_eur=dep, min_payout=MIN_PAYOUT, boot=BOOT)
lad[(dep, tau)] = r
if base is None:
base = r
netto_dep = dep * EURUSD * (HORIZON_DAYS // 30) + pl.START_EUR * EURUSD
lin = netto_dep + (base["med_cash"] - netto_dep) * (1.0 - tau)
print(f" {tau:>8.0%} {r['med']:>10.2f} {r['p_target']:>10.1%} {r['p_zero']:>9.1%} "
f"${r['med_cash']:>13,.0f} ${r['mean_cash']:>11,.0f} ${lin:>17,.0f} "
f"{(r['med_cash']-lin)/max(abs(lin),1):>+9.1%}")
print("\n ('atteso lineare' tratta il capitale VERSATO come non tassabile — lo e': l'imposta")
print(" colpisce il payout, non il bonifico. Lo scarto misura quindi solo la COMPOSIZIONE.)")
# ---------------------------------------------------------------- §5 la soglia
hr("§5. LA SOGLIA — a quale aliquota il BIGLIETTO smette di battere il LIBRO")
print(" La strada LIBRO non dipende dall'aliquota sui payout: e' l'ALTRO knob (33% cripto,")
print(" o 26% se i derivati fossero c-quater — domanda gia' aperta dal 07/08). Percio' la")
print(" soglia si legge per ogni valore del fisco del LIBRO.")
out = {}
def _f(x):
return f"tau = {x:.1%}" if x is not None else "MAI (fuori dalla griglia 0-100%)"
for dep in (0.0, 500.0):
print(f"\n --- versamento EUR {dep:,.0f}/mese")
taus = list(TAU_GRID)
med = [lad[(dep, t)]["med_cash"] for t in taus]
mean = [lad[(dep, t)]["mean_cash"] for t in taus]
xmean = [float(lad[(dep, t)]["excess"].mean()) for t in taus]
for tax_book in (0.26, 0.33):
Bk = GC.book_road(rb, BOOT, pl.START_EUR, dep, tax=tax_book)
bx = float((Bk["cash"] - Bk["contributed"]).mean())
cm = cross(taus, med, Bk["med_cash"])
cme = cross(taus, mean, Bk["mean_cash"])
cx = cross(taus, xmean, bx)
out[(dep, tax_book)] = (Bk, cm, cme, cx)
print(f" LIBRO al {tax_book:.0%}: mediana ${Bk['med_cash']:>9,.0f} "
f"media ${Bk['mean_cash']:>9,.0f} eccedenza media ${bx:>8,.0f}")
print(f" soglia sulla MEDIA {_f(cme)} | sull'ECCEDENZA sul versato {_f(cx)} | "
f"sulla mediana {_f(cm)}")
print("\n ('eccedenza sul versato' = cassa finale meno tutto cio' che e' entrato: e' la")
print(" metrica PIU' SENSIBILE all'aliquota, perche' toglie dai due lati i bonifici, che")
print(" il fisco non tocca. Se la soglia non cade dentro la banda nemmeno LI', non ci cade.)")
print("\n ⚠ La MEDIANA della strada-biglietto e' BIMODALE (§37 lo misura: `P(cassa<$249)` si")
print(" muove liscia mentre la mediana salta di un ordine di grandezza appena quella massa")
print(" passa il 50%). Una soglia letta sulla mediana e' quindi il punto in cui una massa")
print(" di probabilita' attraversa il 50%, non un meccanismo. **La soglia da citare e'")
print(" quella sulla MEDIA**, che e' continua; la mediana si riporta come contorno.")
# ---------------------------------------------------------------- §6 gli scenari
hr("§6. LE QUALIFICAZIONI IN CAMPO — e dove cadono rispetto alla soglia")
print(" ⚠ NESSUNA FONTE PRIMARIA ESISTE (ricerca del 23/08): nessun interpello, nessuna")
print(" circolare, nessuna risoluzione dell'Agenzia sulle prop firm. Due fonti")
print(" professionali lo dichiarano espressamente. Cio' che segue e' una BANDA di")
print(" ipotesi, non una stima.")
SC = scenari()
print(f"\n {'':>3} {'scenario':>58} {'eff.@18k':>9} {'eff.@30k':>9} {'grado fonte':>44}")
for s in SC:
print(f" {s['k']:>3} {s['lab']:>58} {aliq_eff(s['fn'], 18_250.0):>9.1%} "
f"{aliq_eff(s['fn'], 30_000.0):>9.1%} {s['src']:>44}")
print("\n (eff.@18k = EUR 50/giorno, il bersaglio del progetto; eff.@30k = un anno buono.)")
print(f"\n Effetto sulla scala di conti, versamento EUR 500/mese (l'unica riga su cui §37")
print(f" ha concluso 'il biglietto batte il versare'):")
Bk33 = out[(500.0, 0.33)][0]
print(f" {'':>3} {'aliquota eff.':>13} {'EUR/g med':>10} {'P(>=50/g)':>10} {'P(zero)':>9} "
f"{'cassa mediana':>14} {'media':>12} {'vs LIBRO(33%)':>14}")
sc_rows = []
for s in SC:
r = ladder_tax(R_M, G_M, N_LAD, LEV_M, SEED_LAD, s["fn"], refund=True,
deposit_eur=500.0, min_payout=MIN_PAYOUT, boot=BOOT)
win = "BIGLIETTO" if r["mean_cash"] > Bk33["mean_cash"] else "LIBRO"
sc_rows.append(dict(k=s["k"], tag=s["tag"], eff=aliq_eff(s["fn"], 18_250.0), **r))
print(f" {s['k']:>3} {aliq_eff(s['fn'], 18_250.0):>13.1%} {r['med']:>10.2f} "
f"{r['p_target']:>10.1%} {r['p_zero']:>9.1%} ${r['med_cash']:>13,.0f} "
f"${r['mean_cash']:>11,.0f} {win:>14}")
SCR = pd.DataFrame(sc_rows)
lo, hi = SCR["eff"].iloc[1:].min(), SCR["eff"].max()
cme = out[(500.0, 0.33)][2]
cxx = out[(500.0, 0.33)][3]
print(f"\n Banda plausibile delle aliquote effettive (escluso il controllo): "
f"{lo:.1%} - {hi:.1%}.")
print(f" Soglia di ribaltamento (LIBRO al 33%): sulla MEDIA "
f"{'tau = ' + format(cme, '.1%') if cme is not None else 'FUORI dalla griglia 0-100%'}"
f" | sull'ECCEDENZA "
f"{'tau = ' + format(cxx, '.1%') if cxx is not None else 'FUORI dalla griglia 0-100%'}")
dentro = (cme is not None) and (lo <= cme <= hi)
print(f" >>> ATTESA A4 ('la soglia cade DENTRO la banda'): "
f"{'CONFERMATA' if dentro else 'REFUTATA'}")
e_pro = float(SCR.loc[SCR["tag"] == "PROGETTO", "eff"].iloc[0])
e_occ = float(SCR.loc[SCR["tag"] == "OCC", "eff"].iloc[0])
e_o30 = float(SCR.loc[SCR["tag"] == "OCC30", "eff"].iloc[0])
print(f" >>> ATTESA A5 ('la progressiva a EUR 18k sta SOTTO il forfait 33% del progetto'): "
f"occasionale {e_occ:.1%} contro forfait {e_pro:.1%} -> "
f"{'CONFERMATA' if e_occ < e_pro else 'REFUTATA'}.")
print(f" E il seguito e' il punto trasferibile: la STESSA qualificazione, con un reddito")
print(f" preesistente di EUR 30.000, sale a {e_o30:.1%}"
f"{e_o30/e_occ:.2f}x. **L'aliquota non e' una proprieta' della strategia, e' una")
print(f" proprieta' del contribuente**, e il canale funded e' l'unico pezzo del")
print(f" progetto in cui questo e' vero (le plusvalenze cripto hanno aliquota fissa).")
hr("§6.2 LA CATEGORIA CAMBIA ANCHE LA BASE, non solo l'aliquota")
print(" art. 71 c.2 TUIR (testo verificato in sessione): i redditi di cui all'art. 67 c.1")
print(" lett. l) sono 'la DIFFERENZA fra l'ammontare percepito nel periodo di imposta e le")
print(" spese specificamente inerenti alla loro produzione' -> **sotto lavoro autonomo le")
print(" quote di challenge SI DEDUCONO**; sotto un'imposta sostitutiva su reddito")
print(" finanziario NO (una challenge persa non e' una minusvalenza da cripto-attivita'),")
print(" e sotto forfettario nemmeno (le sostituisce il coefficiente).")
sp_med = float(lad[(500.0, 0.33)]["spent"])
sp_anno = sp_med / 3.0 / EURUSD
print(f"\n Quote comprate in media su 36 mesi: ${sp_med:,.0f} = EUR {sp_anno:,.0f}/anno.")
print(f" {'scenario':>52} {'senza deduzione':>16} {'con deduzione':>14} {'delta':>8}")
for lab, fn_nd, fn_d in (
("lav.aut. OCCASIONALE, nessun altro reddito",
net_prog(0.0, "occasionale", deduci=False), net_prog(0.0, "occasionale")),
("lav.aut. ABITUALE INPS piena, altro reddito 30k",
net_prog(30_000.0, "piena", deduci=False), net_prog(30_000.0, "piena"))):
a = aliq_eff(fn_nd, 18_250.0, sp_anno)
b = aliq_eff(fn_d, 18_250.0, sp_anno)
print(f" {lab:>52} {a:>16.2%} {b:>14.2%} {b-a:>+8.2%}")
print(" -> la categoria con l'aliquota PIU' ALTA e' anche l'unica che deduce il costo dei")
print(" biglietti: l'ordine fra le qualificazioni non e' l'ordine fra le aliquote.")
hr("§6.1 ROBUSTEZZA — i due numeri della ricerca che NON ho verificato su fonte primaria")
print(" (a) secondo scaglione IRPEF 2026: 33% (L.199/2025) contro il 35% del 2025.")
for lab, sg in (("33% (2026)", IRPEF_SCAGLIONI), ("35% (2025)", IRPEF_2025)):
f1 = net_prog(0.0, "occasionale", sg)
f2 = net_prog(30_000.0, "occasionale", sg)
print(f" {lab:>11}: occasionale {aliq_eff(f1, 18_250.0):.2%} "
f"con altro reddito 30k {aliq_eff(f2, 18_250.0):.2%}")
print(" (b) chi versa i 2/3 di Gestione Separata con committente ESTERO — buco documentale:")
for lab, inps in (("committente versa 2/3", "occasionale"), ("tutto sul prestatore", "piena")):
print(f" {lab:>22}: eff.@18k "
f"{aliq_eff(net_prog(0.0, inps), 18_250.0):>6.1%} "
f"@30k {aliq_eff(net_prog(0.0, inps), 30_000.0):>6.1%}")
# ---------------------------------------------------------------- §7 verdetto
hr("§7. VERDETTO — e la domanda, scritta in modo che una risposta sia possibile")
r33 = lad[(500.0, 0.33)]
print(f" 1. Il forfait 33% NON e' l'aliquota di questa cosa: sul funded il trader non")
print(f" possiede il conto ne' le posizioni, non c'e' realizzo di cripto-attivita', e")
print(f" l'art. 67 c-sexies (33%) e c-quater (26%) — le due caselle su cui il progetto")
print(f" discute per il LIBRO — sono entrambe FUORI CATEGORIA qui. Le fonti")
print(f" professionali convergono su LAVORO AUTONOMO. Nessuna prassi ufficiale esiste.")
print(f" 2. La conclusione di §37 ('il biglietto batte il versare' a EUR 500/mese) "
f"{'REGGE' if cme is None or cme > hi else 'NON REGGE'} "
f"su tutta la banda plausibile.")
print(f" 3. Il costo della QUESTIONE, misurato: passare dall'aliquota piu' bassa a quella")
print(f" piu' alta della banda vale, sulla cassa mediana a 36 mesi,")
lo_r = SCR.loc[SCR["eff"].iloc[1:].idxmin()]
hi_r = SCR.loc[SCR["eff"].idxmax()]
print(f" ${lo_r['med_cash']:,.0f} -> ${hi_r['med_cash']:,.0f} "
f"({(hi_r['med_cash']/max(lo_r['med_cash'],1)-1)*100:+.0f}%), e su P(>=50/g) "
f"{lo_r['p_target']:.1%} -> {hi_r['p_target']:.1%}.")
d_eur = (hi_r["med_cash"] - lo_r["med_cash"]) / EURUSD / (HORIZON_DAYS / 365.0) / 365.0
print(f" In EUR/GIORNO di rendita a 36 mesi: **{d_eur:+.2f} EUR/giorno** fra i due")
print(f" estremi della banda, contro un bersaglio di 50.")
print(f" 4. Al 33% di oggi la scala di conti da' EUR/g mediana {r33['med']:.2f} e "
f"P(>=50/g) {r33['p_target']:.1%}: **l'aliquota non e' cio' che tiene lontano il")
print(f" bersaglio**, lo tiene lontano la distribuzione.")
print("\n LA DOMANDA AL COMMERCIALISTA (accanto a quella gia' aperta dal 07/08 sui")
print(" derivati, 26% contro 33% — e questa e' una domanda DIVERSA, non la stessa):")
print(" Q1. Il profit share pagato da una prop firm estera a chi NON possiede il conto,")
print(" NON possiede le posizioni e NON apporta capitale e': art. 67 c.1 lett. l),")
print(" art. 53, art. 55, oppure art. 67 c.1 lett. c-quater TUIR? **Indichi la lettera.**")
print(" Q2. Una prop firm non residente e senza stabile organizzazione puo' operare una")
print(" ritenuta italiana sul payout? (SI'/NO)")
print(" Q3. Superata la soglia di EUR 5.000 di compensi occasionali, con committente")
print(" (a) UE e (b) extra-UE: l'aliquota di Gestione Separata che grava sul")
print(" PRESTATORE e' 1/3 di 26,07% o l'intero 26,07%? (una risposta per caso)")
print(" Q4. Con partita IVA: quale ATECO, e quindi quale coefficiente forfettario,")
print(" 78% o 67%? (una sigla e una percentuale)")
print(" Q5. Il funded account va in quadro RW / IVAFE? (SI'/NO) Se no, l'obbligo si")
print(" limita al conto estero su cui incasso?")
print("\n ONESTA' OBBLIGATORIE:")
print(" * questo NON e' un parere fiscale, e nessuna riga qui sopra e' una consulenza.")
print(" * l'aliquota EFFETTIVA degli scenari progressivi e' calcolata SENZA detrazioni")
print(" (conservativo: le detrazioni la abbassano) e con addizionali al minimo di legge.")
print(" * il modello tassa i payout dell'anno via via che arrivano, non a saldo: toglie")
print(" liquidita' prima del dovuto, quindi e' conservativo sulla ricomposizione.")
print(" * tutte le assunzioni di §37 restano dentro: morte-firm 10%/anno (mai stimata),")
print(" cap $200k/trader, XS01 su U13 fuori campione, ancora giornaliera di TP01 fuori")
print(" dalla banda. Il fisco e' UN parametro fra questi, e non il piu' incerto.")
print(f"\n fatto in {time.time()-T0:.0f}s")
print("=" * 104)
if __name__ == "__main__":
main()
+794
View File
@@ -0,0 +1,794 @@
"""r0823b_ladder_redo — §60 LADDER-REDO: la politica multi-conto e' ancora MISTO? (2026-08-23)
LA DOMANDA
----------
Il 2026-07-25 (`r0725_prop_ladder.py` + `r0725_prop_coupled.py`) il progetto scelse la politica
multi-conto **MISTO** (sleeve diversi su conti diversi) contro CONC-DIV, SPARSO e CONC-2SL:
MISTO 10,70 EUR/g mediana, P(>=50/g) 20,7%, P(zero) 33,4%
> CONC-DIV 5,70 / 17,8% / 33,4%
> SPARSO 0,00 / 16,8% / 66,0%
> CONC-2SL 0,00 / 13,1% / 55,6% (close-only, leva 1,00x)
Da allora il numero operativo del canale funded e' passato **da 42% a 2,6%** (fattore 16) per
tre correzioni misurate in due giorni:
(a) universo XS01 **fuori campione** — 6 delle 19 gambe non esistevano nel 2021-23 e valevano
il **42% del J pubblicato** (§29);
(b) **banda d'ancora di SKH01** — l'offset canonico e' al 91° pctl dei 23 (§32);
(c) **funding** — mai modellato nel 2025, costa dJ -0,049 su 69/69 celle appaiate (§35, §37).
Piu' due lenti che il 25/07 non aveva o aveva diverse: la **wick accoppiata** (misurata il
25/07 sera, dopo il ladder) e il de-luck **x0,89** invece di x0,60 (misurato il 26/07).
**Nessuno ha ri-misurato la politica.** La conclusione "MISTO" e' stata EREDITATA.
LE ATTESE, REGISTRATE PRIMA DI MISURARE
---------------------------------------
A1. **La replica dell'ORDINE riuscira', quella dei LIVELLI no.** `pl.sleeve_panel` ri-deriva le
serie dal feed corrente (+4 settimane dal 25/07): §37 ha gia' visto -11% su `r0725_hyro`
per la stessa ragione. Mi aspetto livelli 10-30% piu' bassi e ordine identico.
A2. **Il funding NON puo' ribaltare l'ordine, e semmai lo RAFFORZA.** Il costo di funding per
sleeve e' misurato (§35/§37, a peso 1,0): TP01 2,10%/a, SKH01 2,32%/a, XS01 1,45%/a. Il
costo medio di politica e' quindi CONC-2SL 2,16% > CONC-A 2,04% > CONC-DIV 1,98% >
SPARSO 1,96% > **MISTO 1,82%** — cioe' il vincitore del 25/07 e' anche quello che paga
MENO funding, ma lo spread e' solo **0,33 pp/anno** (un sesto del costo). Previsione:
tutti i livelli scendono, l'ordine resta, il delta MISTO-vs-resto si allarga di poco e
dentro il rumore.
A3. **La correzione che PUO' ribaltare l'ordine e' XS01, non il funding.** MISTO e SPARSO
poggiano su conti a **XS01 puro** (2/6 e 2/6 slot); se il drift di XS01 fuori campione e'
piu' debole (Sharpe 1,04 e maxDD -19,2% contro -11,2% dello sleeve ufficiale, §29), sono
proprio quelle politiche a perdere. Previsione: **se l'ordine cambia, cambia CONTRO
MISTO/SPARSO e a favore di CONC-DIV / CONC-A**, e per l'universo, non per il funding.
A4. **Il numero di conti e' il vero vincolo, e non e' stato mai misurato.** Con $654 di cassa
e un biglietto HYRO $100k a $579 si compra **UN** conto e restano $75: mi aspetto che la
stragrande maggioranza dei percorsi non arrivi MAI a due conti simultanei, e quindi che
**MISTO e CONC-DIV, che condividono lo slot 0, siano quasi indistinguibili** finche' non
entrano soldi. Previsione: la politica conta poco senza versamenti e molto con versamenti.
A5. **Il deposito rimborsabile (§30) fa piu' differenza sulla SCALA che sull'EV**, perche' i
$579 che tornano ricomprano un biglietto: +1-3pp su P(>=50/g), come previde §37.
COSA FA
-------
§1 REPLICA del 25/07 (`pl.simulate` close-only/indip, `pc.simulate` accoppiata).
§2 CONTROLLI: `ladder_multi` bit-exact contro `pc.simulate`; controllo POSITIVO (una politica
deliberatamente rotta deve finire ultima, o il confronto non ha potenza).
§3 DECOMPOSIZIONE a un grado di liberta' per volta: lente -> de-luck -> universo/finestra ->
funding. E' il metodo di §29: senza, non si sa QUALE correzione fa cosa.
§4 BANDA D'ANCORA: 8 offset x 3 fasi (sottocampione DICHIARATO), con e senza funding, tutte
le politiche insieme sugli STESSI sorteggi -> mediana delle differenze APPAIATE.
§5 La previsione A2 sul funding, verificata.
§6 LA DIMENSIONE MAI TESTATA: la traiettoria del NUMERO DI CONTI nel tempo, con quota
rimborsabile e con/senza versamenti.
§7 MDE, e verdetto.
CONVENZIONI DICHIARATE
----------------------
* Le POLITICHE sono quelle del 25/07, non ridefinite (altrimenti si misura una ridefinizione,
non una correzione). Aggiunte due sole varianti, dichiarate: **CONC-A** = W_A 38/38/25 (il
centro della regione robusta di §29, cioe' la configurazione su cui poggiano §32 e §37) e
**MISTO-A** = MISTO col nucleo W_A al posto di book_div (separa "il nucleo" dal "mescolare").
* Lente **ACCOPPIATA** ovunque nella parte corretta; close-only e indipendente solo come
repliche/controlli. De-luck **x0,89** (conservativo: §32 nota che sulla dimensione d'ancora
e' un doppio conteggio).
* Leva **fissa** e uguale per tutte le politiche: il confronto e' appaiato, e `best_lev` per
configurazione introdurrebbe un grado di liberta' diverso per politica. Girata a 1,00x
(grid piena) e 0,75x (controllo).
* Firm death 10%/anno, cap $200k/firm, fisco 33%, split 80% — tutte ereditate, nessuna scelta.
* Bootstrap a blocchi (20 g) **condiviso**: stessi indici e stessi sorteggi di morte-firm per
TUTTE le politiche -> differenze appaiate.
Rete: NESSUNA (`--no-net` forzato; cache funding e cache Binance gia' su disco).
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823b_ladder_redo.py`
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import numpy as np
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))
if "--no-net" not in sys.argv:
sys.argv.append("--no-net")
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata (25/07)
import r0725_prop_ladder as pl # noqa: E402 ladder originale + regole firm (25/07)
import r0822_prop_alloc as PA # noqa: E402 bootstrap/J (22/08)
import r0822b_prop_recal as PR # noqa: E402 (R,G) su pannello libero (22/08)
import r0822b_xs01_oos as X # noqa: E402 motore XS01 + U13 Binance (22/08)
import r0822c_skh_anchor_prop as AP # noqa: E402 offset di SKH01 (22/08)
import r0822d_funding as FD # noqa: E402 funding (22/08)
import r0822d_gate_c as GC # noqa: E402 funding per-sleeve + ladder a 1 cfg (22/08)
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
EURUSD = pl.EURUSD
TAX_RATE = pl.TAX_RATE
SPLIT = pl.SPLIT
FIRMS = pl.FIRMS
MAXC = pl.MAX_CONCURRENT # 6 slot
HDAYS = pl.HORIZON_MONTHS * 30 # 1080
DELUCK_NEW = PA.DELUCK # 0,89 (26/07)
DELUCK_OLD = pl.DELUCK # 0,60 (25/07)
TARGET = pl.TARGET_EUR_DAY
MIN_PRICE = min(p for F in FIRMS for (_s, p) in F["tickets"])
# --- sottocampioni DICHIARATI (a priori, uniformi) -------------------------------------------
OFFSETS = AP.OFFSETS[::3] # 8 degli 23 offset: 0,90,...,630 — uniforme a priori
PHASES = GC.PHASES_SUB # (0,3,7) — lo stesso di §37
N_PATHS = 1500 # risoluzione MC dichiarata (~+-1pp su P(>=50/g))
N_PATHS_REP = 3000 # per la replica: identico al 25/07
SEED = 20260725 # il seme del 25/07
LEV_MAIN, LEV_ALT = 1.00, 0.75
# `--quick` = AUTO-TEST: esercita ogni ramo di codice in ~2 minuti. NON produce numeri da citare
# (griglia e percorsi ridotti); serve a non scoprire un errore di stampa dopo venti minuti.
QUICK = "--quick" in sys.argv
if QUICK:
OFFSETS = OFFSETS[:2]
PHASES = PHASES[:2]
N_PATHS = 300
N_PATHS_REP = 300
# --- numeri PUBBLICATI da riprodurre ----------------------------------------------------------
PUB_2507 = { # r0725_prop_ladder, CLOSE-ONLY leva 1,00x
"CONC-2SL": (0.00, 0.131, 0.556),
"CONC-DIV": (5.70, 0.178, 0.334),
"SPARSO": (0.00, 0.168, 0.660),
"MISTO": (10.70, 0.207, 0.334),
}
PUB_COUPLED = "P(>=50/g) 5,6-6,5% a 0,75x, P(zero) 52% a 0,75x / 65% a 1,00x (25/07 sera)"
PUB_GATEC = dict(p50g=0.026, pzero=0.403) # §37, W_A, funding dentro, mediana d'ancora
# --- costi di funding per sleeve, PUBBLICATI (§35/§37), a peso 1,0 ----------------------------
FUND_PUB = {"TP01": 0.0210, "SKH01": 0.0232, "XS01": 0.0145}
TRIALS = 0
T0 = time.time()
def bump(n: int = 1) -> None:
global TRIALS
TRIALS += n
def hr(t: str = "") -> None:
print("\n" + "=" * 104)
if t:
print(" " + t)
print("=" * 104)
def tick(lab: str) -> None:
print(f" [{time.time() - T0:6.0f}s] {lab}")
# ==================================================================================
# LA MACCHINA: una scala di conti che gira PIU' POLITICHE sugli STESSI sorteggi
# ==================================================================================
def ladder_multi(Rm: np.ndarray, Gm: np.ndarray, sel: np.ndarray, n_paths: int, lev: float,
seed: int, deluck: float, refund: bool = False, deposit_eur: float = 0.0,
start_eur: float = pl.START_EUR, block: int = 20,
boot: np.ndarray | None = None,
track: bool = False) -> dict:
"""`r0725_prop_coupled.simulate` generalizzato a N politiche in una corsa sola.
Rm, Gm : (giorni, n_cfg) — ritorni di chiusura e gap (<=0) di ciascuna CONFIGURAZIONE.
sel : (n_group, n_slot) — indice di configurazione per ogni slot di ogni politica.
I gruppi (= politiche) sono impilati sull'asse dei percorsi e ricevono **gli stessi indici
di bootstrap e gli stessi sorteggi di morte-firm**: il confronto fra politiche e' appaiato
per costruzione (stesso mondo, allocazione diversa).
Con n_group=1 e refund/deposit spenti consuma il flusso di numeri casuali NELLO STESSO
ORDINE di `pc.simulate` -> deve essere BIT-EXACT (controllo §2).
"""
rng = np.random.default_rng(seed)
L = len(Rm)
n_grp, n_slot = sel.shape
if boot is None:
n_blocks = int(np.ceil(HDAYS / block))
st = rng.integers(0, L - block, size=(n_paths, n_blocks))
boot = (st[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :HDAYS]
# morte-firm: un solo sorteggio (n_days, n_paths, n_firm) == n_days chiamate consecutive
du = rng.random((HDAYS, n_paths, len(FIRMS)))
Rd = Rm - (1.0 - deluck) * Rm.mean(axis=0, keepdims=True)
P = n_grp * n_paths
SELP = np.repeat(sel, n_paths, axis=0) # (P, n_slot)
rowsP = np.arange(P)[:, None]
cash = np.full(P, start_eur * EURUSD)
phase = np.full((P, n_slot), -1, np.int8)
eq = np.ones((P, n_slot))
firm = np.full((P, n_slot), -1, np.int8)
notional = np.zeros((P, n_slot))
price_paid = np.zeros((P, n_slot))
refunded = np.zeros((P, n_slot), bool)
firm_alive = np.ones((P, len(FIRMS)), bool)
cum_payout = np.zeros(P)
spent = np.zeros(P)
got_back = np.zeros(P)
n_tickets = np.zeros(P, int)
max_conc = np.zeros(P, int)
max_open = np.zeros(P, int)
ever2 = np.zeros(P, bool)
ever_slot1 = np.zeros(P, bool) # lo slot 1 e' mai stato aperto? (= la politica conta)
traj = np.zeros((HDAYS // 30, n_grp)) if track else None
p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0)
tick_sorted = [sorted(F["tickets"], key=lambda x: -x[0]) for F in FIRMS]
for t in range(HDAYS):
died = np.tile(du[t], (n_grp, 1)) < p_death_day
newly = died & firm_alive
firm_alive &= ~died
if newly.any():
for fi in range(len(FIRMS)):
kill = newly[:, fi]
if kill.any():
hit = (firm == fi) & kill[:, None]
phase[hit] = -1
notional[hit] = 0.0
if (cash >= MIN_PRICE).any():
for k in range(n_slot):
free = phase[:, k] == -1
if not free.any():
continue
for fi, F in enumerate(FIRMS):
used = np.where(firm == fi, notional, 0.0).sum(axis=1)
for (size, price) in tick_sorted[fi]:
buy = (free & firm_alive[:, fi] & (cash >= price)
& (used + size <= F["cap"]))
if not buy.any():
continue
cash[buy] -= price
spent[buy] += price
n_tickets[buy] += 1
phase[buy, k] = 0
eq[buy, k] = 1.0
firm[buy, k] = fi
notional[buy, k] = size
price_paid[buy, k] = price
refunded[buy, k] = False
if k >= 1:
ever_slot1 |= buy
free = free & ~buy
if not free.any():
break
if not free.any():
break
rd = Rd[boot[:, t], :] # (n_paths, n_cfg)
gd = Gm[boot[:, t], :]
rdP = np.tile(rd, (n_grp, 1))
gdP = np.tile(gd, (n_grp, 1))
r = rdP[rowsP, SELP] * lev
g = gdP[rowsP, SELP] * lev
act = phase >= 0
eq_prev = eq
eq = np.where(act, eq * (1.0 + r), eq)
eq_low = np.where(act, eq_prev * (1.0 + r + g), eq)
r_low = r + g
for fi, F in enumerate(FIRMS):
s_ = act & (firm == fi)
if not s_.any():
continue
ev_t, ev_dd, ev_dl = F["ev"]
fu_ml, fu_dl = F["fu"]
in_ev = s_ & (phase == 0)
in_fu = s_ & (phase == 1)
bust = ((in_ev & ((eq_low < 1.0 - ev_dd) | (r_low < -ev_dl)))
| (in_fu & ((eq_low < 1.0 - fu_ml) | (r_low < -fu_dl))))
phase[bust] = -1
notional[bust] = 0.0
passed = in_ev & (eq >= 1.0 + ev_t) & ~bust
phase[passed] = 1
eq[passed] = 1.0
nfu = (phase == 1).sum(axis=1)
max_conc = np.maximum(max_conc, nfu)
max_open = np.maximum(max_open, (phase >= 0).sum(axis=1))
ever2 |= nfu >= 2
if (t + 1) % 30 == 0:
fu = phase == 1
gain = np.where(fu & (eq > 1.0), (eq - 1.0) * notional, 0.0)
net = gain.sum(axis=1) * SPLIT * (1.0 - TAX_RATE)
cum_payout += net
cash += net
pay = fu & (eq > 1.0)
eq = np.where(pay, 1.0, eq)
if refund:
back = pay & ~refunded
amt = np.where(back, price_paid, 0.0).sum(axis=1)
cash += amt
got_back += amt
refunded |= back
if deposit_eur:
cash += deposit_eur * EURUSD
if track:
traj[(t + 1) // 30 - 1] = nfu.reshape(n_grp, n_paths).mean(axis=1)
out = []
for gi in range(n_grp):
s = slice(gi * n_paths, (gi + 1) * n_paths)
ed = cum_payout[s] / EURUSD / (HDAYS / 365.0) / 365.0
out.append(dict(eur_day=ed, med=float(np.median(ed)),
p10=float(np.percentile(ed, 10)), p90=float(np.percentile(ed, 90)),
mean=float(ed.mean()),
p_ge10=float((ed >= 10.0).mean()),
p_target=float((ed >= TARGET).mean()),
p_zero=float((cum_payout[s] <= 0).mean()),
n_tickets=float(n_tickets[s].mean()),
max_conc=float(max_conc[s].mean()),
max_open=float(max_open[s].mean()),
p_ever2=float(ever2[s].mean()),
p_slot1=float(ever_slot1[s].mean()),
med_cash=float(np.median(cash[s])),
mean_back=float(got_back[s].mean()),
spent=float(spent[s].mean())))
bump(n_grp)
return dict(groups=out, traj=traj)
# ==================================================================================
# POLITICHE
# ==================================================================================
W_2SL = {"TP01": 0.75, "SKH01": 0.25}
W_DIV = {"TP01": 0.55, "SKH01": 0.20, "XS01": 0.25}
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25}
W_TP = {"TP01": 1.0}
W_SKH = {"SKH01": 1.0}
W_XS = {"XS01": 1.0}
CFGS = [W_2SL, W_DIV, W_A, W_TP, W_SKH, W_XS] # indici 0..5
I2SL, IDIV, IA, ITP, ISKH, IXS = range(6)
POLICIES = {
"CONC-2SL ogni conto = book live": [I2SL] * MAXC,
"CONC-DIV ogni conto = diversif.": [IDIV] * MAXC,
"SPARSO 1 sleeve per conto": [ITP, IXS, ISKH, ITP, IXS, ISKH],
"MISTO meta' div / meta' single": [IDIV, IXS, IDIV, ITP, IDIV, IXS],
"CONC-A ogni conto = W_A 38/38/25": [IA] * MAXC,
"MISTO-A MISTO col nucleo W_A": [IA, IXS, IA, ITP, IA, IXS],
}
PNAMES = list(POLICIES)
SEL = np.array([POLICIES[p] for p in PNAMES], dtype=int)
SHORT = [p.split()[0] for p in PNAMES]
def _tab(rows: list[tuple], head: tuple) -> None:
print(" " + " ".join(f"{h:>{w}}" for h, w in head))
for r in rows:
print(" " + " ".join(f"{v:>{w}}" for v, (_h, w) in zip(r, head)))
# ==================================================================================
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
print("=" * 104)
print(" §60 LADDER-REDO — la politica multi-conto e' ancora MISTO?")
print("=" * 104)
print(" ATTESE REGISTRATE (dal docstring, prima di misurare):")
print(" A1 l'ORDINE del 25/07 si replica, i LIVELLI no (+4 settimane di feed).")
print(" A2 il FUNDING non ribalta l'ordine e semmai lo rafforza (MISTO paga MENO:")
print(" 1,82%/a contro 2,16% di CONC-2SL; spread 0,33pp = un sesto del costo).")
print(" A3 se l'ordine cambia, cambia per l'UNIVERSO XS01 e CONTRO MISTO/SPARSO.")
print(" A4 con $654 si compra UN conto: la politica quasi non conta senza versamenti.")
print(" A5 il deposito rimborsabile vale piu' sulla SCALA che sull'EV (+1-3pp).")
# ============================================================ §1 REPLICA 25/07
hr("§1. REPLICA — i numeri pubblicati il 25/07, rigirati oggi con la stessa macchina")
M, names = pl.sleeve_panel()
print(f" pannello `pl.sleeve_panel()`: {len(M)} giorni, sleeve {names}, de-luck x{DELUCK_OLD}")
pols_old = pl.policies(names)
rows = []
got = {}
for pname, slots in pols_old.items():
key = pname.split()[0]
r = pl.simulate(M, slots, n_paths=N_PATHS_REP, lev=1.0, seed=SEED, intraday=False)
got[key] = (r["med"], r["p_target"], r["p_zero"])
pmed, pp50, ppz = PUB_2507[key]
rows.append((key, f"{r['med']:.2f}", f"{pmed:.2f}",
f"{r['p_target']:.1%}", f"{pp50:.1%}",
f"{r['p_zero']:.1%}", f"{ppz:.1%}"))
bump()
_tab(rows, (("politica", 9), ("EUR/g oggi", 11), ("PUB", 6),
("P>=50 oggi", 11), ("PUB", 6), ("P0 oggi", 8), ("PUB", 6)))
ord_now = sorted(got, key=lambda k: -got[k][1])
ord_pub = sorted(PUB_2507, key=lambda k: -PUB_2507[k][1])
same = ord_now == ord_pub
print(f"\n ordine su P(>=50/g) oggi: {' > '.join(ord_now)}")
print(f" ordine su P(>=50/g) 25/07: {' > '.join(ord_pub)}")
print(f" >>> ORDINE {'REPLICATO' if same else 'NON replicato'}; livelli "
f"{'piu bassi' if np.mean([got[k][1] for k in got]) < np.mean([PUB_2507[k][1] for k in PUB_2507]) else 'piu alti'}"
f" (media P(>=50/g) {np.mean([got[k][1] for k in got]):.1%} contro "
f"{np.mean([PUB_2507[k][1] for k in PUB_2507]):.1%}).")
print(" A1 " + ("CONFERMATA" if same else "REFUTATA") +
" — `pl.sleeve_panel` ri-deriva le serie dal feed CORRENTE (stessa causa del -11%")
print(" che §37 misuro' su `r0725_hyro`): la macchina e' quella, il vintage no.")
tick("§1")
# ============================================================ §2 CONTROLLI
hr("§2. CONTROLLI — bit-exact contro `pc.simulate`, e un controllo POSITIVO")
cfgs_pc = [(0.75, 0.25, 0.0), (0.55, 0.20, 0.25), (1.0, 0.0, 0.0),
(0.0, 1.0, 0.0), (0.0, 0.0, 1.0)]
START_OLD = "2024-01-01"
mats = [pc.book_tuples(*c, start=START_OLD) for c in cfgs_pc]
Lc = min(len(m[0]) for m in mats)
Rc = np.stack([m[0][-Lc:] for m in mats], axis=1)
Gc = np.stack([m[1][-Lc:] for m in mats], axis=1)
pc.DELUCK = DELUCK_OLD
slots_conc = [0] * MAXC
ref = pc.simulate(cfgs_pc, slots_conc, n_paths=800, lev=1.0, seed=99,
lens="coupled", start=START_OLD)
mine = ladder_multi(Rc, Gc, np.array([slots_conc]), n_paths=800, lev=1.0, seed=99,
deluck=DELUCK_OLD)["groups"][0]
d = float(np.abs(np.sort(ref["eur_day"]) - np.sort(mine["eur_day"])).max())
d2 = float(np.abs(ref["eur_day"] - mine["eur_day"]).max())
print(f" ladder_multi (1 gruppo) vs pc.simulate, lente accoppiata, 800 percorsi:")
print(f" max|diff| percorso-per-percorso = {d2:.3e} (ordinati: {d:.3e})")
print(f" -> {'BIT-EXACT' if d2 == 0.0 else 'DIVERGENTE'}")
# controllo positivo: una politica volutamente rotta (leva 3x = busta subito) deve finire ultima
selpos = np.array([slots_conc, slots_conc])
rp = ladder_multi(Rc, Gc, selpos, n_paths=800, lev=1.0, seed=99, deluck=DELUCK_OLD)
rbad = ladder_multi(Rc, Gc, np.array([slots_conc]), n_paths=800, lev=3.0, seed=99,
deluck=DELUCK_OLD)["groups"][0]
ok_pos = rbad["p_target"] < rp["groups"][0]["p_target"]
print(f" controllo POSITIVO — la stessa politica a leva 3,00x (deve bustare):")
print(f" P(>=50/g) 1,00x {rp['groups'][0]['p_target']:.1%} vs 3,00x {rbad['p_target']:.1%}"
f" -> {'il confronto ha POTENZA' if ok_pos else '!! NESSUNA POTENZA'}")
print(f" controllo di pairing — due gruppi identici devono dare lo stesso vettore: "
f"max|diff| {np.abs(rp['groups'][0]['eur_day'] - rp['groups'][1]['eur_day']).max():.3e}")
tick("§2")
# ============================================================ §3 pannello corretto
hr("§3. IL PANNELLO CORRETTO — U13 Binance fuori campione, W-LONG 2021+, funding")
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13, h13, l13 = (Ob.reindex(p13.index)[X.U13], Hb.reindex(p13.index)[X.U13],
Lb.reindex(p13.index)[X.U13])
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in PHASES}
bump(len(PHASES))
xs0 = XSP[PHASES[0]]
cry_idx = pc.crypto_daily_tuples(1.0, 0.0).index
base = cry_idx.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
S_LONG = max(first13, pd.Timestamp("2021-01-01", tz="UTC"))
I = base[base >= S_LONG]
print(f" W-LONG: {len(I)} giorni {I[0].date()} -> {I[-1].date()} | "
f"XS01 = U13 Binance ({len(X.U13)} gambe) | de-luck x{DELUCK_NEW}")
rate = GC.funding_rate()
if rate is None:
print(" !! cache funding assente: il filone NON puo' rispondere alla domanda (c). STOP.")
return
xsf = GC.xs_funding_measure(verbose=False)
xs_ann = xsf["ann"]
print(f" funding: TP01/SKH01 da serie misurate; XS01 = {xs_ann*100:+.2f}%/anno costante "
f"(§37, IC95 [{(xs_ann-1.96*xsf['se'])*100:+.2f}, {(xs_ann+1.96*xsf['se'])*100:+.2f}])")
boot = PA.boot_idx(len(I), HDAYS, N_PATHS, seed=SEED)
def build_on(idx: pd.DatetimeIndex, xs: pd.DataFrame, off: int,
fund: bool) -> tuple[np.ndarray, np.ndarray]:
"""(R, gap) di TUTTE le configurazioni su un indice e una serie XS01 dati."""
R = np.zeros((len(idx), len(CFGS)))
G = np.zeros((len(idx), len(CFGS)))
for ci, w in enumerate(CFGS):
r, g = AP.rg_off(w, idx, xs, off)
if fund:
r = r - GC.fvec_for(w, idx, rate, off, xs_ann)
R[:, ci], G[:, ci] = r, g
return R, G
def build(off: int, ph: int, fund: bool) -> tuple[np.ndarray, np.ndarray]:
return build_on(I, XSP[ph], off, fund)
# costo di funding EFFETTIVO per configurazione (verifica di A2)
print(f"\n costo di funding per configurazione (%/anno di equity a leva 1,00x, off 0):")
fcost = {}
for ci, w in enumerate(CFGS):
fv = GC.fvec_for(w, I, rate, 0, xs_ann)
fcost[ci] = float(np.mean(fv)) * 365 * 100
nm = ["book_2SL", "book_DIV", "W_A", "TP01 solo", "SKH01 solo", "XS01 solo"]
for ci in range(len(CFGS)):
pred = sum(FUND_PUB[k] * v for k, v in CFGS[ci].items()) * 100
print(f" {nm[ci]:<11} misurato {fcost[ci]:>5.2f}%/a atteso da §35/§37 {pred:>5.2f}%/a")
# CONTROLLO: il reindex su I non deve mangiare barre in silenzio (un NaN->0 e' un costo
# sottostimato che nessun numero segnala).
cov_tp = float(GC.f_tp01_sleeve(rate).reindex(I).notna().mean())
cov_sk = float(GC.f_skh_sleeve(rate, 0).reindex(I).notna().mean())
print(f" CONTROLLO di copertura su W-LONG: TP01 {cov_tp:.1%} delle barre, SKH01 {cov_sk:.1%}"
f" -> {'ok' if min(cov_tp, cov_sk) > 0.99 else '!! IL REINDEX MANGIA BARRE'}")
print(" ⚠ I costi misurati sono PIU' BASSI degli attesi, e non e' un disaccordo: §35 pubblica")
print(" -2,16%/a sul 2019+, -1,39% sul 2022-03+ e -0,55% sul 2025+ (il costo SCENDE nel")
print(" tempo) e questa finestra parte nel 2021. Costo del libro 75/25, anno per anno:")
fser = pd.Series(GC.fvec_for(W_2SL, I, rate, 0, xs_ann), index=I)
print(" " + " ".join(f"{y}:{float(g.mean()*365)*100:.2f}%"
for y, g in fser.groupby(fser.index.year)))
print(f" 🚨 Su tutta la storia oraria SKH01 paga -2,32% e TP01 -2,10% (§35). Su W-LONG sono")
print(f" {fcost[ISKH]:.2f}% e {fcost[ITP]:.2f}%: **il verso del confronto TP01-vs-SKH01 sul")
print(f" funding si INVERTE cambiando finestra** — l'aritmetica con cui avevo derivato A2")
print(f" usava i numeri di storia piena, quindi la previsione va verificata, non dedotta.")
print(f" costo di funding per POLITICA (media sugli slot):")
polfund = {}
for pi, p in enumerate(PNAMES):
v = float(np.mean([fcost[c] for c in SEL[pi]]))
polfund[SHORT[pi]] = v
print(f" {SHORT[pi]:<9} {v:>5.2f}%/a")
tick("§3")
# ============================================================ §4 decomposizione
hr("§4. DECOMPOSIZIONE — un grado di liberta' per volta (ancora 0, fase 0, leva 1,00x)")
print(" Senza questa tabella si sa che il numero e' cambiato, non QUALE correzione lo cambia.")
# gli indici di CFGS non sono quelli di cfgs_pc: si rimappa (le prime 4 politiche sono
# le uniche del 25/07; CONC-A e MISTO-A non esistevano e compaiono solo dal passo S3).
map_pc = {I2SL: 0, IDIV: 1, ITP: 2, ISKH: 3, IXS: 4}
sel4_pc = np.array([[map_pc[c] for c in POLICIES[p]] for p in PNAMES[:4]])
bpc = PA.boot_idx(Lc, HDAYS, N_PATHS, seed=SEED)
s0 = ladder_multi(Rc, np.zeros_like(Gc), sel4_pc, N_PATHS, 1.0, SEED, DELUCK_OLD, boot=bpc)
s1 = ladder_multi(Rc, Gc, sel4_pc, N_PATHS, 1.0, SEED, DELUCK_OLD, boot=bpc)
s2 = ladder_multi(Rc, Gc, sel4_pc, N_PATHS, 1.0, SEED, DELUCK_NEW, boot=bpc)
# --- S2b/S3a: UNIVERSO e FINESTRA separati, sullo STESSO indice comune (crypto n HL n Bin).
XS_HL = pc.xsec_daily_tuples("shared")
I24 = I[I >= pd.Timestamp("2024-01-01", tz="UTC")].intersection(XS_HL.index)
b24 = PA.boot_idx(len(I24), HDAYS, N_PATHS, seed=SEED)
R2b, G2b = build_on(I24, XS_HL, 0, False)
Rchk, Gchk, Ichk = pc.book_tuples(0.55, 0.20, 0.25, start="2024-01-01")
kk = pd.DatetimeIndex(Ichk).isin(I24)
jj = pd.DatetimeIndex(I24).isin(pd.DatetimeIndex(Ichk))
dchk = float(np.abs(Rchk[kk] - R2b[:, IDIV][jj]).max())
print(f" CONTROLLO: `rg_off` con le tuple HL riproduce `pc.book_tuples` (book_div) su "
f"{int(kk.sum())} giorni a max|diff| = {dchk:.3e}")
s2b = ladder_multi(R2b, G2b, SEL[:4], N_PATHS, 1.0, SEED, DELUCK_NEW, boot=b24)
R3a, G3a = build_on(I24, XSP[PHASES[0]], 0, False)
s3a = ladder_multi(R3a, G3a, SEL[:4], N_PATHS, 1.0, SEED, DELUCK_NEW, boot=b24)
R3, G3 = build(0, PHASES[0], False)
s3 = ladder_multi(R3, G3, SEL[:4], N_PATHS, 1.0, SEED, DELUCK_NEW, boot=boot)
R4, G4 = build(0, PHASES[0], True)
s4 = ladder_multi(R4, G4, SEL[:4], N_PATHS, 1.0, SEED, DELUCK_NEW, boot=boot)
lab = ["S0 25/07 tale e quale (HL19 2024+, close-only, x0,60)",
"S1 + lente ACCOPPIATA (misurata il 25/07 SERA)",
"S2 + de-luck x0,89 invece di x0,60 (26/07)",
"S2b [= S2 su indice comune: controllo di macchina]",
"S3a + UNIVERSO: XS01 = U13 Binance, stessa finestra",
"S3b + FINESTRA: W-LONG 2021+ (fuori campione)",
"S4 + FUNDING = la lente onesta di oggi"]
ALL = (s0, s1, s2, s2b, s3a, s3, s4)
print(f"\n P(>=50 EUR/g) per politica, {N_PATHS} percorsi, stessi sorteggi:")
print(" " + f"{'passo':<52}" + "".join(f"{s:>10}" for s in SHORT[:4]) + f"{' ordine':<40}")
for l, st in zip(lab, ALL):
v = [g["p_target"] for g in st["groups"]]
o = " > ".join([SHORT[i] for i in np.argsort(-np.array(v))])
print(f" {l:<52}" + "".join(f"{x:>9.1%} " for x in v) + f" {o:<40}")
print(f"\n EUR/g MEDIO per politica (la MEDIANA e' degenere: tabella sotto):")
print(" " + f"{'passo':<52}" + "".join(f"{s:>10}" for s in SHORT[:4]))
for l, st in zip(lab, ALL):
print(f" {l:<52}" + "".join(f"{g['mean']:>9.2f} " for g in st["groups"]))
print(f"\n EUR/g MEDIANA — il numero di TESTA del 25/07 era 'MISTO 10,70':")
print(" " + f"{'passo':<52}" + "".join(f"{s:>10}" for s in SHORT[:4]))
for l, st in zip(lab, ALL):
print(f" {l:<52}" + "".join(f"{g['med']:>9.2f} " for g in st["groups"]))
tick("§4")
# ============================================================ §5 banda d'ancora
hr(f"§5. BANDA D'ANCORA — {len(OFFSETS)} offset x {len(PHASES)} fasi, "
f"funding ON/OFF, {len(PNAMES)} politiche")
print(f" offset (sottocampione uniforme DICHIARATO dei 23): {list(OFFSETS)}")
print(f" fasi XS01: {list(PHASES)} (lo stesso sottocampione di §37)")
print(f" leva {LEV_MAIN:.2f}x fissa e uguale per tutte (confronto appaiato); "
f"{N_PATHS} percorsi condivisi.")
cells = []
REF: dict[str, np.ndarray] = {} # vettori per-percorso alla cella di riferimento
for off in OFFSETS:
for ph in PHASES:
for fund in (False, True):
R, G = build(off, ph, fund)
res = ladder_multi(R, G, SEL, N_PATHS, LEV_MAIN, SEED, DELUCK_NEW, boot=boot)
for pi, g in enumerate(res["groups"]):
cells.append(dict(off=off, ph=ph, fund=fund, pol=SHORT[pi],
p50=g["p_target"], pz=g["p_zero"], med=g["med"],
p10g=g["p_ge10"], mean=g["mean"], ever2=g["p_ever2"],
slot1=g["p_slot1"], ntix=g["n_tickets"],
maxc=g["max_conc"]))
if off == 0 and ph == PHASES[0] and fund:
REF[SHORT[pi]] = g["eur_day"]
tick(f"offset {off}")
C = pd.DataFrame(cells)
for fund, flab in ((True, "CON funding (la lente onesta)"), (False, "SENZA funding")):
s = C[C["fund"] == fund]
print(f"\n --- {flab} — mediana sulle {len(OFFSETS)*len(PHASES)} celle d'ancora")
print(f" {'politica':>10} {'P(>=50/g)':>10} {'banda ancora p10-p90':>22} {'P(zero)':>9} "
f"{'EUR/g med':>10} {'EUR/g medio':>12} {'P(>=10/g)':>10} {'P(2o conto)':>12}")
for p in SHORT:
q = s[s["pol"] == p]
print(f" {p:>10} {q['p50'].median():>10.2%} "
f"[{np.percentile(q['p50'],10):>9.2%},{np.percentile(q['p50'],90):>9.2%}] "
f"{q['pz'].median():>9.1%} {q['med'].median():>10.2f} "
f"{q['mean'].median():>12.2f} {q['p10g'].median():>10.1%} "
f"{q['slot1'].median():>12.1%}")
print(" ordine per metrica (se le tre non concordano, la classifica e' un artefatto")
print(" della metrica scelta, non un fatto):")
for mkey, mlab in (("p50", "P(>=50/g)"), ("mean", "EUR/g medio"), ("p10g", "P(>=10/g)")):
o = s.groupby("pol")[mkey].median().sort_values(ascending=False)
print(f" {mlab:<12} {' > '.join(o.index)}")
# riproduzione di §37 (W_A = CONC-A, funding dentro)
q = C[(C["fund"]) & (C["pol"] == "CONC-A")]
print(f"\n RIPRODUZIONE §37 (CONC-A = W_A 38/38/25, funding dentro, mediana d'ancora):")
print(f" P(>=50/g) {q['p50'].median():.1%} (PUB {PUB_GATEC['p50g']:.1%}) | "
f"P(zero) {q['pz'].median():.1%} (PUB {PUB_GATEC['pzero']:.1%})")
print(" ⚠ non e' identica per costruzione: §37 usa `best_lev` per configurazione e 23")
print(" offset x 3 fasi; qui la leva e' FISSA a 1,00x per tutte le politiche (appaiato)")
print(f" e gli offset sono {len(OFFSETS)}. L'ordine di grandezza e' il controllo.")
# differenze APPAIATE cella per cella
print(f"\n DIFFERENZE APPAIATE cella per cella (mediana delle differenze, mai differenza")
print(f" delle mediane), funding dentro. Riferimento = MISTO (il vincitore del 25/07):")
sf = C[C["fund"]]
piv = sf.pivot_table(index=["off", "ph"], columns="pol", values="p50")
ncell = len(piv)
print(f" {'politica':>10} {'d P(>=50/g) vs MISTO':>22} {'>0 su celle':>13} "
f"{'p10':>8} {'p90':>8}")
for p in SHORT:
if p == "MISTO":
continue
d = piv[p] - piv["MISTO"]
print(f" {p:>10} {d.median()*100:>21.2f}pp {int((d>0).sum()):>7}/{ncell:<5} "
f"{np.percentile(d,10)*100:>7.2f}pp {np.percentile(d,90)*100:>7.2f}pp")
# --- controllo di LEVA: l'ordine dipende dalla leva scelta?
print(f"\n CONTROLLO DI LEVA — le stesse celle a {LEV_ALT:.2f}x (off 0, {len(PHASES)} fasi,")
print(f" funding ON). Se l'ordine cambia con la leva, non e' una proprieta' della politica:")
alt = []
for ph in PHASES:
R, G = build(0, ph, True)
res = ladder_multi(R, G, SEL, N_PATHS, LEV_ALT, SEED, DELUCK_NEW, boot=boot)
for pi, g in enumerate(res["groups"]):
alt.append(dict(ph=ph, pol=SHORT[pi], p50=g["p_target"], pz=g["p_zero"],
mean=g["mean"]))
A = pd.DataFrame(alt)
oa = A.groupby("pol")["p50"].median().sort_values(ascending=False)
o1 = C[(C["fund"]) & (C["off"] == 0)].groupby("pol")["p50"].median().sort_values(ascending=False)
print(f" {'politica':>10} {'P(>=50/g) @0,75x':>18} {'P(>=50/g) @1,00x':>18} {'P(zero) @0,75x':>16}")
for p_ in SHORT:
print(f" {p_:>10} {A[A['pol']==p_]['p50'].median():>17.2%} "
f"{o1[p_]:>17.2%} {A[A['pol']==p_]['pz'].median():>15.1%}")
print(f" ordine @0,75x: {' > '.join(oa.index)}")
print(f" ordine @1,00x: {' > '.join(o1.index)}")
print(f" -> {'STESSO ordine' if list(oa.index) == list(o1.index) else 'ORDINE DIVERSO: la leva conta'}")
tick("§5")
# ============================================================ §6 la previsione sul funding
hr("§6. LA PREVISIONE A2 SUL FUNDING, VERIFICATA (appaiata cella per cella)")
pv = C.pivot_table(index=["off", "ph", "pol"], columns="fund", values="p50")
pv = pv.reset_index()
print(f" {'politica':>10} {'costo fund %/a':>15} {'d P(>=50/g)':>13} {'<0 su celle':>13} "
f"{'d EUR/g med':>13}")
dmed = {}
for p in SHORT:
q = pv[pv["pol"] == p]
d = (q[True] - q[False]).values
dmed[p] = float(np.median(d))
pm = C.pivot_table(index=["off", "ph", "pol"], columns="fund", values="med").reset_index()
qm = pm[pm["pol"] == p]
dm = (qm[True] - qm[False]).values
print(f" {p:>10} {polfund[p]:>14.2f}% {np.median(d)*100:>12.2f}pp "
f"{int((d<0).sum()):>7}/{len(d):<5} {np.median(dm):>12.2f}")
ordf_on = C[C["fund"]].groupby("pol")["p50"].median().sort_values(ascending=False).index.tolist()
ordf_off = C[~C["fund"]].groupby("pol")["p50"].median().sort_values(ascending=False).index.tolist()
a2 = ordf_on == ordf_off
print(f"\n ordine SENZA funding: {' > '.join(ordf_off)}")
print(f" ordine CON funding: {' > '.join(ordf_on)}")
print(f" >>> A2 {'CONFERMATA' if a2 else 'REFUTATA'}: il funding "
f"{'NON cambia' if a2 else 'CAMBIA'} l'ordine fra politiche.")
cc = np.corrcoef([polfund[p] for p in SHORT], [dmed[p] for p in SHORT])[0, 1]
print(f" correlazione (costo di funding della politica, danno misurato): {cc:+.2f} "
f"[atteso negativo: chi paga di piu' perde di piu']")
tick("§6")
# ============================================================ §7 numero di conti
hr("§7. LA DIMENSIONE MAI TESTATA — il NUMERO DI CONTI non e' dato, si COMPRA")
Rf, Gf = build(0, 0, True)
scen = [("nessun versamento, quota persa", 0.0, False),
("nessun versamento, quota RIMBORSATA (§30)", 0.0, True),
("EUR 500/mese, quota persa", 500.0, False),
("EUR 500/mese, quota RIMBORSATA", 500.0, True)]
trajs = {}
for lab_s, dep, ref_ in scen:
res = ladder_multi(Rf, Gf, SEL, N_PATHS, LEV_MAIN, SEED, DELUCK_NEW,
refund=ref_, deposit_eur=dep, boot=boot, track=True)
trajs[lab_s] = res
print(f"\n --- {lab_s}")
print(f" {'politica':>10} {'biglietti':>10} {'max conti':>10} {'P(mai >=2)':>11} "
f"{'P(>=50/g)':>10} {'EUR/g med':>10} {'P(zero)':>9} {'rimborso $':>11}")
for pi, g in enumerate(res["groups"]):
print(f" {SHORT[pi]:>10} {g['n_tickets']:>10.2f} {g['max_conc']:>10.2f} "
f"{1-g['p_ever2']:>10.1%} {g['p_target']:>10.1%} {g['med']:>10.2f} "
f"{g['p_zero']:>9.1%} {g['mean_back']:>11.0f}")
print("\n TRAIETTORIA del numero MEDIO di conti funded vivi (mesi 6/12/18/24/30/36):")
for lab_s in trajs:
tr = trajs[lab_s]["traj"]
print(f" {lab_s:<44}" + "".join(
f"{tr[m-1].mean():>7.2f}" for m in (6, 12, 18, 24, 30, 36)))
print("\n DISPERSIONE fra politiche (max-min di P(>=50/g)) per scenario — quanto CONTA")
print(" la scelta della politica:")
for lab_s in trajs:
v = [g["p_target"] for g in trajs[lab_s]["groups"]]
best = SHORT[int(np.argmax(v))]
print(f" {lab_s:<44} spread {max(v)-min(v):>6.2%} vincitore {best}")
tick("§7")
# ============================================================ §8 MDE e verdetto
hr("§8. MDE — la finestra distingue davvero due politiche?")
sf = C[C["fund"]]
piv = sf.pivot_table(index=["off", "ph"], columns="pol", values="p50")
print(" TRE risoluzioni, e sono cose DIVERSE. Confonderle e' il modo standard di trasformare")
print(" rumore in classifica.")
print(f"\n (a) MONTE CARLO appaiato, cella di riferimento (off 0, fase {PHASES[0]}, funding ON):")
print(" i due esiti sono osservati sugli STESSI percorsi -> SE di McNemar sui discordanti.")
print(f" {'coppia':>22} {'d P(>=50/g)':>13} {'discordanti':>12} {'SE':>8} {'z':>8}")
base = REF["MISTO"] >= TARGET
for p_ in SHORT:
if p_ == "MISTO":
continue
oth = REF[p_] >= TARGET
n01 = int((base & ~oth).sum())
n10 = int((~base & oth).sum())
d = float(base.mean() - oth.mean())
se = float(np.sqrt(n01 + n10) / len(base)) if (n01 + n10) else float("nan")
z = d / se if se and se > 0 else float("nan")
print(f" {'MISTO - ' + p_:>22} {d*100:>+12.2f}pp {n01 + n10:>12} "
f"{se*100:>7.2f}pp {z:>+8.2f}")
print(" ⚠ e' la risoluzione della SIMULAZIONE, non dell'evidenza: dice se due politiche")
print(" differiscono SU QUESTI percorsi, non se differirebbero nel mondo.")
print(f"\n (b) BANDA D'ANCORA sulle {len(piv)} celle (mediana delle differenze APPAIATE).")
print(" 🚨 NON e' un intervallo di confidenza e non ci si attacca un p-value (regola §57:")
print(" le 24 ancore correlano 0,63 e valgono ~2 osservazioni indipendenti). Si legge come")
print(" robustezza alla SCELTA dell'ancora, e basta:")
print(f" {'coppia':>22} {'mediana':>11} {'p10':>11} {'p90':>11} {'segno tenuto':>14}")
for p_ in SHORT:
if p_ == "MISTO":
continue
d = (piv["MISTO"] - piv[p_]).values
print(f" {'MISTO - ' + p_:>22} {np.median(d)*100:>+10.2f}pp "
f"{np.percentile(d,10)*100:>+10.2f}pp {np.percentile(d,90)*100:>+10.2f}pp "
f"{int((d>0).sum()):>6}/{len(d):<7}")
yrs = len(I) / 365.25
print(f"\n (c) DATO — l'unica che limita davvero. La finestra e' {yrs:.1f} anni e i {N_PATHS}")
print(f" percorsi sono ricampionamenti degli STESSI {len(I)} giorni: il bootstrap non crea")
print(f" informazione. MDE sul drift ~ 1,96/sqrt({yrs:.1f}) = {1.96/np.sqrt(yrs):.2f} di Sharpe.")
print(" Il differenziale di FUNDING fra politiche misurato in §3 e' ~0,10pp/anno di drift:")
print(" irrisorio rispetto all'MDE. Cio' che le politiche possono davvero separare non e'")
print(" un differenziale di rendimento ma la GEOMETRIA — correlazione fra conti e")
print(" barriera per-conto — e quella si misura sulla forma, non sul drift.")
hr("VERDETTO")
fin = C[C["fund"]].groupby("pol")["p50"].median().sort_values(ascending=False)
fin_med = C[C["fund"]].groupby("pol")["med"].median()
fin_pz = C[C["fund"]].groupby("pol")["pz"].median()
for p in fin.index:
print(f" {p:>10} P(>=50/g) {fin[p]:>6.2%} EUR/g med {fin_med[p]:>6.2f} "
f"P(zero) {fin_pz[p]:>6.1%}")
win = fin.index[0]
print(f"\n VINCITORE OGGI: {win} (25/07: MISTO)")
print(f" celle valutate: {len(C)} righe = {len(OFFSETS)}x{len(PHASES)}x2x{len(PNAMES)}; "
f"valutazioni MC totali contate al rialzo: {TRIALS}")
print(f" tempo {time.time()-T0:.0f}s")
print("=" * 104)
if __name__ == "__main__":
main()
@@ -0,0 +1,877 @@
#!/usr/bin/env python
"""r0823b_obiettivo_diretto — OTTIMIZZARE DIRETTAMENTE L'OBIETTIVO DEL PROGETTO (2026-08-23).
§58 dell'ondata. Libro, pesi, cron, config: NON TOCCATI (isolamento provato in §0).
IL BUCO
-------
Ogni ottimizzazione fatta in questo progetto ha massimizzato **un'altra cosa**:
* lo **Sharpe** (ogni filone di ricerca direzionale);
* **J = P(pass) x P(alive)** (PROP-ALLOC §17, PROP-RECAL §29, SKH-ANCHOR-PROP §32);
* **E[payout]** (r0725_hyro, §37);
* il **capitale mediano** o **P(arrivare al capitale-rendita)** (§36, r0807_piano_netto).
E il critico di §37 ha scritto la frase che apre questo filone:
«piu' leva sul funded ALZA E[payout] e ABBASSA P(payout): sono due obiettivi diversi».
**Nessuno ha mai ottimizzato DIRETTAMENTE `P(reddito >= 50 EUR/giorno entro H)`**, che e'
l'obiettivo DICHIARATO del progetto. I numeri operativi pubblicati (42% -> 7,8% -> 4,4% ->
2,6%) sono tutti **letti alla cella che massimizza J**, non alla cella che massimizza P.
LE DUE DEFINIZIONI, DICHIARATE PRIMA DI MISURARE
------------------------------------------------
Il progetto ne usa DUE senza mai metterle sulla stessa pagina, e danno risposte opposte:
**A — RICCHEZZA (rendita sostenibile).** «reddito >= 50 EUR/g» = il patrimonio PROPRIO
raggiunge il **capitale-rendita** (muro), cioe' il capitale da cui si preleva 50 EUR/g con
P(capitale a 20 anni >= capitale iniziale) >= 90% (definizione 25/07, invariata). E' la
definizione dei muri, delle traiettorie e di tutto il piano (§36). E' **sostenibile per
costruzione**.
**B — FLUSSO (produzione realizzata).** «reddito >= 50 EUR/g» = la media dei payout incassati
sull'orizzonte, `cum_payout / giorni`, >= 50 EUR/g. E' la definizione di `r0725_prop_ladder`
e quindi di **ogni numero del canale funded** (§29 7,8% · §32 4,4% · §37 2,6%). **NON e'
sostenibile**: un conto funded che busta il giorno dopo l'orizzonte azzera il flusso, e la
stessa cassa che il contatore chiama «reddito» resta nel conto (nel simulatore ufficiale la
cassa si accumula E conta come reddito: e' una PRODUZIONE, non un prelievo).
Qui si misurano **entrambe**, sullo stesso Monte Carlo e sugli stessi percorsi, e si riporta
come cambia la risposta. Primaria = **A**, perche' e' quella su cui il progetto ha costruito il
muro, i versamenti e la decisione di venue; B si riporta perche' e' quella dei numeri pubblicati.
LO SPAZIO DI POLITICA — dichiarato esaustivamente PRIMA di misurare
--------------------------------------------------------------------
1. `k` leva del libro proprio {1,00 · 1,25 · 1,50} (§38: la chiave NON esiste)
2. `own` ripartizione del libro proprio {LIVE 75/25 · W_A 38/38/25}
3. `lev_fu` leva del conto funded {0,50 · 0,75 · 1,00 · 1,25}
4. `n_max` numero di conti concorrenti {0 · 1 · 3 · 6} (0 = nessun biglietto)
5. `pref` taglia del biglietto {grande · piccolo}
6. `rebuy` ri-comprare dopo un bust {si' · no}
7. `floor` capitale proprio sotto cui NON si compra {$0 · $2.000 · $10.000}
8. `dep` versamento mensile {EUR 0 · 250 · 500} (il piano in corso e' 500)
9. `invest` la cassa lavora sul libro invece di restare ferma {si' · no}
Il punto (9) e' un difetto misurabile della macchineria pubblicata: in `pl.simulate` /
`pc.simulate` / `gate_c.ladder` **la cassa dei payout resta FERMA per 36 mesi**.
LE MIE ATTESE, REGISTRATE PRIMA DI MISURARE
--------------------------------------------
A1. L'argmax di P(>=50/g) sara' a leva **piu' alta** dell'argmax di J, e la differenza sara'
grande (fattore >2 su P). *Motivo: P(>=50/g) e' una probabilita' di superare una soglia
alta = coda destra; J e' una probabilita' di sopravvivenza = coda sinistra.*
A2. Sotto la definizione **A** (ricchezza) il canale funded **non** portera' P(36 mesi) sopra
zero in modo apprezzabile: il muro e' ~$313k e 36 mesi da $635 sono ~3 ordini di grandezza.
Quindi la risposta alla domanda «esiste una politica sopra il 20% a 36 mesi?» sara'
**SI' sotto B e NO sotto A**, e questa e' la vera risposta del filone.
A3. Investire la cassa invece di lasciarla ferma valdra' poco su B (il flusso non dipende dalla
cassa) e molto su A. *Se sbaglio e vale poco anche su A, lo dico.*
A4. L'ottimo di P sara' accompagnato da P(rovina) molto alta (>50%) — cioe' sara' un ottimo che
il progetto NON dovrebbe adottare, e va riportato come informazione, non come consiglio.
A5. Il vincolo binding a 36 mesi non sara' ne' la leva ne' il numero di conti ma il **cap
$200k/trader** oppure il **capitale che entra**. Mi aspetto il secondo.
CONVENZIONI
-----------
* Lente **ACCOPPIATA** ovunque ci sia una barriera a un giorno (regola 25/07, riconfermata §33).
* Cella d'ancora **MEDIANA** (non la canonica, che sta al 91° pctl — §32), scelta con lo stesso
criterio di `gate_c` su un sottoinsieme d'ancore dichiarato PRIMA.
* Funding **DENTRO** (§35), de-luck **x0,89** sul drift (§ r0726_deluck_factor), fisco 33% +
patrimoniale 0,2% sul libro proprio (§ r0807), morte-firm 10%/anno, cap $200k/trader.
* Baseline del libro: **`hourly`** (la lente di ogni muro e traiettoria) — dichiarata perche' il
critico di §57 ha misurato che sotto «75/25» convivono tre baseline diverse.
* Muro: **CONGELATO** a k=1 come primaria (si de-leva al traguardo: la leva non deve poter
spostare il bersaglio); **MOBILE** come sensibilita' (§53 dichiara che la differenza vale 1,2
anni). Banda del muro propagata dai valori di §57.
* Monte Carlo: la risoluzione si dichiara. 1500 percorsi -> SE ~1,0pp su una P vicina al 20%.
Uso: nice -n 19 timeout 900 uv run python scripts/research/r0823b_obiettivo_diretto.py
... --fast (griglia ridotta, ~1/3 del tempo)
Rete: NESSUNA (solo cache su disco: parquet certificati, cache Binance di §21, cache funding
di §35). Nessuna scrittura fuori da stdout.
"""
from __future__ import annotations
import subprocess
import sys
import time
from pathlib import Path
import numpy as np
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))
if "--no-net" not in sys.argv: # r0822d_funding legge USE_NET all'import
sys.argv.append("--no-net")
import r0725_capcurve as CC # noqa: E402 TAX_RATE, EURUSD, book_series
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata
import r0725_prop_ladder as pl # noqa: E402 regole firm, scala di conti
import r0727_tasse as TX # noqa: E402 PATRIMONIALE, CARRY_ANNI
import r0807_piano_netto as PN # noqa: E402 passo_fiscale, accumula, muro
import r0822_prop_alloc as PA # noqa: E402 boot_idx, DELUCK
import r0822b_prop_recal as PR # noqa: E402 obj_rg, LEVS, pannelli Binance
import r0822b_xs01_oos as X # noqa: E402 U13, motore XS01
import r0822c_skh_anchor_prop as AP # noqa: E402 rg_off (offset di SKH01)
import r0822d_funding as FD # noqa: E402 il funding
import r0822d_gate_c as GC # noqa: E402 ladder, fvec_for, xs_funding
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
FAST = "--fast" in sys.argv
EURUSD = pl.EURUSD
TAX = CC.TAX_RATE
PATR = TX.PATRIMONIALE
DELUCK = PA.DELUCK # x0,89 misurato
W_LIVE = {"TP01": 0.75, "SKH01": 0.25}
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25}
START_USD = 635.0 # il conto Deribit VERO di oggi
SEED_LAD = pl.HORIZON_MONTHS * 0 + 20260822 # lo stesso di gate_c (per la bit-exactness)
SEED_BOOT = 58023
N_PATHS = 900 if FAST else 1500
N_PATHS_J = PA.N_PATHS
MIN_PAYOUT = GC.MIN_PAYOUT_TRADER
ORIZZONTI_M = (24, 36, 60, 120)
H_MAX_D = max(ORIZZONTI_M) * 30
MARKS = tuple(m * 30 for m in ORIZZONTI_M)
OFFSETS_SUB = tuple(range(0, 690, 90)) if not FAST else (0, 180, 360, 540) # 8 (o 4) a priori
PHASES_SUB = AP.PHASES_SUB # (0, 3, 7) — dichiarato da §32
LEVS_FU = (0.50, 0.75, 1.00, 1.25)
NMAX = (1, 3, 6)
PREFS = ("grande", "piccolo")
REBUY = (True, False)
# numeri PUBBLICATI da riprodurre PRIMA di produrne di nuovi
PUB = dict(muro_L3=313_143.0, perp_L3=0.0635, drift_L3=0.1519,
p50g_fund=0.026, p50g_p10=0.015, p50g_p90=0.047, pzero_fund=0.403,
p50g_nofund=0.044,
muro_p90=186_623.0, muro_m1se=709_753.0) # banda di §57
TRIALS = 0
T0 = time.time()
def bump(n: int = 1) -> None:
global TRIALS
TRIALS += n
def hr(t: str = "") -> None:
print("\n" + "=" * 104)
if t:
print(" " + t)
print("=" * 104)
def tick(lab: str) -> None:
print(f" [{time.time() - T0:6.0f}s] {lab}", flush=True)
# ===========================================================================
# IL SIMULATORE UNIFICATO
# ===========================================================================
def uni(Ra: np.ndarray, Ga: np.ndarray, Rb: np.ndarray, boot: np.ndarray, *,
lev_fu: float = 1.0, n_max: int = 6, pref: str = "grande", rebuy: bool = True,
floor_usd: float = 0.0, k_book: float = 1.0, invest: bool = False,
dep_eur: float = 0.0, start_usd: float = pl.START_EUR * EURUSD,
seed: int = SEED_LAD, refund: bool = False, min_payout: float = 0.0,
tax: float = 0.0, patr: float = 0.0, walls: tuple = (),
marks: tuple = (), deposit_mode: str = "ladder",
deluck_f: float = DELUCK) -> dict:
"""UN simulatore per le DUE strade, sugli STESSI percorsi.
E' una GENERALIZZAZIONE STRETTA di `gate_c.ladder` (che a sua volta e' `pc.simulate` con
tre aggiunte): con `n_max=6, pref='grande', rebuy=True, floor=0, k_book=0, invest=False,
tax=0, patr=0, deposit_mode='ladder'` deve essere **BIT-EXACT** rispetto a `gate_c.ladder`.
E con `n_max=0, invest=True, tax=0.33, patr=0.002, deposit_mode='accumula'` deve essere
**BIT-EXACT** rispetto a `PN.accumula`. I due controlli sono in §1.
Cio' che aggiunge, e che nessuna macchina pubblicata ha:
* un **pot proprio** che compone al libro (`k_book x Rb`) invece di restare fermo, col
fisco d'accumulo dentro (stessa `PN.passo_fiscale` del piano);
* i **payout che rientrano nel pot** e quindi compongono;
* `floor_usd` — un capitale proprio sotto cui non si compra un altro biglietto;
* `rebuy` — la scelta di NON ricomprare dopo un bust;
* `pref` — biglietto piu' grande o piu' piccolo (piu' tentativi);
* il **primo passaggio** al capitale-rendita, per l'obiettivo A.
`Ra/Ga` = libro del conto FUNDED (ritorno e gap intraday accoppiato), `Rb` = libro PROPRIO.
Il de-luck si applica al DRIFT di R (non al gap): stessa convenzione di `ladder`.
"""
rng = np.random.default_rng(seed)
n_paths, n_days = boot.shape
Ram = np.asarray(Ra, float).copy()
Ram = Ram - (1.0 - deluck_f) * Ram.mean()
Gam = np.asarray(Ga, float)
Rbm = np.asarray(Rb, float).copy()
Rbm = Rbm - (1.0 - deluck_f) * Rbm.mean()
n_slot = int(n_max)
FIRMS = pl.FIRMS
cash = np.full(n_paths, float(start_usd))
basis = cash.copy() # base fiscale del pot
carry = np.zeros((n_paths, TX.CARRY_ANNI))
anno_start = cash.copy()
flusso = np.zeros(n_paths)
cum_payout = np.zeros(n_paths)
spent = np.zeros(n_paths)
got_back = np.zeros(n_paths)
deposited = np.zeros(n_paths)
n_payouts = np.zeros(n_paths, int)
n_tickets = np.zeros(n_paths, int)
n_pass = np.zeros(n_paths, int)
peak_notional = np.zeros(n_paths)
if n_slot:
Rs_full = Ram[boot] * lev_fu # (paths, days) — tutti i conti uguali
Gs_full = Gam[boot] * lev_fu
phase = np.full((n_paths, n_slot), -1, np.int8)
eq = np.ones((n_paths, n_slot))
firm = np.full((n_paths, n_slot), -1, np.int8)
notional = np.zeros((n_paths, n_slot))
price_paid = np.zeros((n_paths, n_slot))
refunded = np.zeros((n_paths, n_slot), bool)
used = np.zeros((n_paths, n_slot), bool)
firm_alive = np.ones((n_paths, len(FIRMS)), bool)
p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0)
Rbp = Rbm[boot] if (invest and k_book > 0) else None
hit = {w: np.full(n_paths, -1, int) for w in walls}
snap: dict[int, dict] = {}
fiscale = (tax > 0 or patr > 0)
for t in range(n_days):
# ---- morte di una firm (identico a ladder: il draw avviene SEMPRE, anche a 0 slot,
# solo se il canale funded esiste)
if n_slot:
died = rng.random((n_paths, len(FIRMS))) < p_death_day
newly_dead = died & firm_alive
firm_alive &= ~died
if newly_dead.any():
for fi in range(len(FIRMS)):
kill = newly_dead[:, fi]
if kill.any():
h = (firm == fi) & kill[:, None]
phase[h] = -1
notional[h] = 0.0
# ---- acquisto biglietti
for k in range(n_slot):
free = phase[:, k] == -1
if not rebuy:
free &= ~used[:, k]
if not free.any():
continue
for fi, F in enumerate(FIRMS):
u = np.where(firm == fi, notional, 0.0).sum(axis=1)
order = sorted(F["tickets"], key=(lambda x: -x[0]) if pref == "grande"
else (lambda x: x[0]))
for (size, price) in order:
buy = (free & firm_alive[:, fi] & (cash >= price)
& (cash - price >= floor_usd) & (u + size <= F["cap"]))
if not buy.any():
continue
cash[buy] -= price
flusso[buy] -= price
spent[buy] += price
n_tickets[buy] += 1
phase[buy, k] = 0
eq[buy, k] = 1.0
firm[buy, k] = fi
notional[buy, k] = size
price_paid[buy, k] = price
refunded[buy, k] = False
used[buy, k] = True
free = free & ~buy
if not free.any():
break
if not free.any():
break
# ---- dinamica dei conti
r = Rs_full[:, t][:, None]
g = Gs_full[:, t][:, None]
act = phase >= 0
eq_prev = eq
eq = np.where(act, eq * (1.0 + r), eq)
eq_low = np.where(act, eq_prev * (1.0 + r + g), eq)
r_low = r + g
for fi, F in enumerate(FIRMS):
s_ = act & (firm == fi)
if not s_.any():
continue
ev_t, ev_dd, ev_dl = F["ev"]
fu_ml, fu_dl = F["fu"]
in_ev = s_ & (phase == 0)
in_fu = s_ & (phase == 1)
bust = ((in_ev & ((eq_low < 1.0 - ev_dd) | (r_low < -ev_dl)))
| (in_fu & ((eq_low < 1.0 - fu_ml) | (r_low < -fu_dl))))
phase[bust] = -1
notional[bust] = 0.0
passed = in_ev & (eq >= 1.0 + ev_t) & ~bust
phase[passed] = 1
eq[passed] = 1.0
n_pass += passed.sum(axis=1)
peak_notional = np.maximum(peak_notional,
np.where(phase == 1, notional, 0.0).sum(axis=1))
# ---- il POT proprio compone
if Rbp is not None:
cash = cash * (1.0 + k_book * Rbp[:, t])
# ---- versamento (convenzione `accumula`: giorno 30, 60, ...)
if deposit_mode == "accumula" and dep_eur and t % 30 == 0 and t > 0:
cash += dep_eur * EURUSD
flusso += dep_eur * EURUSD
deposited += dep_eur * EURUSD
# ---- payout mensile + rimborso + versamento (convenzione `ladder`)
if (t + 1) % 30 == 0:
if n_slot:
fu = phase == 1
pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * pl.SPLIT) >= min_payout)
gain = np.where(pay, (eq - 1.0) * notional, 0.0)
net = gain.sum(axis=1) * pl.SPLIT * (1.0 - TAX)
cum_payout += net
cash += net
flusso += net
n_payouts += (pay.sum(axis=1) > 0).astype(int)
eq = np.where(pay, 1.0, eq)
if refund:
back = pay & ~refunded
amt = np.where(back, price_paid, 0.0).sum(axis=1)
cash += amt
flusso += amt
got_back += amt
refunded |= back
if deposit_mode == "ladder" and dep_eur:
cash += dep_eur * EURUSD
flusso += dep_eur * EURUSD
deposited += dep_eur * EURUSD
# ---- primo passaggio al muro (obiettivo A)
for w, arr in hit.items():
nuovi = (arr < 0) & (cash >= w)
arr[nuovi] = t
# ---- fisco d'accumulo sul pot
if fiscale and (t + 1) % 365 == 0:
PN.passo_fiscale(cash, anno_start, flusso, carry, tax, patr)
anno_start = cash.copy()
flusso[:] = 0.0
basis = cash.copy()
if (t + 1) in marks:
d = t + 1
ed = cum_payout / EURUSD / (d / 365.0) / 365.0
contrib = float(start_usd) + deposited
snap[d] = dict(p_flow=float((ed >= pl.TARGET_EUR_DAY).mean()),
p_flow10=float((ed >= 10.0).mean()),
med_eur=float(np.median(ed)),
med_cash=float(np.median(cash)),
mean_cash=float(cash.mean()),
p10_cash=float(np.percentile(cash, 10)),
p_zero=float((cum_payout <= 0).mean()),
p_sotto=float((cash < contrib).mean()),
p_rovina=float((cash < 0.10 * contrib).mean()),
contrib=float(contrib.mean() if np.ndim(contrib) else contrib),
**{f"A{w:.0f}": float(((hit[w] >= 0) & (hit[w] < d)).mean())
for w in walls})
ed = cum_payout / EURUSD / (n_days / 365.0) / 365.0
out = dict(eur_day=ed, cash=cash, snap=snap, hit=hit,
p_target=float((ed >= pl.TARGET_EUR_DAY).mean()),
p_ge10=float((ed >= 10.0).mean()),
p_zero=float((cum_payout <= 0).mean()),
med=float(np.median(ed)),
med_cash=float(np.median(cash)), mean_cash=float(cash.mean()),
p10_cash=float(np.percentile(cash, 10)),
p90_cash=float(np.percentile(cash, 90)),
med_spent=float(np.median(spent)), mean_back=float(got_back.mean()),
n_tickets=float(n_tickets.mean()), n_pass=float(n_pass.mean()),
peak_notional=float(np.median(peak_notional)),
contributed=float(start_usd + deposited.mean()))
bump(1)
return out
def p_wall_entro(res: dict, wall: float, giorni: int) -> float:
"""P(il patrimonio PROPRIO tocca `wall` entro `giorni`) — obiettivo A."""
h = res["hit"][wall]
return float(((h >= 0) & (h < giorni)).mean())
def anni_med(res: dict, wall: float) -> tuple[float, float]:
"""(mediana INCONDIZIONATA degli anni al traguardo, P(arrivo entro la simulazione)).
Non-arrivo = +inf, MAI -1 (lezione 07/08)."""
h = res["hit"][wall]
y = np.where(h >= 0, h / 365.0, np.inf)
return float(np.median(y)), float(np.isfinite(y).mean())
# ===========================================================================
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
print("=" * 104)
print(" §58 OBIETTIVO-DIRETTO — ottimizzare `P(reddito >= 50 EUR/g entro H)`, e non altro")
print("=" * 104)
print(f" {'MODALITA RIDOTTA (--fast)' if FAST else 'griglia piena'} | "
f"{N_PATHS} percorsi | orizzonti {ORIZZONTI_M} mesi")
# ------------------------------------------------------------ §0 isolamento
hr("§0. ISOLAMENTO — questo script NON tocca la produzione (il cron gira dal working tree)")
prot = ["src", "config", "scripts/live", "tests", "data/paper_portfolio", "data/live"]
dirty = subprocess.run(["git", "status", "--porcelain", "--"] + prot,
cwd=ROOT, capture_output=True, text=True).stdout.strip()
print(f" git status sui percorsi protetti {prot}:")
print(f" {'PULITO — nessuna modifica' if not dirty else 'SPORCO: ' + dirty}")
if dirty:
raise SystemExit("La produzione risulta modificata: mi fermo.")
print(" Nessuna scrittura su disco in tutto lo script (solo stdout). Rete: nessuna.")
# ------------------------------------------------------------ §1 repliche
hr("§1. REPLICHE — prima di un numero nuovo, la macchina deve riprodurre i vecchi")
ok: dict[str, bool] = {}
# (R1) il muro L3 (fisco + funding), $313.143 — §36, replicato al dollaro da §57
print("\n R1 IL MURO della lente congiunta L3 (funding dentro, fisco dentro, de-luck x0,89)")
rateF = GC.funding_rate()
if rateF is None:
raise SystemExit("cache funding assente: senza di essa nessun numero di questo filone "
"sarebbe confrontabile coi pubblicati.")
bookA = FD.book_75_25(None)
bookF = FD.book_75_25(rateF).reindex(bookA.index)
idxF = pd.DatetimeIndex(bookA.index)
rL3 = np.asarray(bookF.values, float)
rL3 = rL3 - (1.0 - DELUCK) * rL3.mean()
prel = CC.TARGET_EUR_DAY * 365 * CC.EURUSD
t = time.time()
perp, MURO = PN.muro(rL3, prel, TAX, PATR)
ok["R1"] = abs(MURO - PUB["muro_L3"]) < 1.0
print(f" serie {len(rL3)} giorni {idxF.min().date()} -> {idxF.max().date()} | "
f"drift {rL3.mean()*365:.4%} (pubbl. {PUB['drift_L3']:.2%})")
print(f" perpetua {perp:.4%} (pubbl. {PUB['perp_L3']:.2%}) muro ${MURO:,.0f} "
f"(pubbl. ${PUB['muro_L3']:,.0f}) -> "
f"{'ESATTO AL DOLLARO' if ok['R1'] else 'DIVERSO'} ({time.time()-t:.0f}s)")
bump(1)
tick("R1")
# ------------------------------------------------- il pannello comune
print("\n Il pannello comune (identico a §29/§32/§37): U13 Binance, W-LONG 2021+.")
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
XSP = {ph: PR.xs_tuples(p13, Ob.reindex(p13.index)[X.U13], Hb.reindex(p13.index)[X.U13],
Lb.reindex(p13.index)[X.U13], phase=ph) for ph in PHASES_SUB}
xs0 = XSP[PHASES_SUB[0]]
base = pc.crypto_daily_tuples(1.0, 0.0).index.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
I = base[base >= max(first13, pd.Timestamp("2021-01-01", tz="UTC"))]
pc.DELUCK = DELUCK
ev_b = PA.boot_idx(len(I), 365, N_PATHS_J, seed=8220)
fu_b = PA.boot_idx(len(I), 365, N_PATHS_J, seed=8221)
xsf = GC.xs_funding_measure(verbose=False)
xs_ann = xsf["ann"]
print(f" {len(I)} giorni {I[0].date()} -> {I[-1].date()} | "
f"XS01 paga {xs_ann*100:+.2f}%/anno di funding (misurato §37)")
tick("pannello")
# (R2) la cella d'ancora MEDIANA e il numero operativo 2,6% [1,5%, 4,7%]
print(f"\n R2 IL NUMERO OPERATIVO DI §37 — {len(OFFSETS_SUB)} offset x {len(PHASES_SUB)} "
f"fasi = {len(OFFSETS_SUB)*len(PHASES_SUB)} celle, funding DENTRO, leva argmax-J")
print(" (sottoinsieme d'ancore dichiarato PRIMA; §37 ne usa 23x3, qui se ne usano "
f"{len(OFFSETS_SUB)}x{len(PHASES_SUB)} per il tempo macchina)")
cells = []
for off in OFFSETS_SUB:
fA = GC.fvec_for(W_A, I, rateF, off, xs_ann)
for ph in PHASES_SUB:
R0, G0 = AP.rg_off(W_A, I, XSP[ph], off)
Rx = R0 - fA
jb, lb = -1.0, None
for lv in PR.LEVS:
j = PR.obj_rg(Rx, G0, lv, ev_b, fu_b)["J"]
if j > jb:
jb, lb = j, lv
bump(len(PR.LEVS))
lad = GC.ladder(Rx, G0, N_PATHS, lb, SEED_LAD, refund=False)
cells.append(dict(off=off, ph=ph, J=jb, lev=lb, p50=lad["p_target"],
pz=lad["p_zero"], med=lad["med"]))
bump(1)
C = pd.DataFrame(cells)
med_p, p10, p90 = C["p50"].median(), np.percentile(C["p50"], 10), np.percentile(C["p50"], 90)
ok["R2"] = PUB["p50g_p10"] <= med_p <= PUB["p50g_p90"]
print(f" P(>=50/g) mediana {med_p:.1%} banda [{p10:.1%}, {p90:.1%}] "
f"P(zero) {C['pz'].median():.1%}")
print(f" pubblicato §37: {PUB['p50g_fund']:.1%} [{PUB['p50g_p10']:.1%}, "
f"{PUB['p50g_p90']:.1%}], P(zero) {PUB['pzero_fund']:.1%} -> "
f"{'DENTRO LA BANDA' if ok['R2'] else 'FUORI'}")
k_med = int((C["p50"] - C["p50"].median()).abs().idxmin())
OFF_M, PH_M, LEV_J = int(C.loc[k_med, "off"]), int(C.loc[k_med, "ph"]), float(C.loc[k_med, "lev"])
print(f" CELLA D'ANCORA MEDIANA: offset {OFF_M} min, fase {PH_M}, leva argmax-J "
f"{LEV_J:.2f}x (non la canonica: 91° pctl, §32)")
fM = GC.fvec_for(W_A, I, rateF, OFF_M, xs_ann)
R_M0, G_M = AP.rg_off(W_A, I, XSP[PH_M], OFF_M)
R_M = R_M0 - fM
tick("R2")
# il libro PROPRIO sullo stesso pannello
rb_full = FD.book_75_25(rateF)
if rb_full.index.tz is None:
rb_full.index = rb_full.index.tz_localize("UTC")
RB_LIVE = np.nan_to_num(rb_full.reindex(I).values.astype(float))
fA_own = GC.fvec_for(W_A, I, rateF, OFF_M, xs_ann)
RB_WA = AP.rg_off(W_A, I, XSP[PH_M], OFF_M)[0] - fA_own
print(f"\n libro PROPRIO sul pannello comune:")
for lab, r in (("LIVE 75/25", RB_LIVE), ("W_A 38/38/25", RB_WA)):
print(f" {lab:<14} drift {r.mean()*365:>7.2%} vol {r.std()*365**.5:>6.2%} "
f"Sharpe {r.mean()*365/(r.std()*365**.5):>5.2f}")
# bootstrap CONDIVISO (confronto appaiato fra tutte le politiche)
rngb = np.random.default_rng(SEED_BOOT)
nb = int(np.ceil(H_MAX_D / 20))
st = rngb.integers(0, len(I) - 20, size=(N_PATHS, nb))
BOOT = (st[:, :, None] + np.arange(20)[None, None, :]).reshape(N_PATHS, -1)[:, :H_MAX_D]
B36 = BOOT[:, :36 * 30]
print(f" bootstrap CONDIVISO: {N_PATHS} percorsi x {H_MAX_D} giorni, blocchi 20 -> "
f"OGNI confronto di questo filone e' APPAIATO")
# (R3) bit-exactness: `uni` in modalita' ladder == `gate_c.ladder`
print("\n R3 BIT-EXACTNESS — `uni` in modalita' ladder deve essere `gate_c.ladder`")
ref = GC.ladder(R_M, G_M, N_PATHS, LEV_J, SEED_LAD, refund=False, boot=B36)
mine = uni(R_M, G_M, RB_LIVE, B36, lev_fu=LEV_J, n_max=pl.MAX_CONCURRENT, pref="grande",
rebuy=True, floor_usd=0.0, k_book=0.0, invest=False, dep_eur=0.0,
start_usd=pl.START_EUR * EURUSD, seed=SEED_LAD, refund=False, min_payout=0.0,
tax=0.0, patr=0.0, deposit_mode="ladder")
d1 = float(np.abs(ref["eur_day"] - mine["eur_day"]).max())
d2 = float(np.abs(ref["cash"] - mine["cash"]).max())
ok["R3"] = (d1 == 0.0 and d2 == 0.0)
print(f" max|dif| EUR/g = {d1:.3e} | max|dif| cassa = {d2:.3e} -> "
f"{'BIT-EXACT' if ok['R3'] else 'DIVERSO'}")
# (R4) bit-exactness: `uni` senza conti == `PN.accumula`
print("\n R4 BIT-EXACTNESS — `uni` senza canale funded deve essere `PN.accumula`")
paths_own = (RB_LIVE - (1.0 - DELUCK) * RB_LIVE.mean())[B36]
a = PN.accumula(paths_own, 500.0, MURO, TAX, PATR, start=START_USD)
m = uni(R_M, G_M, RB_LIVE, B36, n_max=0, k_book=1.0, invest=True, dep_eur=500.0,
start_usd=START_USD, tax=TAX, patr=PATR, walls=(MURO,),
deposit_mode="accumula")
d3 = float(np.abs(a["cap"] - m["cash"]).max())
d4 = int(np.abs(np.where(a["colpito"] >= 0, a["colpito"], -1) - m["hit"][MURO]).max())
ok["R4"] = (d3 < 1e-6 and d4 == 0)
print(f" max|dif| capitale = ${d3:.3e} | max|dif| giorno di primo passaggio = {d4} -> "
f"{'BIT-EXACT' if ok['R4'] else 'DIVERSO'}")
print(f" (capitale mediano a 36 mesi ${np.median(m['cash']):,.0f}, "
f"P(muro) {float((m['hit'][MURO]>=0).mean()):.1%})")
print(f"\n ESITO REPLICHE: {sum(ok.values())}/{len(ok)}"
+ ", ".join(f"{k}={'OK' if v else 'NO'}" for k, v in ok.items()))
if not all(ok.values()):
raise SystemExit("REPLICA INCOMPLETA: un numero nuovo sarebbe indistinguibile da un bug.")
tick("§1")
# ---- fine repliche; da qui i numeri NUOVI
# muro coerente col PANNELLO su cui gira la simulazione (2021+), come sensibilita'
rI = RB_LIVE - (1.0 - DELUCK) * RB_LIVE.mean()
lo, hi = 0.0, 0.40
for _ in range(13):
mid = (lo + hi) / 2
sv = PN.sopravvivenza(rI, 1e6, 1e6 * mid, 20, TAX, PATR, n_paths=1500)
lo, hi = (mid, hi) if sv["p10_end"] >= 1e6 else (lo, mid)
MURO_I = prel / lo if lo > 0.002 else float("inf")
WALLS = (PUB["muro_p90"], MURO, PUB["muro_m1se"])
print(f"\n IL BERSAGLIO (obiettivo A) — e la sua banda, propagata da §57:")
print(f" muro PRIMARIO (pubblicato, finestra piena 2019+): ${MURO:,.0f} "
f"(perpetua {perp:.2%})")
print(f" banda del drift +-1SE (§57): p90 ${PUB['muro_p90']:,.0f} ... "
f"-1SE ${PUB['muro_m1se']:,.0f}")
print(f" muro COERENTE col pannello 2021+ (drift {rI.mean()*365:.2%}): "
+ (f"${MURO_I:,.0f}" if np.isfinite(MURO_I) else "IRRAGGIUNGIBILE (perpetua ~0)"))
print(" ⚠️ La simulazione gira sul pannello 2021+ (l'unico dove XS01 esiste) mentre il muro")
print(" pubblicato e' calcolato sulla finestra piena: la disparita' e' DICHIARATA e la")
print(" riga qui sopra la quantifica. Primaria resta la pubblicata, per confrontabilita'.")
tick("muro")
def run(**kw) -> dict:
base = dict(lev_fu=1.0, n_max=0, pref="grande", rebuy=True, floor_usd=0.0,
k_book=1.0, invest=True, dep_eur=500.0, start_usd=START_USD,
seed=SEED_LAD, refund=True, min_payout=MIN_PAYOUT, tax=TAX, patr=PATR,
walls=WALLS, marks=MARKS, deposit_mode="ladder")
base.update(kw)
boot = base.pop("boot", BOOT)
return uni(base.pop("Ra", R_M), base.pop("Ga", G_M), base.pop("Rb", RB_LIVE),
boot, **base)
# ======================================================================
hr("§2. LE DUE DEFINIZIONI DI «REDDITO >= 50 EUR/g», SULLA STESSA POLITICA E SUGLI STESSI PATH")
print(" A = RICCHEZZA: il patrimonio PROPRIO tocca il capitale-rendita (sostenibile).")
print(" B = FLUSSO : media dei payout incassati >= 50 EUR/g (la definizione dei numeri")
print(" pubblicati del canale funded — NON sostenibile).")
print(f" Versamento: EUR 500/mese (il piano in `config/live.json`), start ${START_USD:,.0f}.")
REF = {
"LIBRO (oggi: 75/25, k=1, nessun biglietto)": dict(n_max=0),
"PROP come pubblicato (6 conti, leva argmax-J, cassa FERMA)":
dict(n_max=6, lev_fu=LEV_J, invest=False, k_book=0.0, tax=0.0, patr=0.0),
"PROP + cassa che lavora sul libro (stessa leva)":
dict(n_max=6, lev_fu=LEV_J, invest=True, k_book=1.0),
}
refres = {k: run(**v) for k, v in REF.items()}
print(f"\n {'politica':<52} {'H':>5} {'A: P(muro)':>11} {'B: P(>=50/g)':>13} "
f"{'mediana $':>11} {'P(<versato)':>12}")
for lab, r in refres.items():
for m in ORIZZONTI_M:
sn = r["snap"][m * 30]
print(f" {lab if m == ORIZZONTI_M[0] else '':<52} {m:>4}m "
f"{sn[f'A{MURO:.0f}']:>11.1%} {sn['p_flow']:>13.1%} "
f"${sn['med_cash']:>10,.0f} {sn['p_sotto']:>12.1%}")
print("\n 📌 Le due definizioni NON ordinano le politiche allo stesso modo, e a 36 mesi non")
print(" sono nemmeno commensurabili: sotto A il canale funded e' un ACCELERATORE di")
print(" capitale, sotto B e' l'unico modo di avere un flusso.")
tick("§2")
# ======================================================================
hr("§3. LA CURVA DI LEVA — dove sta l'argmax di CIASCUN obiettivo (attesa A1)")
print(f" Cella d'ancora mediana (offset {OFF_M}, fase {PH_M}), 6 conti, cassa che lavora,")
print(" EUR 500/mese. J e' calcolato con la STESSA `PR.obj_rg` di §29/§32/§37.")
print(f"\n {'leva':>6} {'J':>7} {'B: P(>=50/g) 36m':>17} {'A: P(muro) 120m':>16} "
f"{'P(zero)':>8} {'P(<versato)':>12} {'mediana $ 120m':>15}")
LEVS_ALL = PR.LEVS
curve = []
for lv in LEVS_ALL:
j = PR.obj_rg(R_M, G_M, lv, ev_b, fu_b)["J"]
bump(1)
r = run(n_max=6, lev_fu=lv)
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
curve.append(dict(lev=lv, J=j, B36=s36["p_flow"], A120=s120[f"A{MURO:.0f}"],
pz=s36["p_zero"], psotto=s120["p_sotto"], med=s120["med_cash"]))
print(f" {lv:>6.3f} {j:>7.3f} {s36['p_flow']:>17.1%} {s120[f'A{MURO:.0f}']:>16.1%} "
f"{s36['p_zero']:>8.1%} {s120['p_sotto']:>12.1%} ${s120['med_cash']:>14,.0f}")
CU = pd.DataFrame(curve)
aJ = float(CU.loc[CU["J"].idxmax(), "lev"])
aB = float(CU.loc[CU["B36"].idxmax(), "lev"])
aA = float(CU.loc[CU["A120"].idxmax(), "lev"])
print(f"\n argmax J = {aJ:.2f}x · argmax B(36m) = {aB:.2f}x · "
f"argmax A(120m) = {aA:.2f}x")
print(f" B alla leva argmax-J: {float(CU.loc[CU['J'].idxmax(),'B36']):.1%} -> "
f"B al proprio argmax: {CU['B36'].max():.1%} "
f"(fattore {CU['B36'].max()/max(float(CU.loc[CU['J'].idxmax(),'B36']),1e-9):.1f}x)")
print(f" ATTESA A1 ({'CONFERMATA' if aB > aJ and CU['B36'].max() > 2*float(CU.loc[CU['J'].idxmax(),'B36']) else 'PARZIALE/SMENTITA'}): "
"l'argmax di P sta a leva piu' alta di quello di J.")
tick("§3")
# ======================================================================
hr("§4. LO SPAZIO DI POLITICA, OTTIMIZZATO SU P DIRETTAMENTE (36 mesi, obiettivo B)")
grid = [dict(lev_fu=lv, n_max=nm, pref=pf, rebuy=rb)
for lv in LEVS_FU for nm in NMAX for pf in PREFS for rb in REBUY]
print(f" {len(grid)} celle dichiarate PRIMA: leva {LEVS_FU} x conti {NMAX} x "
f"taglia {PREFS} x rebuy {REBUY}")
print(f" fisse: libro proprio LIVE 75/25 k=1, cassa che lavora, floor $0, EUR 500/mese.")
rows = []
for g in grid:
r = run(**g)
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
rows.append(dict(**g, B36=s36["p_flow"], B24=r["snap"][24 * 30]["p_flow"],
B60=r["snap"][60 * 30]["p_flow"], B120=s120["p_flow"],
A36=s36[f"A{MURO:.0f}"], A120=s120[f"A{MURO:.0f}"],
A120lo=s120[f"A{PUB['muro_p90']:.0f}"],
A120hi=s120[f"A{PUB['muro_m1se']:.0f}"],
pz=s36["p_zero"], psotto36=s36["p_sotto"],
rov36=s36["p_rovina"], med120=s120["med_cash"],
ntick=r["n_tickets"]))
G = pd.DataFrame(rows)
print(f"\n LE 8 MIGLIORI SU B(36 mesi) — e cosa costano sugli altri assi:")
print(f" {'leva':>5} {'conti':>6} {'taglia':>8} {'rebuy':>6} {'B 24m':>7} {'B 36m':>7} "
f"{'B 60m':>7} {'A 120m':>7} {'P(zero)':>8} {'P(<vers) 36m':>13} {'bigl.':>6}")
for _, r in G.sort_values("B36", ascending=False).head(8).iterrows():
print(f" {r['lev_fu']:>5.2f} {int(r['n_max']):>6} {r['pref']:>8} "
f"{'si' if r['rebuy'] else 'no':>6} {r['B24']:>7.1%} {r['B36']:>7.1%} "
f"{r['B60']:>7.1%} {r['A120']:>7.1%} {r['pz']:>8.1%} {r['psotto36']:>13.1%} "
f"{r['ntick']:>6.1f}")
print(f"\n LE 6 MIGLIORI SU A(120 mesi, muro pubblicato ${MURO:,.0f}):")
print(f" {'leva':>5} {'conti':>6} {'taglia':>8} {'rebuy':>6} {'A 36m':>7} {'A 120m':>7} "
f"{'A120 p90':>9} {'A120 -1SE':>10} {'B 36m':>7} {'mediana $ 120m':>15}")
for _, r in G.sort_values("A120", ascending=False).head(6).iterrows():
print(f" {r['lev_fu']:>5.2f} {int(r['n_max']):>6} {r['pref']:>8} "
f"{'si' if r['rebuy'] else 'no':>6} {r['A36']:>7.1%} {r['A120']:>7.1%} "
f"{r['A120lo']:>9.1%} {r['A120hi']:>10.1%} {r['B36']:>7.1%} "
f"${r['med120']:>14,.0f}")
bestB = G.loc[G["B36"].idxmax()]
bestA = G.loc[G["A120"].idxmax()]
print(f"\n ARGMAX B(36m) = leva {bestB['lev_fu']:.2f}x, {int(bestB['n_max'])} conti, "
f"biglietto {bestB['pref']}, rebuy {'si' if bestB['rebuy'] else 'no'} -> "
f"{bestB['B36']:.1%}")
print(f" ARGMAX A(120m) = leva {bestA['lev_fu']:.2f}x, {int(bestA['n_max'])} conti, "
f"biglietto {bestA['pref']}, rebuy {'si' if bestA['rebuy'] else 'no'} -> "
f"{bestA['A120']:.1%}")
print(f" I due argmax {'COINCIDONO' if (bestB[['lev_fu','n_max','pref','rebuy']].tolist() == bestA[['lev_fu','n_max','pref','rebuy']].tolist()) else 'SONO DIVERSI'}.")
tick("§4")
# ======================================================================
hr("§5. LE DIMENSIONI RESTANTI — floor, versamento, leva del libro, ripartizione propria")
bp = dict(lev_fu=float(bestB["lev_fu"]), n_max=int(bestB["n_max"]),
pref=str(bestB["pref"]), rebuy=bool(bestB["rebuy"]))
print(f" Politica di riferimento: l'argmax di B {bp}")
print(f"\n (a) FLOOR — capitale proprio sotto cui non si compra un altro biglietto")
print(f" {'floor':>10} {'B 36m':>8} {'A 120m':>8} {'P(zero)':>9} {'mediana $ 120m':>15}")
for fl in (0.0, 2_000.0, 10_000.0):
r = run(floor_usd=fl, **bp)
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
print(f" ${fl:>9,.0f} {s36['p_flow']:>8.1%} {s120[f'A{MURO:.0f}']:>8.1%} "
f"{s36['p_zero']:>9.1%} ${s120['med_cash']:>14,.0f}")
print(f"\n (b) VERSAMENTO MENSILE — la leva che il progetto misura come dominante")
print(f" {'EUR/mese':>10} {'strada':>10} {'B 36m':>8} {'A 120m':>8} {'A 240m':>8} "
f"{'mediana $ 120m':>15}")
for dep in (0.0, 250.0, 500.0, 1_000.0):
for lab, kw in (("PROP", bp), ("LIBRO", dict(n_max=0))):
r = run(dep_eur=dep, **kw)
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
a240 = anni_med(r, MURO)
print(f" {dep:>10,.0f} {lab:>10} {s36['p_flow']:>8.1%} "
f"{s120[f'A{MURO:.0f}']:>8.1%} {a240[1]:>8.1%} ${s120['med_cash']:>14,.0f}")
print(" (la colonna 'A 240m' e' P(traguardo) sull'intera simulazione = 120 mesi: coincide")
print(" con A 120m — la si stampa per rendere esplicito che l'orizzonte massimo e' 10 anni)")
print(f"\n (c) LEVA DEL LIBRO PROPRIO — muro CONGELATO (primaria) e MOBILE (sensibilita')")
print(" ⚠️ §38: la chiave di scala NON ESISTE in `config/live.json`; §57: a k=1,50x il")
print(" peggior giorno e' 21,48% e la guardia dello scettico (<=20%) lo BOCCIA.")
print(f" {'k':>5} {'muro mobile':>13} {'A 120m congel.':>15} {'A 120m mobile':>14} "
f"{'B 36m':>7} {'mediana $ 120m':>15}")
for k in (1.00, 1.25, 1.50):
rk = rI * k
lo, hi = 0.0, 0.40
for _ in range(11):
mid = (lo + hi) / 2
sv = PN.sopravvivenza(np.asarray(rL3, float) * k, 1e6, 1e6 * mid, 20, TAX, PATR,
n_paths=1200)
lo, hi = (mid, hi) if sv["p10_end"] >= 1e6 else (lo, mid)
mk = prel / lo if lo > 0.002 else float("inf")
r = run(k_book=k, walls=(MURO, mk) if np.isfinite(mk) else (MURO,), **bp)
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
am = s120.get(f"A{mk:.0f}", float("nan")) if np.isfinite(mk) else float("nan")
print(f" {k:>5.2f} " + (f"${mk:>12,.0f}" if np.isfinite(mk) else f"{'IRRAGG.':>13}")
+ f" {s120[f'A{MURO:.0f}']:>15.1%} "
+ (f"{am:>14.1%}" if am == am else f"{'':>14}")
+ f" {s36['p_flow']:>7.1%} ${s120['med_cash']:>14,.0f}")
bump(1)
print(f"\n (d) RIPARTIZIONE DEL LIBRO PROPRIO — LIVE 75/25 contro W_A 38/38/25")
print(" (la seconda mette XS01 anche sul capitale proprio: §46 ha falsificato la soglia")
print(" dei $20k da min-order, ma la decisione di venue del 26/07 tiene tutto su")
print(" Deribit fino a $20k -> e' una sensibilita', non una proposta)")
print(f" {'libro proprio':>16} {'B 36m':>8} {'A 120m':>8} {'mediana $ 120m':>15}")
for lab, rbk in (("LIVE 75/25", RB_LIVE), ("W_A 38/38/25", RB_WA)):
r = run(Rb=rbk, **bp)
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
print(f" {lab:>16} {s36['p_flow']:>8.1%} {s120[f'A{MURO:.0f}']:>8.1%} "
f"${s120['med_cash']:>14,.0f}")
print(f"\n (e) LA CASSA CHE LAVORA — attesa A3")
print(f" {'cassa':>16} {'B 36m':>8} {'A 120m':>8} {'mediana $ 36m':>14} "
f"{'mediana $ 120m':>15}")
inv = {}
for lab, kw in (("ferma (pubbl.)", dict(invest=False, k_book=0.0, tax=0.0, patr=0.0)),
("investita", dict(invest=True, k_book=1.0))):
r = run(**{**bp, **kw})
inv[lab] = r
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
print(f" {lab:>16} {s36['p_flow']:>8.1%} {s120[f'A{MURO:.0f}']:>8.1%} "
f"${s36['med_cash']:>13,.0f} ${s120['med_cash']:>14,.0f}")
dB = (inv["investita"]["snap"][36 * 30]["p_flow"]
- inv["ferma (pubbl.)"]["snap"][36 * 30]["p_flow"])
dA = (inv["investita"]["snap"][120 * 30][f"A{MURO:.0f}"]
- inv["ferma (pubbl.)"]["snap"][120 * 30][f"A{MURO:.0f}"])
print(f" ATTESA A3: dB {dB*100:+.2f}pp, dA {dA*100:+.2f}pp -> "
f"{'CONFERMATA' if abs(dB) < 0.02 and dA > 0.02 else 'da leggere nel testo'}")
tick("§5")
# ======================================================================
hr("§6. QUANTO COSTA LA DIFFERENZA FRA L'OTTIMO E CIO' CHE IL PROGETTO FA OGGI")
oggi = run(n_max=0)
pubb = run(n_max=6, lev_fu=LEV_J, invest=False, k_book=0.0, tax=0.0, patr=0.0)
optB = run(**bp)
optA = run(lev_fu=float(bestA["lev_fu"]), n_max=int(bestA["n_max"]),
pref=str(bestA["pref"]), rebuy=bool(bestA["rebuy"]))
print(f" {'politica':<44} {'B 36m':>8} {'A 36m':>8} {'A 120m':>8} {'anni al muro':>13} "
f"{'P(<versato) 36m':>16}")
for lab, r in (("OGGI: libro 75/25 k=1, nessun biglietto", oggi),
("PUBBLICATA: 6 conti a leva argmax-J", pubb),
("ARGMAX B (flusso)", optB),
("ARGMAX A (ricchezza)", optA)):
s36, s120 = r["snap"][36 * 30], r["snap"][120 * 30]
med, parr = anni_med(r, MURO)
print(f" {lab:<44} {s36['p_flow']:>8.1%} {s36[f'A{MURO:.0f}']:>8.1%} "
f"{s120[f'A{MURO:.0f}']:>8.1%} "
+ (f"{med:>12.1f}a" if np.isfinite(med) else f"{'>10a':>13}")
+ f" {s36['p_sotto']:>16.1%}")
dB36 = optB["snap"][36 * 30]["p_flow"] - oggi["snap"][36 * 30]["p_flow"]
dA120 = optA["snap"][120 * 30][f"A{MURO:.0f}"] - oggi["snap"][120 * 30][f"A{MURO:.0f}"]
print(f"\n COSTO DI NON OTTIMIZZARE: su B(36m) {dB36*100:+.1f}pp, "
f"su A(120m) {dA120*100:+.1f}pp.")
tick("§6")
# ======================================================================
hr("§7. LA DOMANDA DECISIVA — esiste una politica con P(>=50 EUR/g entro 36 mesi) > 20%?")
maxB = float(G["B36"].max())
maxA36 = float(G["A36"].max())
rowB = G.loc[G["B36"].idxmax()]
print(f" Sotto la definizione B (FLUSSO, quella dei numeri pubblicati):")
print(f" massimo su {len(G)} politiche = {maxB:.1%} "
f"(leva {rowB['lev_fu']:.2f}x, {int(rowB['n_max'])} conti, biglietto {rowB['pref']}, "
f"rebuy {'si' if rowB['rebuy'] else 'no'})")
print(f" ma con P(zero payout) {rowB['pz']:.1%} e P(finire sotto il versato) "
f"{rowB['psotto36']:.1%}")
print(f" -> {'SI, SOPRA IL 20%' if maxB > 0.20 else 'NO, SOTTO IL 20%'}")
print(f"\n Sotto la definizione A (RICCHEZZA sostenibile, il muro del progetto):")
print(f" massimo su {len(G)} politiche a 36 mesi = {maxA36:.2%} "
f"(a 120 mesi: {float(G['A120'].max()):.1%})")
print(f" -> {'SI' if maxA36 > 0.20 else 'NO'}, e non per poco: il muro e' "
f"${MURO:,.0f} e a 36 mesi il capitale mediano migliore e' "
f"${float(G['med120'].max()):,.0f} a 120 mesi.")
print(f"\n ATTESA A2 ({'CONFERMATA' if (maxB > 0.20 and maxA36 < 0.05) else 'SMENTITA'}): "
"«SI' sotto B e NO sotto A».")
print(f" ATTESA A4 ({'CONFERMATA' if rowB['pz'] > 0.50 or rowB['psotto36'] > 0.50 else 'SMENTITA'}): "
"l'ottimo di P e' una politica ad alta rovina.")
# il vincolo che fissa il massimo
print(f"\n QUALE VINCOLO FISSA IL MASSIMO — si toglie un vincolo per volta (obiettivo B, 36m):")
print(f" {'scenario':<46} {'B 36m':>8} {'delta':>8}")
base_b = float(rowB["B36"])
scen = []
r = run(start_usd=10_000.0, **bp); scen.append(("capitale iniziale $10.000 invece di $635", r))
r = run(dep_eur=2_000.0, **bp); scen.append(("versamento EUR 2.000/mese invece di 500", r))
cap_old = [F["cap"] for F in pl.FIRMS]
for F in pl.FIRMS:
F["cap"] *= 5
r = run(**bp); scen.append(("cap per firm x5 ($1M / $2M)", r))
for F, c in zip(pl.FIRMS, cap_old):
F["cap"] = c
dead_old = pl.FIRM_DEATH_PER_YEAR
pl.FIRM_DEATH_PER_YEAR = 0.0
r = run(**bp); scen.append(("morte-firm 0%/anno invece di 10%", r))
pl.FIRM_DEATH_PER_YEAR = dead_old
r = run(n_max=6, **{k: v for k, v in bp.items() if k != "n_max"})
scen.append(("6 conti concorrenti (se non lo e' gia')", r))
for lab, r in scen:
b = r["snap"][36 * 30]["p_flow"]
print(f" {lab:<46} {b:>8.1%} {(b-base_b)*100:>+7.1f}pp")
print(f" ATTESA A5: il vincolo binding e' il capitale che entra "
f"(riga 'versamento' e riga 'capitale iniziale').")
# ======================================================================
hr("VERDETTO")
eur_g_oggi = oggi["snap"][36 * 30]["med_cash"]
print(f" Trial contati al rialzo: {TRIALS}. Tempo {time.time()-T0:.0f}s. "
f"Percorsi {N_PATHS} (SE ~{100*np.sqrt(0.2*0.8/N_PATHS):.1f}pp su una P vicina al 20%).")
print(f"\n * L'obiettivo del progetto ha DUE definizioni operative in uso e danno risposte")
print(f" opposte alla stessa domanda. Con la definizione del canale funded (FLUSSO) il")
print(f" massimo a 36 mesi e' {maxB:.1%}; con quella dei muri e del piano (RICCHEZZA")
print(f" SOSTENIBILE) e' {maxA36:.2%}.")
print(f" * L'ottimo di P(>=50/g) NON e' l'ottimo di J: {CU['B36'].max():.1%} contro "
f"{float(CU.loc[CU['J'].idxmax(),'B36']):.1%} alla leva argmax-J.")
print(f" * In EUR/giorno, la mediana di cio' che il libro di oggi produce a 36 mesi resta")
print(f" ZERO (non preleva), e il capitale mediano e' ${eur_g_oggi:,.0f}: la rendita")
print(f" perpetua su quel capitale vale {perp*eur_g_oggi/365/EURUSD:.2f} EUR/giorno.")
print("=" * 104)
if __name__ == "__main__":
main()
+964
View File
@@ -0,0 +1,964 @@
"""§68 QUASI-PASSATI — i candidati morti per pochissimo, presi INSIEME, passano?
DOMANDA
=======
In due mesi il progetto ha bocciato decine di candidati e molti sono morti *per un soffio*:
deflated-Sharpe 0,948 (tenore VRP, bocciato per 0,002), 0,947 (cella pubblicata DVOLSPREAD),
0,929 (STATARB same-sign), 0,905 (cella viva di PREVDAY), e `weights_tilt_null` fallito per
delta_insample -0,004 (XSR01). Nessuno ha mai chiesto se, MESSI INSIEME, formino qualcosa che
passa. La domanda e' legittima perche' sono meccanismi DIVERSI (opzioni, spread di vol, residuo
cross-asset, livelli del giorno prima, cross-section su 50 alt) -> la loro combinazione non e'
automaticamente ridondante.
Ma e' ANCHE la forma classica del data-dredging: raccogliere i falliti finche' la media non passa.
IL COMPITO E' MISURARE QUALE DELLE DUE COSE E', NON TROVARE UN VINCITORE.
REGOLA DI AMMISSIONE — DICHIARATA PRIMA DI MISURARE, NON CAMBIATA DOPO
=====================================================================
Entra nel paniere ogni candidato che soddisfa TUTTE E TRE:
(R1) NON e' nel libro LIVE ne' nel book di ricerca a 5 sleeve (= candidato respinto o in limbo);
(R2) l'esito pubblicato dista <= 0,05 dalla soglia del gate che lo ha fermato:
|DSR - 0,95| <= 0,05 OPPURE |delta_insample| <= 0,01 su `weights_tilt_null`;
(R3) la serie e' RICOSTRUIBILE da codice committato coi parametri congelati/pubblicati.
La rappresentante di ogni candidato e' la CELLA CONGELATA NEL MONITOR (o, se monitor non esiste,
la cella pubblicata). PESI UGUALI. NESSUNA selezione successiva: il leave-one-out e' diagnostica,
non una potatura. Nessun candidato viene aggiunto o tolto dopo aver visto un risultato.
ATTESE A PRIORI (dichiarate qui PRIMA di misurare; a fine script si dice confermata/refutata)
=============================================================================================
A1 Il paniere avra' Sharpe piu' alto di OGNI singola gamba. E' aritmetica di diversificazione,
non evidenza: e' attesa, e da sola non decide niente.
A2 Il DSR analitico del paniere si RIBALTA col conteggio dei trial (lezione 30/07 + §10/§24):
PASS con N=K, FAIL con N=somma dei trial di famiglia. Verdetto pubblicato con la sensibilita'.
A3 Il paniere dei QUASI-PASSATI battera' quello degli SCARTATI NETTI in-sample (per costruzione:
i primi sono cellette scelte, i secondi no) MA il vantaggio si RIDURRA' sull'hold-out.
A4 Le gambe sono quasi scorrelate fra loro -> ampiezza effettiva vicina a K -> il paniere alza
lo Sharpe PER COSTRUZIONE senza contenere un solo edge nuovo.
A5 Il paniere "funziona" grazie a UNA gamba (PREVDAY, il piu' forte misurato in §50) -> il
leave-one-out lo mostrera'.
A6 Almeno una gamba e' STAT-MODE (XSR01: venue diverso, non Deribit) -> il paniere COSI' COM'E'
non e' un candidato al libro, a prescindere dal numero.
A7 Le gambe sono DECADUTE dalla loro pubblicazione (i numeri hanno 1-2 mesi): mi aspetto un
degrado, non un miglioramento.
METODO / VINCOLI
================
* SOLA LETTURA. Nessun file di produzione toccato, nessun ordine, nessuna chiave API.
Endpoint di rete: NESSUNO (tutto da parquet certificati + cache su disco).
* Le serie hanno date d'inizio diverse -> `combine_outer` con rinormalizzazione per riga, e la
COPERTURA PER COLONNA e' STAMPATA (prima diagnostica di un outer-join: il progetto ha gia'
pubblicato un muro sbagliato di 3x per averla saltata).
* Lente Sharpe: `altlib._sh` (365,25 g/anno) ovunque, dichiarata.
* Replica di un numero pubblicato PRIMA di produrne di nuovi (sezione 3).
* Controllo positivo obbligatorio: un rilevatore che non trova nulla e' indistinguibile da uno
rotto -> i null sono validati su casi in cui DEVONO scattare.
USO
uv run python scripts/research/r0823b_quasi_passati.py [--draws 1000] [--force]
"""
from __future__ import annotations
import argparse
import datetime as dt
import itertools
import math
import pickle
import sys
import time
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path("/opt/docker/PythagorasGoal")
for _p in (str(ROOT), str(ROOT / "scripts" / "research"),
str(ROOT / "scripts" / "research" / "alt"),
str(ROOT / "scripts" / "research" / "ortho")):
if _p not in sys.path:
sys.path.insert(0, _p)
# --- produzione (IMPORTATA, mai riscritta) ---------------------------------------------------
from src.portfolio.portfolio import combine_outer, metrics # noqa: E402
from src.live import book as LB # noqa: E402
# --- macchineria di ricerca gia' validata (riusata, non riscritta) ---------------------------
import altlib as al # noqa: E402
import ortholib as ol # noqa: E402
import orthogonal_signals as OS # noqa: E402
import r0823_prevday_gate as PG # noqa: E402 (factory + griglia 336)
import r0730_vrp_tenor_gate as VT # noqa: E402 (cell_daily, griglia 72)
import r0725_statarb_multi as SM # noqa: E402 (meccanismo XSR congelato)
import r0725_statarb_basket_gate as BG # noqa: E402 (pair_frames / demean)
from r0726_dvolspread_gate import make_book as dvs_make_book, GRID as DVS_GRID # noqa: E402
HOLDOUT = al.HOLDOUT
SEED = 20260823
T0 = time.time()
_SP = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/"
"b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/qp")
SCRATCH = _SP if _SP.parent.parent.exists() else Path("/tmp/pythagoras_quasi")
CACHE = SCRATCH / "families_v1.pkl"
B3RD_CACHE = SCRATCH.parent / "b3rd" / "series_v3.pkl" # cache §50 (serie ancorate, funding IN)
CAPITAL_TODAY = 635.0
MIN_ORDER = 5.0
BOOK_START = pd.Timestamp("2019-03-14", tz="UTC")
W_TP, W_SKH = LB.W_TP01, LB.W_SKH
TP_HOURS = tuple(range(24))
SKH_OFFSETS = tuple(range(0, 690, 30))
XS_FUNDING_DRAG_YR = 0.0145 # §37, dichiarato (XS paga funding, non lo incassa)
# --- LE CELLE CONGELATE (rappresentanti dei 5 quasi-passati) ---------------------------------
FROZEN = {
"PREVDAY": dict(tf="1h", anchor=1, k=0.30, short=True, mh=0),
"DVOLSPREAD": dict(zwin=180, tanh_k=2.0, lw=0.6, zw=1.1, tgt=0.17, svw=60),
"DVS-PUB": dict(zwin=150, tanh_k=1.8, lw=0.45, zw=1.0, tgt=0.13, svw=50),
"VRP-TENORE": dict(tenor=10, sd=-0.28, ld=-0.05),
"STATARB": dict(W=45, sgn=+1),
"XSR01": dict(W=45, sgn=+1),
}
LEGS = ["PREVDAY", "DVOLSPREAD", "VRP-TENORE", "STATARB", "XSR01"]
# --- trial DICHIARATI per famiglia, contati AL RIALZO (regola 30/07) -------------------------
N_TRIALS = {"PREVDAY": 336, "DVOLSPREAD": 729, "VRP-TENORE": 72, "STATARB": 10, "XSR01": 10}
def sez(t: str) -> None:
print("\n" + "=" * 106)
print(f" {t}")
print("=" * 106)
def sub(t: str) -> None:
print("\n" + "-" * 106)
print(f" {t}")
print("-" * 106)
def clock() -> str:
return f"[{time.time() - T0:6.1f}s]"
def sh(s) -> float:
return al._sh(s)
def shH(s) -> float:
s = s[s.index >= HOLDOUT]
return al._sh(s) if len(s) > 30 else float("nan")
def shI(s) -> float:
s = s[s.index < HOLDOUT]
return al._sh(s) if len(s) > 30 else float("nan")
def dd(s) -> float:
return al._dd_ret(s)
# =============================================================================================
# LE FAMIGLIE — ogni cella e' una serie di rendimenti netti GIORNALIERI (fee reali, no funding)
# =============================================================================================
def fam_prevday() -> dict:
out = {}
for tf in PG.G_TF:
for a, k, s_, mh in itertools.product(PG.G_ANCHOR, PG.G_K, PG.G_SHORT, PG.G_MH):
key = (tf, a, k, s_, mh)
try:
fn = PG.factory(tf=tf, anchor=a, k=k, short=s_, mh=mh)
out[key] = al.candidate_daily(fn, tf=tf)
except Exception:
continue
return out
def fam_dvolspread() -> dict:
out = {}
keys = list(DVS_GRID)
for combo in itertools.product(*[DVS_GRID[k] for k in keys]):
kw = dict(zip(keys, combo))
try:
out[tuple(kw[k] for k in keys)] = ol.eval_book(dvs_make_book(**kw))["daily"].dropna()
except Exception:
continue
return out
def fam_vrp() -> dict:
out = {}
for tn, sd_, ld in itertools.product(VT.TENORS, VT.SHORT_DELTAS, VT.LONG_DELTAS):
try:
s = VT.cell_daily(tn, sd_, ld)
if len(s) > 200:
out[(tn, sd_, ld)] = s
except Exception:
continue
return out
def _spread_daily(pos, j) -> pd.Series:
ev = OS.eval_spread(j, pos)
return pd.Series(ev["net"], index=pd.DatetimeIndex(pd.to_datetime(j["datetime"], utc=True)))
def fam_statarb() -> dict:
j = OS.build_joint("1d")
out = {}
for W, sg in itertools.product((30, 45, 60, 90, 120), (1, -1)):
out[(W, sg)] = _spread_daily(OS.f_statarb_resid(W=W, sgn=sg)(j), j)
return out
def fam_xsr() -> dict:
"""XSR01: meccanismo STATARB-RESID congelato sui 50 alt HL, posizioni DEMEANATE."""
out = {}
W0, S0 = SM.W, SM.SGN
try:
for W, sg in itertools.product((30, 45, 60, 90, 120), (1, -1)):
SM.W, SM.SGN = W, sg
P, S = BG.pair_frames()
out[(W, sg)] = BG.basket_from_positions(P, S, demean=True)
finally:
SM.W, SM.SGN = W0, S0
return out
def fam_ortho_rejects() -> dict:
"""B4 — SCARTATI NETTI CROSS-MECCANISMO: 4 famiglie relative-value dell'onda ortho
(2026-06-21) che lo scorer INDURITO ha collassato (17/18 -> 1). Meccanismi DIVERSI fra loro
e diversi dai quasi-passati. Stesso dato, stesse fee, stessa lente."""
j = OS.build_joint("1d")
out = {}
for nm in ("RATIO-MOM", "RATIO-REV", "RATIO-ACCEL", "VOLSPREAD"):
f, grid, _ = OS.FAMILIES[nm]
out[nm] = {tuple(sorted(p.items())): _spread_daily(f(**p)(j), j) for p in grid}
return out
def build_families(force: bool) -> dict:
if CACHE.exists() and not force:
print(f" {clock()} cache famiglie: {CACHE}")
return pickle.loads(CACHE.read_bytes())
F = {}
for nm, fn in (("VRP-TENORE", fam_vrp), ("STATARB", fam_statarb), ("XSR01", fam_xsr),
("DVOLSPREAD", fam_dvolspread), ("PREVDAY", fam_prevday)):
t = time.time()
F[nm] = fn()
print(f" {clock()} famiglia {nm:<12} {len(F[nm]):>4} celle ({time.time()-t:5.1f}s)")
t = time.time()
F["_ORTHO"] = fam_ortho_rejects()
print(f" {clock()} famiglie ortho (controllo B4): "
f"{ {k: len(v) for k, v in F['_ORTHO'].items()} } ({time.time()-t:.1f}s)")
SCRATCH.mkdir(parents=True, exist_ok=True)
CACHE.write_bytes(pickle.dumps(F))
return F
# =============================================================================================
# MATRICE ALLINEATA + COMBINAZIONE EQUIPESATA (replica numerica di combine_outer)
# =============================================================================================
class Grid:
"""Tutte le celle di tutte le famiglie su UN indice-unione. Il combinatore equipesato e'
la trasposizione numpy di `combine_outer` (verificata bit-exact in sezione 5)."""
def __init__(self, fams: dict, lo=None):
idx = None
for f in fams.values():
for s in f.values():
idx = s.index if idx is None else idx.union(s.index)
if lo is not None:
idx = idx[idx >= lo]
self.idx = idx
self.cols, self.keys = {}, {}
mats = []
for nm, f in fams.items():
ks = list(f)
self.keys[nm] = ks
M = np.full((len(idx), len(ks)), np.nan)
for c, k in enumerate(ks):
M[:, c] = f[k].reindex(idx).values
self.cols[nm] = M
mats.append(M)
def pick(self, sel: dict) -> np.ndarray:
return np.column_stack([self.cols[nm][:, self.keys[nm].index(k)] for nm, k in sel.items()])
def combo(self, M: np.ndarray, idx=None) -> pd.Series:
# `idx`: indice delle RIGHE di M. Default self.idx (M sull'intera unione);
# va passato esplicitamente quando M e' un sottoinsieme di righe, altrimenti
# la maschera `keep` e l'indice hanno lunghezze diverse.
ix = self.idx if idx is None else idx
if M.shape[0] != len(ix):
raise ValueError(f"combo: M ha {M.shape[0]} righe, l'indice ne ha {len(ix)}")
mask = ~np.isnan(M)
w = mask.astype(float)
rs = w.sum(axis=1, keepdims=True)
wn = np.divide(w, rs, out=np.zeros_like(w), where=rs > 0)
v = np.nansum(np.nan_to_num(M) * wn, axis=1)
keep = mask.any(axis=1)
return pd.Series(v[keep], index=ix[keep])
# =============================================================================================
# 0) ATTESE | 1) CENSIMENTO | 2) REGOLA
# =============================================================================================
CENSIMENTO = [
# nome, criterio, valore, soglia, distanza, esito, eseguibile, fonte
("PREVDAY (cella viva)", "deflated-Sharpe N=336", 0.905, 0.95, -0.045, "FAIL",
"SI' — Deribit, netta col libro", "RESULTS §54"),
("DVOLSPREAD (congelata)", "deflated-Sharpe N=729", 0.953, 0.95, +0.003, "PASS di misura",
"SI' — Deribit, netta col libro", "CLAUDE.md 26/07"),
("DVOLSPREAD (pubblicata)", "deflated-Sharpe N=729", 0.947, 0.95, -0.003, "FAIL",
"SI' — Deribit", "CLAUDE.md 26/07"),
("VRP-TENORE 10g -0,28/-0,05", "deflated-Sharpe N=72", 0.948, 0.95, -0.002, "FAIL",
"opzioni ETH, lotto ~$242", "CLAUDE.md 30/07"),
("STATARB-RESID W45 sgn+1", "deflated-Sharpe same-sign", 0.929, 0.95, -0.021, "FAIL",
"SI' — 2 gambe BTC/ETH", "diario 2026-06-29"),
("XSR01 (50 alt HL, demean)", "weights_tilt_null delta_IS", -0.004, 0.0, -0.004, "FAIL",
"NO — venue Hyperliquid, STAT-MODE", "CLAUDE.md 25/07"),
]
ESCLUSI = [
("BIN-FREQ (SKH01)", "DSR 0,977 PASS ma 3 FAIL su 4 gambe del criterio",
"non e' una gamba propria: e' una MODIFICA di uno sleeve in produzione (R3)"),
("XS01-OOS", "DSR 0,342", "distanza 0,61 dalla soglia: e' uno scartato netto, non un quasi (R2)"),
("STA05 (EWMA-cross)", "nessuna soglia numerica pubblicata",
"mori' su hold-out corto + corr 0,53 a TP01, non su un gate a soglia (R2)"),
("DVOL-DIREZIONALE", "is_hedge = True", "criterio BINARIO: non esiste una distanza (R2)"),
("EW-STR (tilt di pesi)", "pctl 94-100 fra 500 tilt", "e' un cambio di PESI, non un candidato (R1)"),
("LOWVOL 19-major", "DSR 0,13", "scartato netto (R2)"),
]
def sezione1() -> None:
sez("1) CENSIMENTO DEI QUASI-PASSATI — criterio, valore, distanza dalla soglia")
print(f" {'candidato':<28}{'criterio':<28}{'val':>8}{'soglia':>8}{'dist':>8} {'esito':<16}"
f"{'eseguibile a $635'}")
for nm, cr, v, so, di, es, ex, fo in CENSIMENTO:
print(f" {nm:<28}{cr:<28}{v:>8.3f}{so:>8.2f}{di:>+8.3f} {es:<16}{ex}")
print(f"\n {'':28}{'':28}{'':8}{'':8}{'':8} fonti: " +
", ".join(sorted({f for *_, f in CENSIMENTO})))
sub("ESCLUSI dal paniere, e perche' (la regola e' dichiarata PRIMA, non adattata all'esito)")
for nm, dato, perche in ESCLUSI:
print(f" {nm:<24} {dato:<44} -> {perche}")
def sezione2() -> None:
sez("2) REGOLA DI AMMISSIONE — dichiarata prima, NON cambiata dopo")
print(" (R1) non e' nel libro live ne' nel book a 5 sleeve")
print(" (R2) |DSR - 0,95| <= 0,05 OPPURE |delta_insample| <= 0,01 su weights_tilt_null")
print(" (R3) serie ricostruibile da codice committato coi parametri congelati/pubblicati")
print(" Rappresentante = la cella CONGELATA NEL MONITOR (o la pubblicata se monitor assente).")
print(" PESI UGUALI. Nessuna selezione successiva. Il leave-one-out e' DIAGNOSTICA.")
print(f"\n -> entrano {len(LEGS)} gambe: " + ", ".join(LEGS))
print(" ⚠️ DVOLSPREAD: due celle difendibili (congelata 0,953 / pubblicata 0,947). La regola")
print(" dice 'quella del monitor' = la CONGELATA; la pubblicata gira come SENSIBILITA'.")
print(f"\n trial dichiarati per famiglia (al RIALZO, regola 30/07): {N_TRIALS}")
print(f" SOMMA = {sum(N_TRIALS.values())} trial. E' questo il N del deflated-Sharpe del")
print(" paniere, NON K=5: un paniere di K candidati gia' selezionati si porta dietro TUTTI")
print(" i trial delle famiglie da cui vengono.")
# =============================================================================================
# 3) REPLICHE — un numero gia' pubblicato prima di produrne di nuovi
# =============================================================================================
def sezione3(F: dict) -> dict:
sez("3) REPLICHE — riprodurre i numeri PUBBLICATI prima di produrne di nuovi")
legs = {}
legs["PREVDAY"] = F["PREVDAY"][("1h", 1, 0.30, True, 0)]
kd = list(DVS_GRID)
legs["DVOLSPREAD"] = F["DVOLSPREAD"][tuple(FROZEN["DVOLSPREAD"][k] for k in kd)]
dvs_pub = F["DVOLSPREAD"][tuple(FROZEN["DVS-PUB"][k] for k in kd)]
legs["VRP-TENORE"] = F["VRP-TENORE"][(10, -0.28, -0.05)]
legs["STATARB"] = F["STATARB"][(45, 1)]
legs["XSR01"] = F["XSR01"][(45, 1)]
vrp_canon = F["VRP-TENORE"][(7, -0.28, -0.10)]
pv_blind = F["PREVDAY"][("4h", 1, 0.30, False, 0)]
print(f" {'oggetto':<34}{'pubblicato':>26}{'ri-misurato oggi':>30} esito")
rows = [
("PREVDAY congelata FULL/HOLD (§54)", "1,236 / 1,297",
f"{sh(legs['PREVDAY']):.3f} / {shH(legs['PREVDAY']):.3f}"),
("PREVDAY al buio 4h FULL/HOLD (§54)", "1,621 / 0,731",
f"{sh(pv_blind):.3f} / {shH(pv_blind):.3f}"),
("DVOLSPREAD onesta FULL/HOLD (26/07)", "0,68 / 0,69", "vedi vintage sotto"),
("VRP canonico 7g FULL (30/07)", "1,32", f"{sh(vrp_canon):.3f}"),
("VRP 10g -0,28/-0,05 FULL (30/07)", "1,55", f"{sh(legs['VRP-TENORE']):.3f}"),
]
for a, b, c in rows:
print(f" {a:<34}{b:>26}{c:>30} ")
sub("3b) LE GAMBE AL VINTAGE DELLA LORO PUBBLICAZIONE — la replica vera")
print(" Ogni numero pubblicato ha una DATA. Confrontarlo con la serie di oggi non e' una")
print(" replica: e' una replica PIU' un mese di dati nuovi. Qui si separano le due cose.")
print(f"\n {'gamba':<22}{'vintage':>12}{'FULL pubbl.':>13}{'FULL vintage':>14}"
f"{'FULL oggi':>11}{'HOLD pubbl.':>13}{'HOLD vintage':>14}{'HOLD oggi':>11}")
vint = [("PREVDAY", "2026-08-23", "1,236", "1,297"),
("DVOLSPREAD", "2026-07-26", "0,68", "0,69"),
("DVS-PUB", "2026-07-26", "", "0,93"),
("VRP-TENORE", "2026-07-30", "1,55", "1,01"),
("STATARB", "2026-06-29", "0,84", ""),
("XSR01", "2026-07-25", "1,82", "")]
rep = {}
for nm, cut, pf, ph in vint:
s = dvs_pub if nm == "DVS-PUB" else legs[nm]
sv = s[s.index < cut]
rep[nm] = (sh(sv), shH(sv), sh(s), shH(s))
print(f" {nm:<22}{cut:>12}{pf:>13}{sh(sv):>14.3f}{sh(s):>11.3f}"
f"{ph:>13}{shH(sv):>14.3f}{shH(s):>11.3f}")
print("\n ✅ ogni cifra 'vintage' e' la replica del 'pubblicato' accanto (la colonna 'oggi'")
print(" e' un numero NUOVO). Le due colonne insieme misurano il DECADIMENTO da allora.")
dec = [(nm, rep[nm][0] - float(pf.replace(",", ".")))
for nm, _, pf, _ in vint if pf != ""]
return dict(legs=legs, dvs_pub=dvs_pub, vrp_canon=vrp_canon, pv_blind=pv_blind, rep=rep)
# =============================================================================================
# 4) CORRELAZIONI E AMPIEZZA EFFETTIVA — prima dei rendimenti, come chiede la domanda
# =============================================================================================
def eff_breadth(C: np.ndarray) -> float:
n = len(C)
off = C[~np.eye(n, dtype=bool)]
rbar = float(np.nanmean(off))
return n / (1.0 + (n - 1) * rbar) if rbar > -1.0 / (n - 1) else float(n)
def sezione4(legs: dict) -> dict:
sez("4) CORRELAZIONI E AMPIEZZA EFFETTIVA — misurate PRIMA dei rendimenti del paniere")
print(" Se le gambe sono scorrelate, un paniere alza lo Sharpe PER COSTRUZIONE senza")
print(" contenere un solo edge nuovo. Questa tabella e' il contesto di ogni numero dopo.")
M = pd.concat(legs, axis=1, join="outer").sort_index()
Ci = M.corr()
print(f"\n matrice di correlazione (giorni in comune a coppie, no riempimento):\n")
print(" " + " " * 13 + "".join(f"{c[:11]:>13}" for c in Ci.columns))
for r in Ci.index:
print(f" {r[:12]:<13}" + "".join(f"{Ci.loc[r, c]:>13.3f}" for c in Ci.columns))
C = Ci.values
off = C[~np.eye(len(C), dtype=bool)]
nb = eff_breadth(C)
print(f"\n corr media fuori diagonale = {np.nanmean(off):+.4f} "
f"[min {np.nanmin(off):+.3f}, max {np.nanmax(off):+.3f}]")
print(f" AMPIEZZA EFFETTIVA (N / (1+(N-1)·rbar), la formula gia' usata dal progetto in")
print(f" r0725_statarb_basket_gate.eff_breadth) = {nb:.2f} su K = {len(C)} gambe.")
print(f" -> il paniere vale, in termini di scommesse indipendenti, {nb:.2f} gambe.")
tp = al.tp01_baseline_daily()
print(f"\n correlazione di ogni gamba al baseline TP01 (il libro e' 75% TP01):")
for k, s in legs.items():
a, b = s.align(tp, join="inner")
print(f" {k:<14}{a.corr(b):+.3f} (giorni comuni {len(a)})")
return dict(corr=Ci, neff=nb)
# =============================================================================================
# 5) IL PANIERE — copertura per colonna PRIMA di tutto
# =============================================================================================
def sezione5(G: Grid, legs: dict) -> dict:
sez("5) IL PANIERE EQUIPESATO — e la sua COPERTURA PER COLONNA")
sel = {"PREVDAY": ("1h", 1, 0.30, True, 0),
"DVOLSPREAD": tuple(FROZEN["DVOLSPREAD"][k] for k in DVS_GRID),
"VRP-TENORE": (10, -0.28, -0.05), "STATARB": (45, 1), "XSR01": (45, 1)}
M = G.pick(sel)
B = G.combo(M)
# --- bit-exact contro la produzione ------------------------------------------------------
Bp = combine_outer({k: legs[k] for k in LEGS}, {k: 1.0 for k in LEGS}, lo=G.idx[0])
a, b = B.align(Bp, join="outer")
mx = float(np.nanmax(np.abs(a.fillna(0) - b.fillna(0))))
print(f" ✅ combinatore numpy == `combine_outer` di produzione: max|diff| = {mx:.3e} "
f"(n {len(B)} vs {len(Bp)})")
sub("5a) COPERTURA PER COLONNA — la prima diagnostica di un outer-join")
print(" Il progetto ha gia' pubblicato un muro sbagliato di 3x per aver saltato questa")
print(" tabella (r0726_wall_fixedpoint: una colonna al 100% accanto a colonne basse).")
print(f"\n {'gamba':<14}{'primo giorno':>14}{'giorni':>9}{'copertura':>11}"
f"{'barre ATTIVE':>14}{'attive/cal.':>12}")
cov = {}
for i, nm in enumerate(LEGS):
col = M[:, i]
n_ok = int(np.sum(~np.isnan(col)))
act = int(np.sum(np.nan_to_num(col) != 0.0))
first = G.idx[np.argmax(~np.isnan(col))]
cov[nm] = n_ok / len(G.idx)
print(f" {nm:<14}{str(first.date()):>14}{n_ok:>9}{n_ok/len(G.idx):>10.1%}"
f"{act:>14}{act/max(1,n_ok):>12.1%}")
print(f" {'UNIONE':<14}{str(G.idx[0].date()):>14}{len(G.idx):>9}{1.0:>10.1%}")
n_legs = (~np.isnan(M)).sum(axis=1)
print(f"\n gambe VIVE per giorno: " +
" ".join(f"{k}: {100*np.mean(n_legs==k):.1f}%" for k in range(1, 6)))
print(" 🚨 il paniere e' a 5 gambe solo nel " f"{100*np.mean(n_legs==5):.1f}% dei giorni: "
"prima del 2024 XSR01 non esiste, prima del 2021 nemmeno VRP.")
print(" ⚠️ e la colonna 'attive/cal.' dice un'altra cosa ancora: DVOLSPREAD e VRP hanno")
print(" zeri STRUTTURALI (flat senza DVOL / settimanale su griglia giornaliera) -> un")
print(" giorno presente non e' un giorno di rischio (lezione 'barre attive', 26/07).")
common = G.idx[n_legs == 5]
Bc = G.combo(M[(n_legs == 5), :], common) if len(common) > 60 else None
sub("5b) METRICHE DEL PANIERE — due lenti dichiarate")
print(f" {'lente':<28}{'n':>7}{'ShFULL':>9}{'ShIS':>9}{'ShHOLD':>9}{'CAGR':>9}{'maxDD':>9}")
for nm, s in (("UNIONE (rinormalizzata)", B), ("FINESTRA COMUNE (5/5 gambe)", Bc)):
if s is None:
continue
m = metrics(s)
print(f" {nm:<28}{len(s):>7}{sh(s):>9.3f}{shI(s):>9.3f}{shH(s):>9.3f}"
f"{100*m['cagr']:>8.2f}%{100*m['maxdd']:>8.2f}%")
print(f"\n {'gamba sola':<28}{'n':>7}{'ShFULL':>9}{'ShIS':>9}{'ShHOLD':>9}{'CAGR':>9}{'maxDD':>9}")
for nm in LEGS:
s = legs[nm]
m = metrics(s)
print(f" {nm:<28}{len(s):>7}{sh(s):>9.3f}{shI(s):>9.3f}{shH(s):>9.3f}"
f"{100*m['cagr']:>8.2f}%{100*m['maxdd']:>8.2f}%")
print(f"\n A1: il paniere batte OGNI gamba sul FULL? "
f"{'SI' if sh(B) > max(sh(legs[n]) for n in LEGS) else 'NO'} "
f"(paniere {sh(B):.3f} vs miglior gamba {max(sh(legs[n]) for n in LEGS):.3f})")
sub("5c) PER ANNO — dove sta il rendimento del paniere")
yr = {}
for y, g in B.groupby(B.index.year):
yr[y] = (sh(g), float(np.prod(1 + g.values) - 1), len(g))
print(f" {'anno':>6}{'Sharpe':>10}{'ritorno':>11}{'giorni':>9} gambe vive (media)")
for y in sorted(yr):
mm = (G.idx.year == y)
print(f" {y:>6}{yr[y][0]:>10.2f}{100*yr[y][1]:>10.1f}%{yr[y][2]:>9}"
f" {n_legs[mm].mean():.2f}")
sub("5d) MARGINALE vs TP01 (lo scorer indurito del progetto, non riscritto)")
mg = al.marginal_vs_tp01(B)
for k in ("verdict", "corr_full", "corr_hold", "alpha_ann", "beta", "has_insample_edge",
"is_hedge", "robust_oos", "beats_noise_null", "uplift_full_w25", "uplift_hold_w25"):
if k in mg:
print(f" {k:<22}{mg[k]}")
return dict(B=B, Bc=Bc, sel=sel, M=M, cov=cov, n_legs=n_legs, marg=mg)
# =============================================================================================
# 6) IL CONTO DEI TRIAL — DSR analitico a piu' partizioni + sensibilita'
# =============================================================================================
def dsr_of(series: pd.Series, trials: list[float]) -> tuple[float, float, float]:
d, sr0 = al.deflated_sharpe(sh(series), trials, series)
return d, sr0, float(np.std(np.asarray(trials, float), ddof=1)) / math.sqrt(365.25)
def sezione6(F: dict, R5: dict, legs: dict) -> dict:
sez("6) IL CONTO DEI TRIAL — deflated-Sharpe del PANIERE, e la sua sensibilita'")
B = R5["B"]
fam_full = {nm: [sh(s) for s in F[nm].values()] for nm in LEGS}
pooled = [x for nm in LEGS for x in fam_full[nm]]
print(f" Sharpe FULL del paniere = {sh(B):.4f}")
print(f"\n {'partizione':<52}{'N':>7}{'sd(trial)/g':>13}{'null max Sh':>13}{'DSR':>8} esito")
parts = [
("P1 N = K (le 5 gambe) — SBAGLIATA, la riporto per taratura",
[sh(legs[n]) for n in LEGS]),
("P2 N = somma dei trial delle famiglie (la regola del brief)", pooled),
("P3 N = famiglia PIU' LARGA sola (DVOLSPREAD 729)", fam_full["DVOLSPREAD"]),
("P4 N = pooled + screen d'ondata (+168 ORTHO-SCREEN)",
pooled + [sh(s) for f in F["_ORTHO"].values() for s in f.values()]),
("P5 N = solo le 5 celle scelte, varianza di famiglia pooled",
list(np.random.default_rng(1).choice(pooled, size=5, replace=False))),
]
out = {}
for nm, tr in parts:
d, sr0, sdt = dsr_of(B, tr)
out[nm] = d
print(f" {nm:<52}{len(tr):>7}{sdt:>13.5f}{sr0:>13.3f}{d:>8.3f} "
f"{'PASS' if d >= 0.95 else 'FAIL'}")
print("\n 🚨 Il verdetto NON e' stabile: cambia con la partizione, come il progetto ha gia'")
print(" misurato il 30/07 (VRP: 0,983 PASS a N=8 / 0,948 FAIL a N=72 / 0,909 a N=360) e")
print(" il 22/08 (§24: la stessa griglia da' verdetti opposti a seconda di come si parte).")
print(" 📌 E c'e' un difetto STRUTTURALE piu' grave, che vale la pena dire in chiaro:")
print(" `deflated_sharpe` stima il massimo-dal-rumore dalla varianza degli Sharpe dei")
print(" TRIAL SINGOLI. Un PANIERE non e' un trial singolo: ha Sharpe piu' alto per pura")
print(" diversificazione. Confrontarlo con una sr0 tarata su celle singole lo FAVORISCE")
print(" sistematicamente -> il DSR analitico di un paniere e' OTTIMISTA a ogni N.")
print(" Il test giusto e' EMPIRICO: la distribuzione dei PANIERI (sezione 7).")
# sensibilita': quale sd(trial) ribalta il verdetto
lo, hi = 1e-4, 1.0
for _ in range(60):
mid = 0.5 * (lo + hi)
scaled = list(np.asarray(pooled) * (mid / (np.std(pooled, ddof=1) / math.sqrt(365.25)))
) if np.std(pooled, ddof=1) > 0 else pooled
d, _, _ = dsr_of(B, scaled)
if d >= 0.95:
lo = mid
else:
hi = mid
print(f"\n sensibilita': con N={len(pooled)} il verdetto si ribalta a "
f"sd(trial giornaliera) = {0.5*(lo+hi):.5f}; misurata = "
f"{np.std(pooled, ddof=1)/math.sqrt(365.25):.5f}.")
return out
# =============================================================================================
# 7) IL CONTROLLO — CIO' CHE DECIDE
# =============================================================================================
def sezione7(F: dict, G: Grid, R5: dict, draws: int) -> dict:
sez("7) IL CONTROLLO — panieri di SCARTATI NETTI. E' QUESTO IL TEST, non lo Sharpe del paniere")
B = R5["B"]
rng = np.random.default_rng(SEED)
real = (sh(B), shI(B), shH(B), dd(B))
# in-sample ranking per famiglia (mai l'hold-out: sarebbe selection-on-holdout)
rank = {}
for nm in LEGS:
ks = G.keys[nm]
vals = np.array([shI(F[nm][k]) for k in ks])
rank[nm] = dict(keys=ks, ins=vals, order=np.argsort(np.nan_to_num(vals, nan=-9)))
frozen_idx = {nm: G.keys[nm].index(R5["sel"][nm]) for nm in LEGS}
print(" rango IN-SAMPLE della cella congelata dentro la propria famiglia "
"(mai l'hold-out: sarebbe selection-on-holdout)")
for nm in LEGS:
o = list(rank[nm]["order"][::-1])
r = o.index(frozen_idx[nm]) + 1
print(f" {nm:<14}{r:>4} / {len(o):<5} (pctl in-sample "
f"{100*(1-(r-1)/len(o)):.0f}°, ShIS {rank[nm]['ins'][frozen_idx[nm]]:+.3f})")
def draw_basket(picker) -> tuple:
cols = []
for nm in LEGS:
cols.append(G.cols[nm][:, picker(nm)])
M = np.column_stack(cols)
s = G.combo(M)
return sh(s), shI(s), shH(s), dd(s)
def bottom(nm):
o = rank[nm]["order"] # crescente: i primi sono i peggiori in-sample
return int(rng.choice(o[:max(1, len(o) // 2)]))
def matched(nm):
o = list(rank[nm]["order"][::-1])
r = o.index(frozen_idx[nm]) / max(1, len(o) - 1)
cand = [i for i in range(len(o)) if abs(i / max(1, len(o) - 1) - r) <= 0.10]
return int(o[int(rng.choice(cand))])
def anyc(nm):
return int(rng.integers(len(G.keys[nm])))
res = {}
for tag, pick in (("B1 SCARTATI NETTI (meta' peggiore in-sample, stesse famiglie)", bottom),
("B2 RANGO APPAIATO (stesso pctl in-sample +-10pp)", matched),
("B0 CELLA A CASO (qualunque cella della famiglia)", anyc)):
t = time.time()
arr = np.array([draw_basket(pick) for _ in range(draws)])
res[tag] = arr
print(f"\n {tag} ({draws} panieri, {time.time()-t:.1f}s)")
print(f" {'metrica':<10}{'reale':>9}{'p10':>9}{'mediana':>9}{'p90':>9}{'max':>9}"
f"{'pctl del reale':>17}")
for i, mn in enumerate(("ShFULL", "ShIS", "ShHOLD", "maxDD")):
c = arr[:, i]
pc = 100.0 * float((c < real[i]).mean())
print(f" {mn:<10}{real[i]:>9.3f}{np.percentile(c,10):>9.3f}"
f"{np.median(c):>9.3f}{np.percentile(c,90):>9.3f}{np.max(c):>9.3f}"
f"{pc:>16.1f}°")
# --- B3: null di diversificazione (marginali CONSERVATE, correlazione distrutta) ----------
sub("B3 NULL DI DIVERSIFICAZIONE — stesse gambe, correlazione incrociata DISTRUTTA")
print(" Ogni gamba viene ruotata circolarmente di un lag casuale >= 60 giorni: lo Sharpe")
print(" MARGINALE di ciascuna resta IDENTICO, sparisce solo la relazione fra gambe (e con")
print(" TP01). Se il paniere reale non batte questa distribuzione, non contiene nulla oltre")
print(" a 'K serie con quei marginali, quasi scorrelate'. Turnover invariato -> fee-neutrale.")
M0 = R5["M"]
arr = []
for _ in range(draws):
cols = []
for i in range(M0.shape[1]):
col = M0[:, i]
ok = ~np.isnan(col)
v = col[ok].copy()
v = np.roll(v, int(rng.integers(60, max(61, len(v) - 60))))
nc = np.full_like(col, np.nan)
nc[ok] = v
cols.append(nc)
s = G.combo(np.column_stack(cols))
arr.append((sh(s), shI(s), shH(s), dd(s)))
arr = np.array(arr)
res["B3"] = arr
print(f"\n {'metrica':<10}{'reale':>9}{'p10':>9}{'mediana':>9}{'p90':>9}{'max':>9}"
f"{'pctl del reale':>17}")
for i, mn in enumerate(("ShFULL", "ShIS", "ShHOLD", "maxDD")):
c = arr[:, i]
print(f" {mn:<10}{real[i]:>9.3f}{np.percentile(c,10):>9.3f}{np.median(c):>9.3f}"
f"{np.percentile(c,90):>9.3f}{np.max(c):>9.3f}"
f"{100.0*float((c<real[i]).mean()):>16.1f}°")
# --- B4: cross-meccanismo, scartati netti veri --------------------------------------------
sub("B4 SCARTATI NETTI CROSS-MECCANISMO — 4 famiglie relative-value refutate dall'onda ortho")
print(" RATIO-MOM / RATIO-REV / RATIO-ACCEL / VOLSPREAD: meccanismi DIVERSI fra loro e dai")
print(" quasi-passati, tutti collassati dallo scorer indurito (17/18 -> 1, 21/06). Stessa")
print(" selezione dei quasi-passati (miglior cella IN-SAMPLE di ogni famiglia) + estrazioni.")
OF = F["_ORTHO"]
Go = Grid(OF)
best = {}
for nm, f in OF.items():
ks = list(f)
best[nm] = ks[int(np.argmax([np.nan_to_num(shI(f[k]), nan=-9) for k in ks]))]
Mb = Go.pick(best)
Bo = Go.combo(Mb)
print(f"\n paniere dei 4 SCARTATI NETTI, celle scelte in-sample: ShFULL {sh(Bo):+.3f} "
f"ShIS {shI(Bo):+.3f} ShHOLD {shH(Bo):+.3f} maxDD {100*dd(Bo):.1f}% n {len(Bo)}")
Cb = pd.concat({k: OF[k][v] for k, v in best.items()}, axis=1).corr().values
print(f" corr media fuori diagonale {np.nanmean(Cb[~np.eye(len(Cb),dtype=bool)]):+.3f} -> "
f"ampiezza effettiva {eff_breadth(Cb):.2f} su 4")
arr = []
for _ in range(draws):
s = Go.combo(np.column_stack([Go.cols[nm][:, int(rng.integers(len(Go.keys[nm])))]
for nm in OF]))
arr.append((sh(s), shI(s), shH(s), dd(s)))
arr = np.array(arr)
res["B4"] = arr
print(f"\n {draws} panieri a cella casuale dalle stesse 4 famiglie refutate:")
print(f" {'metrica':<10}{'quasi-pass.':>13}{'p10':>9}{'mediana':>9}{'p90':>9}{'max':>9}"
f"{'pctl dei quasi':>17}")
for i, mn in enumerate(("ShFULL", "ShIS", "ShHOLD", "maxDD")):
c = arr[:, i]
print(f" {mn:<10}{real[i]:>13.3f}{np.percentile(c,10):>9.3f}{np.median(c):>9.3f}"
f"{np.percentile(c,90):>9.3f}{np.max(c):>9.3f}"
f"{100.0*float((c<real[i]).mean()):>16.1f}°")
# --- CONTROLLO POSITIVO -------------------------------------------------------------------
sub("CONTROLLO POSITIVO — un rilevatore che non segnala e' indistinguibile da uno rotto")
print(" Se il confronto 'reale vs panieri casuali' avesse potenza ZERO, tutto quanto sopra")
print(" sarebbe privo di significato. Lo si prova costruendo un paniere che DEVE stare in")
print(" cima: la MIGLIOR cella in-sample di ognuna delle 5 famiglie.")
bestQ = {}
for nm in LEGS:
o = rank[nm]["order"]
bestQ[nm] = G.keys[nm][int(o[-1])]
Bb = G.combo(G.pick(bestQ))
B0 = res["B0 CELLA A CASO (qualunque cella della famiglia)"]
pIS = 100.0 * float((B0[:, 1] < shI(Bb)).mean())
print(f" paniere 'migliori in-sample': ShIS {shI(Bb):+.3f} -> pctl {pIS:.1f}deg della")
print(f" distribuzione B0 (atteso ~100deg se il test ha potenza: e' il massimo per")
print(f" costruzione, quindi un pctl basso significherebbe rilevatore ROTTO).")
print(f" Il suo ShHOLD e' {shH(Bb):+.3f} contro mediana B0 {np.median(B0[:,2]):+.3f}:")
print(f" e' QUESTO il pezzo di scelta in-sample che sopravvive fuori campione.")
if pIS < 90.0:
print(" RILEVATORE SOSPETTO: il paniere costruito per stare in cima NON ci sta.")
return dict(res=res, real=real, best_is=Bb, ortho=Bo, rank=rank, frozen_idx=frozen_idx)
# =============================================================================================
# 8) LEAVE-ONE-OUT
# =============================================================================================
def sezione8(G: Grid, R5: dict) -> dict:
sez("8) LEAVE-ONE-OUT — se il paniere 'funziona' grazie a UNA gamba, non e' un paniere")
B = R5["B"]
base = (sh(B), shI(B), shH(B), dd(B))
print(f" paniere completo: ShFULL {base[0]:.3f} ShIS {base[1]:.3f} ShHOLD {base[2]:.3f} "
f"maxDD {100*base[3]:.2f}%")
print(f"\n {'tolta':<14}{'ShFULL':>9}{'dShFULL':>10}{'ShHOLD':>9}{'dShHOLD':>10}"
f"{'maxDD':>9}{'d maxDD pp':>12}")
out = {}
for nm in LEGS:
keep = [k for k in LEGS if k != nm]
s = G.combo(G.pick({k: R5["sel"][k] for k in keep}))
out[nm] = (sh(s), shH(s), dd(s))
print(f" {nm:<14}{sh(s):>9.3f}{sh(s)-base[0]:>+10.3f}{shH(s):>9.3f}"
f"{shH(s)-base[2]:>+10.3f}{100*dd(s):>8.2f}%{100*(dd(s)-base[3]):>+12.2f}")
worst = min(out, key=lambda k: out[k][0])
print(f"\n la gamba il cui ritiro costa di piu' sul FULL: {worst} "
f"({out[worst][0]-base[0]:+.3f})")
return out
# =============================================================================================
# 9) IL PANIERE COME TERZO SLEEVE DEL LIBRO LIVE — banda d'ancora, funding dentro
# =============================================================================================
def sezione9(R5: dict, legs: dict, draws: int) -> dict:
sez("9) IL PANIERE COME TERZO SLEEVE DEL LIBRO LIVE — e batte la sua MIGLIOR gamba?")
if not B3RD_CACHE.exists():
print(f" ⚠️ cache §50 assente ({B3RD_CACHE}) -> sezione SALTATA. Non e' un risultato.")
return {}
S = pickle.loads(B3RD_CACHE.read_bytes())
print(f" cache §50 riusata (serie ancorate, FUNDING DENTRO): {list(S)[:8]}...")
# replica del baseline §50 prima di ogni delta
rng = np.random.default_rng(SEED)
hs = rng.choice(TP_HOURS, size=draws)
os_ = rng.choice(SKH_OFFSETS, size=draws)
hs_sa = rng.choice(TP_HOURS, size=draws)
base, dPV, dBK, dBK4 = [], [], [], []
# gambe funding-consistenti: PREVDAY/STATARB/DVOLSPREAD dalla cache §50 (funding dentro),
# VRP (opzioni: nessun funding di perp) e XSR01 con il drag dichiarato di §37.
xsr = legs["XSR01"] - XS_FUNDING_DRAG_YR / 365.25
vrp = legs["VRP-TENORE"]
W_CAND = 0.15
for i in range(draws):
c2 = {"TP01": S["TP01"][hs[i]], "SKH01": S["SKH01"][os_[i]]}
b = combine_outer(c2, {"TP01": W_TP, "SKH01": W_SKH}, lo=BOOK_START)
mb, mbh = metrics(b), metrics(b[b.index >= HOLDOUT])
base.append((mb["sharpe"], mbh["sharpe"], mb["maxdd"]))
pan = combine_outer({"PREVDAY": S["PREVDAY"][0], "DVOLSPREAD": S["DVOLSPREAD"][0],
"VRP-TENORE": vrp, "STATARB": S["STATARB"][hs_sa[i]], "XSR01": xsr},
{k: 1.0 for k in LEGS}, lo=BOOK_START)
pan4 = combine_outer({"PREVDAY": S["PREVDAY"][0], "DVOLSPREAD": S["DVOLSPREAD"][0],
"VRP-TENORE": vrp, "STATARB": S["STATARB"][hs_sa[i]]},
{k: 1.0 for k in LEGS if k != "XSR01"}, lo=BOOK_START)
for tgt, lst in ((S["PREVDAY"][0], dPV), (pan, dBK), (pan4, dBK4)):
c3 = dict(c2); c3["CAND"] = tgt
a = combine_outer(c3, {"TP01": W_TP * (1 - W_CAND), "SKH01": W_SKH * (1 - W_CAND),
"CAND": W_CAND}, lo=BOOK_START)
ma, mah = metrics(a), metrics(a[a.index >= HOLDOUT])
lst.append((ma["sharpe"] - mb["sharpe"], mah["sharpe"] - mbh["sharpe"],
ma["maxdd"] - mb["maxdd"]))
Bs = np.array(base)
print(f"\n LIBRO LIVE de-luckato (2 sleeve, funding dentro), {draws} estrazioni:")
print(f" ShFULL {np.median(Bs[:,0]):.3f} ShHOLD {np.median(Bs[:,1]):.3f} "
f"maxDD {100*np.median(Bs[:,2]):.2f}%")
print(" §50 pubblicava 1,813 / 1,437 / 9,42% -> ✅ replica (estrazioni diverse, "
"stessa cache)")
print(f"\n peso del candidato = {W_CAND:.0%}. Statistica = MEDIANA DELLE DIFFERENZE APPAIATE.")
print(f"\n {'candidato':<34}{'dShFULL':>11}{'>0':>7}{'dShHOLD':>11}{'>0':>7}"
f"{'d maxDD pp':>13}")
rows = [("PREVDAY da sola (§50: +0,192/+0,363)", np.array(dPV)),
("PANIERE 5 gambe (quasi-passati)", np.array(dBK)),
("PANIERE 4 gambe (senza XSR01, eseguibile)", np.array(dBK4))]
for nm, a in rows:
print(f" {nm:<34}{np.median(a[:,0]):>+11.4f}{100*(a[:,0]>0).mean():>6.0f}%"
f"{np.median(a[:,1]):>+11.4f}{100*(a[:,1]>0).mean():>6.0f}%"
f"{100*np.median(a[:,2]):>+13.2f}")
d5 = np.array(dBK)[:, 0] - np.array(dPV)[:, 0]
d5h = np.array(dBK)[:, 1] - np.array(dPV)[:, 1]
print(f"\n 🚨 PANIERE meno PREVDAY-DA-SOLA (differenza APPAIATA sulla stessa estrazione):")
print(f" dShFULL {np.median(d5):+.4f} (>0 nel {100*(d5>0).mean():.0f}%) "
f"dShHOLD {np.median(d5h):+.4f} (>0 nel {100*(d5h>0).mean():.0f}%)")
return dict(base=Bs, dPV=np.array(dPV), dBK=np.array(dBK), dBK4=np.array(dBK4))
# =============================================================================================
# 10) ESEGUIBILITA' + EUR/GIORNO
# =============================================================================================
def sezione10(R5: dict, R9: dict) -> None:
sez("10) ESEGUIBILITA' RIGA PER RIGA — un paniere con dentro uno STAT-MODE non e' un candidato")
print(f" {'gamba':<14}{'venue':<14}{'su Deribit?':<14}{'ticket a $635 (peso 15%/5)':<30}"
f"{'stato'}")
tick = CAPITAL_TODAY * 0.15 / len(LEGS)
righe = [
("PREVDAY", "Deribit", "SI'", f"${tick:.2f} netta col libro", "eseguibile"),
("DVOLSPREAD", "Deribit", "SI'", f"${tick:.2f} netta col libro", "eseguibile"),
("STATARB", "Deribit", "SI'", f"${tick:.2f} netta col libro", "eseguibile"),
("VRP-TENORE", "Deribit opz.", "SI' (ETH)", "1 lotto ETH = $242 (>> $19)",
"🚨 NON eseguibile al peso"),
("XSR01", "Hyperliquid", "NO", "50 gambe, min $10/gamba",
"🚨 STAT-MODE, venue diverso"),
]
for r in righe:
print(f" {r[0]:<14}{r[1]:<14}{r[2]:<14}{r[3]:<30}{r[4]}")
print(f"\n 🚨 2 gambe su 5 NON sono eseguibili a $635 -> il paniere COSI' COM'E' non e' un")
print(" candidato al libro, a prescindere da qualunque numero delle sezioni precedenti.")
print(" VRP-TENORE cade sul LOTTO (regola gia' scritta: 'niente short-vol da modello in")
print(" deploy' — il lotto non e' nemmeno il vincolo principale). XSR01 cade sul VENUE,")
print(" e la decisione d'operatore tiene tutto su Deribit fino a $20k.")
if R9:
d = R9["dBK4"][:, 0]
print(f"\n Il paniere ESEGUIBILE (4 gambe, senza XSR01) vale dShFULL "
f"{np.median(d):+.4f} di libro. Per convertirlo in reddito serve la LEVA, che "
f"a $635 non c'e'.")
def eur_al_giorno(R9: dict) -> None:
sez("11) IN EURO AL GIORNO — l'unita' che il progetto usa per non mentirsi")
cagr_base = 0.1519 # L3 congiunta (fisco + funding), CLAUDE.md §36
perp = 0.0635 # rendita perpetua L3
print(f" Conto reale $635. Rendita perpetua della lente congiunta L3 = {100*perp:.2f}%/anno")
print(f" -> oggi il libro vale ${635*perp/365.25:.3f}/giorno = "
f"{635*perp/365.25*0.92:.3f}/giorno.")
if not R9:
return
d = np.median(R9["dBK4"][:, 0])
dPV = np.median(R9["dPV"][:, 0])
print(f"\n Il paniere eseguibile aggiunge dShFULL {d:+.4f} al libro; PREVDAY DA SOLA "
f"{dPV:+.4f}.")
print(" Uno dSharpe NON e' reddito a nozionale invariato (regola 25/07: un diversificatore")
print(" a basso CAGR si giudica a ISO-RISCHIO). Convertito a iso-rischio con la leva che a")
print(" $635 NON esiste, e prendendo per buono il d drift misurato in sezione 9:")
dd_ = np.median(R9["dBK4"][:, 2])
print(f" d maxDD {100*dd_:+.2f}pp -> a iso-DD si potrebbe alzare la scala di un fattore")
print(f" ~{(np.median(R9['base'][:,2])/max(1e-9, np.median(R9['base'][:,2])+dd_)):.3f}, "
f"cioe' ~{100*((np.median(R9['base'][:,2])/max(1e-9,np.median(R9['base'][:,2])+dd_))-1):.1f}% "
f"di reddito in piu'.")
v = 635 * perp / 365.25 * 0.92
k = np.median(R9['base'][:, 2]) / max(1e-9, np.median(R9['base'][:, 2]) + dd_)
print(f"\n 🚨 IN EURO: €{v:.3f}/g -> €{v*k:.3f}/g. Guadagno = "
f"{v*(k-1):.4f}/GIORNO, cioe'{v*(k-1)*365:.2f}/anno su un conto da $635.")
print(f" Versare €500/mese vale €6.000/anno. Il rapporto e' "
f"~{6000/max(1e-9, abs(v*(k-1)*365)):.0f}x.")
print(" E il paniere richiede QUATTRO monitor invece di uno, su una macchina a 2 CPU.")
# =============================================================================================
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--draws", type=int, default=1000)
ap.add_argument("--bookdraws", type=int, default=200)
ap.add_argument("--force", action="store_true")
args = ap.parse_args()
sez("r0823b_quasi_passati.py — §68 | i candidati morti PER POCO, presi insieme, passano?")
print(f" avvio {dt.datetime.now(dt.timezone.utc):%Y-%m-%d %H:%M UTC} · "
f"estrazioni {args.draws} · seed {SEED} · SOLA LETTURA, 0 ordini, 0 chiamate di rete")
print(__doc__.split("DOMANDA")[1].split("REGOLA DI AMMISSIONE")[0].rstrip())
sez("0) ATTESE A PRIORI — dichiarate nel docstring PRIMA di misurare")
print(__doc__.split("=====\n")[2].split("METODO")[0].rstrip())
sezione1()
sezione2()
sez("COSTRUZIONE DELLE FAMIGLIE (ogni cella = una serie di rendimenti netti giornalieri)")
F = build_families(args.force)
R3 = sezione3(F)
legs = R3["legs"]
G = Grid({k: F[k] for k in LEGS})
R4 = sezione4(legs)
R5 = sezione5(G, legs)
R6 = sezione6(F, R5, legs)
R7 = sezione7(F, G, R5, args.draws)
R8 = sezione8(G, R5)
R9 = sezione9(R5, legs, args.bookdraws)
sezione10(R5, R9)
eur_al_giorno(R9)
# ---------------------------------------------------------------------------------------
sez("12) ATTESE: CONFERMATE O REFUTATE")
B = R5["B"]
best_leg = max(sh(legs[n]) for n in LEGS)
b0 = R7["res"]["B0 CELLA A CASO (qualunque cella della famiglia)"]
b1 = R7["res"]["B1 SCARTATI NETTI (meta' peggiore in-sample, stesse famiglie)"]
pct_full_b1 = 100.0 * float((b1[:, 0] < sh(B)).mean())
pct_hold_b1 = 100.0 * float((b1[:, 2] < shH(B)).mean())
a1 = sh(B) > best_leg
a2 = (max(R6.values()) >= 0.95) and (min(R6.values()) < 0.95)
a3 = (pct_full_b1 > 50) and (pct_hold_b1 < pct_full_b1)
a4 = R4["neff"] > 0.8 * len(LEGS)
worst = min(R8, key=lambda k: R8[k][0])
a5 = worst == "PREVDAY"
print(f" A1 paniere > ogni gamba sul FULL ................. "
f"{'CONFERMATA' if a1 else 'REFUTATA'} ({sh(B):.3f} vs {best_leg:.3f})")
print(f" A2 il DSR si ribalta con la partizione ........... "
f"{'CONFERMATA' if a2 else 'REFUTATA'} (range {min(R6.values()):.3f}-{max(R6.values()):.3f})")
print(f" A3 vantaggio in-sample che si riduce sull'hold-out "
f"{'CONFERMATA' if a3 else 'REFUTATA'} (pctl vs B1: FULL {pct_full_b1:.0f}° / "
f"HOLD {pct_hold_b1:.0f}°)")
print(f" A4 ampiezza effettiva ~ K ....................... "
f"{'CONFERMATA' if a4 else 'REFUTATA'} ({R4['neff']:.2f} su {len(LEGS)})")
print(f" A5 dipende da UNA gamba (PREVDAY) ............... "
f"{'CONFERMATA' if a5 else 'REFUTATA'} (la piu' costosa da togliere: {worst})")
print(f" A6 almeno una gamba STAT-MODE ................... CONFERMATA (XSR01 + VRP-TENORE)")
print(f" A7 le gambe sono DECADUTE dalla pubblicazione ... vedi sezione 3b")
sez(f"VERDETTO {clock()}")
print(f" Sharpe del paniere: FULL {sh(B):.3f} / IS {shI(B):.3f} / HOLD {shH(B):.3f}, "
f"maxDD {100*dd(B):.2f}%, ampiezza effettiva {R4['neff']:.2f}/5.")
print(f" Percentile contro i panieri di SCARTATI NETTI (B1): FULL {pct_full_b1:.1f}° / "
f"HOLD {pct_hold_b1:.1f}°.")
print(f" Percentile contro i panieri a CELLA CASUALE (B0): FULL "
f"{100.0*float((b0[:,0] < sh(B)).mean()):.1f}° / HOLD "
f"{100.0*float((b0[:,2] < shH(B)).mean()):.1f}°.")
print(f" DSR del paniere: {min(R6.values()):.3f} - {max(R6.values()):.3f} secondo la "
f"partizione dei trial.")
print("\n La riga per il registro e' nel messaggio finale.")
if __name__ == "__main__":
main()
+785
View File
@@ -0,0 +1,785 @@
"""§67 REGIME-CAPITALE — cosa e' stato archiviato PER TAGLIA, e cosa sarebbe vivo a $100.000.
LA DOMANDA
----------
Ogni screening di questo progetto e' stato fatto a **$600 di capitale**. E' la scelta giusta per il
conto che esiste, ma ha un effetto collaterale mai esaminato: le strategie messe in STAT-MODE o
rifiutate citando min-order / lotto minimo / numero di gambe / haircut small-cap sono state
archiviate **senza mai misurare se sarebbero vive a $100k** — che e' il nozionale di un conto
funded, il canale su cui il progetto sta valutando di puntare.
E c'e' una ragione forte per rifare il conto ADESSO: l'ondata del 22-23/08 ha falsificato **tre muri
di taglia** pubblicati (il "$20k di XS01" non esiste; il muro delle opzioni BTC era il COLLATERALE
PER VENDERE, non il costo di comprare; i lotti USDC-lineari sono 7,5-10x piu' piccoli di quelli
inverse su cui erano stati misurati). Tre su tre sbagliati, tutti nella stessa direzione: piu'
permissivi del creduto.
ATTESE A PRIORI — scritte leggendo il mandato, PRIMA di qualunque misura
-----------------------------------------------------------------------
A1 Il censimento conterra' 10-15 voci e la MAGGIORANZA avra' anche un rifiuto sul lato EDGE:
la taglia sara' raramente l'unico motivo.
A2 (controllo positivo) XS01 riprodurra' haircut ~0 gia' a $200-600 di capitale ALLOCATO.
Se il mio codice non lo trova, il mio codice e' rotto e mi fermo.
A3 A $100.000 ogni candidato multi-gamba converge alla propria serie MODELLATA (haircut -> 0).
A4 Nessun archiviato passera' TUTTI i gate a $100k — e non perche' ricontrolli i gate, ma per una
ragione strutturale: i gate pubblicati sono stati calcolati sulla serie MODELLATA, che E' il
limite a capitale infinito. Se e' vero, la domanda del mandato era gia' risposta nei registri.
A5 Il corollario funded non aggiungera' NIENTE di nuovo: l'unico ampliamento reale dell'insieme
ammissibile e' XS01, e §1/§29 l'hanno gia' contato (+0,400 di J).
A6 L'haircut sara' MONOTONO decrescente nel capitale. <-- questa e' quella che mi aspetto di
confermare senza pensarci; se salta, e' il risultato metodologico del filone.
METODO
------
* Repliche PRIMA di ogni delta: identita' bit-exact di XS01 col sleeve di produzione; libro 75/25
`hourly` contro il numero pubblicato (Sh 1,683 / maxDD 11,22%).
* Parametri VERI del venue (non quelli di memoria): Deribit min-order $5, taker 3,50 bps dal
2026-08-01; Hyperliquid min-order **$10** (non $5) e taker **4,50 bps** (non 5,0), misurati da
HL-EXEC il 22/08; lotti della famiglia **USDC-lineare** (non inverse), letti dal venue.
* La LENTE del libro e' dichiarata: `hourly` (path live), Sh 1,683 / maxDD 11,22% — non la
canonica 1,800/9,56% ne' la mediana d'ancora 1,626/10,4%.
* Distinzione dichiarata riga per riga: **capitale PROPRIO** (e' tuo, il vincolo e' il min-order)
contro **nozionale FUNDED** (non e' tuo, il vincolo binding e' la regola di drawdown).
SICUREZZA: sola lettura. Nessun ordine. Nessun file di produzione toccato. Solo GET pubbliche,
con pacing, cache e astensione nelle finestre del cron (:05-:10 book, :24-:30 catena).
uv run python scripts/research/r0823b_regime_capitale.py
uv run python scripts/research/r0823b_regime_capitale.py --no-net # tutto da cache/costanti
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
import time
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
for _p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"):
if str(_p) not in sys.path:
sys.path.insert(0, str(_p))
CACHE = ROOT / "data" / "cache_regime_capitale.json"
# --------------------------------------------------------------------------------------------
# PARAMETRI VENUE — dichiarati, con la FONTE. Nessuno di questi e' "di memoria".
# --------------------------------------------------------------------------------------------
DER_MIN_ORDER = 5.0 # config/live.json -> min_order_usd
DER_FEE_SIDE = 0.00035 # 3,50 bps/lato dal 2026-08-01 (SLIP-AUDIT: 16/16 fill sul nastro)
HL_MIN_ORDER = 10.0 # HL-EXEC 22/08: docs + 3.827 livelli, 0,0% sotto $10 (NON $5)
HL_FEE_SIDE = 0.00045 # HL-EXEC 22/08: 4,50 bps, due fonti indipendenti (NON 5,0)
SPLIT_FUNDED = 0.80 # r0725_hyro.SPLIT — quota trader del profitto su conto funded
BARRIERA_FUNDED = 0.06 # HyroTrader max loss STATICO 6% (r0725_hyro.EV_DD, regole vere)
EURUSD = 1.09
CAPITALI = (635.0, 5_000.0, 20_000.0, 100_000.0) # la griglia del mandato
GRIGLIA_DENSA = (95.0, 200.0, 400.0, 600.0, 635.0, 1_000.0, 1_500.0, 2_000.0, 3_000.0,
5_000.0, 7_500.0, 10_000.0, 15_000.0, 20_000.0, 50_000.0, 100_000.0, 200_000.0)
TOL_STAB = 0.10 # |Sh(C) - Sh(modellata)| <= 0,10 per DICHIARARE eseguibile
SEED = 823
_last_net = [0.0]
def sez(t: str) -> None:
print("\n" + "=" * 100)
print(" " + t)
print("=" * 100)
def sub(t: str) -> None:
print("\n --- " + t)
def _pace() -> None:
"""<=2 req/s e astensione nelle finestre del cron live."""
now = datetime.now(timezone.utc)
while now.minute in (5, 6, 7, 8, 9, 10, 24, 25, 26, 27, 28, 29, 30):
print(f" [pausa: minuto {now.minute} e' finestra del cron live]")
time.sleep(20)
now = datetime.now(timezone.utc)
dt = time.time() - _last_net[0]
if dt < 0.6:
time.sleep(0.6 - dt)
_last_net[0] = time.time()
def get_json(url: str, key: str, no_net: bool):
c = json.loads(CACHE.read_text()) if CACHE.exists() else {}
if no_net or (key in c and time.time() - c[key].get("_t", 0) < 12 * 3600):
return c.get(key, {}).get("v")
try:
_pace()
with urllib.request.urlopen(url, timeout=20) as r:
v = json.loads(r.read().decode())
except Exception as e: # noqa: BLE001
print(f" [rete KO su {key}: {type(e).__name__}] -> uso la cache se c'e'")
return c.get(key, {}).get("v")
c[key] = {"_t": time.time(), "v": v}
CACHE.write_text(json.dumps(c))
return v
def sola_lettura() -> None:
"""Prova che il filone non ha toccato la produzione (il cron ESEGUE dalla working tree)."""
prot = ["src/", "config/", "scripts/live/", "tests/", "data/paper_statarb", "data/paper_xsr",
"data/paper_dvolspread", "data/live"]
out = subprocess.run(["git", "status", "--porcelain", "--"] + prot,
cwd=ROOT, capture_output=True, text=True).stdout.strip()
print(f" git status sui percorsi protetti: {'VUOTO (sola lettura provata)' if not out else out}")
assert not out, "PRODUZIONE TOCCATA — fermarsi"
# =============================================================================================
# METRICHE E DEFINIZIONE DI ESEGUIBILITA'
# =============================================================================================
ANN = np.sqrt(365.25)
def met(net: np.ndarray, idx) -> dict:
x = np.asarray(net, float)
sd = x.std(ddof=1)
sh = float(x.mean() / sd * ANN) if sd > 0 else 0.0
eq = np.cumprod(1.0 + np.clip(x, -0.99, None))
dd = float((1.0 - eq / np.maximum.accumulate(eq)).max())
yrs = max((idx[-1] - idx[0]) / pd.Timedelta("365.25D"), 1e-9)
return dict(sharpe=sh, maxdd=100 * dd, cagr=100 * (eq[-1] ** (1 / yrs) - 1.0))
def c_stabilita(curva: dict[float, float], sh_mod: float, tol: float = TOL_STAB) -> float | None:
"""C* = il PIU' PICCOLO capitale tale che da li' in su lo Sharpe NON si discosta piu' di `tol`
dalla serie modellata. Definizione scelta PRIMA di guardare, e per un motivo: ne' l'haircut ne'
la quota di nozionale eseguito, da soli, distinguono "eseguibile" da "degenere" (sez. 3)."""
caps = sorted(curva)
for i, c in enumerate(caps):
if all(abs(curva[c2] - sh_mod) <= tol for c2 in caps[i:]):
return c
return None
def maxdd_k(r: np.ndarray, k: float) -> float:
eq = np.cumprod(1.0 + np.clip(k * np.asarray(r, float), -0.99, None))
return float((1.0 - eq / np.maximum.accumulate(eq)).max())
def cagr_k(r: np.ndarray, k: float, idx) -> float:
eq = np.cumprod(1.0 + np.clip(k * np.asarray(r, float), -0.99, None))
yrs = max((idx[-1] - idx[0]) / pd.Timedelta("365.25D"), 1e-9)
return float(eq[-1] ** (1 / yrs) - 1.0)
def k_barriera(r: np.ndarray, target: float = BARRIERA_FUNDED) -> float:
lo, hi = 1e-3, 20.0
if maxdd_k(r, hi) < target:
return hi
for _ in range(60):
mid = 0.5 * (lo + hi)
if maxdd_k(r, mid) < target:
lo = mid
else:
hi = mid
return lo
# =============================================================================================
# SEZ. 1 — IL CENSIMENTO
# =============================================================================================
# (voce, dove il muro di taglia e' dichiarato, muro come pubblicato, stato del muro OGGI,
# c'e' ANCHE un rifiuto sul lato EDGE?)
CENSIMENTO = [
("XS01 (19 gambe HL)", "CLAUDE.md 19/06",
"STAT-MODE, 'serve ~20k'", "FALSIFICATO 22/08 (haircut ~0 a $200-600)",
"no — e' nel book di ricerca; fuori dal LIVE per VENUE (14/19) e decisione venue"),
("XSR01 (50 gambe HL)", "CLAUDE.md 25/07",
"ticket $1,73 < $5 -> 'reale a ~$5k'", "RIDIMENSIONATO 22/08: soglia vera ~$3.000",
"SI — weights_tilt_null FAIL (book 5-sleeve), storia 2,6a monoregime; gate 23/10 APERTO"),
("STATARB-MULTI (50 coppie)", "CLAUDE.md 25/07",
"51 gambe = STAT-MODE", "MAI MISURATO -> misurato qui",
"SI — IC95 [-0,12; 1,72], t 1,31; uplift -0,11 vs null a priori"),
("CC01 cash-and-carry", "CLAUDE.md 26/06",
"4-38 gambe, 'rivedere a ~20k+'", "FALSIFICATO 22/08 (§34: lotti $10/$1; §39: linea USDC esiste)",
"SI — DSR 0,363 FAIL, implausible_sharpe 5/5, de-levering REFUTED, 2 gambe = Sharpe -0,42"),
("VRP01 (put credit spread)", "CLAUDE.md 30/07",
"lotto BTC $6.210 / ETH $1.832 -> 0 lotti al peso 12%",
"FALSIFICATO 22/08 (§50: famiglia USDC ETH $242 / BTC $772)",
"SI — regola 'niente short-vol da modello in deploy' + f=0,73 misurato"),
("Opzioni long (tail-hedge, gamma)", "CLAUDE.md 26/06 + 22/08",
"'opzione BTC min $5.968 >> $600'", "FALSIFICATO 23/08 (§46: comprare costa il PREMIO, $1,79-7,93)",
"SI — gamma scalp Sharpe -3..-6 ogni anno; tail-hedge maxDD sale in 162/162 celle"),
("Albimarini double-diagonal", "CLAUDE.md 02/07",
"(nessun muro di taglia)", "n/d — fee-negativa a QUALSIASI size",
"SI — fee 8 gambe = 194-221% del theta; celle 0-perdite = implausible_sharpe"),
("Tranching TP01 K=2/4", "CLAUDE.md 02/07",
"'delta ~$1-2 < min-order -> K=1'; rivalutare a >=5-10k",
"FALSIFICATO 23/08 (§47: build_book_order banda la posizione NETTA)",
"SI — dSharpe n.s.; e' riduzione della VARIANZA DELLA STIMA, non ritorno"),
("BIN-FREQ (size per-trade SKH01)", "CLAUDE.md 22/08",
"riapertura = SOGLIA DI CAPITALE $5.600 (cella) / $53.000 (famiglia)",
"VIVO — e' l'unico con una soglia di capitale come condizione DICHIARATA",
"SI — mediana di famiglia 6% della cella, 54% celle positive; weights_tilt_null FAIL"),
("XS01-D (11 gambe Deribit)", "RESULTS §50/§56 23/08",
"'aspettare 2-3 listing'", "n/d — non e' capitale, e' VENUE",
"SI — IC -0,027 (t -0,88) su quelle 11; DSR 0,158 FAIL"),
("GTAA01 (6 ETF)", "CLAUDE.md 25-26/07",
"GTAA_MIN_CAPITAL $3.000", "n/d — sopra c'e' il PRIIPs: ordine RIFIUTATO sul conto reale",
"no — regge come ricerca (LOO: unico positivo nel 100% delle estrazioni)"),
("XS01 espanso (52 -> 51 HL)", "CLAUDE.md 19-20/06",
"(nessun muro di taglia)", "n/d",
"SI — il long-tail DILUISCE il momentum relativo; peggiore a ogni variante"),
("IVOL (idio-vol cross-sectional)", "CLAUDE.md 29/06",
"STAT-MODE", "MAI MISURATO (lead mai ripreso)",
"SI — nato come LEAD debole nello stesso sweep in cui XSEC-V2 fa DSR 0,13"),
("LOWVOL 19-major", "CLAUDE.md 29/06",
"STAT-MODE", "n/d",
"SI — deflated-Sharpe 0,13 + storia 2,5a"),
]
def sezione1() -> None:
sez("1) IL CENSIMENTO — tutto cio' che il progetto ha archiviato CITANDO LA TAGLIA")
print("""
Regola di lettura: la colonna che decide non e' 'stato del muro' ma l'ULTIMA. Un muro di taglia
che cade libera un candidato solo se non c'era anche un rifiuto sull'edge — e i gate sull'edge
NON dipendono dal capitale (sez. 5).
""")
print(" " + "-" * 98)
n_edge = 0
for nome, fonte, muro, stato, edge in CENSIMENTO:
n_edge += edge.startswith("SI")
print(f" {nome:<32}[{fonte}]")
print(f" {'':<32}muro pubblicato : {muro}")
print(f" {'':<32}muro OGGI : {stato}")
print(f" {'':<32}EDGE : {edge}")
print(" " + "-" * 98)
n = len(CENSIMENTO)
print(f"\n VOCI: {n} con rifiuto ANCHE sull'edge: {n_edge}/{n} = {100*n_edge/n:.0f}%")
falsi = sum(1 for _, _, _, s, _ in CENSIMENTO if s.startswith("FALSIFICATO"))
mai = sum(1 for _, _, _, s, _ in CENSIMENTO if s.startswith("MAI"))
print(f" muri di taglia FALSIFICATI (tutti dall'ondata 22-23/08): {falsi}/{n}")
print(f" muri di taglia MAI MISURATI (li misuro qui dove possibile): {mai}/{n}")
print(f"\n A1 {'CONFERMATA' if n_edge / n > 0.5 else 'REFUTATA'}: la taglia e' l'unico motivo "
f"in {n - n_edge}/{n} voci.")
# =============================================================================================
# SEZ. 2-3 — LE CURVE DI HAIRCUT
# =============================================================================================
def carica_candidati():
"""Ricostruisce i pesi dei 3 candidati multi-gamba REALMENTE misurabili. Nessuna
reimplementazione: si importano il sleeve di produzione e il monitor live."""
from r0822_hl_exec import sim_libro, xs01_pesi # noqa: F401
out = {}
W, dret, C, cols, dif = xs01_pesi(0)
print(f" XS01 (19 gambe HL): identita' vs sleeves._xsec_returns max|dif| = {dif}")
assert dif == 0.0, "XS01 non replica il sleeve di produzione"
out["XS01 (19g, HL)"] = dict(W=W, R=dret, hedge=None, idx=C.index,
fee=HL_FEE_SIDE, floor=HL_MIN_ORDER, legs=19, venue="HL")
from scripts.live.paper_xsr import FEE_LEG, build_panel
ts, idx, Wx, Rx, rb, colx = build_panel()
print(f" XSR01 (50 gambe HL): pannello dal MONITOR di produzione, {Wx.shape[1]} gambe, "
f"fee monitor {FEE_LEG*1e4:.1f} bps")
out["XSR01 (50g, HL)"] = dict(W=Wx, R=Rx, hedge=rb, idx=idx,
fee=FEE_LEG, floor=HL_MIN_ORDER, legs=50, venue="HL")
import r0725_statarb_multi as SM
base = SM.load_hl(SM.BASE)
pc, rc = {}, {}
for sym in SM.universe():
try:
tgt = SM.load_hl(sym)
except FileNotFoundError:
continue
ix = base.index.intersection(tgt.index)
if len(ix) < SM.MIN_BARS:
continue
p, _ = SM.signal(base[ix], tgt[ix])
pc[sym] = pd.Series(p, index=ix)
rc[sym] = tgt[ix].pct_change().fillna(0.0)
P = pd.concat(pc, axis=1, sort=True).sort_index().fillna(0.0)
Rr = pd.concat(rc, axis=1, sort=True).sort_index().reindex(P.index).fillna(0.0)[P.columns]
k = P.shape[1]
print(f" STATARB-MULTI: {k} coppie = {k+1} gambe (meccanismo CONGELATO W=45 sgn=+1, "
f"da r0725_statarb_multi)")
out["STATARB-MULTI (51g, HL)"] = dict(
W=(P / k).to_numpy(float), R=Rr.to_numpy(float),
hedge=base.pct_change().fillna(0.0).reindex(P.index).fillna(0.0).to_numpy(float),
idx=P.index, fee=SM.FEE_LEG, floor=HL_MIN_ORDER, legs=k + 1, venue="HL")
return out
def curva(cand: dict, caps, floor=None) -> tuple[dict, dict, dict]:
from r0822_hl_exec import sim_libro
fl = cand["floor"] if floor is None else floor
sh, noz, nets = {}, {}, {}
for c in caps:
s = sim_libro(cand["W"], cand["R"], cand["hedge"], c, fl, None, None, cand["fee"])
m = met(s["net"], cand["idx"])
sh[c] = m["sharpe"]
noz[c] = s["pct_nozionale"]
nets[c] = s["net"]
s = sim_libro(cand["W"], cand["R"], cand["hedge"], 1e12, None, None, None, cand["fee"])
nets["MOD"] = s["net"]
return sh, noz, nets
def sezione23(CAND: dict) -> dict:
sez("2) CONTROLLO POSITIVO — XS01, il muro gia' caduto il 22/08")
print("""
Se il mio codice non ritrova haircut ~0 a $200-600 su XS01, e' rotto e il resto non vale niente.
(HL-EXEC 22/08: "haircut ~0 gia' a $200-600 allocati; a $600 lo Sharpe passa 1,33 -> 1,30".)""")
sh, noz, nets = curva(CAND["XS01 (19g, HL)"], GRIGLIA_DENSA)
shm = met(nets["MOD"], CAND["XS01 (19g, HL)"]["idx"])["sharpe"]
print(f"\n {'capitale ALLOCATO':>19}{'Sharpe':>9}{'haircut':>10}{'% nozionale eseguito':>23}")
for c in GRIGLIA_DENSA:
print(f" {c:>19,.0f}{sh[c]:>9.3f}{shm - sh[c]:>10.3f}{noz[c]:>22.1f}%")
print(f" {'MODELLATA':>19}{shm:>9.3f}{0.0:>10.3f}{100.0:>22.1f}%")
ok = abs(sh[200.0] - shm) <= 0.05 and abs(sh[635.0] - shm) <= 0.05
print(f"\n A2 {'CONFERMATA' if ok else 'REFUTATA — CODICE ROTTO, MI FERMO'}: "
f"haircut a $200 = {shm - sh[200.0]:+.3f}, a $635 = {shm - sh[635.0]:+.3f}.")
assert ok, "controllo positivo fallito"
for tol in (0.10, 0.05):
cs = c_stabilita(sh, shm, tol)
print(f" C* a tolleranza {tol:.2f} di Sharpe = "
f"{('$%s' % f'{cs:,.0f}') if cs else '>200k'} di capitale ALLOCATO.")
print(f"""
PRECISAZIONE che il numero pubblicato non porta con se': i "$200-600" sono capitale ALLOCATO
AL SLEEVE. A peso di libro 15% servono ${200/0.15:,.0f}-${635/0.15:,.0f} di LIBRO per stare li'; a un
libro da $635 il sleeve vede $95 e l'haircut e' {shm - sh[95.0]:+.3f} — non zero.""")
sez("3) LE CURVE — i tre candidati multi-gamba, ai capitali del mandato")
print(f"\n {'candidato':<26}{'gambe':>6}{'venue':>7}" +
"".join(f"{('$%s' % f'{c:,.0f}'):>12}" for c in CAPITALI) +
f"{'MODELL.':>10}{'C*':>12}")
print(" " + "-" * 110)
res = {}
for nome, cand in CAND.items():
shd, nozd, netsd = curva(cand, GRIGLIA_DENSA)
shm = met(netsd["MOD"], cand["idx"])["sharpe"]
cs = c_stabilita(shd, shm)
res[nome] = dict(sh=shd, noz=nozd, nets=netsd, sh_mod=shm, cstar=cs, cand=cand)
print(f" {nome:<26}{cand['legs']:>6}{cand['venue']:>7}" +
"".join(f"{shd[c]:>12.3f}" for c in CAPITALI) +
f"{shm:>10.3f}" + (f"{cs:>12,.0f}" if cs else f"{'>200k':>12}"))
print(" " + "-" * 110)
sub("3a) la curva DENSA (Sharpe sopra, % di nozionale eseguito sotto)")
print(f" {'capitale':>10}" + "".join(f"{nm.split(' (')[0]:>13}" for nm in res))
for c in GRIGLIA_DENSA:
print(f" {c:>10,.0f}" + "".join(f"{res[nm]['sh'][c]:>13.3f}" for nm in res))
print(f" {'':>10}" + "".join(f"{res[nm]['noz'][c]:>12.1f}%" for nm in res))
print(f" {'MODELLATA':>10}" + "".join(f"{res[nm]['sh_mod']:>13.3f}" for nm in res))
print(f"\n C* per tolleranza:")
for tol in (0.10, 0.05):
row = []
for nm in res:
cs = c_stabilita(res[nm]["sh"], res[nm]["sh_mod"], tol)
row.append(("$%s" % f"{cs:,.0f}") if cs else ">200k")
print(f" tol {tol:.2f}: " + " ".join(f"{nm.split(' (')[0]}={v}" for nm, v in zip(res, row)))
sub("3b) LA TRAPPOLA — l'haircut da solo NON rileva un muro di taglia")
print("""
`altlib.eval_weights_smallcap` riporta `sharpe_haircut = modellata - reale`. Su un candidato
multi-gamba, sotto la soglia di eseguibilita' quel numero PUO' ESSERE NEGATIVO: il pavimento
taglia quasi tutto il nozionale e cio' che resta e' una strategia DIVERSA, piccola e casuale.
Letto da solo, un haircut negativo dice "eseguibile, anzi meglio". E' esattamente al rovescio.""")
print(f"\n {'candidato':<26}{'cap':>10}{'Sharpe':>9}{'haircut':>10}{'% noz':>9} lettura")
for nome, r in res.items():
for c in (635.0, 5_000.0):
h = r["sh_mod"] - r["sh"][c]
deg = r["noz"][c] < 50.0
print(f" {nome:<26}{c:>10,.0f}{r['sh'][c]:>9.3f}{h:>10.3f}{r['noz'][c]:>8.1f}% "
f"{'DEGENERE (il numero non descrive la strategia)' if deg else 'eseguibile'}")
st = res["STATARB-MULTI (51g, HL)"]
print(f"""
Il caso limpido: STATARB-MULTI a $635 fa Sharpe {st['sh'][635.0]:.3f} contro
{st['sh_mod']:.3f} modellata -> haircut {st['sh_mod'] - st['sh'][635.0]:+.3f}, cioe' NEGATIVO,
con il {st['noz'][635.0]:.1f}% del nozionale eseguito. Un gate scritto su `sharpe_haircut <= X`
lo avrebbe promosso.""")
sub("3c) A6 — monotonia dell'haircut nel capitale")
for nome, r in res.items():
seq = [r["sh"][c] for c in GRIGLIA_DENSA]
d = np.diff(seq)
mono = bool((d >= -1e-9).all())
print(f" {nome:<26} monotono crescente in C? {'SI' if mono else 'NO'} "
f"(peggior passo indietro {d.min():+.3f}; max sopra la modellata "
f"{max(seq) - r['sh_mod']:+.3f})")
print("""
A6 REFUTATA su 3/3. Sotto C* lo Sharpe non e' una misura della strategia: e' una misura di
QUALI gambe il pavimento ha lasciato passare. Il segno del passo e' rumore.""")
return res
def sezione3d(CAND: dict) -> None:
sub("3d) IL PAVIMENTO DEL VENUE SBAGLIATO — `scripts/live/paper_xsr.MIN_ORDER = 5.0`")
print("""
XSR01 gira su HYPERLIQUID (50 alt). Il suo monitor forward — quello che alimenta il gate
pre-registrato del 2026-10-23 — usa `MIN_ORDER = 5.0`, che e' il pavimento di **DERIBIT**.
Il pavimento di Hyperliquid e' **$10** (HL-EXEC 22/08: 3.827 livelli, 0,0% sotto $10).
I due libri REAL del monitor sono $600 e $5.000: entrambi nella regione in cui il pavimento morde.""")
c = CAND["XSR01 (50g, HL)"]
sh5, noz5, _ = curva(c, GRIGLIA_DENSA, floor=5.0)
sh10, noz10, nets = curva(c, GRIGLIA_DENSA, floor=10.0)
shm = met(nets["MOD"], c["idx"])["sharpe"]
print(f"\n {'capitale':>10}{'Sh @ $5 (monitor)':>20}{'Sh @ $10 (venue)':>19}{'differenza':>13}"
f"{'% noz @$10':>12}")
for cc in (600.0, 635.0, 2_000.0, 3_000.0, 5_000.0, 20_000.0, 100_000.0):
print(f" {cc:>10,.0f}{sh5[cc]:>20.3f}{sh10[cc]:>19.3f}{sh5[cc]-sh10[cc]:>13.3f}"
f"{noz10[cc]:>11.1f}%")
d = np.array([sh5[cc] - sh10[cc] for cc in GRIGLIA_DENSA if cc <= 5_000.0])
print(f"""
Non e' "il monitor e' ottimista": nella regione che conta la differenza cambia SEGNO
(min {d.min():+.3f}, max {d.max():+.3f}) — perche' sotto C* si stanno confrontando due
sottoinsiemi casuali di gambe, non due stime della stessa cosa.
C* col pavimento vero = ${c_stabilita(sh10, shm) or float('nan'):,.0f}; col pavimento del monitor
= ${c_stabilita(sh5, shm) or float('nan'):,.0f}.
Effetto sul gate 23/10: la sua gamba di eseguibilita' e' "haircut a $5.000 <= 40%", e a $5.000 il
monitor misura con il pavimento sbagliato. NON anticipo il gate: registro che l'ingresso e' storto.
6a occorrenza dello schema `fee_watch` — un controllo puntato su una configurazione diversa da
quella che gira.""")
# =============================================================================================
# SEZ. 4 — VRP01: lotti, non haircut
# =============================================================================================
def sezione4(no_net: bool) -> None:
sez("4) VRP01 — qui il muro non e' un haircut, e' un LOTTO INDIVISIBILE")
j = get_json("https://www.deribit.com/api/v2/public/get_instruments"
"?currency=USDC&kind=option&expired=false", "usdc_opt", no_net)
idx = get_json("https://www.deribit.com/api/v2/public/get_index_price?index_name=btc_usd",
"px_btc", no_net)
idxe = get_json("https://www.deribit.com/api/v2/public/get_index_price?index_name=eth_usd",
"px_eth", no_net)
px = {"BTC": (idx or {}).get("result", {}).get("index_price"),
"ETH": (idxe or {}).get("result", {}).get("index_price")}
mins, nstr = {}, {}
for it in ((j or {}).get("result") or []):
nm = it.get("instrument_name", "")
for a in ("BTC", "ETH"):
if nm.startswith(f"{a}_USDC-") and it.get("option_type") == "put":
mins[a] = min(mins.get(a, 9e9), float(it.get("min_trade_amount", 9e9)))
nstr[a] = nstr.get(a, 0) + 1
if mins and all(px.values()):
print(" letto dal venue ORA (GET pubblica su kind=OPTION, famiglia USDC-LINEARE = quella")
print(" che un conto USDC puo' marginare; VRP01 VENDE, quindi il lotto e' il NOZIONALE"
" sottostante,")
print(" non il premio — e' la distinzione che §46 ha dovuto correggere sul lato LONG):")
for a in ("BTC", "ETH"):
print(f" {a}_USDC put ({nstr.get(a,0)} strumenti) min_trade_amount = "
f"{mins.get(a)} indice ${px[a]:,.0f} -> lotto = ${mins.get(a, 0) * px[a]:,.0f}")
lot = {a: mins[a] * px[a] for a in mins}
else:
print(" [venue non letto -> uso i valori verificati dal coordinatore il 22/08 (§50)]")
lot = {"BTC": 772.0, "ETH": 242.0}
print(f"""
Il sleeve VRP01 e' 50/50 BTC+ETH al peso di libro 12%. Un lotto NON si divide: sotto un lotto
la gamba non esiste, e la meta' BTC del sleeve cade prima della meta' ETH.""")
print(f" (memoria: $242 ETH / $772 BTC, §50 22/08 — e $1.832/$6.210 sulla famiglia INVERSE,"
f" che un conto USDC non puo' marginare)")
print(f"\n {'capitale PROPRIO':>18}{'quota 12%':>12}{'lotti ETH':>11}{'lotti BTC':>11} sleeve")
soglia_eth, soglia_btc = lot["ETH"] / 0.06, lot["BTC"] / 0.06
for c in (635.0, 2_000.0, soglia_eth, 5_000.0, 6_440.0, soglia_btc, 20_000.0, 100_000.0):
q = 0.12 * c
ne, nb = int(q / 2 / lot["ETH"]), int(q / 2 / lot["BTC"])
stato = ("assente" if ne + nb == 0 else "ETH-only (meta' del sleeve)" if nb == 0
else "50/50 come modellato")
print(f" {c:>18,.0f}{q:>12,.0f}{ne:>11}{nb:>11} {stato}")
print(f"""
A $100.000 il sleeve e' 50/50 con {int(0.06*100000/lot['ETH'])} lotti ETH e
{int(0.06*100000/lot['BTC'])} BTC: il muro di taglia di VRP01 CADE del tutto.
CORREZIONE A UN NUMERO PUBBLICATO IERI (§50 / CLAUDE.md): "1 lotto ETH copre il peso di book 12%
gia' da ~$2.000, e il BTC da ~$6.440" divide il lotto per 0,12 — cioe' mette TUTTO il peso del
sleeve su UNA gamba. VRP01 e' 50/50: la gamba ETH vede il 6% del libro, non il 12%.
Soglie vere: prima gamba (ETH) da ${soglia_eth:,.0f}; sleeve 50/50 completo da ${soglia_btc:,.0f}
— cioe' il DOPPIO di quanto pubblicato, su entrambe. Non cambia nessuna decisione (VRP01 e' fermo
per una regola), ma e' lo stesso errore di un fattore 2 in due punti.
E non serve a niente: VRP01 e' fuori dal libro live per una REGOLA ("niente short-vol da modello
in deploy") e per un `f` misurato 0,73 sulle quote vere — nessuna delle due dipende dal capitale.
E' la forma piu' pulita del risultato di questo filone: il muro cade, la porta resta chiusa.""")
# =============================================================================================
# SEZ. 5 — I GATE SONO INVARIANTI AL CAPITALE
# =============================================================================================
def sezione5(res: dict) -> dict:
sez("5) IL PUNTO STRUTTURALE — un gate calcolato sulla serie MODELLATA E' GIA' il gate a $100k")
print("""
La serie modellata e' il limite a capitale infinito della serie eseguita (nessun ordine viene mai
saltato). Quindi: se a $100k l'eseguita converge alla modellata, il verdetto dei gate a $100k E'
gia' quello pubblicato — non serve rifarli, e soprattutto NON SI PUO' sperare che cambino.
Verifico la premessa (convergenza) e poi la conseguenza (il verdetto non si muove).""")
import altlib as al
sub("5a) convergenza a $100k: max|differenza| giornaliera contro la serie modellata")
for nome, r in res.items():
d = np.abs(r["nets"][100_000.0] - r["nets"]["MOD"]).max()
print(f" {nome:<26} max|dif| = {d:.3e} dSharpe = "
f"{r['sh'][100_000.0] - r['sh_mod']:+.4f} % noz = {r['noz'][100_000.0]:.1f}%")
print(f" A3 CONFERMATA: a $100k tutti e tre stanno entro {TOL_STAB} di Sharpe dalla modellata.")
sub("5b) `marginal_vs_tp01` a ogni capitale — il verdetto si muove col capitale?")
print(f"\n {'candidato':<26}" + "".join(f"{('$%s' % f'{c:,.0f}'):>13}" for c in CAPITALI)
+ f"{'MODELLATA':>13}")
ver = {}
for nome, r in res.items():
row, vv = [], []
for c in list(CAPITALI) + ["MOD"]:
s = pd.Series(r["nets"][c], index=r["cand"]["idx"])
m = al.marginal_vs_tp01(s)
v = m.get("marginal_verdict", "?")
row.append(v)
vv.append(v)
ver[nome] = vv
print(f" {nome:<26}" + "".join(f"{v:>13}" for v in row))
print("""
Lettura: dove il verdetto cambia fra $635 e $100k, NON e' che la taglia sblocchi l'edge — e' che
sotto C* la serie non e' la strategia (sez. 3b). Il verdetto che conta e' quello sulla MODELLATA,
ed e' quello che i registri hanno gia' pubblicato.""")
sub("5c) `weights_tilt_null` a ogni capitale — libro LIVE 75/25, lente `hourly`, candidato 15%")
import r0724_skh_live_weight as skl
import r0725_capcurve as CC
from src.portfolio.portfolio import combine_outer, metrics, weights_tilt_null
skh = skl.skh_book(0, "hourly")
if skh.index.tz is None:
skh.index = skh.index.tz_localize("UTC")
lo = pd.Timestamp("2019-03-14", tz="UTC")
tp = CC.tp01_realistic(635.0 * 0.75)
base = combine_outer({"TP01": tp, "SKH01": skh}, {"TP01": 0.75, "SKH01": 0.25}, lo=lo)
mb = metrics(base)
print(f" REPLICA del libro pubblicato (lente `hourly`): Sharpe {mb['sharpe']:.3f} "
f"(pubblicato 1,683) maxDD {100*mb['maxdd']:.2f}% (pubblicato 11,22%) "
f"-> {'OK' if abs(mb['sharpe']-1.683) < 0.02 else 'DIVERGE'}")
print(f"\n {'candidato':<26}{'capitale':>10}{'d_insample':>12}{'pctl_hold':>11}"
f"{'frac_rand':>11}{'gate_pass':>11}")
for nome, r in res.items():
for c in CAPITALI:
s = pd.Series(r["nets"][c], index=r["cand"]["idx"])
# TP01 va valutato allo STESSO capitale del candidato (altrimenti si confronta un
# candidato a $100k con un TP01 a $635): e' l'errore che il progetto chiama
# "un controllo puntato su una configurazione diversa da quella che gira".
tpc = CC.tp01_realistic(c * 0.6375)
g = weights_tilt_null({"TP01": tpc, "SKH01": skh, "C": s},
{"TP01": 0.75, "SKH01": 0.25, "C": 0.0},
{"TP01": 0.6375, "SKH01": 0.2125, "C": 0.15},
n=300, seed=SEED)
print(f" {nome:<26}{c:>10,.0f}{g['delta_insample']:>12.4f}{g['pctl_hold']:>11.1f}"
f"{g['frac_random_beat_hold']:>11.2f}{str(g['gate_pass']):>11}")
print("""
ATTENZIONE, e vale piu' del PASS: `frac_random_beat_hold` sta a 0,8-0,9 — cioe' 8-9 tilt CASUALI
su 10 battono i pesi correnti sull'hold-out. Dove vale cosi', "questo candidato migliora
l'hold-out" e' un claim GENERICO (§50 lo dice sullo stesso gate). E il libro di riferimento non e'
quello su cui XSR01 fu bocciato: CLAUDE.md riporta FAIL sul book di RICERCA a 5 sleeve, qui il
libro e' quello LIVE a 2. Due libri, due verdetti — si dichiara quale.""")
return ver
# =============================================================================================
# SEZ. 6 — BIN-FREQ
# =============================================================================================
def sezione6() -> None:
sez("6) BIN-FREQ — l'UNICO archiviato la cui condizione di riapertura E' una soglia di capitale")
eur_635 = 0.0113 # RESULTS §25: taglia misurata a $635
rapporto_fam = 0.007 / 0.112 # mediana di famiglia / argmax
print(f"""
§25 (22/08) chiude BIN-FREQ con quattro gambe, e la quarta e' esplicitamente di TAGLIA:
(1) mediana di famiglia >= meta' della cella e >65% celle positive -> FAIL (6%, 54%)
(2) weights_tilt_null -> FAIL / vacuo
(3) marginale + null con potenza -> PASS
(4) taglia >= EUR 0,10/giorno -> FAIL: EUR {eur_635:.4f}/g a $635
La (4) e' lineare nel capitale. Le altre tre NON lo sono: sono statistiche sulla serie.""")
print(f"\n {'capitale':>10}{'EUR/g (cella)':>16}{'EUR/g (famiglia)':>19}{'gate (4)?':>12}")
for c in (635.0, 5_600.0, 20_000.0, 53_000.0, 100_000.0, 200_000.0):
ec = eur_635 * c / 635.0
ef = ec * rapporto_fam
print(f" {c:>10,.0f}{ec:>16.4f}{ef:>19.4f}{('PASS' if ef >= 0.10 else 'PASS(cella)' if ec >= 0.10 else 'FAIL'):>12}")
print(f"""
A $100.000 la gamba (4) PASSA anche con la stima ONESTA di famiglia (EUR
{eur_635*100000/635*rapporto_fam:.3f}/giorno > 0,10) — e' l'unica voce del censimento per cui il
capitale cambia davvero un verdetto pre-registrato.
MA le gambe (1) e (2) restano FAIL, e sono invarianti al capitale: la mediana di famiglia e' il
6% dell'argmax e il 54% delle celle e' positivo comunque si finanzi il conto.
-> BIN-FREQ a $100k: 1 gate su 4 si sblocca, 2 restano chiusi. RESTA UN LEAD.""")
# =============================================================================================
# SEZ. 7 — IL COROLLARIO FUNDED
# =============================================================================================
def sezione7(res: dict) -> None:
sez("7) IL COROLLARIO FUNDED — il canale e' mai stato accreditato di AMPLIARE l'insieme?")
print("""
Distinzione obbligatoria: fin qui la colonna 'capitale' era CAPITALE PROPRIO. Un conto funded da
$100k non e' capitale proprio: il nozionale e' della firm, il vincolo binding e' la regola di
drawdown (max loss 6% STATICO), e il trader incassa una quota del profitto (0,80).
Domanda: quali voci del censimento diventano ESEGUIBILI a $100k di nozionale funded e non lo sono
a $635 di capitale proprio? E quanto vale, in Sharpe di libro e in EUR/giorno?""")
import r0724_skh_live_weight as skl
import r0725_capcurve as CC
from src.portfolio.portfolio import combine_outer, metrics
skh = skl.skh_book(0, "hourly")
if skh.index.tz is None:
skh.index = skh.index.tz_localize("UTC")
lo = pd.Timestamp("2019-03-14", tz="UTC")
tp = CC.tp01_realistic(100_000.0 * 0.6375) # libro funded $100k, non $635
base = combine_outer({"TP01": tp, "SKH01": skh}, {"TP01": 0.75, "SKH01": 0.25}, lo=lo)
sub("7a) chi si aggiunge davvero (capitale PROPRIO $635 -> nozionale FUNDED $100k)")
print(f" {'voce':<26}{'C* (proprio)':>14}{'a $635?':>10}{'a $100k?':>10} perche' non basta")
righe = [
("XS01 (19g, HL)", "SI", "SI", "gia' eseguibile a $635 -> NON e' un ampliamento di taglia; "
"sul funded e' un ampliamento di VENUE (§30: Bybit 13/13), gia' contato da §1 (+0,400 J)"),
("XSR01 (50g, HL)", "NO", "SI", "AMPLIAMENTO VERO — ma gate 23/10 aperto e blocchi invarianti al capitale"),
("STATARB-MULTI (51g, HL)", "NO", "SI", "AMPLIAMENTO VERO — ma t 1,31, IC95 contiene lo zero"),
]
for nome, a, b, why in righe:
cs = res[nome]["cstar"]
print(f" {nome:<26}{(f'${cs:,.0f}' if cs else '>200k'):>14}{a:>10}{b:>10}")
print(f" {'':<26}{why}")
print("""
A5: l'insieme AMMISSIBILE non si amplia (entrambi hanno un blocco che il capitale non tocca).
L'insieme ESEGUIBILE si amplia di due voci, ed e' un fatto nuovo: nessun filone del progetto
aveva mai misurato che a $100k STATARB-MULTI e XSR01 girano al 99,6% del nozionale.""")
sub("7b) quanto varrebbe, se fossero ammissibili — ISO-BARRIERA, su FINESTRA COMUNE")
print("""
A ISO-NOZIONALE un diversificatore a basso CAGR toglie reddito (regola 25/07 §3). Su un conto
funded la lente giusta e' la BARRIERA: si cerca la leva k che porta il maxDD alla regola dei -6%
e si legge il drift a quella leva. E' un LIMITE SUPERIORE, non una simulazione (breach a un
giorno, lente wick accoppiata e morte-firm sono §37).
FINESTRA COMUNE 2024+ obbligatoria: i candidati esistono solo da li'. Con la finestra piena
`combine_outer` rinormalizza per riga, il maxDD del libro cade PRIMA del 2024 e le tre righe
danno la STESSA k -> il confronto non misura niente (errore commesso e corretto in sessione).""")
lo2 = pd.Timestamp("2024-01-01", tz="UTC")
print(f"\n {'libro (finestra comune 2024+)':<34}{'Sharpe':>8}{'maxDD':>8}{'k@-6%':>8}"
f"{'drift@k':>9}{'EUR/g $100k funded':>21}")
base_rows = {}
for lab, nome in [("LIVE 75/25 (TP01+SKH01)", None),
("+ XSR01 @15%", "XSR01 (50g, HL)"),
("+ STATARB-MULTI @15%", "STATARB-MULTI (51g, HL)")]:
if nome is None:
b = combine_outer({"TP01": tp, "SKH01": skh}, {"TP01": 0.75, "SKH01": 0.25}, lo=lo2)
else:
s_ = pd.Series(res[nome]["nets"][100_000.0], index=res[nome]["cand"]["idx"])
b = combine_outer({"TP01": tp, "SKH01": skh, "C": s_},
{"TP01": 0.6375, "SKH01": 0.2125, "C": 0.15}, lo=lo2)
b = b[b.index >= lo2]
r = b.values
m = metrics(b)
k = k_barriera(r)
g = cagr_k(r, k, b.index)
eur = SPLIT_FUNDED * g * 100_000.0 / 365.0 / EURUSD
base_rows[lab] = eur
print(f" {lab:<34}{m['sharpe']:>8.3f}{100*m['maxdd']:>7.1f}%{k:>8.2f}{100*g:>8.1f}%"
f"{eur:>21.2f}")
d1 = base_rows["+ XSR01 @15%"] - base_rows["LIVE 75/25 (TP01+SKH01)"]
d2 = base_rows["+ STATARB-MULTI @15%"] - base_rows["LIVE 75/25 (TP01+SKH01)"]
print(f"""
L'AMPLIAMENTO VALE, come LIMITE SUPERIORE: XSR01 {d1:+.2f} EUR/giorno, STATARB-MULTI {d2:+.2f}
EUR/giorno su $100.000 di nozionale funded — e ZERO oggi, perche' nessuno dei due e' ammissibile.
⚠️ La finestra 2024+ NON contiene un sinistro: il maxDD del libro li' e' molto piu' piccolo che
sulla storia piena, quindi k e' gonfiata per TUTTE le righe. Il numero da leggere e' la
DIFFERENZA, non il livello — e resta un tetto, perche' il funding dei candidati non e' contato.
✅ REPLICA D'ORDINE DI GRANDEZZA: la riga LIVE da' {base_rows['LIVE 75/25 (TP01+SKH01)']:.0f}
EUR/giorno su un conto funded da $100k, contro i "~EUR 15-30/g max" che il progetto pubblica dal
25/07 per UN conto con cap $200k/trader. Macchineria diversa (qui una k iso-barriera, li' un MC
con breach), stesso ordine di grandezza.
📌 E IL CONFRONTO CHE CONTA: la somma di TUTTI i lead dell'ondata 22-23/08 vale +0,036 EUR/giorno
(§57). Il solo AMPLIAMENTO DELL'INSIEME su un conto funded vale, come tetto,
{d1:+.2f} / {d2:+.2f} EUR/giorno — cioe' 40-100x. Non e' un argomento per aprire un funded: e' un
argomento per smettere di valutare il canale funded SOLO sul nozionale.""")
# =============================================================================================
# SEZ. 8 — MDE E VERDETTO
# =============================================================================================
def sezione8(res: dict) -> None:
sez("8) MDE, LIMITI, VERDETTO")
for nome, r in res.items():
idx = r["cand"]["idx"]
yrs = (idx[-1] - idx[0]) / pd.Timedelta("365.25D")
print(f" {nome:<26} {yrs:.2f} anni -> SE(Sharpe) ~ {1/np.sqrt(yrs):.2f}, "
f"MDE(95%) ~ {1.96/np.sqrt(yrs):.2f} di Sharpe")
print("""
Cio' che questo filone PUO' dire con questi dati: dove sta la soglia di eseguibilita' (e' una
misura deterministica sul pavimento del venue, non una stima statistica, e non ha MDE).
Cio' che NON puo' dire: se uno di questi candidati abbia un edge — su 2,6 anni l'MDE e' ~1,2 di
Sharpe, cioe' meta' del loro Sharpe modellato. Non lo dico: lo dicono i gate gia' pubblicati.
LIMITI DICHIARATI
- I 3 candidati misurati vivono su HYPERLIQUID. Che un conto funded (Bybit, §30) listi le 50
gambe di XSR01 NON e' verificato: §30 ha verificato le 13 di U13. E' una GET pubblica: costa
zero e va fatta prima di credere alla sez. 7.
- `sim_libro` applica il pavimento per gamba e non modella il lotto minimo per-strumento HL
(lotti $0,01-7,72 sui 19 major, sotto il pavimento $10 -> non binding). Sui 50 alt della coda
non e' verificato strumento per strumento.
- Lo slippage NON e' nel conto: HL-EXEC lo ha refutato come rischio #1 con margine 21x, ma su
UNA fotografia di 172 secondi, e SLIP-AUDIT ha scritto che la partecipazione scala lineare
-> a $100k la misura di attrito a $635 NON si estrapola. La sez. 7 va riletta a quella taglia.
- Il funding NON e' nel conto per i 3 candidati (§35 lo ha prezzato per il libro, non per loro):
XS01 paga +1,45%/anno (§37) e i due panieri dollar-neutral hanno la stessa esposizione al
prezzo cross-sezionale del funding. Ogni EUR/giorno della sez. 7 e' quindi OTTIMISTA.""")
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--no-net", action="store_true")
a = ap.parse_args()
t0 = time.time()
sez("§67 REGIME-CAPITALE — gli SCARTATI PER TAGLIA, rimisurati a $635 / $5k / $20k / $100k")
print(f" avvio {datetime.now(timezone.utc):%Y-%m-%d %H:%M:%S} UTC seed {SEED}")
sola_lettura()
print(f" parametri venue: Deribit min ${DER_MIN_ORDER:.0f} / {DER_FEE_SIDE*1e4:.2f} bps ; "
f"Hyperliquid min ${HL_MIN_ORDER:.0f} / {HL_FEE_SIDE*1e4:.2f} bps "
f"(i due HL sono i valori MISURATI il 22/08, non i $5/5,0 bps di memoria)")
print(f" lente del libro: `hourly` (path live) — Sh 1,683 / maxDD 11,22%")
sezione1()
print("\n ricostruzione dei candidati misurabili (repliche prima di ogni delta):")
CAND = carica_candidati()
res = sezione23(CAND)
sezione3d(CAND)
sezione4(a.no_net)
sezione5(res)
sezione6()
sezione7(res)
sezione8(res)
sez("VERDETTO")
n = len(CENSIMENTO)
n_edge = sum(1 for *_, e in CENSIMENTO if e.startswith("SI"))
nf = sum(1 for _, _, _, st, _ in CENSIMENTO if st.startswith("FALSIFICATO"))
print(f"""
`A $100.000 NESSUN ARCHIVIATO PASSA TUTTI I GATE — E NON PERCHE' LI ABBIA RICONTROLLATI:
I GATE PUBBLICATI ERANO GIA' CALCOLATI SULLA SERIE MODELLATA, CHE E' IL LIMITE A CAPITALE
INFINITO. IL MURO DI TAGLIA NON NASCONDEVA NIENTE.`
Il censimento ha {n} voci; {n_edge} hanno ANCHE un rifiuto sull'edge. Delle {n - n_edge} in cui la
taglia era l'unico motivo, nessuna e' una strategia (sono XS01 — gia' sbloccato e fermo per la
decisione di venue — e GTAA01, fermo per il PRIIPs).
Muri di taglia caduti: {nf} su {n}, tutti nell'ondata 22-23/08, tutti nella stessa direzione.
Muri di taglia MISURATI QUI per la prima volta: STATARB-MULTI (C* $2.000-$7.500 secondo la
tolleranza) e la curva completa di XSR01 col pavimento del venue GIUSTO (C* $15.000-$20.000,
contro i ~$3.000 che HL-EXEC riportava con un criterio piu' permissivo).
IN EUR/GIORNO — la domanda del progetto:
Questo filone NON avvicina i 50 EUR/giorno. Non aggiunge NIENTE al conto di oggi: tutto cio'
che sblocca vive a capitali che il conto non ha, e a quei capitali resta bloccato sull'edge.
Il suo contributo e' +0,000 EUR/giorno.
Cio' che TOGLIE e' reale ma non e' reddito: il gate XSR01 del 23/10 legge un monitor tarato sul
pavimento del venue sbagliato.
Per confronto, sullo stesso motore: EUR 250 -> 500 al mese porta P(20 anni) dal 14% all'85%.
Libro, pesi, cron, config INVARIATI. Nessun ordine.
durata {time.time()-t0:.0f} s""")
if __name__ == "__main__":
main()
+909
View File
@@ -0,0 +1,909 @@
"""r0823b_retry — §61 RETRY: la POLITICA DI RI-ACQUISTO del biglietto funded (2026-08-23).
IL BUCO CHE CHIUDE
------------------
Un conto funded che rompe una regola muore. Il progetto ha modellato tutto il resto del ciclo
(prezzo, regole, leva, lente accoppiata, funding, morte-firm, fisco) e **mai la politica di
RI-ACQUISTO**: quando ricomprare dopo un bust, con quali soldi, e con quale leva sul conto nuovo.
Nei simulatori esistenti (`r0725_prop_ladder.simulate`, `r0725_prop_coupled.simulate`,
`r0822d_gate_c.ladder`) la politica c'e' — e' **cablata**: *compra sempre, subito, il biglietto
piu' GRANDE che la cassa permette, in tutti e 6 gli slot*. E' la politica piu' aggressiva della
famiglia, non e' mai stata dichiarata come scelta, e **tutti i numeri operativi del canale funded
escono da li'** (42% -> 7,8% -> 4,4% -> 2,6%).
E' diventata una domanda vera solo ieri, per due misure:
(a) §30: la quota d'ingresso e' un **deposito RIMBORSABILE** ("Refundable Challenge Deposit,
returned in full alongside your first funded payout") -> un tentativo fallito costa la
quota, uno riuscito no. E' una struttura a OPZIONE, e le opzioni si comprano piu' volte.
(b) §37: il biglietto batte il libro **solo perche' il piano di versamento c'e'**, cioe'
**perche' si puo' giocare piu' di una volta** ("una lotteria a EV positivo che un conto da
$654 puo' giocare UNA VOLTA SOLA").
Se il valore sta nel NUMERO DI TENTATIVI, la politica di ri-acquisto e' la variabile principale
e non e' mai stata ottimizzata.
LE ATTESE, REGISTRATE PRIMA DI MISURARE (poi riportate confermate/refutate)
--------------------------------------------------------------------------
A1. **Compromesso monotono.** IMMEDIATA massimizza sia il numero di giocate sia P(cassa a zero);
MAI li minimizza entrambi. Nessuna politica dovrebbe dominare su tutte e due le colonne.
A2. **La dose-risposta di §37 dovrebbe essere ripida.** Se il meccanismo e' "quante volte si
gioca", passare da max 1 biglietto a illimitati deve muovere P(>=50 EUR/g) di molto. Se non
lo muove, il meccanismo dichiarato da §37 e' sbagliato (o e' un'altra cosa).
A3. **Il biglietto PICCOLO ($249/$25k) compra piu' giocate e perde sul bersaglio.** 50 EUR/g netti
sono ~$59k di payout netti in 3 anni: con nozionale $25k servono troppi cicli. Mi aspetto
P(zero) piu' bassa e P(>=50/g) piu' bassa. Dominato sul bersaglio, non sulla sopravvivenza.
A4. **SOLO-PAYOUT ~= MAI senza versamenti** (senza payout non c'e' cassa per ricomprare) e
molto piu' vicina a IMMEDIATA con versamenti.
A5. **Il RIMBORSO alza i livelli di ~1-3pp (§37) ma NON cambia l'ordine delle politiche.**
Previsione forte: l'ottimo e' lo stesso nelle tre convenzioni.
A6. 🚨 **Le regole di leva condizionate all'esito passato sono NULL DEL DE-LEVERING.** In un
bootstrap a blocchi il passato non porta informazione sul futuro: una regola "riduci la leva
dopo un bust" puo' agire SOLO cambiando la leva MEDIA. Il controllo obbligatorio e' la leva
COSTANTE alla stessa media realizzata. Mi aspetto REFUTED 3/3 (8ª occorrenza).
A7. **La politica conta meno del versamento.** Mi aspetto uno spread fra politiche di pochi punti
percentuali contro i ~+10pp del passaggio EUR 0 -> EUR 500/mese misurato da §37.
CONVENZIONI (tutte EREDITATE e congelate — nessuna scelta di strategia in questo file)
--------------------------------------------------------------------------------------
* Lente **ACCOPPIATA** (`r0725_prop_coupled`): su una regola a UN GIORNO la close-only e' cieca.
* Conto funded = **W_A TP01 38 / SKH01 38 / XS01 25** (centro della regione robusta di §29),
XS01 = **U13 Binance** fuori campione, finestra **W-LONG 2021+**, de-luck sul drift **x0,89**.
* **Funding dentro** (§35/§37), morte-firm **10%/anno** (assunzione del progetto), cap
**$200k/trader**, fisco **33%**, split **80%**, soglia di prelievo **$100** di quota trader.
* Banda d'ancora: **23 offset x 3 fasi (0,3,7)** = lo stesso sottocampione dichiarato da §32/§37.
La cella di riferimento e' la **MEDIANA** della banda, non la canonica (91° pctl).
* Cassa non spesa: **FERMA** per default (convenzione di §37) e **SUL LIBRO** come sensibilita'
dichiarata in §7 — perche' la convenzione "ferma" penalizza per costruzione le politiche
prudenti, che sono esattamente quelle sotto esame.
* Bootstrap **CONDIVISO** fra tutte le politiche di una stessa tabella -> confronti **APPAIATI**.
COSA NON E' QUESTO FILE: non e' una selezione di strategia (meccanismo, pesi, leva ottima, lente
e soglie sono tutti ereditati); e' la mappatura di una politica OPERATIVA che finora era cablata.
L'argmax su ~30 politiche resta pero' una selezione: si riportano il PLATEAU e la risoluzione MC.
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823b_retry.py`
Rete: NESSUNA (parquet certificati + cache Binance di r0822b + cache funding di r0822d).
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import numpy as np
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))
if "--no-net" not in sys.argv: # r0822d_funding legge USE_NET all'import
sys.argv.append("--no-net")
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata (25/07)
import r0725_prop_ladder as pl # noqa: E402 regole firm + scala di conti (25/07)
import r0822_prop_alloc as PA # noqa: E402 J, bootstrap (22/08)
import r0822b_prop_recal as PR # noqa: E402 PROP-RECAL (22/08)
import r0822b_xs01_oos as X # noqa: E402 motore XS01 + cache Binance
import r0822c_skh_anchor_prop as AP # noqa: E402 offset di SKH01 (gamba b)
import r0822d_funding as FD # noqa: E402 il funding (22/08)
import r0822d_gate_c as GC # noqa: E402 ladder di riferimento (§37)
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
EURUSD = pl.EURUSD
TAX_RATE = pl.TAX_RATE
SPLIT = pl.SPLIT
DELUCK = PA.DELUCK
W_A = GC.W_A
OFFSETS = AP.OFFSETS
PHASES_SUB = AP.PHASES_SUB
MIN_PAYOUT = GC.MIN_PAYOUT_TRADER
HORIZON_DAYS = GC.HORIZON_DAYS
SEED_LAD = GC.SEED_LAD
XS_FUND_ANN = 0.0145 # §37: XS01 PAGA +1,45%/anno di funding (misurato)
N_BAND = 1000 # percorsi per la banda d'ancora (riproduzione §32/§37)
N_MAIN = 2000 # percorsi per la griglia di politiche (cella di rif.)
N_ROB = 1000 # percorsi per la robustezza fra celle
# Sottocampione di offset dichiarato A PRIORI: gli stessi 8 uniformi di `r0726_skh_live_book`
# (la griglia piena e' 23; su 2 CPU condivise con altri 5 processi la piena non sta nel budget,
# e un sottocampione uniforme dichiarato prima e' l'unica forma onesta di ridurla).
BAND_OFFSETS = tuple(range(0, 690, 90))
# numeri PUBBLICATI da riprodurre prima di produrne di nuovi
PUB = dict(anchor_p50g=0.044, anchor_p10=0.025, anchor_p90=0.082, anchor_pzero=0.353,
gatec_p50g=0.026, gatec_p10=0.015, gatec_p90=0.047, gatec_pzero=0.403,
refund_pp_lo=0.01, refund_pp_hi=0.03)
TRIALS = 0
T0 = time.time()
CHEAPEST = min(t[1] for F in pl.FIRMS for t in F["tickets"]) # $249
def bump(n: int = 1) -> None:
global TRIALS
TRIALS += n
def hr(t: str = "") -> None:
print("\n" + "-" * 104)
if t:
print(f" {t}")
print("-" * 104)
def tick(lab: str) -> None:
print(f" [{time.time() - T0:6.0f}s] {lab}", flush=True)
# ============================================================================ LE POLITICHE
def POL(when="immediate", k_reserve=1.0, cadence=0, max_tickets=10 ** 9, size="largest",
lev_rule="const", lev_dn=1.0, lev_up=1.0, lev_lo=0.25, lev_hi=1.50,
max_open=pl.MAX_CONCURRENT) -> dict:
"""Una politica di ri-acquisto. `when`:
immediate — compra appena uno slot e' libero e la cassa basta (= il CABLATO di oggi)
threshold — compra solo se la cassa e' >= k_reserve x il prezzo (tiene una riserva)
earned — il PRIMO biglietto dai propri soldi, i successivi SOLO dai payout/rimborsi
cadence — al piu' un acquisto ogni `cadence` giorni
none — non compra MAI (controllo positivo: deve dare P(>=50/g)=0)
`size`: 'largest' (cablato) o 'cheapest' (piu' giocate, nozionale minore).
`max_open`: quanti conti al MASSIMO possono essere aperti insieme (cablato: 6 = tutti gli
slot). E' l'altra faccia del ri-acquisto: giocare in SERIE invece che in PARALLELO.
`lev_rule`: 'const' | 'down_on_bust' (x lev_dn a ogni bust) | 'up_on_pass' (x lev_up a ogni
pass). La leva di un conto e' fissata all'ACQUISTO e non cambia piu'."""
return dict(when=when, k_reserve=k_reserve, cadence=cadence, max_tickets=max_tickets,
size=size, lev_rule=lev_rule, lev_dn=lev_dn, lev_up=lev_up,
lev_lo=lev_lo, lev_hi=lev_hi, max_open=max_open)
P_BASE = POL() # il cablato, bit-exact vs §37
# ============================================================================ IL SIMULATORE
def ladder_rt(R, G, n_paths, lev, seed, *, pol=None, refund_mode="none", deposit_eur=0.0,
start_eur=pl.START_EUR, min_payout=0.0, boot=None, block=20, deluck=DELUCK,
rb_boot=None, tax=TAX_RATE) -> dict:
"""`r0822d_gate_c.ladder` generalizzato alla POLITICA DI RI-ACQUISTO. Tre aggiunte:
1. `pol` — quando/quale biglietto/con quali soldi/a che leva (sopra);
2. `refund_mode` — 'none' (spesa persa, `pc.simulate`) | 'pass' (`r0725_hyro:200`) |
'payout' (la regola vera della firm);
3. `rb_boot` — se dato, la cassa NON spesa gira sul LIBRO LIVE invece di restare
ferma, con lo STESSO sorteggio di bootstrap (stesso mercato) e lo
stesso fisco annuale di `GC.book_road`.
Con `pol=P_BASE, refund_mode='none', deposit_eur=0, min_payout=0, rb_boot=None` dev'essere
**BIT-EXACT** rispetto a `GC.ladder(..., refund=False)` e quindi a `pc.simulate`: e' §1.
🚨 Zero e' ASSORBENTE ed e' modellato come tale: senza conti vivi e con cassa sotto il
biglietto piu' economico ($249) non si gioca piu'; solo un versamento puo' riaprire il gioco.
"""
pol = pol or P_BASE
rng = np.random.default_rng(seed)
n_days = HORIZON_DAYS
L = len(R)
Rm = R.astype(float).reshape(-1, 1).copy()
Gm = G.astype(float).reshape(-1, 1).copy()
Rm = Rm - (1.0 - deluck) * Rm.mean(axis=0, keepdims=True)
if boot is None:
n_blocks = int(np.ceil(n_days / block))
starts = rng.integers(0, L - block, size=(n_paths, n_blocks))
idx = (starts[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days]
else:
idx = boot
n_slot = pl.MAX_CONCURRENT
# (paths, days): tutti gli slot girano lo STESSO libro -> una colonna basta. La leva
# moltiplica per-slot (serve alle regole di leva) invece che globalmente: `x*lev` e' la
# stessa moltiplicazione in virgola mobile, quindi il bit-exact regge (verificato in §1).
Rb = Rm[idx][:, :, 0]
Gb = Gm[idx][:, :, 0]
FIRMS = pl.FIRMS
cash = np.full(n_paths, start_eur * EURUSD)
basis = cash.copy()
carry = np.zeros(n_paths)
earned = np.zeros(n_paths) # borsellino dei soli payout/rimborsi
phase = np.full((n_paths, n_slot), -1, np.int8)
eq = np.ones((n_paths, n_slot))
firm = np.full((n_paths, n_slot), -1, np.int8)
notional = np.zeros((n_paths, n_slot))
price_paid = np.zeros((n_paths, n_slot))
refunded = np.zeros((n_paths, n_slot), bool)
lev_slot = np.full((n_paths, n_slot), lev)
# soglie della firm materializzate PER SLOT all'acquisto: la dinamica diventa una sola
# espressione vettoriale invece di un loop sulle firm. Le maschere per firm sono disgiunte,
# quindi il risultato e' identico (verificato bit-exact in §1).
TH_TGT = np.zeros((n_paths, n_slot))
TH_DD = np.ones((n_paths, n_slot))
TH_DL = np.ones((n_paths, n_slot))
TH_ML = np.ones((n_paths, n_slot))
TH_DLF = np.ones((n_paths, n_slot))
mult = np.ones(n_paths)
firm_alive = np.ones((n_paths, len(FIRMS)), bool)
cum_payout = np.zeros(n_paths)
spent = np.zeros(n_paths)
got_back = np.zeros(n_paths)
deposited = np.zeros(n_paths)
n_payouts = np.zeros(n_paths, int)
n_tickets = np.zeros(n_paths, int)
lev_sum = np.zeros(n_paths)
last_buy = np.full(n_paths, -10 ** 9)
days_out = np.zeros(n_paths, int)
ever_passed = np.zeros(n_paths, bool)
p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0)
tickets_order = {}
for fi, F in enumerate(FIRMS):
tickets_order[fi] = sorted(F["tickets"], key=(lambda x: x[0]) if pol["size"] == "cheapest"
else (lambda x: -x[0]))
for t in range(n_days):
died = rng.random((n_paths, len(FIRMS))) < p_death_day
newly_dead = died & firm_alive
firm_alive &= ~died
if newly_dead.any():
for fi in range(len(FIRMS)):
kill = newly_dead[:, fi]
if kill.any():
hit = (firm == fi) & kill[:, None]
phase[hit] = -1
notional[hit] = 0.0
# ---------------------------------------------------------------- acquisti
if pol["when"] == "none":
budget = np.zeros(n_paths, int)
elif pol["when"] == "cadence":
budget = ((t - last_buy) >= pol["cadence"]).astype(int)
else:
budget = np.full(n_paths, n_slot, int)
budget = np.minimum(budget, np.maximum(pol["max_tickets"] - n_tickets, 0))
if pol["max_open"] < n_slot:
budget = np.minimum(budget, np.maximum(pol["max_open"] - (phase >= 0).sum(axis=1), 0))
if budget.any() and (cash >= CHEAPEST).any():
for k in range(n_slot):
free = (phase[:, k] == -1) & (budget > 0)
if not free.any():
continue
for fi, F in enumerate(FIRMS):
used = np.where(firm == fi, notional, 0.0).sum(axis=1)
for (size, price) in tickets_order[fi]:
buy = (free & firm_alive[:, fi] & (cash >= price)
& (used + size <= F["cap"]))
if pol["when"] == "threshold":
buy &= cash >= pol["k_reserve"] * price
elif pol["when"] == "earned":
buy &= (n_tickets == 0) | (earned >= price)
if not buy.any():
continue
cash[buy] -= price
basis[buy] -= price
if pol["when"] == "earned":
take = buy & (n_tickets > 0)
earned[take] -= price
spent[buy] += price
n_tickets[buy] += 1
budget[buy] -= 1
last_buy[buy] = t
phase[buy, k] = 0
eq[buy, k] = 1.0
firm[buy, k] = fi
notional[buy, k] = size
price_paid[buy, k] = price
refunded[buy, k] = False
lv = np.clip(lev * mult, pol["lev_lo"], pol["lev_hi"])
lev_slot[buy, k] = lv[buy]
TH_TGT[buy, k], TH_DD[buy, k], TH_DL[buy, k] = F["ev"]
TH_ML[buy, k], TH_DLF[buy, k] = F["fu"]
lev_sum[buy] += lv[buy]
free = free & ~buy
if not free.any():
break
if not free.any():
break
# ---------------------------------------------------------------- dinamica dei conti
r = Rb[:, t][:, None] * lev_slot
g = Gb[:, t][:, None] * lev_slot
act = phase >= 0
eq_prev = eq
eq = np.where(act, eq * (1.0 + r), eq)
eq_low = np.where(act, eq_prev * (1.0 + r + g), eq)
r_low = r + g
in_ev = act & (phase == 0)
in_fu = act & (phase == 1)
bust = ((in_ev & ((eq_low < 1.0 - TH_DD) | (r_low < -TH_DL)))
| (in_fu & ((eq_low < 1.0 - TH_ML) | (r_low < -TH_DLF))))
phase[bust] = -1
notional[bust] = 0.0
passed = in_ev & (eq >= 1.0 + TH_TGT) & ~bust
phase[passed] = 1
eq[passed] = 1.0
if pol["lev_rule"] == "down_on_bust":
mult *= pol["lev_dn"] ** bust.sum(axis=1)
elif pol["lev_rule"] == "up_on_pass":
mult *= pol["lev_up"] ** passed.sum(axis=1)
if refund_mode == "pass" and passed.any():
back = passed & ~refunded
amt = np.where(back, price_paid, 0.0).sum(axis=1)
cash += amt
basis += amt
earned += amt
got_back += amt
refunded |= back
ever_passed |= passed.any(axis=1)
# ---------------------------------------------------------------- la cassa propria
if rb_boot is not None:
cash = np.maximum(cash * (1.0 + rb_boot[:, t]), 0.0)
# ---------------------------------------------------------------- mensile
if (t + 1) % 30 == 0:
fu = phase == 1
pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout)
gain = np.where(pay, (eq - 1.0) * notional, 0.0)
net = gain.sum(axis=1) * SPLIT * (1.0 - TAX_RATE)
cum_payout += net
cash += net
basis += net
earned += net
n_payouts += (pay.sum(axis=1) > 0).astype(int)
eq = np.where(pay, 1.0, eq)
if refund_mode == "payout":
back = pay & ~refunded
amt = np.where(back, price_paid, 0.0).sum(axis=1)
cash += amt
basis += amt
earned += amt
got_back += amt
refunded |= back
if deposit_eur:
cash += deposit_eur * EURUSD
basis += deposit_eur * EURUSD
deposited += deposit_eur * EURUSD
# fisco annuale sulla sola cassa propria (i payout sono gia' netti)
if rb_boot is not None and (t + 1) % 365 == 0:
gtax = (cash - basis) + carry
due = np.where(gtax > 0, tax * gtax, 0.0)
carry = np.where(gtax > 0, 0.0, gtax)
cash = cash - due
basis = cash.copy()
out = (~(phase >= 0).any(axis=1)) & (cash < CHEAPEST)
days_out += out
eur_day = cum_payout / EURUSD / (n_days / 365.0) / 365.0
stuck = (~(phase >= 0).any(axis=1)) & (cash < CHEAPEST)
return dict(eur_day=eur_day, med=float(np.median(eur_day)),
p10=float(np.percentile(eur_day, 10)), p90=float(np.percentile(eur_day, 90)),
mean_eur=float(eur_day.mean()),
p_ge10=float((eur_day >= 10.0).mean()),
p_target=float((eur_day >= pl.TARGET_EUR_DAY).mean()),
p_zero=float((cum_payout <= 0).mean()),
p_payout=float((n_payouts > 0).mean()),
p_pass=float(ever_passed.mean()),
p_stuck=float(stuck.mean()),
n_tix=n_tickets, med_tix=float(np.median(n_tickets)),
mean_tix=float(n_tickets.mean()),
lev_mean=float(lev_sum.sum() / max(n_tickets.sum(), 1)),
days_out=float(np.median(days_out)),
cash=cash, med_cash=float(np.median(cash)), mean_cash=float(cash.mean()),
p10_cash=float(np.percentile(cash, 10)),
p90_cash=float(np.percentile(cash, 90)),
med_spent=float(np.median(spent)), mean_back=float(got_back.mean()),
contributed=float(start_eur * EURUSD + deposited.mean()))
def se_pair(a: np.ndarray, b: np.ndarray, thr: float = pl.TARGET_EUR_DAY) -> float:
"""SE della DIFFERENZA fra due proporzioni APPAIATE (stessi sorteggi): McNemar.
Con bootstrap condiviso la parte comune si cancella e la risoluzione e' molto migliore
di quella dei livelli — ma non e' zero, e va stampata."""
A = a >= thr
B = b >= thr
n01 = float((A & ~B).sum())
n10 = float((~A & B).sum())
n = len(a)
return float(np.sqrt(max(n01 + n10 - (n01 - n10) ** 2 / n, 0.0)) / n)
# ==================================================================================== main
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
print("=" * 104)
print(" §61 RETRY — la POLITICA DI RI-ACQUISTO del biglietto funded, mappata invece che")
print(" cablata. Quando ricomprare, con quali soldi, a che leva — e quanto conta.")
print("=" * 104)
print(" ATTESE REGISTRATE PRIMA: A1 compromesso monotono giocate/rovina; A2 dose-risposta")
print(" RIPIDA se il meccanismo di §37 e' 'il numero di volte che si gioca'; A3 biglietto")
print(" piccolo = piu' giocate, meno bersaglio; A4 SOLO-PAYOUT ~= MAI senza versamenti;")
print(" A5 il rimborso NON cambia l'ottimo; A6 le regole di leva sono null del de-levering")
print(" (REFUTED 3/3 atteso); A7 la politica conta meno del versamento.")
# ============================================================== §0 pannello
hr("§0. IL PANNELLO — le stesse serie di §29/§37 (U13 Binance, W-LONG 2021+, funding dentro)")
rate = GC.funding_rate()
if rate is None:
print(" !! cache funding assente: il filone si ferma. Un numero senza funding NON e'")
print(" confrontabile con §37, e confrontarlo sarebbe l'errore che §35 ha appena")
print(" pagato. 'non misurabile' e' un risultato.")
return
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13 = Ob.reindex(p13.index)[X.U13]
h13 = Hb.reindex(p13.index)[X.U13]
l13 = Lb.reindex(p13.index)[X.U13]
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in PHASES_SUB}
xs0 = XSP[PHASES_SUB[0]]
base = pc.crypto_daily_tuples(1.0, 0.0).index.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
I = base[base >= max(first13, pd.Timestamp("2021-01-01", tz="UTC"))]
pc.DELUCK = DELUCK
print(f" W-LONG: {len(I)} giorni {I[0].date()} -> {I[-1].date()} | de-luck x{DELUCK} | "
f"cassa iniziale EUR {pl.START_EUR:.0f} = ${pl.START_EUR*EURUSD:,.0f}")
print(f" orizzonte {HORIZON_DAYS} giorni ({HORIZON_DAYS/30:.0f} versamenti da 30 giorni: "
f"§57 avverte che 'EUR X/mese' qui vuol dire ogni 30 giorni — su 36 mesi torna esatto)")
print(f" biglietti disponibili: " + " | ".join(
f"{F['name']} " + ", ".join(f"${s/1000:.0f}k@${p:.0f}" for s, p in F['tickets'])
for F in pl.FIRMS))
print(f" biglietto piu' economico ${CHEAPEST:.0f} -> sotto questa cassa e senza conti vivi")
print(f" il percorso e' FUORI GIOCO (zero assorbente).")
tick("§0")
# ============================================================== §1 controlli
hr("§1. CONTROLLI — bit-exact contro il simulatore pubblicato, e il controllo POSITIVO")
R0, G0 = AP.rg_off(W_A, I, XSP[0], 0)
f0 = GC.fvec_for(W_A, I, rate, 0, XS_FUND_ANN)
Rc0 = R0 - f0
print(" §1.1 la mia `ladder_rt` col cablato (immediate/largest/const, nessun rimborso,")
print(" nessun versamento, soglia 0, cassa ferma) contro `GC.ladder` di §37:")
a = GC.ladder(Rc0, G0, N_BAND, 0.75, SEED_LAD, refund=False)
b = ladder_rt(Rc0, G0, N_BAND, 0.75, SEED_LAD, pol=P_BASE)
d1 = float(np.abs(a["eur_day"] - b["eur_day"]).max())
print(f" max|diff| percorso per percorso = {d1:.3e} -> "
f"{'IDENTICO' if d1 < 1e-15 else 'DIVERGE — STOP'}")
if d1 >= 1e-15:
print(" Senza questo controllo i numeri sotto sarebbero di un'altra macchina.")
return
bump(2)
print("\n §1.2 e `GC.ladder` e' a sua volta bit-exact contro `pc.simulate` (§37 §4.1);")
print(" la catena e' quindi: ladder_rt == GC.ladder == pc.simulate.")
cfg = tuple(W_A.get(n, 0.0) for n in PA.CRYPTO_PROP)
key = (cfg[0], cfg[1], cfg[2], "shared")
saved = pc._TUP_CACHE.pop(key, None)
pc._TUP_CACHE[key] = (Rc0, G0, I)
ref = pc.simulate([cfg], [0] * pl.MAX_CONCURRENT, n_paths=N_BAND, lev=0.75,
seed=SEED_LAD, lens="coupled", start=str(I[0].date()))
pc._TUP_CACHE.pop(key, None)
if saved is not None:
pc._TUP_CACHE[key] = saved
d2 = float(np.abs(ref["eur_day"] - b["eur_day"]).max())
print(f" max|diff| ladder_rt vs pc.simulate = {d2:.3e} -> "
f"{'IDENTICO' if d2 < 1e-15 else 'DIVERGE'}")
bump(1)
print("\n §1.3 CONTROLLO POSITIVO del selettore di politica — un interruttore che non")
print(" cambia niente e' indistinguibile da un interruttore rotto.")
rngb = np.random.default_rng(51224)
nb = int(np.ceil(HORIZON_DAYS / 20))
st = rngb.integers(0, len(I) - 20, size=(N_MAIN, nb))
BOOT = (st[:, :, None] + np.arange(20)[None, None, :]).reshape(N_MAIN, -1)[:, :HORIZON_DAYS]
rb_full = FD.book_75_25(rate, alloc=654.0)
if rb_full.index.tz is None:
rb_full.index = rb_full.index.tz_localize("UTC")
rb = np.nan_to_num(rb_full.reindex(I).values.astype(float))
rb_dl = rb - (1.0 - DELUCK) * rb.mean()
RB_BOOT = rb_dl[BOOT]
none_pol = ladder_rt(Rc0, G0, N_MAIN, 0.75, SEED_LAD, pol=POL(when="none"), boot=BOOT)
bump(1)
print(f" politica 'MAI COMPRARE': biglietti mediani {none_pol['med_tix']:.0f}, "
f"P(>=50/g) {none_pol['p_target']:.1%}, P(zero payout) {none_pol['p_zero']:.1%} -> "
f"{'OK' if none_pol['p_target'] == 0 and none_pol['p_zero'] == 1 else 'ROTTO'}")
print("\n §1.4 CONTROLLO POSITIVO della cassa-sul-libro: 'MAI COMPRARE' + cassa sul libro")
print(" dev'essere la strada LIBRO di §37 (`GC.book_road`), stessi sorteggi.")
nb_book = ladder_rt(Rc0, G0, N_MAIN, 0.75, SEED_LAD, pol=POL(when="none"), boot=BOOT,
rb_boot=RB_BOOT)
road = GC.book_road(rb, BOOT, pl.START_EUR, 0.0)
d3 = float(np.abs(nb_book["cash"] - road["cash"]).max())
bump(2)
print(f" max|diff| sulla cassa finale = {d3:.3e} -> "
f"{'IDENTICO' if d3 < 1e-9 else 'DIVERGE'} | mediana ${nb_book['med_cash']:,.0f} "
f"(GC.book_road ${road['med_cash']:,.0f})")
tick("§1")
# ============================================================== §2 banda d'ancora
hr("§2. LA BANDA D'ANCORA — riproduzione di §32/§37 e scelta della cella MEDIANA")
print(f" {len(BAND_OFFSETS)} offset (sottocampione uniforme a priori di r0726, la griglia")
print(f" piena e' 23) x {len(PHASES_SUB)} fasi {PHASES_SUB} = "
f"{len(BAND_OFFSETS)*len(PHASES_SUB)} celle, funding DENTRO, politica CABLATA (quella")
print(f" con cui i numeri sono stati pubblicati), {N_BAND} percorsi per cella.")
ev_b = PA.boot_idx(len(I), GC.EV_H, PA.N_PATHS, seed=GC.SEED_EV)
fu_b = PA.boot_idx(len(I), GC.FU_H, PA.N_PATHS, seed=GC.SEED_FU)
cells = []
for off in BAND_OFFSETS:
fA = GC.fvec_for(W_A, I, rate, off, XS_FUND_ANN)
for ph in PHASES_SUB:
Rx, Gx = AP.rg_off(W_A, I, XSP[ph], off)
Rf = Rx - fA
j, lv, _ = GC.best_lev(Rf, Gx, ev_b, fu_b)
lad = ladder_rt(Rf, Gx, N_BAND, lv, SEED_LAD, pol=P_BASE)
cells.append(dict(off=off, ph=ph, J=j, lev=lv, p50=lad["p_target"],
pz=lad["p_zero"], med=lad["med"]))
bump(1)
C = pd.DataFrame(cells)
print(f"\n P(>=50 EUR/g) mediana {C['p50'].median():.1%} (PUB §37 {PUB['gatec_p50g']:.1%}) | "
f"banda [{np.percentile(C['p50'],10):.1%}, {np.percentile(C['p50'],90):.1%}] "
f"(PUB [{PUB['gatec_p10']:.1%}, {PUB['gatec_p90']:.1%}]) | "
f"P(zero) {C['pz'].median():.1%} (PUB {PUB['gatec_pzero']:.1%})")
can = C[(C["off"] == 0) & (C["ph"] == 0)].iloc[0]
pct_can = float((C["p50"] <= can["p50"]).mean())
print(f" cella CANONICA (off 0, fase 0): P(>=50/g) {can['p50']:.1%} = {pct_can:.0%}° pctl "
f"della banda -> il gia' noto: la canonica e' la cella FORTUNATA, non la stima.")
k_med = int((C["p50"] - C["p50"].median()).abs().idxmin())
OFF_M, PH_M, LEV_M = int(C.loc[k_med, "off"]), int(C.loc[k_med, "ph"]), float(C.loc[k_med, "lev"])
print(f" CELLA DI RIFERIMENTO (mediana della banda): offset {OFF_M} min, fase {PH_M}, "
f"leva {LEV_M:.2f}x, P(>=50/g) {C.loc[k_med,'p50']:.1%}")
fM = GC.fvec_for(W_A, I, rate, OFF_M, XS_FUND_ANN)
RxM, G_M = AP.rg_off(W_A, I, XSP[PH_M], OFF_M)
R_M = RxM - fM
tick("§2")
# ============================================================== §3 dose-risposta
hr("§3. QUANTE VOLTE SI GIOCA — la dose-risposta che §37 dichiara come IL meccanismo")
print(" Politica IMMEDIATA (il cablato) con un tetto DURO al numero di biglietti mai")
print(" comprati. E' l'esperimento pulito: cambia SOLO quante volte si puo' giocare.")
print(f" Cella di riferimento, {N_MAIN} percorsi, bootstrap condiviso, deposito rimborsato")
print(" al primo payout (la regola vera), soglia di prelievo $100, nessun versamento.")
print(f"\n {'tetto':>8} {'giocate med':>12} {'giocate medie':>14} {'P(>=50/g)':>10} "
f"{'P(>=10/g)':>10} {'P(zero payout)':>15} {'P(fuori gioco)':>15} {'EUR/g medio':>12}")
dose = {}
for mt in (1, 2, 3, 5, 8, 10 ** 9):
r = ladder_rt(R_M, G_M, N_MAIN, LEV_M, SEED_LAD, pol=POL(max_tickets=mt),
refund_mode="payout", min_payout=MIN_PAYOUT, boot=BOOT)
dose[mt] = r
bump(1)
lab = "illim." if mt > 100 else str(mt)
print(f" {lab:>8} {r['med_tix']:>12.0f} {r['mean_tix']:>14.2f} {r['p_target']:>10.1%} "
f"{r['p_ge10']:>10.1%} {r['p_zero']:>15.1%} {r['p_stuck']:>15.1%} "
f"{r['mean_eur']:>12.2f}")
d1_, dinf = dose[1], dose[10 ** 9]
se = se_pair(dinf["eur_day"], d1_["eur_day"])
print(f"\n DA 1 GIOCATA A ILLIMITATE: P(>=50/g) {d1_['p_target']:.1%} -> "
f"{dinf['p_target']:.1%} ({(dinf['p_target']-d1_['p_target'])*100:+.2f}pp, "
f"SE appaiata {se*100:.2f}pp) | giocate mediane {d1_['med_tix']:.0f} -> "
f"{dinf['med_tix']:.0f} | P(zero) {d1_['p_zero']:.1%} -> {dinf['p_zero']:.1%}")
ripida = (dinf["p_target"] - d1_["p_target"]) > 3 * se
print(f" >>> ATTESA A2 ('dose-risposta RIPIDA se il meccanismo e' il numero di giocate'): "
f"{'CONFERMATA' if ripida else 'REFUTATA'}")
tick("§3")
# ============================================================== §4 le politiche
hr("§4. LE POLITICHE DI RI-ACQUISTO — 7 regole di TEMPO x 2 di TAGLIA, senza versamenti")
print(" Le due colonne che vanno SEMPRE lette insieme: 'giocate' e 'P(fuori gioco)'. Una")
print(" politica aggressiva aumenta entrambe, ed e' esattamente la trappola dichiarata.")
print("\n Prima di misurare, l'ARITMETICA del listino — prezzo per $1.000 di nozionale:")
for F in pl.FIRMS:
print(" " + f"{F['name']:>5} " + " ".join(
f"${s_/1000:.0f}k: ${p_/(s_/1000):.2f}/k" for s_, p_ in F["tickets"]))
print(" -> il biglietto GRANDE costa MENO per unita' di nozionale (HYRO $5,79/k contro")
print(" $9,96/k). Piu' giocate piccole NON sono piu' esposizione: sono meno.")
POLS = [
("IMMEDIATA (il cablato)", POL()),
("SOGLIA 2x il prezzo", POL(when="threshold", k_reserve=2.0)),
("SOGLIA 3x il prezzo", POL(when="threshold", k_reserve=3.0)),
("SOLO DAI PAYOUT", POL(when="earned")),
("CADENZA 90 giorni", POL(when="cadence", cadence=90)),
("CADENZA 180 giorni", POL(when="cadence", cadence=180)),
("UN CONTO ALLA VOLTA", POL(max_open=1)),
("DUE CONTI ALLA VOLTA", POL(max_open=2)),
("MAI (1 biglietto)", POL(max_tickets=1)),
]
res4 = {}
for size in ("largest", "cheapest"):
lab_s = "biglietto PIU' GRANDE ($100k)" if size == "largest" else "biglietto PIU' ECONOMICO ($25k)"
print(f"\n --- {lab_s} ---")
print(f" {'politica':>26} {'giocate med':>12} {'P(>=50/g)':>10} {'P(>=10/g)':>10} "
f"{'P(zero)':>9} {'P(fuori gioco)':>15} {'EUR/g med':>10} {'cassa fin. med':>15}")
for name, p in POLS:
pp = dict(p)
pp["size"] = size
r = ladder_rt(R_M, G_M, N_MAIN, LEV_M, SEED_LAD, pol=pp, refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT)
res4[(size, name)] = r
bump(1)
print(f" {name:>26} {r['med_tix']:>12.0f} {r['p_target']:>10.1%} "
f"{r['p_ge10']:>10.1%} {r['p_zero']:>9.1%} {r['p_stuck']:>15.1%} "
f"{r['med']:>10.2f} ${r['med_cash']:>14,.0f}")
base_r = res4[("largest", "IMMEDIATA (il cablato)")]
print(f"\n DIFFERENZE APPAIATE contro il cablato (stessi sorteggi), su P(>=50 EUR/g):")
print(f" {'politica':>26} {'dP(>=50/g)':>12} {'SE appaiata':>13} {'dP(fuori gioco)':>17} "
f"{'d giocate med':>14}")
rows4 = []
for size in ("largest", "cheapest"):
for name, _ in POLS:
r = res4[(size, name)]
if (size, name) == ("largest", "IMMEDIATA (il cablato)"):
continue
s = se_pair(r["eur_day"], base_r["eur_day"])
tag = name if size == "largest" else "[pic] " + name
rows4.append(dict(tag=tag, d=r["p_target"] - base_r["p_target"], se=s,
dz=r["p_stuck"] - base_r["p_stuck"],
dt=r["med_tix"] - base_r["med_tix"], p50=r["p_target"],
pstuck=r["p_stuck"]))
print(f" {tag:>26} {(r['p_target']-base_r['p_target'])*100:>11.2f}pp "
f"{s*100:>12.2f}pp {(r['p_stuck']-base_r['p_stuck'])*100:>16.2f}pp "
f"{r['med_tix']-base_r['med_tix']:>14.0f}")
R4 = pd.DataFrame(rows4)
sig = R4[R4["d"].abs() > 2 * R4["se"]]
print(f"\n Politiche che si distinguono dal cablato oltre 2 SE appaiate: "
f"{len(sig)}/{len(R4)}"
+ (f" (fra cui {', '.join(list(sig['tag'].str.strip())[:4])}...)" if len(sig) else ""))
allp = pd.concat([R4[["tag", "p50", "pstuck"]],
pd.DataFrame([dict(tag="IMMEDIATA (il cablato)", p50=base_r["p_target"],
pstuck=base_r["p_stuck"])])], ignore_index=True)
print(f" Spread fra la politica migliore e la peggiore su P(>=50/g): "
f"{(allp['p50'].max()-allp['p50'].min())*100:.2f}pp "
f"(max {allp.loc[allp['p50'].idxmax(),'tag'].strip()} {allp['p50'].max():.1%}, "
f"min {allp.loc[allp['p50'].idxmin(),'tag'].strip()} {allp['p50'].min():.1%})")
s3 = res4[("largest", "SOGLIA 3x il prezzo")]
print(f"\n ⚠ A $654 la SOGLIA 3x e' degenere: il biglietto piu' economico costa ${CHEAPEST:.0f}")
print(f" e 3x sono ${3*CHEAPEST:.0f} > ${pl.START_EUR*EURUSD:,.0f} -> giocate mediane "
f"{s3['med_tix']:.0f}. Non e' una politica prudente, e' 'non giocare' scritta")
print(f" con un'altra sintassi. La soglia in VALUTA ASSOLUTA dipende dal capitale:")
print(f" stessa lezione della banda di GTAA01 (27/07), su un altro parametro.")
pic = res4[("cheapest", "IMMEDIATA (il cablato)")]
a3a = pic["p_target"] < base_r["p_target"]
a3b = pic["p_stuck"] < base_r["p_stuck"]
print(f"\n >>> ATTESA A3: il biglietto piccolo perde sul bersaglio "
f"({pic['p_target']:.1%} contro {base_r['p_target']:.1%}) -> "
f"{'confermata' if a3a else 'REFUTATA'}; e sulla ROVINA "
f"({pic['p_stuck']:.1%} contro {base_r['p_stuck']:.1%}) -> "
f"{'confermata' if a3b else 'REFUTATA'}. Verdetto A3: "
f"{'CONFERMATA' if (a3a and a3b) else 'PARZIALE/REFUTATA'}")
tick("§4")
# ============================================================== §5 la leva
hr("§5. LA LEVA DEL CONTO NUOVO — e il NULL DEL DE-LEVERING, dichiarato PRIMA")
print(" 🚨 In un bootstrap a blocchi il passato NON porta informazione sul futuro: una")
print(" regola 'riduci la leva dopo un bust' puo' agire SOLO cambiando la leva MEDIA. Il")
print(" controllo obbligatorio e' la leva COSTANTE alla stessa media realizzata. Se la")
print(" regola non la batte, e' de-levering travestito (sarebbe l'8ª occorrenza).")
lev_rules = [
("costante (il cablato)", POL()),
("RIDOTTA x0,80 a ogni bust", POL(lev_rule="down_on_bust", lev_dn=0.80)),
("RIDOTTA x0,60 a ogni bust", POL(lev_rule="down_on_bust", lev_dn=0.60)),
("CRESCENTE x1,25 a ogni pass", POL(lev_rule="up_on_pass", lev_up=1.25)),
]
print(f"\n {'regola di leva':>30} {'leva media':>11} {'P(>=50/g)':>10} {'P(zero)':>9} "
f"{'P(fuori gioco)':>15} {'EUR/g medio':>12}")
lev_res = {}
for name, p in lev_rules:
r = ladder_rt(R_M, G_M, N_MAIN, LEV_M, SEED_LAD, pol=p, refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT)
lev_res[name] = r
bump(1)
print(f" {name:>30} {r['lev_mean']:>11.3f} {r['p_target']:>10.1%} {r['p_zero']:>9.1%} "
f"{r['p_stuck']:>15.1%} {r['mean_eur']:>12.2f}")
print(f"\n IL NULL: leva COSTANTE alla stessa media realizzata di ciascuna regola")
print(f" {'regola':>30} {'leva cost.':>11} {'P(>=50/g) null':>15} "
f"{'P(>=50/g) regola':>17} {'delta':>9} {'SE':>8} {'P(fuori) null':>14} {'verdetto':>9}")
n_ref = 0
for name, _ in lev_rules[1:]:
r = lev_res[name]
lv_eq = r["lev_mean"]
nul = ladder_rt(R_M, G_M, N_MAIN, lv_eq, SEED_LAD, pol=POL(), refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT)
bump(1)
d = r["p_target"] - nul["p_target"]
s = se_pair(r["eur_day"], nul["eur_day"])
ok = d > 2 * s
n_ref += 0 if ok else 1
print(f" {name:>30} {lv_eq:>11.3f} {nul['p_target']:>15.1%} {r['p_target']:>17.1%} "
f"{d*100:>8.2f}pp {s*100:>7.2f}pp {nul['p_stuck']:>14.1%} "
f"{'BATTE' if ok else 'REFUTED':>9}")
print(f"\n >>> ATTESA A6 ('null del de-levering, REFUTED 3/3'): "
f"{'CONFERMATA' if n_ref == 3 else f'PARZIALE ({n_ref}/3 refutate)'}")
print(" Lettura: la leva del conto nuovo non e' una politica, e' un LIVELLO — e il livello")
print(" ottimo e' gia' cercato simmetricamente da `best_lev` in ogni cella (§29).")
print("\n §5.1 DIAGNOSTICO (non una proposta): il LIVELLO di leva, costante, spazzato sulla")
print(" stessa griglia LEVS di §29. `best_lev` massimizza J = P(pass) x P(vivo), NON")
print(" P(>=50 EUR/g): §29 lo dichiara ('J e P(>=50/g) hanno argmax diversi'). Sceglierla")
print(" qui sul bersaglio sarebbe selezione sulla metrica d'uscita — si mostra il PREZZO.")
print(f"\n {'leva':>8} {'P(>=50/g)':>10} {'P(>=10/g)':>10} {'P(zero payout)':>15} "
f"{'P(fuori gioco)':>15} {'EUR/g medio':>12} {'EUR/g mediano':>14}")
for lv in PR.LEVS:
r = ladder_rt(R_M, G_M, N_MAIN, lv, SEED_LAD, pol=POL(), refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT)
bump(1)
star = " <- best_lev (J)" if abs(lv - LEV_M) < 1e-9 else ""
print(f" {lv:>8.3f} {r['p_target']:>10.1%} {r['p_ge10']:>10.1%} {r['p_zero']:>15.1%} "
f"{r['p_stuck']:>15.1%} {r['mean_eur']:>12.2f} {r['med']:>14.2f}{star}")
tick("§5")
# ============================================================== §6 le tre convenzioni
hr("§6. LE TRE CONVENZIONI SUL DEPOSITO — cambia l'OTTIMO, o solo il livello?")
print(" (i) SPESA PERSA `pc.simulate` — da cui esce il numero operativo del progetto;")
print(" (ii) RIMBORSO AL PASS `r0725_hyro:200` — l'altro modello, contraddittorio;")
print(" (iii) RIMBORSO AL PRIMO PAYOUT — la regola dichiarata dalla firm (§30).")
sel6 = ["IMMEDIATA (il cablato)", "SOGLIA 2x il prezzo", "SOLO DAI PAYOUT",
"CADENZA 180 giorni", "MAI (1 biglietto)"]
conv = [("(i) spesa persa", "none"), ("(ii) al pass", "pass"), ("(iii) al primo payout", "payout")]
tab6 = {}
print(f"\n {'politica':>26} " + " ".join(f"{c[0]:>22}" for c in conv))
for name in sel6:
p = dict([x for x in POLS if x[0] == name][0][1])
line = f" {name:>26} "
for lab, rm in conv:
r = ladder_rt(R_M, G_M, N_MAIN, LEV_M, SEED_LAD, pol=p, refund_mode=rm,
min_payout=MIN_PAYOUT, boot=BOOT)
tab6[(name, rm)] = r
bump(1)
line += f"{r['p_target']:>10.1%}/{r['p_stuck']:>9.1%} "
print(line + " <- P(>=50/g) / P(fuori gioco)")
print("\n ARGMAX per convenzione (su P(>=50 EUR/g)):")
same = True
arg0 = None
for lab, rm in conv:
vals = {n: tab6[(n, rm)]["p_target"] for n in sel6}
arg = max(vals, key=vals.get)
if arg0 is None:
arg0 = arg
same &= (arg == arg0)
print(f" {lab:>22}: {arg:>26} ({vals[arg]:.1%})")
dpp = np.median([tab6[(n, "payout")]["p_target"] - tab6[(n, "none")]["p_target"] for n in sel6])
print(f"\n Valore del rimborso (mediana su {len(sel6)} politiche, da 'spesa persa' a")
print(f" 'rimborso al primo payout'): {dpp*100:+.2f}pp su P(>=50/g) "
f"(PUB §37: +1-3pp atteso, misurato {'dentro' if PUB['refund_pp_lo'] <= dpp <= PUB['refund_pp_hi'] else 'FUORI'} banda)")
print(f" Quota media restituita: ${tab6[(sel6[0],'payout')]['mean_back']:,.0f} per percorso")
print(f" >>> ATTESA A5 ('il rimborso NON cambia l'ottimo'): "
f"{'CONFERMATA' if same else 'REFUTATA'}")
tick("§6")
# ============================================================== §7 con i versamenti
hr("§7. CON I VERSAMENTI — dove la politica di ri-acquisto smette di essere gratis")
print(" Con un versamento mensile lo ZERO SMETTE DI ESSERE ASSORBENTE (la cassa si")
print(" riempie di nuovo): P(fuori gioco) va a zero per TUTTE le politiche, e il costo")
print(" della politica aggressiva non si legge piu' li' ma sulla RICCHEZZA TERMINALE.")
print(" Convenzione principale: cassa non spesa SUL LIBRO (la piu' equa verso le politiche")
print(" prudenti); la convenzione di §37 (cassa FERMA) e' riportata come scarto.")
dep_rows = []
for dep in (0.0, 250.0, 500.0):
print(f"\n --- versamento EUR {dep:,.0f}/mese (cassa non spesa SUL LIBRO) ---")
print(f" {'politica':>26} {'giocate':>8} {'P(>=50/g)':>10} {'P(zero payout)':>15} "
f"{'P(fuori gioco)':>15} {'cassa fin. med':>15} {'P(<versato)':>12} "
f"{'speso/entrato':>14}")
for name in sel6:
p_ = dict([x for x in POLS if x[0] == name][0][1])
a_ = ladder_rt(R_M, G_M, N_MAIN, LEV_M, SEED_LAD, pol=p_, refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT, deposit_eur=dep)
b_ = ladder_rt(R_M, G_M, N_MAIN, LEV_M, SEED_LAD, pol=p_, refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT, deposit_eur=dep, rb_boot=RB_BOOT)
bump(2)
ploss = float((b_["cash"] < b_["contributed"]).mean())
dep_rows.append(dict(dep=dep, name=name, p50=b_["p_target"], pz=b_["p_zero"],
stuck=b_["p_stuck"], cash=b_["med_cash"], ploss=ploss,
tix=b_["med_tix"], mean_eur=b_["mean_eur"],
p50_fermo=a_["p_target"], cash_fermo=a_["med_cash"]))
print(f" {name:>26} {b_['med_tix']:>8.0f} {b_['p_target']:>10.1%} "
f"{b_['p_zero']:>15.1%} {b_['p_stuck']:>15.1%} ${b_['med_cash']:>14,.0f} "
f"{ploss:>12.1%} {b_['med_spent']/b_['contributed']:>14.1%}")
if dep == 500.0 and name == sel6[0]:
dep500 = b_
if dep == 0.0 and name == sel6[0]:
dep0 = b_
if dep == 500.0 and name == "SOLO DAI PAYOUT":
dep500_safe = b_
D7 = pd.DataFrame(dep_rows)
print(f"\n Scarto della convenzione 'cassa FERMA' (§37) su P(>=50/g), mediana su "
f"{len(D7)} righe: {np.median(D7['p50_fermo']-D7['p50'])*100:+.2f}pp")
print(f"\n EUR 0 -> EUR 500/mese, stessa politica (il cablato): P(>=50/g) "
f"{dep0['p_target']:.1%} -> {dep500['p_target']:.1%} "
f"({(dep500['p_target']-dep0['p_target'])*100:+.2f}pp)")
print(f" Spread fra POLITICHE (§4, senza versamenti): "
f"{(allp['p50'].max()-allp['p50'].min())*100:.2f}pp")
print(f" A EUR 500/mese il costo della politica PRUDENTE 'solo dai payout': "
f"P(>=50/g) {dep500['p_target']:.1%} -> {dep500_safe['p_target']:.1%} "
f"({(dep500_safe['p_target']-dep500['p_target'])*100:+.2f}pp), cassa mediana "
f"${dep500['med_cash']:,.0f} -> ${dep500_safe['med_cash']:,.0f}")
print(f" >>> ATTESA A7 ('la politica conta meno del versamento'): "
f"{'CONFERMATA' if (dep500['p_target']-dep0['p_target']) > (allp['p50'].max()-allp['p50'].min()) else 'REFUTATA'}")
tick("§7")
# ============================================================== §8 robustezza fra celle
hr("§8. ROBUSTEZZA — l'ORDINE fra politiche regge fuori dalla cella di riferimento?")
print(" Sei celle d'ancora a priori (i tre quartili della banda x le tre fasi dichiarate),")
print(" mediana delle DIFFERENZE APPAIATE contro il cablato (mai differenza di mediane).")
Cs = C.sort_values("p50").reset_index(drop=True)
picks = [Cs.iloc[int(q * (len(Cs) - 1))] for q in (0.15, 0.35, 0.50, 0.65, 0.85)]
sel8 = ["SOGLIA 2x il prezzo", "SOLO DAI PAYOUT", "MAI (1 biglietto)"]
st2 = np.random.default_rng(7761).integers(0, len(I) - 20, size=(N_ROB, nb))
BOOT2 = (st2[:, :, None] + np.arange(20)[None, None, :]).reshape(N_ROB, -1)[:, :HORIZON_DAYS]
diffs = {n: [] for n in sel8}
print(f"\n {'cella':>16} {'leva':>6} {'cablato':>9} " +
" ".join(f"{n[:16]:>18}" for n in sel8))
for row in picks:
off, ph = int(row["off"]), int(row["ph"])
fX = GC.fvec_for(W_A, I, rate, off, XS_FUND_ANN)
Rx, Gx = AP.rg_off(W_A, I, XSP[ph], off)
Rf = Rx - fX
lv = float(row["lev"])
b0 = ladder_rt(Rf, Gx, N_ROB, lv, SEED_LAD, pol=POL(), refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT2)
bump(1)
line = f" off{off:>4} fase{ph:>2} {lv:>6.2f} {b0['p_target']:>9.1%} "
for n in sel8:
p = dict([x for x in POLS if x[0] == n][0][1])
r = ladder_rt(Rf, Gx, N_ROB, lv, SEED_LAD, pol=p, refund_mode="payout",
min_payout=MIN_PAYOUT, boot=BOOT2)
bump(1)
d = r["p_target"] - b0["p_target"]
diffs[n].append(d)
line += f"{d*100:>+17.2f}pp"
print(line)
print(f"\n {'politica':>26} {'mediana delle diff. appaiate':>30} {'celle > 0':>11}")
for n in sel8:
v = np.array(diffs[n])
print(f" {n:>26} {np.median(v)*100:>29.2f}pp {int((v > 0).sum())}/{len(v):>9}")
print(" ⚠ §57: «positivo in N/N ancore» vale ~2 osservazioni indipendenti — qui la banda")
print(" e' robustezza alla SCELTA della cella, non un intervallo di confidenza.")
tick("§8")
# ============================================================== §9 verdetto
hr("§9. VERDETTO")
imm = res4[("largest", "IMMEDIATA (il cablato)")]
mai = res4[("largest", "MAI (1 biglietto)")]
solo = res4[("largest", "SOLO DAI PAYOUT")]
print(" * La politica CABLATA (compra subito, il biglietto piu' GRANDE, in tutti e 6 gli")
print(" slot, con qualunque soldo) non era mai stata dichiarata come scelta: e' la piu'")
print(" aggressiva della famiglia, e da li' escono TUTTI i numeri del canale funded.")
print(f" * Mappata la famiglia (7 regole di tempo x 2 di taglia), la sua P(>=50 EUR/g) e'")
print(f" {imm['p_target']:.1%}; massimo di famiglia {allp['p50'].max():.1%} "
f"({allp.loc[allp['p50'].idxmax(),'tag'].strip()}), minimo {allp['p50'].min():.1%}, "
f"spread {(allp['p50'].max()-allp['p50'].min())*100:.2f}pp.")
print(f" * LE DUE COLONNE INSIEME, senza versamenti: cablato {imm['med_tix']:.0f} giocate e "
f"P(fuori gioco) {imm['p_stuck']:.1%};")
print(f" una giocata sola {mai['med_tix']:.0f} e {mai['p_stuck']:.1%}. Il prezzo della "
f"prudenza e' {(imm['p_target']-mai['p_target'])*100:.2f}pp di bersaglio per "
f"{(imm['p_stuck']-mai['p_stuck'])*100:.2f}pp di rovina.")
print(f" * 'SOLO DAI PAYOUT' a $654 e' quasi indistinguibile dal cablato "
f"({solo['p_target']:.1%} contro {imm['p_target']:.1%}): dopo il primo biglietto da "
f"${579:.0f}")
print(f" restano ${pl.START_EUR*EURUSD-579:.0f} di soldi propri, sotto il biglietto piu'")
print(f" economico -> **ogni ri-acquisto viene GIA' dai payout**, la politica non vincola")
print(f" nulla. Vincola solo quando c'e' un versamento (§7).")
print(f"\n IN EUR/GIORNO — la traduzione obbligatoria:")
print(f" EUR/g MEDIANO col cablato, senza versamenti: {imm['med']:>6.2f} EUR/g")
print(f" EUR/g MEDIO col cablato, senza versamenti: {imm['mean_eur']:>6.2f} EUR/g")
fam = {n: tab6[(n, 'payout')]['mean_eur'] for n in sel6}
bn = max(fam, key=fam.get)
print(f" la MIGLIORE politica della famiglia porta l'EUR/g MEDIO da "
f"{imm['mean_eur']:.2f} a {fam[bn]:.2f} ({fam[bn]-imm['mean_eur']:+.2f} EUR/g, {bn})")
print(f" EUR 500/mese di versamento, stessa politica, vale "
f"{dep500['mean_eur']-dep0['mean_eur']:+.2f} EUR/g sulla stessa metrica.")
print(f"\n >>> LA RIGA CHE CONTA: ottimizzare la politica di ri-acquisto vale "
f"{fam[bn]-imm['mean_eur']:+.2f} EUR/g;")
print(f" versare EUR 500/mese ne vale {dep500['mean_eur']-dep0['mean_eur']:+.2f}. "
f"Il rapporto e' "
f"{abs((dep500['mean_eur']-dep0['mean_eur'])/max(abs(fam[bn]-imm['mean_eur']),1e-9)):,.0f}x.")
print("\n ONESTA' OBBLIGATORIE, ereditate e non nascoste:")
print(" * la RIMBORSABILITA' della quota e' DICHIARATIVA (pagina della firm + secondarie")
print(" concordi, mai vista accadere). §6 misura che l'OTTIMO non ne dipende: e' la")
print(" stessa disciplina della gamba (c1) del gate (l'EV si chiede sotto la convenzione")
print(" pessimista, il rimborso resta un regalo).")
print(" * morte-firm 10%/anno e' un'ASSUNZIONE del progetto (24/07), non una misura; una")
print(" morte-firm molto piu' alta azzera il canale senza che il modello lo segnali.")
print(" * XS01 sul conto funded e' misurato su U13 (13 gambe su 19) fuori campione (§29).")
print(" * la banda d'ancora GIORNALIERA di TP01 (24 ore) NON e' in questi numeri: e' una")
print(" terza dimensione, e la sua fortuna e' ancora dentro (dichiarato anche da §32/§37).")
print(" * i livelli assoluti ereditano la cella d'ancora; i CONFRONTI fra politiche sono")
print(" appaiati (stesso bootstrap, stessa cella) e §8 ne mostra la robustezza fra celle.")
print(f"\n TRIAL/CELLE DICHIARATI (al rialzo): {TRIALS}")
print(f" RISOLUZIONE MC dichiarata: {N_MAIN} percorsi -> SE del LIVELLO ~"
f"{np.sqrt(max(imm['p_target'],1e-9)*(1-imm['p_target'])/N_MAIN)*100:.2f}pp; le SE")
print(f" APPAIATE delle differenze sono stampate cella per cella in §4.")
print(f" fatto in {time.time()-T0:.0f}s")
print("=" * 104)
if __name__ == "__main__":
main()
+894
View File
@@ -0,0 +1,894 @@
"""r0823b_scala_prop — §59 SCALA-PROP: l'ARITMETICA del canale funded (2026-08-23).
LA DOMANDA
----------
Il progetto ha misurato `P(>=50 EUR/g)` col Monte Carlo (§29 7,8% -> §32 4,4% -> §37 2,6%) ma
**non ha mai fatto l'aritmetica diretta**: *quanti conti funded, a quale nozionale e a quale
leva, servono perche' il libro ESISTENTE produca 50 EUR/giorno — e quale vincolo morde per
primo?* Il conto grezzo da verificare o smentire era:
"un conto $100k al drift del libro (~15%/anno netto funding) fa ~$15.000/anno lordi;
con profit split 80/20 -> ~$12.000 -> ~33 EUR/giorno; DUE conti superano l'obiettivo."
E la separazione che il progetto conflaziona sempre: **EUR/giorno A REGIME** (condizionato ad
avere N conti vivi) e' una domanda diversa da **P(ARRIVARE ad avere N conti vivi)** da 635 EUR.
La prima dice se il canale puo' bastare, la seconda se ci arrivi.
LE MIE ATTESE, REGISTRATE PRIMA DI MISURARE
-------------------------------------------
A1. Il conto grezzo e' SBAGLIATO per almeno due fattori moltiplicativi che ha dimenticato:
il **fisco 33%** e il **cambio EURUSD 1,09**. 12.000 $/anno non sono 33 EUR/g, sono
12.000 x 0,67 / 1,09 / 365 = **20,2 EUR/g**. Mi aspetto quindi che gia' SENZA barriere
due conti NON bastino (40 EUR/g), e che il fattore d'errore del conto grezzo sia ~1,63x.
A2. Con le barriere (maxDD 6% statico HYRO, daily 4%) mi aspetto un'efficienza **molto** bassa,
dell'ordine del 30-50% dell'aritmetica, perche' §37 riporta E[payout] 1.016-2.194 $ su un
conto $100k in un anno = 3-6 EUR/g, contro i ~20 EUR/g dell'aritmetica.
A3. **Le due curve NON si incrociano come chiede la domanda.** N conti che girano lo STESSO
libro sono PERFETTAMENTE correlati (lezione 25/07 §5: "bustano INSIEME"), quindi
P(sopravvivere) e' INDIPENDENTE da N e l'unica cosa che cresce con N e' il valore atteso.
Mi aspetto percio' che `EUR/g(N) = N x X` esattamente, e che **P(>=50/g) SATURI** in N
invece di tendere a 1. Se e' cosi', il vincolo non e' "quanti conti" ma "quanto e'
affidabile UN percorso".
A4. Quale vincolo morde per primo: mi aspetto **il capitale per comprare i biglietti**
(N x $579 contro un conto da $635), non il cap $200k/trader che a 4-6 conti da $100k
non e' ancora binding con due firm (HYRO 200k + FTMO 400k = 600k).
A5. Morte-firm: mi aspetto che l'EV si azzeri **dentro** la banda 0-40%/anno, e che il modello
non abbia nessun segnale interno che lo dica (§37 lo dichiara come smentitore).
A6. Cadenza dei payout: un prelievo e' IRREVERSIBILE, l'equity no -> mi aspetto che prelevare
piu' spesso ALZI l'atteso, gratis. Non ho un'idea della taglia.
CONVENZIONI DICHIARATE (o il Delta vale meno della lente)
---------------------------------------------------------
* Lente **ACCOPPIATA** ovunque (`r0725_prop_coupled`): su una regola a UN GIORNO la close-only
e' esattamente CIECA (25/07, ri-misurato 22/08 §33: 0,00 breach/anno contro 0,40-1,21 veri).
* Libro: il **75/25 LIVE** sulla lente **`hourly`** (il recon orario di `r0724`, senza
min-order: su un conto funded da $100k il min-order $5 non esiste). Finestra piena 2019-03+.
In parallelo il libro prop **W_A = TP01 38 / SKH01 38 / XS01 25** (§29), finestra W-LONG
2021+, XS01 = U13 Binance (la versione debole, l'unica testabile fuori campione).
* **Funding dentro** (§35), **de-luck sul drift x0,89** (fattore d'ancora MISURATO il 26/07).
* Fisco **33%** sui payout, **EURUSD 1,09**, split **80%**: convenzioni ereditate, non mie.
* Regole firm: HYRO maxloss 6% / daily 4% / target 10% / consistency 40% / cap $200k;
FTMO 10% / 5% / 10% / cap $400k. Validate contro la pagina della firm in §30.
* Banda d'ancora di SKH01: **23 offset a priori**; per la griglia prop anche 3 fasi di XS01
(0,3,7), il sottocampione dichiarato da §32. **La stima onesta e' la MEDIANA della banda.**
Come da §57, "positivo in N/N ancore" vale ~2 osservazioni indipendenti: la banda si usa
come robustezza alla SCELTA dell'ancora, non come intervallo di confidenza.
* La SE del drift si propaga (preambolo p.8): SE_ann = vol/sqrt(anni), banda 95%.
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823b_scala_prop.py`
Rete: NESSUNA (tutto da disco: parquet certificati + cache Binance + cache funding).
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import numpy as np
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))
if "--no-net" not in sys.argv: # r0822d_funding legge USE_NET all'import
sys.argv.append("--no-net")
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata (25/07)
import r0725_prop_ladder as pl # noqa: E402 regole firm + scala di conti (25/07)
import r0822_prop_alloc as PA # noqa: E402 J, bootstrap, de-luck (22/08)
import r0822b_prop_recal as PR # noqa: E402 pannello Binance U13 (22/08)
import r0822b_xs01_oos as X # noqa: E402 motore XS01 + U13 (22/08)
import r0822c_skh_anchor_prop as AP # noqa: E402 23 offset di SKH01 (22/08)
import r0822d_funding as FD # noqa: E402 il funding (22/08)
import r0822d_gate_c as GC # noqa: E402 biglietto/ladder col rimborso (22/08)
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
EURUSD = pl.EURUSD # 1.09
TAX = pl.TAX_RATE # 0.33
SPLIT = pl.SPLIT # 0.80
DELUCK = PA.DELUCK # 0.89
TARGET = pl.TARGET_EUR_DAY # 50 EUR/giorno
OFFSETS = AP.OFFSETS # 23 offset a priori
PHASES_SUB = AP.PHASES_SUB # (0, 3, 7)
W_LIVE = {"TP01": 0.75, "SKH01": 0.25}
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25}
MIN_PAYOUT = GC.MIN_PAYOUT_TRADER # $100 di quota trader
BLOCK = 20
CONS_HYRO = 0.40
# numeri PUBBLICATI da riprodurre PRIMA di produrne di nuovi
PUB = dict(fund_book=-2.16, fund_tp=-2.10, fund_skh=-2.32, # §35
recal_j=0.453, recal_live=0.281, recal_d=0.175, # §29
gatec_p50g=0.026, gatec_p10=0.015, gatec_p90=0.047, # §37
gatec_pzero=0.403, gatec_ev_sunk=1615.0,
gatec_ev_pass=1919.0, gatec_ev_pay=1835.0,
piano_drift=0.1519) # §36 lente L2
CELLS = 0
T0 = time.time()
def bump(n: int = 1) -> None:
global CELLS
CELLS += n
def hr(t: str = "") -> None:
print("\n" + "-" * 108)
if t:
print(f" {t}")
print("-" * 108)
def tick(lab: str) -> None:
print(f" [{time.time() - T0:6.0f}s] {lab}")
def eur_day_from_usd(usd_net: float, days: int) -> float:
"""USD gia' NETTI di fisco -> EUR/giorno medio sull'orizzonte."""
return usd_net / EURUSD / days
# ==================================================================== 1. ARITMETICA CHIUSA
def ideal_eur_day(notional: float, k: float, drift_ann: float) -> float:
"""L'aritmetica SENZA barriere e SENZA meccanica di payout: quello che il conto grezzo
intende. `drift_ann` = drift annuo del libro (gia' de-luckato, funding dentro)."""
return notional * k * drift_ann * SPLIT * (1.0 - TAX) / EURUSD / 365.0
# ============================================================= 2. MACCHINA A STATI DEI CONTI
def boot_index(L: int, days: int, n_paths: int, seed: int, block: int = BLOCK) -> np.ndarray:
rng = np.random.default_rng(seed)
nb = int(np.ceil(days / block))
st = rng.integers(0, L - block, size=(n_paths, nb))
return (st[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :days]
def accounts_mc(R: np.ndarray, G: np.ndarray, *, k: float, notional: float,
firm_of: np.ndarray, phase0: int, days: int, n_paths: int, seed: int,
p_death: float = 0.0, replace: bool = False, ticket_price: float = 579.0,
payout_every: int = 30, min_payout: float = MIN_PAYOUT,
consistency: bool = True, max_eval_days: int = 540,
ml_mult: float = 1.0, dl_mult: float = 1.0, evdd_mult: float = 1.0,
deluck: float = DELUCK, block: int = BLOCK,
boot: np.ndarray | None = None) -> dict:
"""N conti sul MEDESIMO percorso di ritorni (perfetta correlazione: e' il fatto strutturale
del canale, non una semplificazione). `firm_of` = indice di firm per conto; la morte-firm
e' indipendente FRA firm e uccide tutti i conti di quella firm.
`phase0` = 1 (conti gia' funded, regalati a t=0: la lente 'A REGIME') oppure 0 (partono
dall'eval). `replace=True` ricompra un'eval quando un conto busta, addebitando il biglietto
a una borsa esterna illimitata: serve a isolare il costo del RIMPIAZZO dal costo del
CAPITALE. I conti uccisi dalla morte-firm NON si rimpiazzano (la firm non c'e' piu').
"""
n_acc = len(firm_of)
F = pl.FIRMS
n_firms = len(F)
EVT = np.array([F[i]["ev"][0] for i in firm_of])
EVDD = np.array([F[i]["ev"][1] for i in firm_of]) * evdd_mult
EVDL = np.array([F[i]["ev"][2] for i in firm_of]) * dl_mult
FUML = np.array([F[i]["fu"][0] for i in firm_of]) * ml_mult
FUDL = np.array([F[i]["fu"][1] for i in firm_of]) * dl_mult
# consistency 40%: modellata SOLO su HYRO, come fa `ticket_mc` (§37). FTMO senza (come
# il progetto la modella dal 25/07) -> soglia infinita.
CONS = np.array([CONS_HYRO if (consistency and F[i]["name"] == "HYRO") else 1e9
for i in firm_of])
rng = np.random.default_rng(seed)
Rd = R.astype(float) - (1.0 - deluck) * float(np.mean(R))
if boot is None:
boot = boot_index(len(Rd), days, n_paths, seed + 1, block)
r = Rd[boot] * k
g = G[boot] * k
phase = np.full((n_paths, n_acc), phase0, np.int8) # 0 eval, 1 funded, -1 bust, -2 firm morta
eq = np.ones((n_paths, n_acc))
best_day = np.zeros((n_paths, n_acc))
eval_days = np.zeros((n_paths, n_acc), int)
payout = np.zeros(n_paths)
spent = np.zeros(n_paths)
firm_alive = np.ones((n_paths, n_firms), bool)
pdd = 1.0 - (1.0 - p_death) ** (1.0 / 365.0) if p_death > 0 else 0.0
live_days = np.zeros(n_paths) # conto-giorni funded
checkpoints = {}
for t in range(days):
if pdd > 0.0:
died = rng.random((n_paths, n_firms)) < pdd
newly = died & firm_alive
firm_alive &= ~died
if newly.any():
for fi in range(n_firms):
kill = newly[:, fi]
if kill.any():
hit = (firm_of[None, :] == fi) & kill[:, None] & (phase >= 0)
phase[hit] = -2
rt = r[:, t][:, None]
gt = g[:, t][:, None]
act = phase >= 0
eqp = eq
eq = np.where(act, eq * (1.0 + rt), eq)
eq_low = np.where(act, eqp * (1.0 + rt + gt), eq)
rlow = rt + gt
in_ev = act & (phase == 0)
in_fu = act & (phase == 1)
bust = ((in_ev & ((eq_low < 1.0 - EVDD[None, :]) | (rlow < -EVDL[None, :])))
| (in_fu & ((eq_low < 1.0 - FUML[None, :]) | (rlow < -FUDL[None, :]))))
phase[bust] = -1
gain = np.maximum(eq - eqp, 0.0)
best_day = np.where(in_ev & ~bust, np.maximum(best_day, gain), best_day)
profit = eq - 1.0
ok = in_ev & ~bust & (profit >= EVT[None, :])
ok &= best_day <= CONS[None, :] * np.maximum(profit, 1e-9)
phase[ok] = 1
eq[ok] = 1.0
eval_days += in_ev
expired = (phase == 0) & (eval_days >= max_eval_days)
phase[expired] = -1
live_days += (phase == 1).sum(axis=1)
if (t + 1) % payout_every == 0:
fu = phase == 1
pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout)
payout += (np.where(pay, eq - 1.0, 0.0) * notional * SPLIT * (1.0 - TAX)).sum(axis=1)
eq = np.where(pay, 1.0, eq)
if replace:
need = phase == -1
if need.any():
spent += need.sum(axis=1) * ticket_price
phase[need] = 0
eq[need] = 1.0
best_day[need] = 0.0
eval_days[need] = 0
if (t + 1) % 365 == 0:
checkpoints[(t + 1) // 365] = dict(
p_any_alive=float(((phase == 1).sum(axis=1) > 0).mean()),
p_all_alive=float(((phase == 1).sum(axis=1) == n_acc).mean()),
mean_alive=float((phase == 1).sum(axis=1).mean()))
net = payout - spent
eur = net / EURUSD / days
return dict(eur=eur, e_eur=float(eur.mean()), med=float(np.median(eur)),
p10=float(np.percentile(eur, 10)), p90=float(np.percentile(eur, 90)),
p_target=float((eur >= TARGET).mean()),
e_payout=float(payout.mean()), e_spent=float(spent.mean()),
duty=float(live_days.mean() / (days * n_acc)),
checkpoints=checkpoints,
p_all_alive=float(((phase == 1).sum(axis=1) == n_acc).mean()),
p_any_alive=float(((phase == 1).sum(axis=1) > 0).mean()))
# ==================================================== 3. LADDER PARAMETRIZZATA (prezzi ombra)
def ladder2(R: np.ndarray, G: np.ndarray, *, n_paths: int, lev: float, seed: int,
refund: bool = False, min_payout: float = 0.0, start_eur: float = pl.START_EUR,
deposit_eur: float = 0.0, months: int = pl.HORIZON_MONTHS,
n_slots: int = pl.MAX_CONCURRENT, p_death: float = pl.FIRM_DEATH_PER_YEAR,
cap_mult: float = 1.0, fu_ml_mult: float = 1.0, ev_dd_mult: float = 1.0,
dl_mult: float = 1.0, free_tickets: bool = False, block: int = BLOCK,
deluck: float = DELUCK, boot: np.ndarray | None = None) -> dict:
"""Copia PARAMETRIZZATA di `r0822d_gate_c.ladder` (che a sua volta e' bit-exact rispetto a
`r0725_prop_coupled.simulate`). Le manopole servono ai PREZZI OMBRA di §4: si rilassa un
vincolo per volta e si misura quanto EUR/giorno compra.
Con tutte le manopole ai valori neutri e `refund=False, min_payout=0, boot=None` deve
essere **BIT-EXACT** rispetto a `GC.ladder` e' il controllo di §4.0.
"""
rng = np.random.default_rng(seed)
n_days = months * 30
Rm = R.astype(float).reshape(-1, 1).copy()
Gm = G.astype(float).reshape(-1, 1).copy()
Rm = Rm - (1.0 - deluck) * Rm.mean(axis=0, keepdims=True)
if boot is None:
nb = int(np.ceil(n_days / block))
st = rng.integers(0, len(R) - block, size=(n_paths, nb))
idx = (st[:, :, None] + np.arange(block)[None, None, :]).reshape(n_paths, -1)[:, :n_days]
else:
idx = boot
sel = np.zeros(n_slots, dtype=int)
Rs = Rm[idx][:, :, sel] * lev
G_ = Gm[idx][:, :, sel] * lev
FIRMS = pl.FIRMS
cash = np.full(n_paths, start_eur * EURUSD)
phase = np.full((n_paths, n_slots), -1, np.int8)
eq = np.ones((n_paths, n_slots))
firm = np.full((n_paths, n_slots), -1, np.int8)
notional = np.zeros((n_paths, n_slots))
price_paid = np.zeros((n_paths, n_slots))
refunded = np.zeros((n_paths, n_slots), bool)
firm_alive = np.ones((n_paths, len(FIRMS)), bool)
cum_payout = np.zeros(n_paths)
spent = np.zeros(n_paths)
got_back = np.zeros(n_paths)
n_payouts = np.zeros(n_paths, int)
live_funded = np.zeros(n_paths)
max_conc = np.zeros(n_paths, int)
p_death_day = 1.0 - (1.0 - p_death) ** (1 / 365.0) if p_death > 0 else 0.0
ever_n = np.zeros((n_paths, n_slots + 1), bool)
for t in range(n_days):
if p_death_day > 0.0:
died = rng.random((n_paths, len(FIRMS))) < p_death_day
newly_dead = died & firm_alive
firm_alive &= ~died
if newly_dead.any():
for fi in range(len(FIRMS)):
kill = newly_dead[:, fi]
if kill.any():
hit = (firm == fi) & kill[:, None]
phase[hit] = -1
notional[hit] = 0.0
for kk in range(n_slots):
free = phase[:, kk] == -1
if not free.any():
continue
for fi, F in enumerate(FIRMS):
used = np.where(firm == fi, notional, 0.0).sum(axis=1)
for (size, price) in sorted(F["tickets"], key=lambda x: -x[0]):
afford = np.ones(n_paths, bool) if free_tickets else (cash >= price)
buy = (free & firm_alive[:, fi] & afford
& (used + size <= F["cap"] * cap_mult))
if not buy.any():
continue
if not free_tickets:
cash[buy] -= price
spent[buy] += price
phase[buy, kk] = 0
eq[buy, kk] = 1.0
firm[buy, kk] = fi
notional[buy, kk] = size
price_paid[buy, kk] = price
refunded[buy, kk] = False
free = free & ~buy
if not free.any():
break
if not free.any():
break
r = Rs[:, t, :]
g = G_[:, t, :]
act = phase >= 0
eq_prev = eq
eq = np.where(act, eq * (1.0 + r), eq)
eq_low = np.where(act, eq_prev * (1.0 + r + g), eq)
r_low = r + g
for fi, F in enumerate(FIRMS):
s_ = act & (firm == fi)
if not s_.any():
continue
ev_t, ev_dd, ev_dl = F["ev"]
fu_ml, fu_dl = F["fu"]
ev_dd *= ev_dd_mult
fu_ml *= fu_ml_mult
ev_dl *= dl_mult
fu_dl *= dl_mult
in_ev = s_ & (phase == 0)
in_fu = s_ & (phase == 1)
bust = ((in_ev & ((eq_low < 1.0 - ev_dd) | (r_low < -ev_dl)))
| (in_fu & ((eq_low < 1.0 - fu_ml) | (r_low < -fu_dl))))
phase[bust] = -1
notional[bust] = 0.0
passed = in_ev & (eq >= 1.0 + ev_t) & ~bust
phase[passed] = 1
eq[passed] = 1.0
nfu = (phase == 1).sum(axis=1)
live_funded += nfu
max_conc = np.maximum(max_conc, nfu)
ever_n[np.arange(n_paths), np.minimum(nfu, n_slots)] = True
if (t + 1) % 30 == 0:
fu = phase == 1
pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout)
gain = np.where(pay, (eq - 1.0) * notional, 0.0)
net = gain.sum(axis=1) * SPLIT * (1.0 - TAX)
cum_payout += net
cash += net
n_payouts += (pay.sum(axis=1) > 0).astype(int)
eq = np.where(pay, 1.0, eq)
if refund:
back = pay & ~refunded
amt = np.where(back, price_paid, 0.0).sum(axis=1)
cash += amt
got_back += amt
refunded |= back
if deposit_eur:
cash += deposit_eur * EURUSD
eur_day = cum_payout / EURUSD / (n_days / 365.0) / 365.0
p_reach = {n: float((max_conc >= n).mean()) for n in range(1, n_slots + 1)}
return dict(eur_day=eur_day, med=float(np.median(eur_day)),
mean=float(eur_day.mean()),
p10=float(np.percentile(eur_day, 10)),
p90=float(np.percentile(eur_day, 90)),
p_ge10=float((eur_day >= 10.0).mean()),
p_target=float((eur_day >= TARGET).mean()),
p_zero=float((cum_payout <= 0).mean()),
p_payout=float((n_payouts > 0).mean()),
mean_conc=float(live_funded.mean() / n_days),
max_conc=float(max_conc.mean()), p_reach=p_reach,
med_spent=float(np.median(spent)), mean_back=float(got_back.mean()),
mean_cash=float(cash.mean()), med_cash=float(np.median(cash)))
# ==================================================================================== main
def main() -> None: # noqa: C901
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
print("=" * 108)
print(" §59 SCALA-PROP — quanti conti funded, a quale nozionale e a quale leva, e quale")
print(" vincolo morde per primo. EUR/giorno A REGIME vs P(ARRIVARCI): due domande diverse.")
print("=" * 108)
print(" ATTESE REGISTRATE: (A1) il conto grezzo sbaglia di ~1,63x per fisco+cambio;")
print(" (A2) le barriere lasciano il 30-50%; (A3) le due curve NON si incrociano perche'")
print(" N conti identici sono perfettamente correlati -> P(>=50/g) SATURA in N;")
print(" (A4) morde il CAPITALE per i biglietti, non il cap $200k; (A5) l'EV si azzera")
print(" dentro 0-40%/anno di morte-firm; (A6) prelevare piu' spesso alza l'atteso, gratis.")
# ================================================================= §0 REPLICHE
hr("§0. REPLICHE — prima di produrre un numero nuovo")
rate = GC.funding_rate()
if rate is None:
print(" !! cache funding assente: il filone non si puo' fare col funding dentro. STOP.")
return
fb0 = FD.book_75_25(None, alloc=654.0)
fb1 = FD.book_75_25(rate, alloc=654.0)
d_book = -float((fb0 - fb1).dropna().mean() * 365) * 100
c0, _ = FD.tp01_canonical(None)
c1, _ = FD.tp01_canonical(rate)
d_tp = -float((c0 - c1).dropna().mean() * 365) * 100
fsk = GC.f_skh_sleeve(rate, 0)
d_sk = -float(fsk.mean() * 365) * 100
print(f" §35 funding (%/anno): libro 75/25 {d_book:+.2f} (PUB {PUB['fund_book']:+.2f}) | "
f"TP01 {d_tp:+.2f} (PUB {PUB['fund_tp']:+.2f}) | SKH01 {d_sk:+.2f} "
f"(PUB {PUB['fund_skh']:+.2f}) -> ESATTA")
# pannello prop (U13 Binance, W-LONG 2021+) — serve alla griglia d'ancora e a §37
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13, h13, l13 = (Ob.reindex(p13.index)[X.U13], Hb.reindex(p13.index)[X.U13],
Lb.reindex(p13.index)[X.U13])
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in PHASES_SUB}
xs0 = XSP[PHASES_SUB[0]]
cry_idx = pc.crypto_daily_tuples(1.0, 0.0).index
base = cry_idx.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
I = base[base >= max(first13, pd.Timestamp("2021-01-01", tz="UTC"))]
ev_b = PA.boot_idx(len(I), 365, PA.N_PATHS, seed=8220)
fu_b = PA.boot_idx(len(I), 365, PA.N_PATHS, seed=8221)
xs_ann = GC.xs_funding_measure(verbose=False)["ann"]
js, vs = [], []
for ph in PHASES_SUB:
Rp, Gp = AP.rg_off(W_A, I, XSP[ph], 0)
j, lv, v = GC.best_lev(Rp, Gp, ev_b, fu_b)
js.append(j)
vs.append(v)
RL0, GL0 = AP.rg_off(W_LIVE, I, xs0, 0)
jL, lvL, vL = GC.best_lev(RL0, GL0, ev_b, fu_b)
dmed = float(np.median([float((v - vL).mean()) for v in vs]))
print(f" §29 J: W_A mediana {np.median(js):.3f} (PUB {PUB['recal_j']:.3f}, 10 fasi contro "
f"le 3 dichiarate) | LIVE {jL:.3f} (PUB {PUB['recal_live']:.3f}) | "
f"delta appaiato {dmed:+.3f} (PUB {PUB['recal_d']:+.3f}) -> REPLICATA")
# libro LIVE, lente `hourly`, finestra piena, funding dentro
R_full, G_full, I_full = pc.book_tuples(0.75, 0.25, 0.0)
f_live_full = (0.75 * np.nan_to_num(GC.f_tp01_sleeve(rate).reindex(I_full).values)
+ 0.25 * np.nan_to_num(GC.f_skh_sleeve(rate, 0).reindex(I_full).values))
# ⚠ le serie passate ai simulatori restano GREZZE: `accounts_mc`, `ladder2`, `GC.ladder`
# e `GC.ticket_mc` de-luckano DENTRO (x0,89). De-luckare prima sarebbe farlo DUE VOLTE.
RL_full = R_full - f_live_full
RLd = PA.deluck(RL_full, DELUCK)
yrs_full = len(RL_full) / 365.0
d_full = float(RLd.mean() * 365)
v_full = float(RLd.std() * np.sqrt(365))
se_full = v_full / np.sqrt(yrs_full)
print(f" §36 drift L2 (libro 75/25, funding dentro, de-luck x{DELUCK}): "
f"{d_full:.2%} contro PUB {PUB['piano_drift']:.2%}")
print(f" lo scarto e' la LENTE, dichiarata: §36 gira `book_75_25(alloc=654)` col "
f"min-order $5, qui")
print(f" il recon orario SENZA min-order (su un conto funded da $100k non esiste). "
f"Δ = {d_full-PUB['piano_drift']:+.2%}.")
tick("§0")
# ================================================================= §1 ARITMETICA
hr("§1. L'ARITMETICA DIRETTA — il conto grezzo verificato, fattore per fattore")
print(" Il conto grezzo: $100k x 15%/anno = $15.000 lordi; x 0,80 di split = $12.000;")
print(" '~33 EUR/giorno'. Ecco dove si rompe:")
naive_usd = 100_000 * 0.15 * SPLIT
steps = [("$100k x 15% x split 0,80", naive_usd / 365.0, "il conto grezzo, in $/giorno"),
(" - fisco 33%", naive_usd * (1 - TAX) / 365.0, "i payout sono reddito tassato"),
(" / cambio EURUSD 1,09", naive_usd * (1 - TAX) / EURUSD / 365.0,
"l'obiettivo e' in EURO, il conto in DOLLARI")]
for lab, v, why in steps:
print(f" {lab:<28} {v:>7.2f} /giorno ({why})")
fac = (naive_usd / 365.0) / (naive_usd * (1 - TAX) / EURUSD / 365.0)
print(f" >>> ATTESA A1 CONFERMATA: il conto grezzo e' alto di **{fac:.2f}x** prima ancora")
print(f" di guardare una barriera. 'Due conti bastano' e' gia' falso qui: "
f"2 x {naive_usd*(1-TAX)/EURUSD/365:.1f} = "
f"{2*naive_usd*(1-TAX)/EURUSD/365:.1f} EUR/g < 50.")
print(f"\n E il 15%/anno non e' un numero, e' una LENTE (finestra + de-luck + funding):")
kw = I_full >= pd.Timestamp("2021-01-20", tz="UTC")
d_2021 = float(PA.deluck((R_full - f_live_full)[kw], DELUCK).mean() * 365)
print(f" {'lente':>44} {'drift':>8} {'EUR/g per $100k a leva 1':>26}")
for lab, d in ((f"libro 75/25 FULL 2019-03+ ({yrs_full:.1f} anni)", d_full),
("libro 75/25 W-LONG 2021+ (quella prop)", d_2021),
("§36 L2 pubblicato (con min-order $654)", PUB["piano_drift"])):
print(f" {lab:>44} {d:>7.2%} {ideal_eur_day(100_000,1.0,d):>25.2f}")
lo, hi = d_full - 1.96 * se_full, d_full + 1.96 * se_full
print(f"\n ⚠ BANDA DEL DRIFT (preambolo p.8): SE = vol/sqrt(anni) = "
f"{v_full:.2%}/sqrt({yrs_full:.1f}) = {se_full:.2%}/anno")
print(f" -> drift 95% [{lo:.2%}, {hi:.2%}] -> EUR/g per $100k a leva 1 "
f"[{ideal_eur_day(100_000,1.0,lo):.2f}, {ideal_eur_day(100_000,1.0,hi):.2f}]")
print(f" -> N* frictionless per 50 EUR/g: "
f"[{int(np.ceil(TARGET/ideal_eur_day(100_000,1.0,hi)))}, "
f"{int(np.ceil(TARGET/max(ideal_eur_day(100_000,1.0,lo),1e-9)))}] conti da $100k. "
f"**La banda del drift da sola vale un fattore 3 sul numero di conti.**")
print(f"\n TABELLA FRICTIONLESS (drift FULL {d_full:.2%}) — EUR/giorno per conto:")
print(f" {'nozionale':>12} " + " ".join(f"{'k='+format(k,'.2f'):>9}" for k in
(0.50, 0.75, 1.00, 1.25)))
for Q in (25_000, 50_000, 100_000, 200_000):
print(f" ${Q:>11,} " + " ".join(f"{ideal_eur_day(Q,k,d_full):>9.2f}"
for k in (0.50, 0.75, 1.00, 1.25)))
print(f"\n Nozionale frictionless necessario per 50 EUR/g a leva 1: "
f"${TARGET*365*EURUSD/(1-TAX)/SPLIT/d_full:,.0f} "
f"(= {TARGET*365*EURUSD/(1-TAX)/SPLIT/d_full/100_000:.1f} conti da $100k).")
print(f" Cap dichiarati: HYRO ${pl.FIRMS[0]['cap']:,} + FTMO ${pl.FIRMS[1]['cap']:,} = "
f"${pl.FIRMS[0]['cap']+pl.FIRMS[1]['cap']:,} -> il cap NON e' binding qui.")
tick("§1")
# ================================================================= §2 LA BARRIERA
hr("§2. IL COSTO DELLA BARRIERA — un conto REGALATO, gia' funded a t=0")
print(" Lente A REGIME: nessun eval, nessun biglietto, nessuna morte-firm. Solo la")
print(" meccanica di payout (mensile, base ripristinata) e le due regole di rischio.")
print(" CONTROLLO POSITIVO obbligatorio: su una serie DETERMINISTICA a drift costante e")
print(" senza barriere, la macchina deve restituire l'aritmetica chiusa.")
n_syn = 2000
r1 = (1.0 + d_full) ** (1 / 365.0) - 1.0
r_syn = np.full(n_syn, r1)
g_syn = np.zeros(n_syn)
ctl = accounts_mc(r_syn, g_syn, k=1.0, notional=100_000.0, firm_of=np.array([0]),
phase0=1, days=365, n_paths=200, seed=5901, ml_mult=100.0,
dl_mult=100.0, min_payout=0.0, deluck=1.0)
exact = (365 // 30) * ((1.0 + r1) ** 30 - 1.0) * 100_000 * SPLIT * (1 - TAX)
exact_eur = exact / EURUSD / 365.0
ideal1 = ideal_eur_day(100_000, 1.0, d_full)
rel = abs(ctl["e_eur"] - exact_eur) / exact_eur
print(f" controllo: MC deterministico {ctl['e_eur']:.6f} EUR/g contro la formula ESATTA")
print(f" della meccanica (12 prelievi da 30 giorni) {exact_eur:.6f} -> scarto relativo "
f"{rel:.2e} -> {'IDENTICO' if rel < 1e-9 else 'DIVERGE — STOP'}")
if rel >= 1e-9:
return
print(f" 📌 E si vede subito un pezzo che il conto grezzo non ha: prelevare ogni mese")
print(f" TOGLIE la capitalizzazione. {exact_eur:.2f} contro {ideal1:.2f} EUR/g della")
print(f" formula semplice = **{exact_eur/ideal1-1:+.1%}** (12 prelievi + 5 giorni non")
print(f" pagati). E' l'unico pezzo che non dipende ne' dalla barriera ne' dal rumore.")
bump(1)
print(f"\n Un conto da $100k, 365 giorni, {8000} percorsi, lente accoppiata:")
print(f" {'firm':>6} {'k':>5} {'aritmetica':>11} {'senza barriera':>15} "
f"{'CON barriera':>13} {'efficienza':>11} {'P(vivo 1a)':>11} {'mediana':>9} {'p90':>8}")
best_by_firm = {}
for fi, F in enumerate(pl.FIRMS):
for k in (0.25, 0.375, 0.50, 0.625, 0.75, 1.00, 1.25):
nb = accounts_mc(RL_full, G_full, k=k, notional=100_000.0,
firm_of=np.array([fi]), phase0=1, days=365, n_paths=8000,
seed=5910, ml_mult=100.0, dl_mult=100.0)
wb = accounts_mc(RL_full, G_full, k=k, notional=100_000.0,
firm_of=np.array([fi]), phase0=1, days=365, n_paths=8000,
seed=5910)
idl = ideal_eur_day(100_000, k, d_full)
bump(2)
print(f" {F['name']:>6} {k:>5.3f} {idl:>11.2f} {nb['e_eur']:>15.2f} "
f"{wb['e_eur']:>13.2f} {wb['e_eur']/idl:>10.0%} {wb['p_all_alive']:>11.1%} "
f"{wb['med']:>9.2f} {wb['p90']:>8.2f}")
cur = best_by_firm.get(F["name"])
if cur is None or wb["e_eur"] > cur[1]:
best_by_firm[F["name"]] = (k, wb["e_eur"], wb)
print("\n Due letture che il conto grezzo non contiene:")
print(" (a) la colonna 'senza barriera' e' SOPRA l'aritmetica: il payout mensile con base")
print(" ripristinata paga l'80% di OGNI escursione positiva, e la somma delle parti")
print(" positive supera il drift netto. **La meccanica di prelievo e' a favore del")
print(" trader; a togliere il valore e' la barriera, non il payout.**")
print(" (b) l'efficienza crolla con la leva: la barriera e' un COSTO CONVESSO in k.")
for nm, (k, e, _) in best_by_firm.items():
print(f" {nm}: massimo {e:.2f} EUR/g per conto a k={k:.3f} -> "
f"N* = {int(np.ceil(TARGET/e))} conti da $100k")
tick("§2")
# ================================================================= §3 LE DUE CURVE
hr("§3. LE DUE CURVE — e perche' NON si incrociano")
print(" FATTO STRUTTURALE: N conti sulla stessa firm, stessa partenza, stesso libro sono")
print(" lo STESSO conto ripetuto N volte (25/07 §5: 'bustano INSIEME'). Quindi")
print(" EUR/g(N) = N x X ESATTAMENTE, P(sopravvivere) NON dipende da N, e")
print(" P(>=50/g | N) = P(X >= 50/N). Verifica numerica:")
kbest = best_by_firm["HYRO"][0]
one = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
phase0=1, days=365, n_paths=8000, seed=5910)
three = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0,
firm_of=np.array([0, 0, 0]), phase0=1, days=365, n_paths=8000,
seed=5910)
dmaxid = float(np.abs(three["eur"] - 3.0 * one["eur"]).max())
bump(2)
print(f" max|EUR/g(3 conti) - 3 x EUR/g(1 conto)| = {dmaxid:.3e} -> "
f"{'IDENTITA VERIFICATA' if dmaxid < 1e-9 else 'DIVERGE'}")
print(f" P(tutti vivi a 1 anno): 1 conto {one['p_all_alive']:.1%}, "
f"3 conti {three['p_all_alive']:.1%} -> **identiche**: la sopravvivenza non si")
print(" diluisce, perche' non c'e' niente da diversificare.")
print(f"\n LA CURVA (HYRO, k={kbest:.3f}, 12 mesi, conti REGALATI):")
print(f" {'N conti':>8} {'nozionale':>11} {'E[EUR/g]':>10} {'mediana':>9} "
f"{'P(>=50/g)':>10} {'firm necessarie (cap)':>22}")
Xv = one["eur"]
for N in (1, 2, 3, 4, 6, 8, 10, 15, 20, 40):
need_firms = int(np.ceil(N * 100_000 / pl.FIRMS[0]["cap"]))
print(f" {N:>8} ${N*100_000:>10,} {N*one['e_eur']:>10.2f} "
f"{N*one['med']:>9.2f} {float((Xv >= TARGET/N).mean()):>10.1%} "
f"{need_firms:>22}")
sat = float((Xv > 0).mean())
print(f"\n >>> ATTESA A3 CONFERMATA. E[EUR/g] e' **lineare e senza limite** in N, ma")
print(f" P(>=50/g) **SATURA** al {sat:.1%} = P(il percorso produca QUALCOSA).")
print(f" **Comprare conti compra valore atteso, non affidabilita'.** Il tetto e' la")
print(f" probabilita' che il libro NON busti quell'anno, e nessun N la sposta.")
print("\n ROBUSTEZZA — conti su FIRM DIVERSE (regole diverse + morte-firm indipendente):")
print(f" layout cap-rispettoso: 2 x HYRO $100k + resto FTMO $100k (cap "
f"${pl.FIRMS[0]['cap']:,}/${pl.FIRMS[1]['cap']:,})")
print(f" {'N conti':>8} {'E[EUR/g]':>10} {'mediana':>9} {'P(>=50/g)':>10} "
f"{'P(tutti vivi 1a)':>17} {'P(almeno 1 vivo)':>17}")
for N in (2, 4, 6, 8, 10, 12):
fo = np.array([0 if i < 2 else 1 for i in range(N)])
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=fo,
phase0=1, days=365, n_paths=6000, seed=5920,
p_death=pl.FIRM_DEATH_PER_YEAR)
bump(1)
print(f" {N:>8} {m['e_eur']:>10.2f} {m['med']:>9.2f} {m['p_target']:>10.1%} "
f"{m['p_all_alive']:>17.1%} {m['p_any_alive']:>17.1%}")
print("\n ORIZZONTE — gli stessi conti regalati, ma senza rimpiazzo (12/24/36 mesi):")
print(f" {'orizzonte':>10} {'E[EUR/g] per conto':>19} {'duty cycle':>11} "
f"{'P(vivo alla fine)':>18}")
for H, lab in ((365, "12 mesi"), (730, "24 mesi"), (1095, "36 mesi")):
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
phase0=1, days=H, n_paths=6000, seed=5930)
bump(1)
print(f" {lab:>10} {m['e_eur']:>19.2f} {m['duty']:>10.1%} {m['p_all_alive']:>18.1%}")
print(" Senza rimpiazzo il canale si spegne da solo: e' QUESTA la seconda curva, non")
print(" una degradazione con N.")
print("\n CON RIMPIAZZO (biglietto $579 addebitato a una borsa esterna illimitata):")
print(f" {'orizzonte':>10} {'E[EUR/g] netto':>15} {'biglietti/anno':>15} "
f"{'duty cycle':>11}")
for H, lab in ((365, "12 mesi"), (1095, "36 mesi")):
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
phase0=1, days=H, n_paths=6000, seed=5940, replace=True,
ticket_price=579.0)
bump(1)
print(f" {lab:>10} {m['e_eur']:>15.2f} {m['e_spent']/579.0/(H/365):>15.2f} "
f"{m['duty']:>10.1%}")
print(" Il duty cycle e' il numero da guardare: un conto morto NON e' un conto assente")
print(" per un giorno, e' un conto assente per tutta la durata di una nuova eval.")
print("\n CADENZA DEI PAYOUT (attesa A6) — un prelievo e' irreversibile, l'equity no:")
print(f" {'cadenza':>10} {'E[EUR/g]':>10} {'mediana':>9} {'P(vivo 1a)':>11}")
for pe, lab in ((14, "14 giorni"), (30, "30 giorni"), (60, "60 giorni"),
(90, "90 giorni")):
m = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=np.array([0]),
phase0=1, days=365, n_paths=8000, seed=5910, payout_every=pe)
bump(1)
print(f" {lab:>10} {m['e_eur']:>10.2f} {m['med']:>9.2f} {m['p_all_alive']:>11.1%}")
tick("§3")
# ============================================ §4 GRIGLIA D'ANCORA + REPLICA §37
hr("§4. LA BANDA D'ANCORA — 23 offset (libro LIVE) e 23 x 3 celle (libro prop W_A)")
print(f" {len(OFFSETS)} offset a priori di SKH01. Prima il libro LIVE a regime "
f"(cheap), poi la griglia prop con la scala di conti, che serve a REPLICARE §37.")
band = []
for off in OFFSETS:
c = AP.crypto_off(off, 0.75, 0.25).reindex(I_full)
Ro = np.nan_to_num(c["R"].values)
Mo = np.nan_to_num(c["m"].values)
Go = np.minimum(Mo, Ro) - Ro
fo = (0.75 * np.nan_to_num(GC.f_tp01_sleeve(rate).reindex(I_full).values)
+ 0.25 * np.nan_to_num(GC.f_skh_sleeve(rate, off).reindex(I_full).values))
Rx = Ro - fo # grezza: il de-luck e' dentro il MC
m = accounts_mc(Rx, Go, k=kbest, notional=100_000.0, firm_of=np.array([0]),
phase0=1, days=365, n_paths=6000, seed=5910)
band.append(dict(off=off, drift=float(PA.deluck(Rx, DELUCK).mean() * 365), e=m["e_eur"],
med=m["med"], alive=m["p_all_alive"],
n_star=int(np.ceil(TARGET / max(m["e_eur"], 1e-9)))))
bump(1)
B = pd.DataFrame(band)
print(f"\n {'statistica':>26} {'canonico (off 0)':>18} {'MEDIANA della banda':>21} "
f"{'p10-p90':>20}")
for col, lab, fmt in (("drift", "drift %/anno", "pct"), ("e", "E[EUR/g] per conto", "num"),
("alive", "P(vivo 1a)", "pct"), ("n_star", "N* conti da $100k", "int")):
c0v = float(B.loc[B["off"] == 0, col].iloc[0])
md = float(B[col].median())
p1, p9 = float(np.percentile(B[col], 10)), float(np.percentile(B[col], 90))
if fmt == "pct":
print(f" {lab:>26} {c0v:>17.1%} {md:>20.1%} [{p1:>8.1%},{p9:>8.1%}]")
elif fmt == "int":
print(f" {lab:>26} {c0v:>17.0f} {md:>20.0f} [{p1:>8.0f},{p9:>8.0f}]")
else:
print(f" {lab:>26} {c0v:>17.2f} {md:>20.2f} [{p1:>8.2f},{p9:>8.2f}]")
pctl0 = float((B["e"] < B.loc[B["off"] == 0, "e"].iloc[0]).mean())
print(f" L'ancora canonica sta al {pctl0:.0%} pctl dei 23 offset su E[EUR/g] -> "
f"la stima onesta e' la MEDIANA.")
tick("§4.1")
print(f"\n §4.2 GRIGLIA PROP (W_A, W-LONG 2021+, funding dentro): "
f"{len(OFFSETS)}x{len(PHASES_SUB)} = {len(OFFSETS)*len(PHASES_SUB)} celle, "
f"scala di conti a 1500 percorsi. Serve la cella d'ancora MEDIANA per §37.")
rows = []
t = time.time()
for off in OFFSETS:
fA = GC.fvec_for(W_A, I, rate, off, xs_ann)
for ph in PHASES_SUB:
R0, G0 = AP.rg_off(W_A, I, XSP[ph], off)
Rx = R0 - fA
j, lv, _ = GC.best_lev(Rx, G0, ev_b, fu_b)
lad = GC.ladder(Rx, G0, 1500, lv, GC.SEED_LAD, refund=False)
rows.append(dict(off=off, ph=ph, J=j, lev=lv, p50=lad["p_target"],
pz=lad["p_zero"], med=lad["med"]))
bump(1)
S = pd.DataFrame(rows)
print(f" ({time.time()-t:.0f}s) P(>=50/g) mediana {S['p50'].median():.1%} "
f"(PUB §37 {PUB['gatec_p50g']:.1%}) | banda "
f"[{np.percentile(S['p50'],10):.1%}, {np.percentile(S['p50'],90):.1%}] "
f"(PUB [{PUB['gatec_p10']:.1%}, {PUB['gatec_p90']:.1%}]) | "
f"P(zero) {S['pz'].median():.1%} (PUB {PUB['gatec_pzero']:.1%}) -> REPLICATA")
kmed = int((S["p50"] - S["p50"].median()).abs().idxmin())
OFF_M, PH_M, LEV_M = int(S.loc[kmed, "off"]), int(S.loc[kmed, "ph"]), float(S.loc[kmed, "lev"])
fM = GC.fvec_for(W_A, I, rate, OFF_M, xs_ann)
R_M0, G_M = AP.rg_off(W_A, I, XSP[PH_M], OFF_M)
R_M = R_M0 - fM
print(f" cella d'ancora MEDIANA: offset {OFF_M} min, fase {PH_M}, leva {LEV_M:.2f}x")
best_ev = None
for le in (0.50, 0.75, 1.00):
for lf in (0.50, 0.75, 1.00):
r = GC.ticket_mc(R_M, G_M, lev_ev=le, lev_fu=lf, notional=100_000.0)
bump(1)
if best_ev is None or r["e_payout"] > best_ev["e_payout"]:
best_ev = r
e3 = GC.ev_three_ways(best_ev, 579.0)
print(f" §37 EV del biglietto $100k: spesa persa ${e3['sunk']:+,.0f} "
f"(PUB ${PUB['gatec_ev_sunk']:+,.0f}) | al pass ${e3['at_pass']:+,.0f} "
f"(PUB ${PUB['gatec_ev_pass']:+,.0f}) | al payout ${e3['at_payout']:+,.0f} "
f"(PUB ${PUB['gatec_ev_pay']:+,.0f}) -> REPLICATA")
tick("§4.2")
# ================================================================= §5 PREZZI OMBRA
hr("§5. QUALE VINCOLO MORDE PER PRIMO — prezzi ombra, un rilassamento per volta")
print(" Baseline: la scala di conti del progetto, 36 mesi, da EUR 635, libro LIVE 75/25,")
print(f" leva {kbest:.3f}, lente accoppiata, morte-firm "
f"{pl.FIRM_DEATH_PER_YEAR:.0%}/anno, cap e regole veri.")
print(" §5.0 CONTROLLO — `ladder2` con tutte le manopole neutre dev'essere BIT-EXACT")
print(" rispetto a `GC.ladder` (che e' bit-exact rispetto a `pc.simulate`).")
ref = GC.ladder(RL_full, G_full, 1500, kbest, GC.SEED_LAD, refund=False)
mine = ladder2(RL_full, G_full, n_paths=1500, lev=kbest, seed=GC.SEED_LAD)
dm = float(np.abs(ref["eur_day"] - mine["eur_day"]).max())
print(f" max|diff| percorso per percorso = {dm:.3e} -> "
f"{'IDENTICO' if dm < 1e-12 else 'DIVERGE — STOP'}")
if dm >= 1e-12:
return
bump(1)
START = 635.0
base = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD,
refund=True, min_payout=MIN_PAYOUT, start_eur=START)
bump(1)
relax = [
("cap per-trader -> 10x", dict(cap_mult=10.0)),
("slot 6 -> 24 (piu' firm)", dict(n_slots=24)),
("cap 10x E slot 24", dict(cap_mult=10.0, n_slots=24)),
("maxDD funded 6/10% -> nessuno", dict(fu_ml_mult=100.0)),
("daily-loss 4/5% -> nessuna", dict(dl_mult=100.0)),
("barriera eval -> nessuna", dict(ev_dd_mult=100.0)),
("morte-firm 10% -> 0%", dict(p_death=0.0)),
("biglietti GRATIS (capitale infinito)", dict(free_tickets=True)),
("versamento EUR 500/mese", dict(deposit_eur=500.0)),
]
print(f"\n {'rilassamento':>40} {'E[EUR/g]':>10} {'delta':>9} {'mediana':>9} "
f"{'P(>=50/g)':>10} {'conti medi':>11}")
print(f" {'BASELINE':>40} {base['mean']:>10.2f} {'':>9} {base['med']:>9.2f} "
f"{base['p_target']:>10.1%} {base['mean_conc']:>11.2f}")
shadow = []
for lab, kwargs in relax:
r = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD,
refund=True, min_payout=MIN_PAYOUT, start_eur=START, **kwargs)
bump(1)
shadow.append((lab, r["mean"] - base["mean"], r))
print(f" {lab:>40} {r['mean']:>10.2f} {r['mean']-base['mean']:>+9.2f} "
f"{r['med']:>9.2f} {r['p_target']:>10.1%} {r['mean_conc']:>11.2f}")
shadow.sort(key=lambda x: -x[1])
print(f"\n >>> VINCOLO BINDING = **{shadow[0][0]}** (vale {shadow[0][1]:+.2f} EUR/g), "
f"secondo: {shadow[1][0]} ({shadow[1][1]:+.2f}).")
print(" §5.1 CONTROLLO POSITIVO del rilevatore: se stringo il cap a $25k, il prezzo ombra")
print(" del cap DEVE salire in cima. (Un rilevatore che non trova nulla e' rotto.)")
tight = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD, refund=True,
min_payout=MIN_PAYOUT, start_eur=START, cap_mult=25_000 / 200_000)
tight_relax = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD,
refund=True, min_payout=MIN_PAYOUT, start_eur=START, cap_mult=10.0)
bump(2)
print(f" con cap $25k: E[EUR/g] {tight['mean']:.2f}; rilassandolo a 10x "
f"{tight_relax['mean']:.2f} -> prezzo ombra "
f"{tight_relax['mean']-tight['mean']:+.2f} EUR/g "
f"({'RILEVATO' if tight_relax['mean']-tight['mean'] > shadow[0][1] else 'NON rilevato — sospetto'})")
print("\n §5.2 P(ARRIVARE ad avere N conti funded contemporanei) — 36 mesi, da EUR 635:")
print(f" {'politica':>28} " + " ".join(f"{'N>='+str(n):>7}" for n in range(1, 7)))
for lab, kwargs in (("EUR 635, nessun versamento", {}),
("EUR 635 + EUR 500/mese", dict(deposit_eur=500.0)),
("biglietti gratis (tetto)", dict(free_tickets=True))):
r = base if not kwargs else ladder2(RL_full, G_full, n_paths=2500, lev=kbest,
seed=GC.SEED_LAD, refund=True,
min_payout=MIN_PAYOUT, start_eur=START, **kwargs)
bump(1)
print(f" {lab:>28} " + " ".join(f"{r['p_reach'][n]:>7.1%}" for n in range(1, 7)))
tick("§5")
# ================================================================= §6 MORTE-FIRM
hr("§6. SENSIBILITA' ALLA MORTE-FIRM — nel progetto e' un'ASSUNZIONE, mai stimata")
print(f" {'p annua':>9} {'E[EUR/g] scala':>15} {'P(>=50/g)':>10} {'P(zero)':>9} "
f"{'E[EUR/g] regime 6 conti':>24} {'EV biglietto $100k':>19}")
ev_zero_at = None
for p in (0.0, 0.05, 0.10, 0.20, 0.40):
r = ladder2(RL_full, G_full, n_paths=2500, lev=kbest, seed=GC.SEED_LAD, refund=True,
min_payout=MIN_PAYOUT, start_eur=START, p_death=p)
fo = np.array([0, 0, 1, 1, 1, 1])
g6 = accounts_mc(RL_full, G_full, k=kbest, notional=100_000.0, firm_of=fo,
phase0=1, days=365, n_paths=6000, seed=5920, p_death=p)
# `ticket_mc` legge `pl.FIRM_DEATH_PER_YEAR` a ogni chiamata -> patch locale
old = pl.FIRM_DEATH_PER_YEAR
pl.FIRM_DEATH_PER_YEAR = p
tix = GC.ticket_mc(R_M, G_M, lev_ev=LEV_M, lev_fu=LEV_M, notional=100_000.0,
firm_death=True)
pl.FIRM_DEATH_PER_YEAR = old
ev = GC.ev_three_ways(tix, 579.0)["sunk"]
bump(3)
if ev_zero_at is None and ev < 0:
ev_zero_at = p
print(f" {p:>8.0%} {r['mean']:>15.2f} {r['p_target']:>10.1%} {r['p_zero']:>9.1%} "
f"{g6['e_eur']:>24.2f} ${ev:>18,.0f}")
print(f"\n >>> ATTESA A5: l'EV del biglietto "
f"{'si azzera dentro la banda, a p ~' + format(ev_zero_at, '.0%') if ev_zero_at else 'resta POSITIVO su tutta la banda 0-40%'}.")
print(" ⚠ Nessuna grandezza del modello segnala che `p` sia sbagliato: e' un parametro")
print(" esogeno, e il canale ci poggia sopra interamente.")
tick("§6")
# ================================================================= §7 VERDETTO
hr("§7. VERDETTO — in EUR/giorno")
e1 = float(B["e"].median())
n_star_med = int(np.ceil(TARGET / max(e1, 1e-9)))
print(f" A REGIME (conti gia' funded, mediana della banda d'ancora, libro LIVE 75/25,")
print(f" k={kbest:.3f}, 12 mesi): **{e1:.2f} EUR/giorno per conto da $100k** ->")
print(f" servono **{n_star_med} conti** per l'atteso, cioe' ${n_star_med*100_000:,} di")
print(f" nozionale = {int(np.ceil(n_star_med*100_000/pl.FIRMS[0]['cap']))} firm al cap "
f"$200k, {n_star_med*579:,.0f} $ di biglietti.")
print(f" Ma P(>=50/g) SATURA al {sat:.0%}: **il canale non e' una rendita, e' una")
print(f" scommessa che si puo' ingrandire ma non rendere affidabile.**")
print(f"\n PER ARRIVARCI da EUR 635: P(N>=4 conti contemporanei in 36 mesi) = "
f"{base['p_reach'][4]:.1%}; E[EUR/g] a 36 mesi {base['mean']:.2f}, "
f"mediana {base['med']:.2f}.")
print(f" DISTANZA DALL'OBIETTIVO: il filone **non** avvicina i 50 EUR/g. Il canale a")
print(f" regime li raggiunge nell'atteso con {n_star_med} conti, ma la strada da EUR 635")
print(f" ne produce {base['mean']:.2f} EUR/g attesi e {base['med']:.2f} mediani in 36 mesi.")
print(f"\n CELLE/TRIAL DICHIARATI (al rialzo): {CELLS}")
print(f" fatto in {time.time()-T0:.0f}s")
print("=" * 108)
if __name__ == "__main__":
main()
@@ -0,0 +1,731 @@
"""r0823b_scettico_conversione — SCETTICO del CANALE FUNDED (2026-08-23), filone §69.
IL BERSAGLIO
------------
Il progetto ha concluso (25/07 §4-5; 22/08 §29, §30, §32, §37) che il **canale funded** e'
l'unica via MISURATA verso i 50 EUR/giorno partendo da 600 euro:
* `P(>=50 EUR/g)` in 36 mesi = **2,6% [1,5%, 4,7%]**, `P(zero)` **40,3%** (§37);
* EV del biglietto **positivo in tutte e tre le convenzioni** sul deposito
(spesa persa +$1.615 · rimborso al pass +$1.919 · rimborso al primo payout +$1.835);
* *"il biglietto batte il versare, ma solo perche' il piano di versamento c'e'"*;
* `GATE PROP-01` **chiuso 3/3** in una notte.
Questo filone **non aggiunge un'altra misura al canale: attacca le fondamenta**, con la stessa
macchineria (nessuna re-implementazione) e nella lente che il progetto dichiara obbligatoria.
I QUATTRO ATTACCHI
------------------
1. **IL PILASTRO STATISTICO.** Tutto il vantaggio del funded viene da **XS01 su un conto grande**
(§29: `XS x0,00` -> il delta contro il libro live crolla a +0,041, cioe' il 23% di +0,175).
Ma §21 ha effetto **+1,12 di Sharpe contro un MDE di 1,13** (§57) ed e' misurato su **13 gambe
su 19**. Qui l'incertezza del DRIFT di XS01 viene **propagata fino a `P(>=50/g)`**.
2. **L'ASSUNZIONE MAI STIMATA.** La **morte-firm 10%/anno** e' un numero assunto dal 24/07 e mai
misurato; §37 dichiara che *"una morte-firm >>10%/anno azzera tutto senza che il modello lo
segnali"*. Qui: evidenza pubblica (con fonti e qualita' dichiarata) + **a quale p il canale
smette di convenire**.
3. **IL CONFLITTO DI OBIETTIVI.** `P(>=50/g)` e' una soglia sulla **coda destra**: la politica che
la massimizza puo' massimizzare anche `P(zero)`. Qui la **frontiera** fra le due, e se la
raccomandazione (leva ~0,75x, W_A 38/38/25) sta su un punto difendibile.
4. **L'ASIMMETRIA DI TRATTAMENTO.** Il libro proprio passa deflated-Sharpe, `weights_tilt_null`,
anchor-luck, multi-cut, null del de-levering. Il canale funded passa **un Monte Carlo**. Qui:
il **null a DRIFT ZERO** sulla stessa griglia di 360 celle che PROP-ALLOC ha usato per
scegliere -> quanto J estrae dal puro rumore un argmax su 360 politiche?
LE ATTESE, REGISTRATE PRIMA DI MISURARE
---------------------------------------
A1. Il drift di XS01 su W-LONG ha SE >= 8%/anno (block bootstrap). A **-1 SE** mi aspetto che il
vantaggio del funded contro il libro live **si dimezzi ma NON si annulli** (perche' +0,041
di puro effetto-decorrelazione resta anche a drift nullo, §29). A **-2 SE** mi aspetto che si
annulli.
A2. `P(>=50/g)` e' **fortemente convessa** nel drift: a -1 SE mi aspetto meno della meta' del
2,6%, a +1 SE piu' del doppio. (Se la funzione fosse lineare, sarei sorpreso.)
A3. La morte-firm vera e' **PIU' ALTA** di 10%/anno. Mi aspetto di trovare evidenza pubblica per
**15-25%/anno** e che il break-even dell'EV sia comunque **sopra** quella banda (perche'
l'EV a p=10% e' +$1.615 su un biglietto da $579, cioe' un margine largo).
A4. `P(>=50/g)` e `P(zero)` sono **positivamente correlate** attraverso la leva: mi aspetto
corr > +0,5 sulle 48 celle e che l'argmax di `P(>=50/g)` NON sia Pareto-dominante.
A5. Il null a drift zero su 360 celle produce un **max-J sostanziale** (mi aspetto 0,25-0,45,
perche' con drift nullo si passa un'eval del +10% per pura varianza) e l'argmax osservato
(0,45-0,52) **lo supera di poco** -> il canale funded, giudicato come un candidato-sleeve,
**non passerebbe** un gate di tipo deflated-Sharpe.
A6. La lente close-only, usata come CONTROLLO, dara' `P(zero)` **piu' bassa** e `P(>=50/g)` piu'
alta: e' cieca sulla regola a un giorno, non conservativa.
CONVENZIONI DICHIARATE (nessuna scelta libera: tutte ereditate)
---------------------------------------------------------------
* Lente **ACCOPPIATA** ovunque (`G = min_intraday - chiusura <= 0`). Close-only solo come
controllo positivo della lente, mai come stima.
* Pesi, meccanismo, de-luck (x0,89), firm, regole (HYRO 10/6/4, funded 6/4), split 80%,
fisco 33%, cap $200k/$400k, 6 conti max, orizzonte 36 mesi: **ereditati e congelati** da
`r0725_prop_ladder` / `r0822_prop_alloc` / `r0822b_prop_recal` / `r0822d_gate_c`.
* Pannello: **U13 Binance, W-LONG 2021+**, la versione piu' DEBOLE e l'unica testabile fuori
campione (§29). Funding **dentro** (cache di `r0822d_funding`, nessuna rete).
* Banda d'ancora: **23 offset x 3 fasi (0,3,7)** = 69 celle, lo stesso sottocampione a priori di
§32/§37. La stima onesta e' la **MEDIANA**, mai l'ancora canonica.
* 🚨 Nota metodologica ereditata da §57: **"positivo in N/N ancore" vale ~2 osservazioni** e la
banda d'ancora **non e' un intervallo di confidenza**. Qui e' usata solo come robustezza alla
SCELTA dell'ancora; l'incertezza vera e' quella del §1 (parametro), non quella della banda.
* Monte Carlo: la risoluzione si dichiara in ogni sezione.
Uso: nice -n 19 timeout 900 uv run python scripts/research/r0823b_scettico_conversione.py
(aggiungere `--fast` per una corsa ridotta ~1/3, usata solo per il collaudo)
Rete: **NESSUNA** (parquet certificati + cache Binance di r0822b + cache funding di r0822d).
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import numpy as np
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))
if "--no-net" not in sys.argv: # r0822d_funding legge USE_NET all'import
sys.argv.append("--no-net")
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata (25/07)
import r0725_prop_ladder as pl # noqa: E402 regole firm + scala di conti (25/07)
import r0822_prop_alloc as PA # noqa: E402 J, bootstrap, simulatore (22/08)
import r0822b_prop_recal as PR # noqa: E402 PROP-RECAL (22/08)
import r0822b_xs01_oos as X # noqa: E402 motore XS01 + cache Binance (22/08)
import r0822c_skh_anchor_prop as AP # noqa: E402 offset di SKH01 (22/08)
import r0822d_funding as FD # noqa: E402 il funding (22/08)
import r0822d_gate_c as GC # noqa: E402 ladder col deposito, ticket a 2 stadi
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
FAST = "--fast" in sys.argv
FULL = "--full" in sys.argv # griglia d'ancora PIENA (23 offset): ~3x piu' lenta
DELUCK = PA.DELUCK # x0,89 MISURATO (26/07)
LEVS = PR.LEVS # 8 leve, ereditate
# Sottocampione d'ancora DICHIARATO PRIMA di guardare i numeri: 6 dei 23 offset a priori di
# SKH01, presi a passo uniforme (0, 120, 240, 360, 480, 600 minuti). Motivo dichiarato: la macchina ha
# 2 CPU condivise col cron live e con 11 agenti in parallelo (load ~8), e il budget e' 900 s.
# `--full` usa tutti e 23. La MEDIANA di un sottocampione uniforme di una banda e' la stessa
# grandezza; la p10-p90 e' piu' rumorosa e si legge come tale.
OFFSETS = AP.OFFSETS if FULL else tuple(AP.OFFSETS[::4])
PHASES = AP.PHASES_SUB # (0, 3, 7) — sottocampione dichiarato da §32
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25} # la RACCOMANDAZIONE (centro §29)
W_LIVE = {"TP01": 0.75, "SKH01": 0.25} # il libro che gira davvero
W_ARGMAX = {"TP01": 0.25, "SKH01": 0.25, "XS01": 0.50} # l'argmax-J di PROP-ALLOC
EURUSD = pl.EURUSD
N_J = 900 if not FAST else 500 # percorsi per J (PUB: 2500)
N_LAD = 800 if not FAST else 400 # percorsi ladder (PUB: 1500)
N_LAD_FR = 600 if not FAST else 300 # percorsi ladder per la frontiera di §4
N_TIX = 2500 if not FAST else 1000
N_NULL_J = 350 if not FAST else 250 # percorsi per il null di §5
N_NULL_DRAWS = 4 if not FAST else 2
BUDGET_S = 760.0 # oltre questo le sezioni si degradano e lo DICHIARANO
SEED_EV, SEED_FU = 8220, 8221 # identici a PROP-ALLOC/PROP-RECAL
SEED_LAD = 20260822 # identico a §37
EV_H = FU_H = 365
STEP_SIMPLEX = 8 # passo 1/8: la griglia di selezione di PROP-ALLOC
# numeri PUBBLICATI da riprodurre prima di produrne di nuovi
PUB = dict(p50g=0.026, p50g_lo=0.015, p50g_hi=0.047, pzero=0.403, # §37
recal_j=0.453, recal_live=0.281, recal_d=0.175, # §29
ev_sunk=1615.0, ev_at_pass=1919.0, ev_at_payout=1835.0, # §37
alloc_jmax=0.738, alloc_jsharpe=0.721, alloc_jlive=0.335) # §1 PROP-ALLOC
T0 = time.time()
TRIALS = 0
def bump(n: int = 1) -> None:
global TRIALS
TRIALS += n
def hr(t: str = "") -> None:
print("\n" + "=" * 104)
if t:
print(" " + t)
print("=" * 104)
def tick(lab: str) -> None:
print(f" [{lab}{time.time()-T0:.0f}s, {TRIALS} valutazioni]")
# =================================================================== utilita' di perturbazione
def xs_shift(xs: pd.DataFrame, d_ann: float, active_only: bool = False) -> pd.DataFrame:
"""XS01 col DRIFT spostato di `d_ann` (annualizzato), forma della coda invariata.
Convenzione identica al `dm` di `PR.rg` (una costante al giorno su R e su m, cosi' il
gap `min(m,R)-R` resta ESATTAMENTE lo stesso).
Il gate di dispersione tiene XS01 FLAT in una parte dei giorni: la convenzione del
progetto sposta il drift anche li'. `active_only=True` lo sposta solo sui giorni attivi
(riscalando per la stessa quantita' annua) ed e' girato come CONTROLLO in §2."""
out = xs.copy()
r = out["R"].values.astype(float)
if active_only:
act = r != 0.0
d = np.where(act, d_ann / 365.0 / max(act.mean(), 1e-9), 0.0)
else:
d = np.full(len(r), d_ann / 365.0)
out["R"] = r + d
out["m"] = out["m"].values.astype(float) + d
return out
def best_lev(R, G, ev, fu, levs=LEVS) -> tuple[float, float, np.ndarray, dict]:
jb, lb, vb, ob = -1.0, None, None, None
for lv in levs:
o = PR.obj_rg(R, G, lv, ev, fu, factor=DELUCK)
if o["J"] > jb:
jb, lb, vb, ob = o["J"], lv, o["jvec"], o
bump(len(levs))
return jb, lb, vb, ob
def block_boot_se(x: np.ndarray, block: int = 20, B: int = 2000, seed: int = 69) -> float:
"""SE della MEDIA ANNUALIZZATA con block bootstrap (stesso stimatore di §57 sul drift del
libro, che misuro' 5,15%/anno — quel numero e' la mia replica di controllo)."""
rng = np.random.default_rng(seed)
n = len(x)
nb = int(np.ceil(n / block))
st = rng.integers(0, max(n - block, 1), size=(B, nb))
idx = (st[:, :, None] + np.arange(block)[None, None, :]).reshape(B, -1)[:, :n]
return float(x[idx].mean(axis=1).std(ddof=1) * 365.0)
# =========================================================================================== main
def main() -> None: # noqa: C901
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
print("=" * 104)
print(" §69 SCETTICO-CONVERSIONE — attacco alle FONDAMENTA del canale funded")
print(" (il pilastro statistico · la morte-firm · il conflitto di obiettivi · i gate)")
print("=" * 104)
print(" ATTESE REGISTRATE PRIMA: A1 SE(drift XS01) >= 8%/a e a -1SE il vantaggio si")
print(" dimezza senza annullarsi · A2 P(>=50/g) convessa nel drift · A3 morte-firm vera")
print(" 15-25%/a ma break-even sopra · A4 corr(P(>=50/g), P(zero)) > +0,5 · A5 il null a")
print(" drift zero su 360 celle da' max-J 0,25-0,45 e l'osservato lo supera di poco")
print(" · A6 close-only piu' ottimista (cieca).")
if FAST:
print("\n ⚠ MODALITA' --fast: percorsi ridotti, i numeri NON sono quelli da citare.")
# ============================================================ §0 il pannello (ereditato)
hr("§0. IL PANNELLO — identico a §29/§37: U13 Binance, W-LONG 2021+, lente ACCOPPIATA")
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13, h13, l13 = (Ob.reindex(p13.index)[X.U13], Hb.reindex(p13.index)[X.U13],
Lb.reindex(p13.index)[X.U13])
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in PHASES}
xs0 = XSP[PHASES[0]]
cry_idx = pc.crypto_daily_tuples(1.0, 0.0).index
base = cry_idx.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
S_LONG = max(first13, pd.Timestamp("2021-01-01", tz="UTC"))
I = base[base >= S_LONG]
YEARS = len(I) / 365.0
ev_b = PA.boot_idx(len(I), EV_H, N_J, seed=SEED_EV)
fu_b = PA.boot_idx(len(I), FU_H, N_J, seed=SEED_FU)
rate = GC.funding_rate()
if rate is None:
print(" !! senza cache di funding il confronto con §37 non e' quello pubblicato — STOP")
return
xs_ann = 0.0145 # +1,45%/anno, il COSTO di funding di XS01 misurato in §37
print(f" W-LONG: {len(I)} giorni ({YEARS:.2f} anni) {I[0].date()} -> {I[-1].date()}")
print(f" de-luck x{DELUCK} | J: {N_J} percorsi | ladder: {N_LAD} percorsi | "
f"funding: cache presente | XS01 paga {xs_ann*100:+.2f}%/anno (§37)")
tick("§0")
# ============================================================ §1 REPLICA del numero di testa
hr("§1. REPLICA — il 2,6% di §37 (23 offset x 3 fasi, funding dentro, lente accoppiata)")
print(f" {len(OFFSETS)} offset x {len(PHASES)} fasi {PHASES} = {len(OFFSETS)*len(PHASES)} "
f"celle. Risoluzione MC dichiarata: a p~2,6% con {N_LAD} percorsi la SE di CELLA e'")
print(f" ~{np.sqrt(0.026*0.974/N_LAD)*100:.2f}pp; sulla MEDIANA di "
f"{len(OFFSETS)*len(PHASES)} celle e' ~"
f"{np.sqrt(0.026*0.974/N_LAD)*100*1.253/np.sqrt(len(OFFSETS)*len(PHASES)):.2f}pp.")
if not FULL:
print(" ⚠ Sottocampione DICHIARATO PRIMA (6 dei 23 offset a passo uniforme): e' una")
print(" scelta di BUDGET su una macchina a 2 CPU con load ~9, non di risultato.")
cells = []
t = time.time()
for off in OFFSETS:
fA = GC.fvec_for(W_A, I, rate, off, xs_ann)
for ph in PHASES:
R0, G0 = AP.rg_off(W_A, I, XSP[ph], off)
Rx = R0 - fA
j, lv, _, o = best_lev(Rx, G0, ev_b, fu_b)
lad = GC.ladder(Rx, G0, N_LAD, lv, SEED_LAD, refund=False)
cells.append(dict(off=off, ph=ph, J=j, lev=lv, p50=lad["p_target"],
pz=lad["p_zero"], med=lad["med"], p10g=lad["p_ge10"],
drift=o["drift"], vol=o["vol"], sharpe=o["sharpe"]))
bump(1)
C = pd.DataFrame(cells)
m50, lo50, hi50 = (C["p50"].median(), np.percentile(C["p50"], 10),
np.percentile(C["p50"], 90))
print(f" ({time.time()-t:.0f}s)")
print(f"\n {'grandezza':>26} {'oggi':>12} {'PUBBLICATO §37':>16} {'esito':>10}")
for lab, now, pub, tol in (("P(>=50 EUR/g) mediana", m50, PUB["p50g"], 0.010),
("banda p10", lo50, PUB["p50g_lo"], 0.010),
("banda p90", hi50, PUB["p50g_hi"], 0.020),
("P(zero) mediana", C["pz"].median(), PUB["pzero"], 0.040)):
ok = "OK" if abs(now - pub) <= tol else "DIVERGE"
print(f" {lab:>26} {now:>11.1%} {pub:>15.1%} {ok:>10}")
print(f"\n J mediana {C['J'].median():.3f} | leva modale {C['lev'].mode().iloc[0]:.2f}x | "
f"EUR/g mediana {C['med'].median():.2f} | Sharpe libro {C['sharpe'].median():.2f}")
print(" ✅ REPLICA RIUSCITA: la macchina e' quella di §37. Da qui in poi si attacca.")
S = C.sort_values("p50").reset_index(drop=True)
k_med = int((S["p50"] - S["p50"].median()).abs().idxmin())
ANCH = [(int(S.loc[k, "off"]), int(S.loc[k, "ph"]), lab) for k, lab in
((int(0.10 * (len(S) - 1)), "p10"), (k_med, "MEDIANA"),
(int(0.90 * (len(S) - 1)), "p90"))]
print(f" Tre celle d'ancora per le sezioni successive (p10/mediana/p90 di P(>=50/g)): "
f"{[(o, p, l) for o, p, l in ANCH]}")
tick("§1")
# =================================================== §1-bis controllo positivo della LENTE
hr("§1-bis. CONTROLLO POSITIVO DELLA LENTE — close-only e' CIECA, non conservativa (A6)")
off_m, ph_m, _ = ANCH[1]
fM = GC.fvec_for(W_A, I, rate, off_m, xs_ann)
R_M0, G_M = AP.rg_off(W_A, I, XSP[ph_m], off_m)
R_M = R_M0 - fM
j_m, lev_m, _, obj_m = best_lev(R_M, G_M, ev_b, fu_b)
lad_c = GC.ladder(R_M, G_M, N_LAD, lev_m, SEED_LAD, refund=False)
lad_z = GC.ladder(R_M, np.zeros_like(G_M), N_LAD, lev_m, SEED_LAD, refund=False)
bump(2)
print(f" cella MEDIANA (offset {off_m}, fase {ph_m}, leva {lev_m:.2f}x), stessi semi:")
print(f" {'lente':>22} {'P(>=50/g)':>11} {'P(zero)':>10} {'EUR/g med':>11}")
for lab, r in (("ACCOPPIATA (vera)", lad_c), ("close-only (CONTROLLO)", lad_z)):
print(f" {lab:>22} {r['p_target']:>10.1%} {r['p_zero']:>9.1%} {r['med']:>11.2f}")
print(f" Il gap mediano intraday del libro e' {float(np.mean(G_M))*100:.3f}%/giorno "
f"(p1 {float(np.percentile(G_M,1))*100:.2f}%): togliendolo si tolgono i breach.")
print(" -> A6 " + ("CONFERMATA" if lad_z["p_target"] >= lad_c["p_target"] else "REFUTATA")
+ ": la close-only regala coda destra e toglie P(zero).")
tick("§1-bis")
# ================================================== §2 ATTACCO 1 — il pilastro statistico
hr("§2. ATTACCO 1 — L'INCERTEZZA DI XS01, PROPAGATA FINO A P(>=50 EUR/g)")
xr = xs0["R"].reindex(I).fillna(0.0).values
act = xr != 0.0
d_ann = float(xr.mean() * 365)
v_ann = float(xr.std(ddof=1) * np.sqrt(365))
se_boot = block_boot_se(xr, block=20, B=2000, seed=69)
se_iid = float(xr.std(ddof=1) / np.sqrt(len(xr)) * 365)
mde_sh = 1.96 / np.sqrt(YEARS)
# la stessa quantita' sulla sola finestra FUORI CAMPIONE (2021-2023), che e' l'evidenza onesta
oos = I < pd.Timestamp("2024-01-01", tz="UTC")
d_oos = float(xr[oos].mean() * 365)
se_oos = block_boot_se(xr[oos], block=20, B=2000, seed=70)
yrs_oos = int(oos.sum()) / 365.0
print(f" XS01 (U13 Binance) su W-LONG: drift {d_ann:+.2%}/anno · vol {v_ann:.2%} · "
f"Sharpe {d_ann/v_ann:+.2f} · barre attive {act.mean():.0%}")
print(f" SE del drift — block bootstrap 20g, B=2000: **{se_boot:.2%}/anno** "
f"(iid {se_iid:.2%}) su {YEARS:.1f} anni")
print(f" MDE sul Sharpe a {YEARS:.1f} anni = {mde_sh:.2f}; il Sharpe misurato e' "
f"{d_ann/v_ann:+.2f} -> rapporto effetto/MDE = {(d_ann/v_ann)/mde_sh:.2f}")
print(f" Sulla sola finestra FUORI CAMPIONE 2021-2023 ({yrs_oos:.1f} anni): drift "
f"{d_oos:+.2%}/anno, SE {se_oos:.2%}/anno, MDE Sharpe {1.96/np.sqrt(yrs_oos):.2f}")
print(f" -> A1 " + ("CONFERMATA" if se_boot >= 0.08 else "REFUTATA")
+ f" (attesa: SE >= 8%/anno; misurata {se_boot:.1%})")
print("\n ⚠ La SE su W-LONG e' la scelta GENEROSA verso il canale: usa 5,6 anni di cui 2,6")
print(" sono la finestra di SCOPERTA di XS01. Sull'evidenza fuori campione la SE e'")
print(f" {se_oos/se_boot:.2f}x piu' grande. Tutto cio' che segue usa la SE piu' PICCOLA.")
print(f"\n Perturbazione: drift di XS01 spostato di k x SE ({se_boot:.2%}/anno), forma della")
print(" coda invariata (una costante al giorno su R e su m: il gap resta identico).")
print(" Confronto APPAIATO con il libro LIVE 75/25 sulla stessa cella e sugli stessi semi.")
KS = (-2.0, -1.0, -0.5, 0.0, 1.0)
rows = []
for off, ph, lab in ANCH:
fA = GC.fvec_for(W_A, I, rate, off, xs_ann)
fL = GC.fvec_for(W_LIVE, I, rate, off, xs_ann)
RL, GL = AP.rg_off(W_LIVE, I, XSP[ph], off)
jL, lvL, _, _ = best_lev(RL - fL, GL, ev_b, fu_b)
ladL = GC.ladder(RL - fL, GL, N_LAD, lvL, SEED_LAD, refund=False)
bump(1)
for k in KS:
xsk = xs_shift(XSP[ph], k * se_boot)
R0, G0 = AP.rg_off(W_A, I, xsk, off)
j, lv, _, o = best_lev(R0 - fA, G0, ev_b, fu_b)
lad = GC.ladder(R0 - fA, G0, N_LAD, lv, SEED_LAD, refund=False)
bump(1)
rows.append(dict(anchor=lab, off=off, ph=ph, k=k, J=j, lev=lv,
p50=lad["p_target"], pz=lad["p_zero"], med=lad["med"],
drift=o["drift"], J_live=jL, p50_live=ladL["p_target"],
pz_live=ladL["p_zero"], med_live=ladL["med"]))
D = pd.DataFrame(rows)
print(f"\n {'k x SE':>7} {'drift XS01':>11} {'J funded':>9} {'J live':>8} {'dJ':>8} "
f"{'P(>=50/g)':>10} {'P(zero)':>9} {'EUR/g':>8}")
for k in KS:
s = D[D["k"] == k]
dj = float((s["J"] - s["J_live"]).median())
print(f" {k:>+7.1f} {d_ann + k*se_boot:>10.1%} {s['J'].median():>9.3f} "
f"{s['J_live'].median():>8.3f} {dj:>+8.3f} {s['p50'].median():>9.1%} "
f"{s['pz'].median():>8.1%} {s['med'].median():>8.2f}")
base = D[D["k"] == 0.0]
dj0 = float((base["J"] - base["J_live"]).median())
dj_m1 = float((D[D["k"] == -1.0]["J"].values - D[D["k"] == -1.0]["J_live"].values).mean())
dj_m2 = float((D[D["k"] == -2.0]["J"].values - D[D["k"] == -2.0]["J_live"].values).mean())
p50_0, p50_m1 = base["p50"].median(), D[D["k"] == -1.0]["p50"].median()
p50_p1 = D[D["k"] == 1.0]["p50"].median()
print(f"\n IL VANTAGGIO DEL CANALE, in unita' di SE del suo pilastro:")
print(f" dJ a 0 SE = {dj0:+.3f} (il vantaggio pubblicato, §29: {PUB['recal_d']:+.3f})")
print(f" dJ a -1 SE = {dj_m1:+.3f} ({dj_m1/dj0*100 if dj0 else 0:.0f}% di quello a 0 SE)")
print(f" dJ a -2 SE = {dj_m2:+.3f} ({dj_m2/dj0*100 if dj0 else 0:.0f}%)")
conv = (p50_p1 - p50_0) / max(p50_0 - p50_m1, 1e-9)
print(f" P(>=50/g): -1 SE {p50_m1:.1%} · punto {p50_0:.1%} · +1 SE {p50_p1:.1%} "
f"-> asimmetria (su/giu) {conv:.1f}x")
# CONTROLLO: lo stesso -1 SE messo SOLO sui giorni attivi di XS01
off_c, ph_c, _ = ANCH[1]
fC = GC.fvec_for(W_A, I, rate, off_c, xs_ann)
Rc, Gc = AP.rg_off(W_A, I, xs_shift(XSP[ph_c], -se_boot, active_only=True), off_c)
jc, lvc, _, _ = best_lev(Rc - fC, Gc, ev_b, fu_b)
ladc = GC.ladder(Rc - fC, Gc, N_LAD, lvc, SEED_LAD, refund=False)
bump(1)
rowm = D[(D["k"] == -1.0) & (D["anchor"] == "MEDIANA")].iloc[0]
print(f"\n CONTROLLO di convenzione (cella MEDIANA, -1 SE): spostare il drift su TUTTI i")
print(f" giorni da' J {rowm['J']:.3f} / P(>=50/g) {rowm['p50']:.1%}; spostarlo solo sui")
print(f" giorni ATTIVI ({act.mean():.0%} del campione) da' J {jc:.3f} / "
f"P(>=50/g) {ladc['p_target']:.1%} -> la conclusione NON dipende dalla convenzione.")
a1 = "CONFERMATA" if (0.2 * dj0 < dj_m1 < 0.8 * dj0) else "REFUTATA"
a2 = "CONFERMATA" if conv > 1.5 else "REFUTATA"
print(f" -> A1 (vantaggio a -1 SE fra il 20% e l'80%) {a1}; A2 (convessita') {a2}")
print("\n ⚠ Cio' che NON e' propagato qui, e va nel MIO verso (piu' incertezza, non meno):")
print(" (a) le 6 gambe non testabili — §29 misura che valgono il 42% di J e non sono")
print(" nel pannello U13 usato qui; (b) la SE del drift CRYPTO (5,15%/anno, §57),")
print(" che agisce su ENTRAMBE le strade e in parte si cancella nell'appaiato;")
print(" (c) l'incertezza sul maxDD di XS01 (10,8% -> 20,9-22,0% fuori campione),")
print(" che e' cio' che la barriera guarda.")
tick("§2")
# ============================================== §3 ATTACCO 2 — la morte-firm, misurata
hr("§3. ATTACCO 2 — LA MORTE-FIRM: cosa dice l'evidenza pubblica, e dove sta il break-even")
print(" Il progetto usa 10%/anno dal 24/07 ('il settore ha perso il 13% delle firm nel")
print(" 2024'), mai ri-misurato. Ricerca pubblica del 2026-08-23 (fonti SECONDARIE, in")
print(" larga parte siti di settore/affiliazione: qualita' dichiarata, non primaria):")
EVID = [
("Finance Magnates (stampa di settore)",
"80-100 firm chiuse fra 02/2024 e fine 2025", 0.18,
"~90 chiusure in ~22 mesi su una base attiva ~230 -> ~21%/anno lordo, ~18% netto entrate"),
("proptradingvibes (tracker, 07/2026)",
"302 tracciate, 230 attive, 72 chiuse", 0.12,
"23,8% cumulato sulla vita del tracker (orizzonte non dichiarato -> limite INFERIORE)"),
("thepropfirmguide / tradernotion (aggregatori)",
"55-65% della coorte 2020-2023 chiusa/sospesa/ristrutturata entro il 2026", 0.19,
"survival 0,40 su ~4 anni -> 20,5%/anno; a 55%: 18,1%/anno"),
("track360 / tradersyard (report di mercato)",
"120-150 firm retail attive nel 2026 contro 220+ nel 2023", 0.15,
"135/220 su 3 anni = 14,9%/anno, ma e' NETTO delle nuove entranti -> limite INFERIORE"),
]
print(f"\n {'fonte':>44} {'implica p/anno':>15}")
for src, claim, p, why in EVID:
print(f" {src:>44} {p:>14.0%} {claim}")
print(f" {'':>44} {why}")
p_lo = min(e[2] for e in EVID)
p_hi = max(e[2] for e in EVID)
p_mid = float(np.median([e[2] for e in EVID]))
print(f"\n BANDA IMPLICATA: **{p_lo:.0%} - {p_hi:.0%}/anno, mediana {p_mid:.0%}** contro il "
f"**{pl.FIRM_DEATH_PER_YEAR:.0%}** assunto.")
print(" ⚠ Tre riserve, tutte a favore del canale e tutte dichiarate: (i) 'chiusa/sospesa/")
print(" ristrutturata' e' piu' largo di 'sparita col saldo dentro'; (ii) i denominatori")
print(" delle fonti non coincidono (2.000 / 302 / 135 firm) e nessuna e' primaria;")
print(" (iii) FTMO (2015) e HyroTrader (2022) sono SOPRAVVISSUTE — condizionarsi su una")
print(" firm gia' vecchia abbassa il suo hazard, ma e' esattamente il survivorship bias")
print(" che non si puo' rivendicare a priori su un orizzonte di 36 mesi.")
PS = (0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.40, 0.55)
print(f"\n SWEEP su p (cella d'ancora MEDIANA, leva {lev_m:.2f}x, {N_LAD} percorsi ladder,")
print(f" {N_TIX} percorsi biglietto; tutto il resto CONGELATO):")
print(f" {'p/anno':>7} {'P(>=50/g)':>10} {'P(zero)':>9} {'EUR/g med':>10} "
f"{'EV spesa persa':>15} {'EV rimb.payout':>15} {'P(pass)':>8} {'P(payout)':>10}")
p_saved = pl.FIRM_DEATH_PER_YEAR
sw = []
for p in PS:
pl.FIRM_DEATH_PER_YEAR = p
lad = GC.ladder(R_M, G_M, N_LAD, lev_m, SEED_LAD, refund=True)
tix = GC.ticket_mc(R_M, G_M, lev_m, lev_m, 100_000.0, n_paths=N_TIX)
ev3 = GC.ev_three_ways(tix, 579.0)
bump(2)
sw.append(dict(p=p, p50=lad["p_target"], pz=lad["p_zero"], med=lad["med"],
sunk=ev3["sunk"], at_payout=ev3["at_payout"],
p_pass=tix["p_pass"], p_pay=tix["p_payout"]))
print(f" {p:>7.0%} {lad['p_target']:>9.1%} {lad['p_zero']:>8.1%} {lad['med']:>10.2f} "
f"${ev3['sunk']:>14,.0f} ${ev3['at_payout']:>14,.0f} {tix['p_pass']:>7.1%} "
f"{tix['p_payout']:>9.1%}")
pl.FIRM_DEATH_PER_YEAR = p_saved
SW = pd.DataFrame(sw)
neg = SW[SW["sunk"] <= 0]
if len(neg):
i1 = SW.index[SW["sunk"] <= 0][0]
x0, x1 = SW.loc[i1 - 1, "p"], SW.loc[i1, "p"]
y0, y1 = SW.loc[i1 - 1, "sunk"], SW.loc[i1, "sunk"]
p_be = x0 + (x1 - x0) * y0 / (y0 - y1)
print(f"\n BREAK-EVEN dell'EV (spesa persa, la convenzione conservativa): "
f"p* = **{p_be:.0%}/anno**")
else:
p_be = float("nan")
print(f"\n BREAK-EVEN dell'EV: **oltre {max(PS):.0%}/anno** (EV positivo su tutto lo sweep)")
p50_at_mid = float(np.interp(p_mid, SW["p"], SW["p50"]))
print(f" A p = {p_mid:.0%} (mediana dell'evidenza) P(>=50/g) scende "
f"{SW.loc[SW['p']==0.10,'p50'].iloc[0]:.1%} -> {p50_at_mid:.1%} "
f"({p50_at_mid/max(SW.loc[SW['p']==0.10,'p50'].iloc[0],1e-9)-1:+.0%})")
a3 = "CONFERMATA" if (0.15 <= p_mid <= 0.25 and (np.isnan(p_be) or p_be > p_hi)) else "PARZIALE"
print(f" -> A3 {a3}: la morte-firm vera e' plausibilmente {p_lo:.0%}-{p_hi:.0%}/anno, e il")
print(f" break-even dell'EV sta {'sopra' if (np.isnan(p_be) or p_be > p_hi) else 'DENTRO'} "
f"quella banda.")
print(" 📌 La lettura che conta NON e' l'EV (che regge): e' che P(>=50/g) e' gia' 2,6% e")
print(" l'errore sulla morte-firm la muove del ~{:.0f}% senza toccare nulla d'altro."
.format(abs(p50_at_mid / max(SW.loc[SW['p'] == 0.10, 'p50'].iloc[0], 1e-9) - 1) * 100))
tick("§3")
# ================================= §4 ATTACCO 3 — la frontiera P(>=50/g) contro P(zero)
hr("§4. ATTACCO 3 — LA FRONTIERA: P(>=50/g) e' una soglia sulla CODA DESTRA")
POL = [("LIVE 75/25/0", W_LIVE), ("W_A 38/38/25 (racc.)", W_A),
("38/25/38", {"TP01": 0.38, "SKH01": 0.25, "XS01": 0.38}),
("argmaxJ 25/25/50", W_ARGMAX),
("55/20/25 (book div 25/07)", {"TP01": 0.55, "SKH01": 0.20, "XS01": 0.25})]
LEVS_FR = LEVS
if time.time() - T0 > 0.45 * BUDGET_S:
LEVS_FR = LEVS[::2]
print(f" ⚠ BUDGET: leve della frontiera ridotte da {len(LEVS)} a {len(LEVS_FR)} "
f"(dichiarato).")
fr = []
for nm, w in POL:
fw = GC.fvec_for(w, I, rate, off_m, xs_ann)
Rw, Gw = AP.rg_off(w, I, XSP[ph_m], off_m)
Rw = Rw - fw
for lv in LEVS_FR:
lad = GC.ladder(Rw, Gw, N_LAD_FR, lv, SEED_LAD, refund=False)
bump(1)
fr.append(dict(pol=nm, lev=lv, p50=lad["p_target"], pz=lad["p_zero"],
med=lad["med"], p10g=lad["p_ge10"]))
F = pd.DataFrame(fr)
rho = float(np.corrcoef(F["p50"], F["pz"])[0, 1])
print(f" {len(POL)} politiche x {len(LEVS_FR)} leve = {len(F)} celle, cella d'ancora MEDIANA.")
print(f"\n corr( P(>=50/g) , P(zero) ) sulle {len(F)} celle = **{rho:+.3f}** -> A4 "
+ ("CONFERMATA" if rho > 0.5 else "REFUTATA"))
b50 = F.loc[F["p50"].idxmax()]
bz = F.loc[F["pz"].idxmin()]
bmed = F.loc[F["med"].idxmax()]
b10 = F.loc[F["p10g"].idxmax()]
print(f"\n {'obiettivo massimizzato':>28} {'politica':>26} {'leva':>6} {'P(>=50/g)':>10} "
f"{'P(zero)':>9} {'EUR/g med':>10}")
for lab, r in (("P(>=50 EUR/g) <- il citato", b50), ("P(zero) minima", bz),
("EUR/g mediana", bmed), ("P(>=10 EUR/g)", b10)):
print(f" {lab:>28} {r['pol']:>26} {r['lev']:>6.2f} {r['p50']:>9.1%} "
f"{r['pz']:>8.1%} {r['med']:>10.2f}")
rec = F[(F["pol"] == "W_A 38/38/25 (racc.)")]
rr = rec.loc[rec["p50"].idxmax()]
print(f"\n LA RACCOMANDAZIONE ({rr['pol']} @ {rr['lev']:.2f}x): P(>=50/g) {rr['p50']:.1%}, "
f"P(zero) {rr['pz']:.1%}, EUR/g {rr['med']:.2f}")
dom = F[(F["p50"] >= rr["p50"]) & (F["pz"] <= rr["pz"]) &
((F["p50"] > rr["p50"]) | (F["pz"] < rr["pz"]))]
print(f" Celle che la DOMINANO (>= su P(>=50/g) e <= su P(zero)): **{len(dom)}** su {len(F)}")
if len(dom):
d0 = dom.loc[dom["p50"].idxmax()]
print(f" la migliore: {d0['pol']} @ {d0['lev']:.2f}x -> {d0['p50']:.1%} / {d0['pz']:.1%}")
# frontiera di Pareto
pts = F.sort_values("pz")
par, best = [], -1.0
for _, r in pts.iterrows():
if r["p50"] > best:
par.append(r)
best = r["p50"]
P = pd.DataFrame(par)
print(f"\n FRONTIERA DI PARETO ({len(P)} punti non dominati su {len(F)}):")
print(f" {'P(zero)':>9} {'P(>=50/g)':>10} {'EUR/g med':>10} {'politica':>26} {'leva':>6}")
for _, r in P.iterrows():
star = " <== raccomandazione" if (r["pol"] == rr["pol"] and r["lev"] == rr["lev"]) else ""
print(f" {r['pz']:>8.1%} {r['p50']:>9.1%} {r['med']:>10.2f} {r['pol']:>26} "
f"{r['lev']:>6.2f}{star}")
print("\n 📌 Il prezzo del punto scelto, in unita' comprensibili: passare dal punto a")
print(" P(zero) minima a quello a P(>=50/g) massima costa "
f"{(F['pz'].max()-F['pz'].min())*100:.0f}pp di probabilita' di finire a ZERO per "
f"comprare {(F['p50'].max()-F['p50'].min())*100:.1f}pp di probabilita' di 50 EUR/g.")
tick("§4")
# ============================== §5 ATTACCO 4 — il gate che il canale non ha mai passato
hr("§5. ATTACCO 4 — IL NULL A DRIFT ZERO sulla griglia di 360 celle di PROP-ALLOC")
grid = PA.simplex_grid(("TP01", "SKH01", "XS01"), STEP_SIMPLEX)
n_cells = len(grid) * len(LEVS)
print(f" Griglia di SELEZIONE dichiarata da PROP-ALLOC: {len(grid)} vettori di peso "
f"(passo 1/{STEP_SIMPLEX}) x {len(LEVS)} leve = **{n_cells} celle**.")
print(" Conteggio dei trial al RIALZO, come vuole il progetto: PROP-ALLOC 360 di selezione")
print(" + la seconda firm (360) + PROP-RECAL su 3 finestre + le politiche del 25/07")
print(" -> l'ordine di grandezza del multiple testing sul CANALE e' ~1.000-2.000 celle,")
print(" e nessuno di questi filoni ha mai calcolato un deflated-Sharpe sul canale.")
print("\n IL NULL: stesso pannello, stessa lente, stesse regole, stessa griglia — ma il")
print(" DRIFT del libro azzerato (`R - mean(R)` per ogni vettore di peso). Domanda: quanto")
print(" J estrae un argmax su 360 politiche quando NON c'e' edge? (Con drift zero si passa")
print(" un'eval del +10% per pura varianza: il null NON e' zero.)")
ev_n = PA.boot_idx(len(I), EV_H, N_NULL_J, seed=SEED_EV)
fu_n = PA.boot_idx(len(I), FU_H, N_NULL_J, seed=SEED_FU)
RG = {}
for w in grid:
key = PA.wkey(w)
fw = GC.fvec_for(w, I, rate, off_m, xs_ann)
Rw, Gw = AP.rg_off(w, I, XSP[ph_m], off_m)
RG[key] = (Rw - fw, Gw)
def scan_grid(shift_to_zero: bool, ev_i, fu_i) -> np.ndarray:
out = np.empty(n_cells)
i = 0
for w in grid:
Rw, Gw = RG[PA.wkey(w)]
Rz = Rw - Rw.mean() if shift_to_zero else Rw
for lv in LEVS:
out[i] = PR.obj_rg(Rz, Gw, lv, ev_i, fu_i, factor=DELUCK)["J"]
i += 1
bump(n_cells)
return out
t = time.time()
J_real = scan_grid(False, ev_n, fu_n)
print(f"\n OSSERVATO ({time.time()-t:.0f}s): max-J su {n_cells} celle = **{J_real.max():.3f}** "
f"| mediana {np.median(J_real):.3f} | p90 {np.percentile(J_real,90):.3f}")
el = time.time() - T0
per_scan = max(time.time() - t, 1.0)
n_draws = N_NULL_DRAWS
while n_draws > 2 and el + (n_draws + 1) * per_scan > BUDGET_S:
n_draws -= 1
if n_draws != N_NULL_DRAWS:
print(f" ⚠ BUDGET: a {el:.0f}s con {per_scan:.0f}s a scansione, le estrazioni di null")
print(f" scendono da {N_NULL_DRAWS} a {n_draws}. Dichiarato, non nascosto: la MEDIA")
print(f" del max-J del null e' piu' rumorosa, la sua TAGLIA no.")
nulls = []
for b in range(n_draws):
ev_i = PA.boot_idx(len(I), EV_H, N_NULL_J, seed=9000 + 2 * b)
fu_i = PA.boot_idx(len(I), FU_H, N_NULL_J, seed=9001 + 2 * b)
Jn = scan_grid(True, ev_i, fu_i)
nulls.append(Jn)
print(f" null #{b+1}: max-J {Jn.max():.3f} · mediana {np.median(Jn):.3f} "
f"({time.time()-t:.0f}s)")
N = np.vstack(nulls)
nmax = N.max(axis=1)
print(f"\n NULL A DRIFT ZERO ({n_draws} estrazioni, {N_NULL_J} percorsi ciascuna):")
print(f" max-J atteso dal PURO RUMORE = **{nmax.mean():.3f}** "
f"[min {nmax.min():.3f}, max {nmax.max():.3f}]")
print(f" OSSERVATO {J_real.max():.3f} -> eccesso **{J_real.max()-nmax.mean():+.3f}** "
f"({(J_real.max()/max(nmax.mean(),1e-9)-1)*100:+.0f}%)")
print(f" estrazioni di null che BATTONO l'osservato: {int((nmax>=J_real.max()).sum())}"
f"/{n_draws}")
a5 = "CONFERMATA" if (0.25 <= nmax.mean() <= 0.45) else "REFUTATA"
print(f" -> A5 (max-J del null fra 0,25 e 0,45) {a5}")
# crescita del massimo col numero di celle: la sensibilita' del verdetto al CONTEGGIO
print(f"\n Sensibilita' al CONTEGGIO dei trial (max-J del null su sottoinsiemi casuali):")
rng = np.random.default_rng(4242)
print(f" {'k celle':>9} {'max-J null':>11} {'osservato':>11} {'eccesso':>9}")
for k in (45, 90, 180, 360):
mk = np.array([N[b, rng.choice(n_cells, size=min(k, n_cells), replace=False)].max()
for b in range(n_draws) for _ in range(4)])
ok = np.array([J_real[rng.choice(n_cells, size=min(k, n_cells), replace=False)].max()
for _ in range(20)]).mean()
print(f" {k:>9} {mk.mean():>11.3f} {ok:>11.3f} {ok-mk.mean():>+9.3f}")
# il gate STANDARD del progetto, puntato sul canale — e la sua CECITA' alla leva
import altlib as AL # noqa: E402
sh_w, ret_w = {}, {}
for w in grid:
Rw, Gw = RG[PA.wkey(w)]
Rd = PA.deluck(Rw, DELUCK)
v = float(Rd.std(ddof=1) * np.sqrt(365))
sh_w[PA.wkey(w)] = float(Rd.mean() * 365 / v) if v > 0 else 0.0
ret_w[PA.wkey(w)] = Rd
kb = max(sh_w, key=sh_w.get)
dsr, sr0 = AL.deflated_sharpe(sh_w[kb], list(sh_w.values()), ret_w[kb])
print(f"\n IL GATE STANDARD, per confronto — `altlib.deflated_sharpe` sulla stessa griglia:")
print(f" Sharpe migliore {sh_w[kb]:.3f} ({kb}) · attesa dal rumore su N={len(sh_w)} "
f"trial {sr0:.3f} · **DSR {dsr:.3f} {'PASS' if dsr >= 0.95 else 'FAIL'}**")
print(f" 🚨 Ma N e' **{len(sh_w)}, non {n_cells}**: lo Sharpe e' INVARIANTE ALLA LEVA")
print(f" (`obj_rg` lo calcola su `Rd`, non su `Rd*lev`), quindi le {n_cells-len(sh_w)}")
print(" celle che differiscono SOLO per la leva sono **invisibili** al gate standard.")
print(" E' esattamente la lezione di §33/§53: la leva non la vede nessun gate del")
print(" progetto. Su un conto a BARRIERA la leva e' la variabile che decide.")
# il libro LIVE, che NON e' stato selezionato, come incumbent
fL = GC.fvec_for(W_LIVE, I, rate, off_m, xs_ann)
RL, GL = AP.rg_off(W_LIVE, I, XSP[ph_m], off_m)
jL_all = [PR.obj_rg(RL - fL, GL, lv, ev_n, fu_n, factor=DELUCK)["J"] for lv in LEVS]
bump(len(LEVS))
print(f"\n L'INCUMBENT (libro LIVE 75/25, che NON e' stato selezionato su questa griglia):")
print(f" J max sulle 8 leve = {max(jL_all):.3f} -> anche l'incumbent "
f"{'SUPERA' if max(jL_all) > nmax.mean() else 'NON supera'} il max del null.")
print(" ⚠ CONTROLLO NEGATIVO obbligatorio: una estrazione di null trattata come se fosse")
print(" il candidato deve dare eccesso ~0 contro le altre.")
held = nmax[0]
rest = nmax[1:]
print(f" null #1 come candidato: max-J {held:.3f} contro media delle altre "
f"{rest.mean():.3f} -> eccesso {held-rest.mean():+.3f} "
f"({'OK, ~0' if abs(held-rest.mean()) < 0.05 else 'ATTENZIONE'})")
print("\n 📌 Cosa dice e cosa NON dice: **non** dice che il canale sia rumore — l'eccesso")
print(" e' misurato e positivo. Dice che **una parte non piccola del J pubblicato la")
print(" produce la varianza attraverso una barriera**, e che nessuno l'aveva sottratta.")
tick("§5")
# ================================================================== §6 il verdetto
hr("§6. LA DOMANDA FINALE — se il canale funded fosse un CANDIDATO-SLEEVE, passerebbe?")
ded = J_real.max() - nmax.mean()
checks = [
("edge in-sample (J > incumbent)", dj0 > 0.05,
f"dJ {dj0:+.3f} contro il libro live"),
("deflated / null di selezione", ded > 0.10,
f"eccesso sul max del null a drift zero {ded:+.3f} su {n_cells} celle"),
("robustezza al PARAMETRO (-1 SE)", dj_m1 > 0.05,
f"dJ a -1 SE del drift di XS01 = {dj_m1:+.3f}"),
("robustezza a -2 SE", dj_m2 > 0.05, f"dJ a -2 SE = {dj_m2:+.3f}"),
("assunzione esogena stimata", (not np.isnan(p_be)) and p_be > p_hi,
f"morte-firm: assunta {p_saved:.0%}, evidenza {p_lo:.0%}-{p_hi:.0%}, "
f"break-even {p_be:.0%}" if not np.isnan(p_be) else
f"morte-firm: assunta {p_saved:.0%}, evidenza {p_lo:.0%}-{p_hi:.0%}, break-even >55%"),
("obiettivo non degenere", len(dom) == 0,
f"{len(dom)} celle dominano la raccomandazione su (P(>=50/g), P(zero))"),
("deflated-Sharpe standard (cieco alla leva)", dsr >= 0.95,
f"DSR {dsr:.3f} su N={len(sh_w)} trial visibili di {n_cells} reali"),
("fuori dalla finestra di scoperta", True,
"§21: XS01 regge fuori campione (+1,12), MA a 13 gambe su 19 e a effetto/MDE ~1,0"),
("eseguibilita' verificata al venue", True,
"§30: 13/13 gambe `Trading` su Bybit, min notional $5 — PASS misurato"),
]
print(f" {'gate':>36} {'esito':>8} dettaglio")
npass = 0
for nm, ok, det in checks:
npass += int(ok)
print(f" {nm:>36} {'PASS' if ok else 'FAIL':>8} {det}")
print(f"\n {npass}/{len(checks)} gate superati.")
hr("VERDETTO")
print(f" 1. REPLICA: P(>=50 EUR/g) {m50:.1%} contro il pubblicato {PUB['p50g']:.1%}, "
f"P(zero) {C['pz'].median():.1%} contro {PUB['pzero']:.1%} -> **la macchina e' quella**.")
print(f" 2. IL PILASTRO: SE del drift di XS01 = {se_boot:.1%}/anno. A -1 SE il vantaggio "
f"del canale passa da {dj0:+.3f} a {dj_m1:+.3f} di J e P(>=50/g) da {p50_0:.1%} a "
f"{p50_m1:.1%}.")
print(f" 3. LA MORTE-FIRM: evidenza pubblica {p_lo:.0%}-{p_hi:.0%}/anno contro il "
f"{p_saved:.0%} assunto; l'EV regge (break-even "
f"{'>' + f'{max(PS):.0%}' if np.isnan(p_be) else f'{p_be:.0%}'}), "
f"ma P(>=50/g) scende a {p50_at_mid:.1%}.")
print(f" 4. LA FRONTIERA: corr(P(>=50/g), P(zero)) = {rho:+.3f}; "
f"{len(dom)} celle dominano il punto raccomandato.")
print(f" 5. IL GATE MANCANTE: max-J dal puro rumore su {n_cells} celle = {nmax.mean():.3f}, "
f"osservato {J_real.max():.3f}, eccesso {ded:+.3f}.")
eur_pt = float(C["med"].median())
eur_m1 = float(D[D["k"] == -1.0]["med"].median())
eur_live = float(D[D["k"] == 0.0]["med_live"].median())
print(f"\n IN EUR/GIORNO (mediana, 36 mesi, da 600 euro, funding e fisco dentro):")
print(f" canale funded al drift misurato ... {eur_pt:>6.2f} EUR/g")
print(f" canale funded a -1 SE ............ {eur_m1:>6.2f} EUR/g")
print(f" libro LIVE sullo stesso conto .... {eur_live:>6.2f} EUR/g")
print(f" obiettivo dichiarato ............. {pl.TARGET_EUR_DAY:>6.2f} EUR/g")
print(f"\n Questo filone NON avvicina i 50 EUR/giorno: non produce ritorno, "
f"riduce la fiducia")
print(f" nel numero gia' pubblicato. Il suo effetto sulla rendita attesa e' "
f"**{eur_m1-eur_pt:+.2f} EUR/g**")
print(f" se si crede a -1 SE, e **0,00 EUR/g** se non si cambia nulla (ed e' cio' che "
f"raccomanda).")
print(f"\n [{TRIALS} valutazioni, {time.time()-T0:.0f}s]")
print("=" * 104)
if __name__ == "__main__":
main()
+784
View File
@@ -0,0 +1,784 @@
"""r0823b_split_deposito — §62 SPLIT-DEPOSITO: quanta parte del versamento va ai BIGLIETTI (2026-08-23).
IL BUCO CHE CHIUDE
------------------
`RESULTS-0822 §37` (GATE-C) ha confrontato **due strade INTERE** tutto sul libro contro tutto in
biglietti prop e il biglietto ha vinto a EUR 500/mese (mediana $39.820 contro $22.444,
P(>=50/g) 13,7% contro 0,0%). Ma **«tutto o niente» non e' una politica: e' un caso limite.**
Nessuno ha misurato (a) la FRAZIONE ottima del versamento, (b) se debba CAMBIARE nel tempo,
(c) su quale colonna si sta ottimizzando e le colonne hanno ottimi diversi.
LE MIE ATTESE, REGISTRATE PRIMA DI MISURARE (metodo obbligatorio, punto 1)
-------------------------------------------------------------------------
A1. **La forma della politica ottima, PREVISTA dalla struttura** (mandato punto 4). I due canali
hanno rendimenti di scala OPPOSTI:
* il LIBRO e' *neutrale alla scala* — Sharpe piatto da $600 a $200k (25/07 §3), quindi
ogni dollaro rende la stessa percentuale: la ricchezza e' LINEARE nel capitale versato;
* il BIGLIETTO ha rendimenti di scala **DECRESCENTI e a gradini**: il prezzo e' FISSO
($249-$1.080) e il nozionale che sblocca e' FISSO ($25k-$200k) — **indipendente dal
proprio capitale**; sotto $249 un dollaro in piu' non fa nulla, e sopra il cap
($200k/firm, 6 slot concorrenti) nemmeno.
**PREVISIONE: la politica ottima e' FRONT-LOADED e SATURANTE** — biglietti prima (quando il
libro ha una base di capitalizzazione ridicola e un biglietto sblocca 150x il conto), libro
dopo (quando il canale biglietto e' saturo). Cioe': `TIXFIRST` batte `BOOKFIRST`, e phi
DECRESCENTE col capitale batte phi CRESCENTE.
A2. **Gli ottimi delle tre colonne NON coincidono**: P(>=50/g) crescente in phi con argmax a
phi=1 (a 36-60 mesi **solo** il canale biglietto puo' produrre un prelievo); ricchezza
MEDIANA con argmax a phi BASSO (la strada-biglietto e' bimodale, §37); P(rovina) crescente
in phi. **Se e' cosi', «la frazione ottima» non esiste finche' non si dice quale colonna.**
A3. **A 60 mesi l'ottimo phi SCENDE**: la morte-firm 10%/anno rende il canale biglietto un canale
a VITA FINITA (P(entrambe le firm morte a 5 anni) ~17%), mentre il libro capitalizza per
sempre. E' l'unica ragione strutturale per cui l'orizzonte dovrebbe muovere l'ottimo.
A4. **Il trabocchetto §44 si eredita, e con lo stesso segno**: mettere una CONDIZIONE RARA davanti
all'acquisto di un biglietto costa, perche' ritarda le giocate di una lotteria ripetuta.
Mi aspetto un costo **proporzionale al tempo d'attesa**, come in §44 — e lo misuro contro un
**gemello a ritardo deterministico** di pari attesa media, che e' il modo per separare il
costo della CONDIZIONE da quello del RITARDO.
A5. **La riga onesta**: mi aspetto che l'ampiezza dovuta alla frazione sia GRANDE sulla colonna
P(>=50/g) ma solo perche' il libro vale ZERO su quella colonna a questo orizzonte (non e'
un difetto del libro, e' l'orizzonte: servono $313k) e PICCOLA sulla colonna che misura la
ricchezza. Se esce cosi', va detto che **la frazione conta sulla metrica sbagliata**.
LA FAMIGLIA, DICHIARATA PRIMA DI MISURARE (mandato punto 3)
-----------------------------------------------------------
F(phi) frazione FISSA del versamento ai biglietti, phi = 0,00 ... 1,00 (11 celle).
phi=1 == la strada BIGLIETTO di §37; phi=0 == la strada LIBRO di §37.
TIX(X) **biglietti prima**: phi=1 finche' il cumulato versato ai biglietti < $X,
poi phi=0. X = 700 (= solo il capitale iniziale = "biglietti solo dai payout",
perche' i payout tornano sempre nel pot biglietti), 2.000, 5.000, 10.000.
BOOK(X) **libro prima**: phi=0 finche' il libro < $X, poi phi=1. X = 2.000, 5.000,
10.000, 20.000.
PHI_UP(X) phi CRESCE col capitale: phi = clip(libro / X, 0, 1), X = 10.000.
PHI_DN(X) phi DECRESCE col capitale: phi = 1 - clip(libro / X, 0, 1), X = 10.000.
CASH(B) phi=1 ma la cassa in eccesso oltre $B va al libro (B = 1.200 = il biglietto piu'
caro). E' l'unica politica che puo' DOMINARE phi=1 per costruzione.
RARE(d) phi=1 ma si COMPRA solo dopo un drawdown >= d dell'equity della strategia
(d = 10%, 20%): il trabocchetto §44 applicato al biglietto.
DELAY(g) phi=1 ma non si compra prima del giorno g: **gemello deterministico** di RARE.
Il capitale iniziale ($654) e' trattato come il "versamento del mese 0" e segue la stessa regola.
CONVENZIONI EREDITATE E CONGELATE (nessuna scelta nuova => nessun trial da deflazionare)
-----------------------------------------------------------------------------------------
* Simulatore biglietti: copia di `r0822d_gate_c.ladder` (che e' bit-exact vs `r0725_prop_coupled.
simulate`) con l'aggiunta del pot LIBRO. Lente **ACCOPPIATA** (tuple (R, gap) del recon): su una
regola a UN GIORNO la close-only e' esattamente CIECA (25/07).
* Conto funded: W_A = TP01 38 / SKH01 38 / XS01 25 (centro della regione robusta di PROP-RECAL),
XS01 = U13 Binance, finestra W-LONG 2021+, **funding DENTRO**, de-luck sul drift **x0,89**.
* Pot LIBRO: `FD.book_75_25(rate, alloc=654)` = libro LIVE 75/25 con min-order $5 e funding
dentro, de-luck x0,89, fisco 33% sulla variazione annua con riporto (convenzione di
`r0822d_gate_c.book_road` = quella di `r0807_piano_netto`). Dichiarato: NON contiene la
patrimoniale 0,2%/anno che `PN.accumula` invece include; la differenza e' misurata in §1.
* Morte-firm 10%/anno, cap $200k/firm, 6 slot, deposito **rimborsato al primo payout** (§37),
soglia di prelievo $100 di quota trader, fisco 33% sui payout, EUR/USD 1,09.
* Rendita: `perp` e muro dalla lente **L3 CONGIUNTA** (`PN.muro`), cioe' la stessa che produce i
$313k del piano.
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0823b_split_deposito.py`
Rete: NESSUNA (`--no-net` forzato: parquet certificati + cache Binance/funding gia' su disco).
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import numpy as np
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))
if "--no-net" not in sys.argv: # r0822d_funding legge USE_NET all'import
sys.argv.append("--no-net")
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata (25/07)
import r0725_prop_ladder as pl # noqa: E402 regole firm + scala di conti (25/07)
import r0807_piano_netto as PN # noqa: E402 muro / accumula (07/08)
import r0727_tasse as TX # noqa: E402 patrimoniale
import r0822_prop_alloc as PA # noqa: E402 J, bootstrap (22/08)
import r0822b_prop_recal as PR # noqa: E402 PROP-RECAL (22/08)
import r0822b_xs01_oos as X # noqa: E402 universo U13 + cache Binance
import r0822c_skh_anchor_prop as AP # noqa: E402 offset di SKH01
import r0822d_funding as FD # noqa: E402 il funding (22/08)
import r0822d_gate_c as GC # noqa: E402 §37: ladder + book_road
from src.portfolio.sleeves import XS_UNIVERSE # noqa: E402
FD.USE_NET = False
EURUSD = pl.EURUSD
TAX_RATE = pl.TAX_RATE
SPLIT = pl.SPLIT
DELUCK = PA.DELUCK # x0,89 MISURATO (r0726_deluck_factor)
FIRMS = pl.FIRMS
N_SLOT = pl.MAX_CONCURRENT
TARGET = pl.TARGET_EUR_DAY
START_EUR = 600.0 # $654 = il conto reale (convenzione di §37)
MIN_PAYOUT = GC.MIN_PAYOUT_TRADER # $100 di quota trader
BLOCK = 20
SEED = 20260823
N_BASE = 1500 # risoluzione dichiarata: ~+-1pp su P(>=50/g)
DEP_MAIN = 500.0 # il piano in corso (config/live.json, 26/07)
CHEAP = min(t[1] for F in FIRMS for t in F["tickets"]) # $249
# celle d'ancora: sotto-campione SISTEMATICO dichiarato a priori sui 23 offset x 3 fasi di §32
CELLS = ((0, 0), (150, 3), (300, 7), (450, 0), (600, 3))
# numeri PUBBLICATI da riprodurre prima di produrne di nuovi
PUB = dict(muro=313_000.0, p50g_37=0.137, med_tix_37=39_820.0, med_book_37=22_444.0,
med_tix_0=132.0, med_book_0=787.0, p50g_0=0.042, pzero_37=0.403, p50g_anchor=0.026)
T0 = time.time()
def clock() -> str:
return f"[{time.time()-T0:5.0f}s]"
def hr(t: str = "") -> None:
print("\n" + "=" * 108)
if t:
print(f" {t}")
print("=" * 108)
# ===========================================================================================
# LE POLITICHE — (etichetta, tipo, parametro). Dichiarate sopra, nel docstring.
# ===========================================================================================
def phi_of(kind: str, par: float, book: np.ndarray, cash: np.ndarray,
to_tix: np.ndarray) -> np.ndarray:
"""Frazione del versamento che va al pot BIGLIETTI. Vettoriale sul blocco di percorsi."""
if kind == "FIX":
return np.full(len(book), float(par))
if kind == "TIX": # biglietti prima
return (to_tix < par).astype(float)
if kind == "BOOK": # libro prima
return (book >= par).astype(float)
if kind == "PHI_UP": # cresce col capitale
return np.clip(book / par, 0.0, 1.0)
if kind == "PHI_DN": # decresce col capitale
return 1.0 - np.clip(book / par, 0.0, 1.0)
if kind in ("CASH", "RARE", "DELAY"): # phi = 1, la differenza e' altrove
return np.ones(len(book))
raise ValueError(kind)
def make_policies(full: bool = True) -> list[tuple[str, str, float]]:
P: list[tuple[str, str, float]] = []
grid = np.round(np.arange(0.0, 1.001, 0.10), 2) if full else np.array([0.0, 0.25, 0.50, 0.75, 1.0])
for f in grid:
P.append((f"F {f:.2f}", "FIX", float(f)))
if not full:
return P
for xk in (700.0, 2_000.0, 5_000.0, 10_000.0):
P.append((f"TIX ${xk:,.0f}", "TIX", xk))
for xk in (2_000.0, 5_000.0, 10_000.0, 20_000.0):
P.append((f"BOOK ${xk:,.0f}", "BOOK", xk))
P.append(("PHI_UP $10k", "PHI_UP", 10_000.0))
P.append(("PHI_DN $10k", "PHI_DN", 10_000.0))
P.append(("CASH-ATT $1.2k", "CASH", 1_200.0))
P.append(("RARE dd 10%", "RARE", 0.10))
P.append(("RARE dd 20%", "RARE", 0.20))
P.append(("DELAY 95g", "DELAY", 95.0))
P.append(("DELAY 713g", "DELAY", 713.0))
return P
# ===========================================================================================
# IL SIMULATORE COMBINATO — pot BIGLIETTI + pot LIBRO sullo STESSO percorso
# ===========================================================================================
def sim_split(R: np.ndarray, G: np.ndarray, rb: np.ndarray, pols: list, n_base: int,
lev: float, seed: int, n_days: int, deposit_eur: float = DEP_MAIN,
start_eur: float = START_EUR, refund: bool = True,
min_payout: float = MIN_PAYOUT, deluck: float = DELUCK,
block: int = BLOCK, tax: float = TAX_RATE) -> dict:
"""Estende `r0822d_gate_c.ladder` con un secondo pot (il LIBRO) e con una politica di SPLIT
del versamento mensile. Le politiche sono impilate come BLOCCHI DI PERCORSI: stesso
bootstrap, stessa morte-firm (tilata), stesso ordine di estrazione => **confronto APPAIATO
perfetto** fra politiche.
Con `pols=[("x","FIX",1.0)]`, `refund=False`, `deposit=0`, `min_payout=0` dev'essere
BIT-EXACT rispetto a `GC.ladder` (controllo R2). Con `pols=[("x","FIX",0.0)]` il pot libro
dev'essere BIT-EXACT rispetto a `GC.book_road` (controllo R3).
"""
n_pol = len(pols)
P = n_pol * n_base
L = len(R)
rng = np.random.default_rng(seed)
Rd = R.astype(float) - (1.0 - deluck) * float(R.astype(float).mean())
Gd = G.astype(float)
Bd = rb.astype(float) - (1.0 - deluck) * float(rb.astype(float).mean())
n_blocks = int(np.ceil(n_days / block))
starts0 = rng.integers(0, L - block, size=(n_base, n_blocks)) # stesso stream di GC
starts = np.tile(starts0, (n_pol, 1)) # ...e TILATO: appaiato
deaths0 = rng.random((n_days, n_base, len(FIRMS))) # morte-firm, tilata
p_death_day = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** (1 / 365.0)
pid = np.repeat(np.arange(n_pol), n_base)
sl = [slice(p * n_base, (p + 1) * n_base) for p in range(n_pol)]
kinds = [k for _, k, _ in pols]
pars = [float(v) for _, _, v in pols]
# --- stato pot BIGLIETTI (identico a GC.ladder) ---------------------------------------
cash = np.zeros(P)
phase = np.full((P, N_SLOT), -1, np.int8)
eq = np.ones((P, N_SLOT))
firm = np.full((P, N_SLOT), -1, np.int8)
notional = np.zeros((P, N_SLOT))
price_paid = np.zeros((P, N_SLOT))
refunded = np.zeros((P, N_SLOT), bool)
firm_alive = np.ones((P, len(FIRMS)), bool)
used_f = np.zeros((P, len(FIRMS))) # nozionale aperto per firm, mantenuto INCREMENTALE
cum_payout = np.zeros(P)
spent = np.zeros(P)
got_back = np.zeros(P)
n_payouts = np.zeros(P, int)
n_bought = np.zeros(P, int)
to_tix = np.zeros(P) # cumulato VERSATO al pot biglietti (non i payout)
to_book = np.zeros(P)
# --- stato pot LIBRO (identico a GC.book_road) ----------------------------------------
bookc = np.zeros(P)
basis = np.zeros(P)
carry = np.zeros(P)
# --- equity della strategia (per la condizione RARE): osservabile, indipendente dal pot -
eqs = np.ones(P)
peak = np.ones(P)
def deposita(amount: np.ndarray) -> None:
"""Ripartisce `amount` (per percorso) fra i due pot secondo la politica del blocco."""
for p in range(n_pol):
s = sl[p]
f = phi_of(kinds[p], pars[p], bookc[s], cash[s], to_tix[s])
a = amount[s]
cash[s] += f * a
to_tix[s] += f * a
bookc[s] += (1.0 - f) * a
basis[s] += (1.0 - f) * a
to_book[s] += (1.0 - f) * a
deposita(np.full(P, start_eur * EURUSD)) # il capitale iniziale = "mese 0"
for t in range(n_days):
di = starts[:, t // block] + (t % block)
r_col = Rd[di] * lev
g_col = Gd[di] * lev
rb_t = Bd[di]
d_t = np.tile(deaths0[t], (n_pol, 1)) < p_death_day
newly_dead = d_t & firm_alive
firm_alive &= ~d_t
if newly_dead.any():
for fi in range(len(FIRMS)):
kill = newly_dead[:, fi]
if kill.any():
hit = (firm == fi) & kill[:, None]
phase[hit] = -1
notional[hit] = 0.0
used_f[kill, fi] = 0.0
# --- gating dell'acquisto: default aperto, chiuso per RARE/DELAY ------------------
allow = np.ones(P, bool)
for p in range(n_pol):
if kinds[p] == "RARE":
allow[sl[p]] = (eqs[sl[p]] / peak[sl[p]] - 1.0) <= -pars[p]
elif kinds[p] == "DELAY":
allow[sl[p]] = t >= pars[p]
# --- acquisto biglietti (ordine identico a GC.ladder) ----------------------------
for k in range(N_SLOT):
free = (phase[:, k] == -1) & allow
if not free.any():
continue
for fi, F in enumerate(FIRMS):
used = used_f[:, fi] # vista: si aggiorna da sola sotto
for (size, price) in sorted(F["tickets"], key=lambda x: -x[0]):
buy = (free & firm_alive[:, fi] & (cash >= price)
& (used + size <= F["cap"]))
if not buy.any():
continue
cash[buy] -= price
spent[buy] += price
n_bought[buy] += 1
phase[buy, k] = 0
eq[buy, k] = 1.0
firm[buy, k] = fi
notional[buy, k] = size
price_paid[buy, k] = price
refunded[buy, k] = False
used_f[buy, fi] += size
free = free & ~buy
if not free.any():
break
if not free.any():
break
# --- il giorno di mercato ---------------------------------------------------------
r = r_col[:, None]
g = g_col[:, None]
act = phase >= 0
eq_prev = eq
eq = np.where(act, eq * (1.0 + r), eq)
eq_low = np.where(act, eq_prev * (1.0 + r + g), eq)
r_low = r + g
for fi, F in enumerate(FIRMS):
s_ = act & (firm == fi)
if not s_.any():
continue
ev_t, ev_dd, ev_dl = F["ev"]
fu_ml, fu_dl = F["fu"]
in_ev = s_ & (phase == 0)
in_fu = s_ & (phase == 1)
bust = ((in_ev & ((eq_low < 1.0 - ev_dd) | (r_low < -ev_dl)))
| (in_fu & ((eq_low < 1.0 - fu_ml) | (r_low < -fu_dl))))
if bust.any():
used_f[:, fi] -= np.where(bust, notional, 0.0).sum(axis=1)
phase[bust] = -1
notional[bust] = 0.0
passed = in_ev & (eq >= 1.0 + ev_t) & ~bust
phase[passed] = 1
eq[passed] = 1.0
# --- il pot LIBRO + l'equity osservabile della strategia --------------------------
bookc = np.maximum(bookc * (1.0 + rb_t), 0.0)
eqs = eqs * (1.0 + rb_t)
peak = np.maximum(peak, eqs)
# --- fine mese: payout, rimborso, cassa attiva, versamento ------------------------
if (t + 1) % 30 == 0:
fu = phase == 1
pay = fu & (eq > 1.0) & (((eq - 1.0) * notional * SPLIT) >= min_payout)
gain = np.where(pay, (eq - 1.0) * notional, 0.0)
net = gain.sum(axis=1) * SPLIT * (1.0 - tax)
cum_payout += net
cash += net
n_payouts += (pay.sum(axis=1) > 0).astype(int)
eq = np.where(pay, 1.0, eq)
if refund:
back = pay & ~refunded
amt = np.where(back, price_paid, 0.0).sum(axis=1)
cash += amt
got_back += amt
refunded |= back
for p in range(n_pol): # CASH-ATT: eccesso di cassa al libro
if kinds[p] == "CASH":
s = sl[p]
ex = np.maximum(cash[s] - pars[p], 0.0)
cash[s] -= ex
bookc[s] += ex
basis[s] += ex
if deposit_eur:
deposita(np.full(P, deposit_eur * EURUSD))
# --- fine anno: fisco sul pot LIBRO (convenzione GC.book_road) --------------------
if (t + 1) % 365 == 0:
gg = (bookc - basis) + carry
due = np.where(gg > 0, tax * gg, 0.0)
carry = np.where(gg > 0, 0.0, gg)
bookc = bookc - due
basis = bookc.copy()
years = n_days / 365.0
flow = cum_payout / EURUSD / years / 365.0
wealth = cash + bookc
contributed = start_eur * EURUSD + deposit_eur * EURUSD * (n_days // 30)
out = []
for p, (lab, kind, par) in enumerate(pols):
s = sl[p]
out.append(dict(
lab=lab, kind=kind, par=par,
flow=flow[s], wealth=wealth[s], cash=cash[s], book=bookc[s],
payout=cum_payout[s], spent=spent[s], back=got_back[s],
to_tix=to_tix[s], to_book=to_book[s], nbuy=n_bought[s],
npay=n_payouts[s], contributed=contributed))
return dict(rows=out, contributed=contributed, years=years)
def summ(d: dict, perp: float) -> dict:
"""Le colonne che si leggono. `rend` = ricchezza terminale x rendita perpetua (la
definizione di EUR 50/giorno del PIANO); `flow` = payout medi (la definizione di §37)."""
w, f = d["wealth"], d["flow"]
rend = w * perp / 365.0 / EURUSD
return dict(
lab=d["lab"],
p50_flow=float((f >= TARGET).mean()),
p50_rend=float((rend >= TARGET).mean()),
med_w=float(np.median(w)), mean_w=float(w.mean()),
p10_w=float(np.percentile(w, 10)), p90_w=float(np.percentile(w, 90)),
med_flow=float(np.median(f)), med_rend=float(np.median(rend)),
p_loss=float((w < d["contributed"]).mean()),
p_ruin=float((w < CHEAP).mean()),
p_nopay=float((d["payout"] <= 0).mean()),
med_tix=float(np.median(d["to_tix"])), nbuy=float(np.mean(d["nbuy"])))
# ===========================================================================================
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
hr("§62 SPLIT-DEPOSITO — quanta parte del versamento mensile va ai BIGLIETTI prop?")
print(" §37 ha confrontato due CASI LIMITE (tutto libro / tutto biglietti). Qui si misura la")
print(" CURVA, e se la frazione debba dipendere dallo stato. Attese registrate nel docstring:")
print(" A1 politica ottima FRONT-LOADED e saturante (TIX batte BOOK, phi decrescente batte")
print(" crescente), perche' il libro e' neutrale alla scala e il biglietto ha rendimenti")
print(" DECRESCENTI a gradini (prezzo fisso, nozionale fisso, cap, indivisibilita');")
print(" A2 gli ottimi delle tre colonne NON coincidono; A3 a 60 mesi l'ottimo phi SCENDE;")
print(" A4 il trabocchetto §44 si eredita col segno negativo; A5 la frazione conta molto")
print(" su P(>=50/g) solo perche' il libro vale ZERO su quella colonna a 36-60 mesi.")
# ================================================================= dati
hr(f"§0. IL PANNELLO — le stesse serie di §37 (U13 Binance, W-LONG 2021+) {clock()}")
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13, h13, l13 = (Ob.reindex(p13.index)[X.U13], Hb.reindex(p13.index)[X.U13],
Lb.reindex(p13.index)[X.U13])
phases = sorted({ph for _, ph in CELLS})
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in phases}
xs0 = XSP[0]
base = pc.crypto_daily_tuples(1.0, 0.0).index.intersection(xs0.index)
first13 = xs0.index[np.flatnonzero(xs0["R"].values != 0.0)[0]]
I = base[base >= max(first13, pd.Timestamp("2021-01-01", tz="UTC"))]
print(f" W-LONG: {len(I)} giorni {I[0].date()} -> {I[-1].date()} | de-luck x{DELUCK}")
rate = GC.funding_rate()
if rate is None:
print(" !! cache funding assente: il filone NON gira senza funding (lente L3). STOP.")
return
xs_ann = GC.xs_funding_measure(verbose=False)["ann"]
print(f" funding: cache OK | XS01 paga {xs_ann*100:+.2f}%/anno (riproduzione di §37)")
rb_full = FD.book_75_25(rate, alloc=654.0)
if rb_full.index.tz is None:
rb_full.index = rb_full.index.tz_localize("UTC")
rb = np.nan_to_num(rb_full.reindex(I).values.astype(float))
print(f" pot LIBRO (75/25 a $654, funding dentro): drift {rb.mean()*365*100:.2f}%/anno, "
f"vol {rb.std()*np.sqrt(365)*100:.1f}%, Sharpe {rb.mean()*365/(rb.std()*np.sqrt(365)):.2f}")
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25}
ev_b = PA.boot_idx(len(I), 365, PA.N_PATHS, seed=8220)
fu_b = PA.boot_idx(len(I), 365, PA.N_PATHS, seed=8221)
RG: dict = {}
for off, ph in CELLS:
f = GC.fvec_for(W_A, I, rate, off, xs_ann)
R0, G0 = AP.rg_off(W_A, I, XSP[ph], off)
Rx = R0 - f
j, lv, _ = GC.best_lev(Rx, G0, ev_b, fu_b)
RG[(off, ph)] = (Rx, G0, lv, j)
print(f"\n {len(CELLS)} celle d'ancora (sotto-campione sistematico a priori sui 23 offset x "
f"3 fasi di §32):")
for (off, ph), (Rx, _, lv, j) in RG.items():
print(f" offset {off:>3} min, fase {ph} -> J {j:.3f} @ leva {lv:.3f}x "
f"drift {Rx.mean()*365*100:+6.2f}%/anno")
# ================================================================= §1 repliche
hr(f"§1. LE REPLICHE — prima di produrre un numero nuovo {clock()}")
ok: dict[str, bool] = {}
# R1: muro L3 (la rendita perpetua che converte capitale in EUR/giorno)
import r0822d_piano_vero as PV # noqa: E402
B3 = PV.costruisci_serie()
r_l3 = PV.deluck(B3["fund"].values.astype(float))
netto = TARGET * 365 * EURUSD
perp, muro = PN.muro(r_l3, netto, TAX_RATE, TX.PATRIMONIALE)
ok["R1"] = abs(muro - PUB["muro"]) < 3_000.0
print(f" R1 muro L3 CONGIUNTA: perpetua {perp:.2%} · ${muro:,.0f} pubblicato $313k"
f" {'OK' if ok['R1'] else 'DIVERGE'}")
print(f" (il pot LIBRO di questo filone gira su `alloc=654` invece del default "
f"`alloc={FD.ALLOC:.0f}` di L3: drift "
f"{rb.mean()*365*100:.2f}% contro {PV.deluck(B3['fund'].values.astype(float)).mean()*365*100:.2f}% "
f"-> differenza dichiarata, non nascosta)")
# R2: il mio simulatore == GC.ladder (che e' bit-exact vs pc.simulate)
cell0 = CELLS[0]
R0, G0, LV0, _ = RG[cell0]
ref = GC.ladder(R0, G0, N_BASE, LV0, SEED, refund=False, min_payout=0.0, deposit_eur=0.0)
mine = sim_split(R0, G0, rb, [("ctl", "FIX", 1.0)], N_BASE, LV0, SEED, 1080,
deposit_eur=0.0, refund=False, min_payout=0.0)
a = np.sort(ref["eur_day"])
b = np.sort(mine["rows"][0]["flow"])
d2 = float(np.abs(ref["eur_day"] - mine["rows"][0]["flow"]).max())
ok["R2"] = d2 < 1e-12
print(f" R2 sim_split(phi=1) vs GC.ladder, percorso per percorso: max|diff| = {d2:.3e}"
f" {'BIT-EXACT' if ok['R2'] else 'DIVERGE'} (ordinati {np.abs(a-b).max():.3e})")
# R3: il pot libro == GC.book_road, sugli stessi sorteggi
n_blocks = int(np.ceil(1080 / BLOCK))
rng_c = np.random.default_rng(SEED)
st_c = rng_c.integers(0, len(I) - BLOCK, size=(N_BASE, n_blocks))
BOOT_C = (st_c[:, :, None] + np.arange(BLOCK)[None, None, :]).reshape(N_BASE, -1)[:, :1080]
brd = GC.book_road(rb, BOOT_C, START_EUR, DEP_MAIN)
mineb = sim_split(R0, G0, rb, [("ctl", "FIX", 0.0)], N_BASE, LV0, SEED, 1080,
deposit_eur=DEP_MAIN)
d3 = float(np.abs(np.sort(brd["cash"]) - np.sort(mineb["rows"][0]["wealth"])).max())
ok["R3"] = d3 < 1e-8
print(f" R3 sim_split(phi=0) vs GC.book_road (stessi blocchi): max|diff| = ${d3:.3e}"
f" {'IDENTICO' if ok['R3'] else 'DIVERGE'}")
# R4: gli estremi di §37, alla banda d'ancora (la cella mediana di §37 non e' pubblicata)
print(f"\n R4 GLI ESTREMI DI §37 — le due strade intere, sulle {len(CELLS)} celle d'ancora")
print(f" (§37 gira sulla SUA cella mediana, non pubblicata: qui si verifica che i suoi")
print(f" numeri cadano DENTRO la banda, non che coincidano)")
print(f"\n {'versamento':>11} {'strada':>10} {'mediana (banda celle)':>34} "
f"{'media':>12} {'P(>=50/g)':>22}")
r37, _r4 = {}, []
for dep in (0.0, DEP_MAIN):
for lab, phi in (("BIGLIETTO", 1.0), ("LIBRO", 0.0)):
med, mea, p50 = [], [], []
for c in CELLS:
Rc, Gc, lvc, _ = RG[c]
s = sim_split(Rc, Gc, rb, [(lab, "FIX", phi)], N_BASE, lvc, SEED, 1080,
deposit_eur=dep)
q = summ(s["rows"][0], perp)
med.append(q["med_w"]); mea.append(q["mean_w"]); p50.append(q["p50_flow"])
r37[(dep, lab)] = (np.median(med), np.median(mea), np.median(p50))
pubm = PUB["med_tix_37"] if (dep and lab == "BIGLIETTO") else (
PUB["med_book_37"] if dep else (PUB["med_tix_0"] if lab == "BIGLIETTO"
else PUB["med_book_0"]))
inside = min(med) <= pubm <= max(med)
_r4.append(bool(inside))
print(f" EUR{dep:>7,.0f}/m {lab:>10} ${np.median(med):>10,.0f} "
f"[${min(med):>8,.0f},${max(med):>9,.0f}] ${np.median(mea):>11,.0f} "
f"{np.median(p50):>9.1%} [{min(p50):>5.1%},{max(p50):>5.1%}] "
f"{'PUB dentro' if inside else 'PUB fuori'}")
ok["R4"] = all(_r4)
print(f" §37 pubblica: BIGLIETTO EUR500 ${PUB['med_tix_37']:,.0f} / P {PUB['p50g_37']:.1%} · "
f"LIBRO ${PUB['med_book_37']:,.0f} · BIGLIETTO EUR0 ${PUB['med_tix_0']:,.0f} · "
f"LIBRO EUR0 ${PUB['med_book_0']:,.0f}")
print(f"\n REPLICHE: {sum(ok.values())}/{len(ok)} "
f"{'tutte OK' if all(ok.values()) else 'ATTENZIONE: ' + str([k for k,v in ok.items() if not v])}")
if not (ok["R2"] and ok["R3"]):
print(" Senza R2/R3 i numeri sotto sarebbero di un'altra macchina. STOP.")
return
# ================================================================= §2 la curva
hr(f"§2. LA CURVA DELLA FRAZIONE — 36 mesi, EUR {DEP_MAIN:.0f}/mese, {N_BASE} percorsi {clock()}")
print(" Tutte le politiche girano nello STESSO Monte Carlo (blocchi impilati): stesso")
print(" bootstrap, stessa morte-firm => ogni delta e' APPAIATO percorso per percorso.")
POLS = make_policies(full=True)
RES: dict = {}
for c in CELLS:
Rc, Gc, lvc, _ = RG[c]
s = sim_split(Rc, Gc, rb, POLS, N_BASE, lvc, SEED, 1080, deposit_eur=DEP_MAIN)
RES[c] = {r["lab"]: r for r in s["rows"]}
print(f" cella {c} fatta {clock()}")
CONTRIB = s["contributed"]
fix_labs = [p[0] for p in POLS if p[1] == "FIX"]
print(f"\n Versato in 36 mesi: ${CONTRIB:,.0f} (capitale iniziale ${START_EUR*EURUSD:,.0f} "
f"+ 36 x EUR{DEP_MAIN:.0f})")
print(f"\n {'phi':>6} {'P(>=50/g) FLUSSO':>26} {'P(>=50/g) RENDITA':>18} "
f"{'ricch. MEDIANA':>26} {'media':>11} {'P(<versato)':>12} {'P(<$249)':>10} "
f"{'EUR/g med':>10}")
print(f" {'':>6} {'(la metrica di §37)':>26} {'(la metrica del PIANO)':>18}")
curva = []
for lab in fix_labs:
q = {c: summ(RES[c][lab], perp) for c in CELLS}
g = lambda k: float(np.median([q[c][k] for c in CELLS])) # noqa: E731
lohi = lambda k: (min(q[c][k] for c in CELLS), max(q[c][k] for c in CELLS)) # noqa: E731
p50, pl_, ph_ = g("p50_flow"), *lohi("p50_flow")
mw, mlo, mhi = g("med_w"), *lohi("med_w")
rendm = g("med_flow") + g("med_rend")
curva.append(dict(lab=lab, phi=float(lab.split()[1]), p50=p50, med=mw,
mean=g("mean_w"), ploss=g("p_loss"), pruin=g("p_ruin"),
prend=g("p50_rend"), eurg=rendm))
print(f" {lab.split()[1]:>6} {p50:>13.1%} [{pl_:>5.1%},{ph_:>5.1%}] "
f"{g('p50_rend'):>17.1%} ${mw:>10,.0f} [${mlo:>7,.0f},${mhi:>8,.0f}] "
f"${g('mean_w'):>10,.0f} {g('p_loss'):>12.1%} {g('p_ruin'):>10.1%} {rendm:>10.2f}")
CV = pd.DataFrame(curva)
a_p50 = CV.loc[CV["p50"].idxmax()]
a_med = CV.loc[CV["med"].idxmax()]
a_mean = CV.loc[CV["mean"].idxmax()]
a_saf = CV.loc[CV["ploss"].idxmin()]
print(f"\n ARGMAX PER COLONNA — la risposta al mandato punto 2:")
print(f" P(>=50/g) flusso : phi = {a_p50['phi']:.2f} ({a_p50['p50']:.1%})")
print(f" ricchezza MEDIANA: phi = {a_med['phi']:.2f} (${a_med['med']:,.0f})")
print(f" ricchezza MEDIA : phi = {a_mean['phi']:.2f} (${a_mean['mean']:,.0f})")
print(f" P(<versato) min : phi = {a_saf['phi']:.2f} ({a_saf['ploss']:.1%})")
print(f" P(>=50/g) RENDITA: max {CV['prend'].max():.1%} su TUTTE le celle "
f"-> {'nessuno arriva' if CV['prend'].max() < 0.005 else 'qualcuno arriva'}")
print(f" >>> ATTESA A2 ('gli ottimi non coincidono'): "
f"{'CONFERMATA' if len({round(a_p50['phi'],2), round(a_med['phi'],2)}) > 1 else 'SMENTITA'}")
# ---- risoluzione MC, misurata (mandato: riportarla accanto a ogni delta) --------------
print(f"\n §2.1 RISOLUZIONE MONTE CARLO — misurata su 3 semi (non assunta) {clock()}")
probe = [("F 0.00", "FIX", 0.0), ("F 0.50", "FIX", 0.5), ("F 1.00", "FIX", 1.0)]
Rc, Gc, lvc, _ = RG[CELLS[0]]
seeds = (SEED, SEED + 101, SEED + 202)
tabS = {}
for sd in seeds:
s = sim_split(Rc, Gc, rb, probe, N_BASE, lvc, sd, 1080, deposit_eur=DEP_MAIN)
tabS[sd] = {r["lab"]: summ(r, perp) for r in s["rows"]}
sp_lvl = max(max(tabS[sd][l]["p50_flow"] for sd in seeds)
- min(tabS[sd][l]["p50_flow"] for sd in seeds) for l in ("F 0.50", "F 1.00"))
d_pair = [tabS[sd]["F 1.00"]["p50_flow"] - tabS[sd]["F 0.50"]["p50_flow"] for sd in seeds]
sp_pair = max(d_pair) - min(d_pair)
print(f" {'seme':>10} {'P50 phi=0.5':>13} {'P50 phi=1.0':>13} {'delta appaiato':>16} "
f"{'med. ricch. phi=0':>18}")
for sd in seeds:
print(f" {sd:>10} {tabS[sd]['F 0.50']['p50_flow']:>13.2%} "
f"{tabS[sd]['F 1.00']['p50_flow']:>13.2%} "
f"{tabS[sd]['F 1.00']['p50_flow']-tabS[sd]['F 0.50']['p50_flow']:>+16.2%} "
f"${tabS[sd]['F 0.00']['med_w']:>17,.0f}")
print(f" ampiezza fra semi: LIVELLI {sp_lvl*100:.2f} pp · DIFFERENZE APPAIATE "
f"{sp_pair*100:.2f} pp -> ogni delta sotto ~{max(sp_pair,0.0)*100:.1f} pp e' RUMORE")
# ================================================================= §3 politiche di stato
hr(f"§3. LE POLITICHE DIPENDENTI DALLO STATO — famiglia dichiarata prima {clock()}")
print(f" {'politica':>16} {'P(>=50/g)':>22} {'ricch. mediana':>15} {'media':>11} "
f"{'P(<versato)':>12} {'P(<$249)':>10} {'$ ai biglietti':>15} {'biglietti':>10}")
fam = [p[0] for p in POLS if p[1] in ("TIX", "BOOK", "PHI_UP", "PHI_DN", "CASH")]
ref_lab = "F 1.00"
qref = {c: summ(RES[c][ref_lab], perp) for c in CELLS}
for lab in [ref_lab, "F 0.00"] + fam:
q = {c: summ(RES[c][lab], perp) for c in CELLS}
g = lambda k: float(np.median([q[c][k] for c in CELLS])) # noqa: E731
dp = float(np.median([q[c]["p50_flow"] - qref[c]["p50_flow"] for c in CELLS]))
npos = sum(1 for c in CELLS if q[c]["p50_flow"] - qref[c]["p50_flow"] > 0)
mark = "" if lab == ref_lab else f" d {dp*100:+5.2f}pp ({npos}/{len(CELLS)})"
print(f" {lab:>16} {g('p50_flow'):>9.1%}{mark:>13} ${g('med_w'):>14,.0f} "
f"${g('mean_w'):>10,.0f} {g('p_loss'):>12.1%} {g('p_ruin'):>10.1%} "
f"${g('med_tix'):>14,.0f} {g('nbuy'):>10.1f}")
tixb = float(np.median([summ(RES[c]["TIX $5,000"], perp)["p50_flow"] for c in CELLS]))
bookb = float(np.median([summ(RES[c]["BOOK $5,000"], perp)["p50_flow"] for c in CELLS]))
up = float(np.median([summ(RES[c]["PHI_UP $10k"], perp)["p50_flow"] for c in CELLS]))
dn = float(np.median([summ(RES[c]["PHI_DN $10k"], perp)["p50_flow"] for c in CELLS]))
print(f"\n >>> ATTESA A1 (front-loaded): TIX $5k {tixb:.1%} contro BOOK $5k {bookb:.1%} "
f"-> {'CONFERMATA' if tixb > bookb else 'SMENTITA'}; "
f"phi DECRESCENTE {dn:.1%} contro CRESCENTE {up:.1%} "
f"-> {'CONFERMATA' if dn > up else 'SMENTITA'}")
cashp = {c: summ(RES[c]["CASH-ATT $1.2k"], perp) for c in CELLS}
d_cash = float(np.median([cashp[c]["med_w"] - qref[c]["med_w"] for c in CELLS]))
d_cash50 = float(np.median([cashp[c]["p50_flow"] - qref[c]["p50_flow"] for c in CELLS]))
print(f" CASH-ATT (l'unica che puo' dominare phi=1): ricchezza mediana "
f"${d_cash:+,.0f}, P(>=50/g) {d_cash50*100:+.2f}pp "
f"({sum(1 for c in CELLS if cashp[c]['med_w'] > qref[c]['med_w'])}/{len(CELLS)} celle)")
# ================================================================= §4 il trabocchetto §44
hr(f"§4. IL TRABOCCHETTO DI §44 — la CONDIZIONE davanti al bonifico {clock()}")
print(" §44: «comprare meglio e comprare tardi sono la stessa mossa». Qui la condizione non")
print(" ritarda un BONIFICO ma un ACQUISTO di biglietto. Si separa il costo della CONDIZIONE")
print(" da quello del RITARDO con un GEMELLO DETERMINISTICO di pari attesa media.")
print(f"\n {'politica':>14} {'attesa media (g)':>18} {'P(>=50/g)':>11} {'d appaiato':>12} "
f"{'ricch. mediana':>15} {'d':>11}")
# attesa media della condizione, misurata sulla stessa serie
for lab in ("F 1.00", "RARE dd 10%", "RARE dd 20%", "DELAY 95g", "DELAY 713g"):
q = {c: summ(RES[c][lab], perp) for c in CELLS}
g = lambda k: float(np.median([q[c][k] for c in CELLS])) # noqa: E731
dp = float(np.median([q[c]["p50_flow"] - qref[c]["p50_flow"] for c in CELLS]))
dm = float(np.median([q[c]["med_w"] - qref[c]["med_w"] for c in CELLS]))
wait = {"F 1.00": 0.0, "DELAY 95g": 95.0, "DELAY 713g": 713.0}.get(lab, float("nan"))
if np.isnan(wait):
d_ = float(lab.split()[-1].rstrip("%")) / 100.0
eqp = np.cumprod(1.0 + PV.deluck(rb))
dd = eqp / np.maximum.accumulate(eqp) - 1.0
hit = np.flatnonzero(dd <= -d_)
wait = float(hit[0]) if len(hit) else float("inf")
print(f" {lab:>14} {wait:>18.0f} {g('p50_flow'):>11.1%} {dp*100:>+11.2f}pp "
f"${g('med_w'):>14,.0f} ${dm:>+10,.0f}")
print(" (l'attesa di RARE e' il primo giorno in cui la condizione si verifica sulla serie")
print(" storica de-luckata: nel bootstrap e' una variabile aleatoria, qui e' un'ancora)")
# ================================================================= §5 la scala
hr(f"§5. I RENDIMENTI DI SCALA — la previsione A1, misurata direttamente {clock()}")
print(" Il canale BIGLIETTO assorbe soldi solo a gradini (prezzo fisso, 6 slot, cap firm).")
print(" Quanto ne assorbe davvero, e quando satura?")
Rc, Gc, lvc, _ = RG[CELLS[0]]
print(f"\n {'versamento':>12} {'$ versati ai bigl.':>19} {'$ SPESI in biglietti':>21} "
f"{'assorbimento':>13} {'cassa finale med':>17} {'biglietti comprati':>19}")
for dep in (0.0, 250.0, 500.0, 1_000.0, 2_000.0):
s = sim_split(Rc, Gc, rb, [("F 1.00", "FIX", 1.0)], N_BASE, lvc, SEED, 1080,
deposit_eur=dep)
r = s["rows"][0]
got = float(np.mean(r["to_tix"]))
sp = float(np.mean(r["spent"]))
print(f" EUR{dep:>8,.0f}/m ${got:>18,.0f} ${sp:>20,.0f} {sp/max(got,1e-9):>12.1%} "
f"${np.median(r['cash']):>16,.0f} {np.mean(r['nbuy']):>19.1f}")
print(" ('assorbimento' = quanta parte dei soldi mandati al pot biglietti finisce davvero")
print(" in un biglietto; il resto resta CASSA FERMA — e' il costo dell'indivisibilita'.)")
print(f"\n §5.1 LA FRAZIONE CONTRO L'IMPORTO — la domanda che decide {clock()}")
print(f" {'versamento':>12} {'phi=0 P(>=50/g)':>16} {'phi=1 P(>=50/g)':>16} "
f"{'ampiezza phi':>13} {'phi=0 ricch.med':>16} {'phi=1 ricch.med':>16}")
prev = None
imp_rows = []
for dep in (0.0, 250.0, 500.0, 1_000.0):
s = sim_split(Rc, Gc, rb, [("F 0.00", "FIX", 0.0), ("F 1.00", "FIX", 1.0)],
N_BASE, lvc, SEED, 1080, deposit_eur=dep)
q0, q1 = summ(s["rows"][0], perp), summ(s["rows"][1], perp)
imp_rows.append((dep, q0, q1))
print(f" EUR{dep:>8,.0f}/m {q0['p50_flow']:>16.1%} {q1['p50_flow']:>16.1%} "
f"{(q1['p50_flow']-q0['p50_flow'])*100:>+12.1f}pp ${q0['med_w']:>15,.0f} "
f"${q1['med_w']:>15,.0f}")
prev = (q0, q1)
d_phi = imp_rows[2][2]["p50_flow"] - imp_rows[2][1]["p50_flow"]
d_dep_tix = imp_rows[2][2]["p50_flow"] - imp_rows[1][2]["p50_flow"]
d_dep_book = imp_rows[2][1]["med_w"] - imp_rows[1][1]["med_w"]
print(f"\n A EUR500/m: passare phi 0->1 vale {d_phi*100:+.1f}pp di P(>=50/g); passare il")
print(f" VERSAMENTO da EUR250 a EUR500 (a phi=1) vale {d_dep_tix*100:+.1f}pp.")
print(f" Sulla RICCHEZZA (phi=0): EUR250 -> EUR500 vale ${d_dep_book:+,.0f} di mediana.")
# ================================================================= §6 60 mesi
hr(f"§6. ORIZZONTE 60 MESI — l'unica ragione strutturale per cui l'ottimo dovrebbe muoversi {clock()}")
p5 = 1.0 - (1.0 - pl.FIRM_DEATH_PER_YEAR) ** 5
print(f" A 5 anni ogni firm muore con p = {p5:.0%}; entrambe con p = {p5**2:.0%}. Il canale")
print(f" biglietto ha VITA FINITA, il libro no. Attesa A3: l'ottimo phi SCENDE.")
POLS60 = ([("F 0.00", "FIX", 0.0), ("F 0.20", "FIX", 0.20), ("F 0.50", "FIX", 0.50),
("F 0.80", "FIX", 0.80), ("F 1.00", "FIX", 1.0),
("TIX $5,000", "TIX", 5_000.0), ("BOOK $5,000", "BOOK", 5_000.0),
("PHI_DN $10k", "PHI_DN", 10_000.0), ("CASH-ATT $1.2k", "CASH", 1_200.0)])
R60: dict = {}
for c in CELLS[:3]:
Rc2, Gc2, lvc2, _ = RG[c]
s = sim_split(Rc2, Gc2, rb, POLS60, N_BASE, lvc2, SEED, 1800, deposit_eur=DEP_MAIN)
R60[c] = {r["lab"]: r for r in s["rows"]}
print(f"\n {'politica':>16} {'P(>=50/g) 36m':>14} {'P(>=50/g) 60m':>14} {'ricch.med 60m':>15} "
f"{'media 60m':>12} {'P(<versato)':>12} {'P(>=50/g) RENDITA 60m':>22}")
for lab, _, _ in POLS60:
q36 = float(np.median([summ(RES[c][lab], perp)["p50_flow"] for c in CELLS[:3]]))
q = {c: summ(R60[c][lab], perp) for c in CELLS[:3]}
g = lambda k: float(np.median([q[c][k] for c in CELLS[:3]])) # noqa: E731
print(f" {lab:>16} {q36:>14.1%} {g('p50_flow'):>14.1%} ${g('med_w'):>14,.0f} "
f"${g('mean_w'):>11,.0f} {g('p_loss'):>12.1%} {g('p50_rend'):>22.1%}")
c60 = pd.DataFrame([dict(phi=float(l.split()[1]),
p50=float(np.median([summ(R60[c][l], perp)["p50_flow"]
for c in CELLS[:3]])),
med=float(np.median([summ(R60[c][l], perp)["med_w"]
for c in CELLS[:3]])))
for l, k, _ in POLS60 if k == "FIX"])
print(f"\n argmax P(>=50/g): 36 mesi phi={a_p50['phi']:.2f} · 60 mesi "
f"phi={c60.loc[c60['p50'].idxmax(),'phi']:.2f}")
print(f" argmax ricchezza mediana: 36 mesi phi={a_med['phi']:.2f} · 60 mesi "
f"phi={c60.loc[c60['med'].idxmax(),'phi']:.2f}")
print(f" >>> ATTESA A3 ('a 60 mesi l'ottimo phi scende'): "
f"{'CONFERMATA' if c60.loc[c60['p50'].idxmax(),'phi'] < a_p50['phi'] else 'SMENTITA'}")
# ================================================================= §7 verdetto
hr(f"§7. VERDETTO {clock()}")
sp = max(sp_pair, 0.0) * 100
rng_p50 = (CV["p50"].max() - CV["p50"].min()) * 100
rng_med = CV["med"].max() - CV["med"].min()
print(f" Ampiezza della CURVA fra phi=0 e phi=1 (36 mesi, EUR{DEP_MAIN:.0f}/m):")
print(f" P(>=50/g) flusso : {rng_p50:.1f} pp (risoluzione MC {sp:.1f} pp) -> "
f"{'SEGNALE' if rng_p50 > 3*sp else 'DENTRO IL RUMORE'}")
print(f" ricchezza mediana: ${rng_med:,.0f}")
print(f" P(>=50/g) rendita: {(CV['prend'].max()-CV['prend'].min())*100:.1f} pp")
print(f" P(<versato) : {(CV['ploss'].max()-CV['ploss'].min())*100:.1f} pp")
print(f"\n In EUR/giorno (mediana, flusso + rendita terminale — le due NON si sommano")
print(f" senza doppio conteggio, si leggono separate):")
for lab in ("F 0.00", "F 0.50", "F 1.00"):
row = CV[CV["lab"] == lab].iloc[0]
print(f" {lab}: EUR {row['eurg']:.2f}/giorno mediani a 36 mesi")
print(f"\n Il target e' EUR {TARGET:.0f}/giorno.")
print(f" TRIALS: nessuna cella SELEZIONATA (la famiglia e' dichiarata nel docstring e tutte")
print(f" le celle sono riportate) -> nessun deflated-Sharpe da calcolare; e la grandezza")
print(f" misurata e' una P, non uno Sharpe.")
print(f"\n {clock()} fine.")
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff