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:
@@ -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** |
|
| 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** |
|
| 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** |
|
| 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_ |
|
| 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? | _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? | **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? | _in corso_ |
|
| 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? | _in corso_ |
|
| 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? | _in corso_ |
|
| 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? | _in corso_ |
|
| 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? | _in corso_ |
|
| 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? | _in corso_ |
|
| 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 | _in corso_ |
|
| 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? | _in corso_ |
|
| 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? | _in corso_ |
|
| 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? | _in corso_ |
|
| 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%]) |
|
| — | **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 `<=` |
|
| 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 |
|
| 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ª.
|
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
|
❌ **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.
|
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**.
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
@@ -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
Reference in New Issue
Block a user