From ee5c6ed53945b5ce4fa1d0f96c65c956ec3ddd17 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Tue, 25 Aug 2026 18:13:15 +0000 Subject: [PATCH] ricerca: il piano a 10 anni dal conto vero, l'ETF-quando-flat SCARTATO, slippage rifatto Giornata partita da "stato trades" e finita sul disegno del sistema. Versamento di 1.400 USDC atterrato alle 11:03:35Z (equity $667,88 -> $2.066,88); il giro delle 11:47 ha ribilanciato correttamente e ha ripiazzato i disaster-SL alla taglia nuova -- prima volta che il ramo riparato stamattina gira sul serio. (1) PIANO A 10 ANNI DAL CONTO VERO -- r0825_piano_10a_500.py (nuovo) Le tabelle pubblicate partono da $600/$635: rifatte su $2.067, lente L3 CONGIUNTA. Replica superata: chiede EUR 1.718/m dove la tabella da $635 chiedeva EUR 1.733. EUR 500/mese per 10 anni -> mediana $114.934, rendita 18,35 EUR/g, P(>=50 EUR/g) = 0,0% su 3.000 traiettorie. Il bersaglio con EUR 500/m arriva al 17o anno. L'EQUIVALENZA CHE ORDINA IL PIANO: EUR 100/mese in piu' == +4,07%/anno di drift, cioe' +27% su TUTTO il drift del libro. A 10 anni i bonifici fanno il 59%. Tutta la leva autorizzabile vale quanto EUR 100-150/mese: k=1,25 (+15,6%) vale MENO di EUR 100/mese in piu' (+19,1%), e si porta dietro il peggior giorno al 21,48%. E il libro a k=1 rende MENO dell'S&P (15,19% vs 17,40%): il vantaggio sta nello Sharpe (1,35 vs 0,89) e senza leva NON si converte in rendimento. Senza leva il libro non si giustifica come veicolo di ACCUMULO -- si giustifica come veicolo di RENDITA, dove serve 2,5x meno capitale ($254k contro $646k) perche' la perpetua vive sul DD. (2) "ETF QUANDO IL LIBRO E' FLAT" -- SCARTATO, r0825_capitale_fermo.py (nuovo) Il libro e' flat il 28,0% dei giorni (2,7% nel 2021, 77,0% nel 2022, 75,6% nel 2026); live, esposto 14 giorni su 64, leva lorda mediana 0,00x e max 0,52x su un tetto di 1,0x -- il cap non ha mai morso, il vincolo e' il segnale. A ISO-RISCHIO il dinamico PERDE: Sharpe 1,01 contro 1,40 del 50/50 e 1,35 del libro. E il null a maschera casuale (400 estrazioni, stessa quota, blocchi 20g) lo mette al 30o percentile: fa PEGGIO di commutare a caso. A iso-nozionale sembrava vincere (drift 17,09%, il piu' alto) perche' aveva la vol piu' alta: e' la trappola di M6, il de-levering e' il PRIMO test. E IL MECCANISMO CHE SEMBRAVA OVVIO NON ESISTE. Aggregato: SPY 4,91% nei giorni flat contro 21,48% negli altri (-16,57%), e la storia si scrive da sola. FALSA: scomposta per anno il segno ALTERNA (3 su, 4 giu') e il 2022 -- l'anno che doveva reggerla -- ha il segno OPPOSTO. Artefatto di composizione: i giorni flat stanno negli ANNI brutti per l'azionario, non nei GIORNI brutti. M9 ha fatto il suo lavoro su di me. (3) SLIPPAGE RIFATTO ALLA TAGLIA VERA -- r0822_slip_audit.py riparato 26 fill (erano 18), $6-$319. I due piu' grandi mai eseguiti sono di oggi e hanno preso 1,01% e 0,54% della loro barra 5m, con il print a meta' del range (q=0,50). Il caso peggiore resta un fill da $74 del 18/07 al 21,9%: un sabato, nastro inesistente. L'attrito non e' funzione della TAGLIA ma di taglia/volume-della-barra -- l'estrapolazione lineare che prevedeva ~71% a questo capitale e' refutata dalla misura diretta. NB non e' "assente", e' "non ancora testato": manca un fill grande in una barra sottile, e il weekend e' dove TP01 fa il 38% del proprio gross. DIFETTO P1 RIPARATO: l'equity era CABLATA a 636.0 con un commento che diceva di leggerla dal watermark. Il giorno del versamento avrebbe stampato $636 sbagliando di 3,3x proprio la sezione che esiste per dire QUANDO la misura scade. Riparata leggendo il watermark -- e poi riparata di nuovo, perche' i fill del campione sono stati eseguiti a conti diversi ($597-$2.067) e una base sola e' sbagliata comunque la si scelga. Ora la normalizzazione e' PER FILL, e a $600 riproduce il 22,0% originale. DUE ERRORI MIEI, CATTURATI PRIMA DI PUBBLICARLI - maschera VUOTA vestita da risultato: la prima stesura di r0825_capitale_fermo prendeva i giorni flat dalla serie DE-LUCKATA, ma `deluck` sottrae una costante e lo zero esatto sparisce -> maschera vuota, dinamico identico al book, e lo script ha stampato tabelle piene e un p-value. Lo ha rivelato solo la riga "0 = 0.0%". REGOLA: stampare la CARDINALITA' di una maschera prima di usarla. - il meccanismo falso della (2), demolito dalla scomposizione per anno. Suite: 730 passati, 1 fallito -- quello gia' noto di 5.9 (deriva dati, non codice). Watermark del libro live sopravvissuto intatto alla suite (fixture autouse di stamattina). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1 --- CLAUDE.md | 16 ++ ...manutenzione-deribit-e-resilienza-venue.md | 75 +++++++ docs/memory/20-ondate-e-scartati.md | 53 +++++ docs/memory/30-piano-capitale-fisco.md | 69 ++++++ scripts/research/r0822_slip_audit.py | 78 ++++++- scripts/research/r0825_capitale_fermo.py | 210 ++++++++++++++++++ scripts/research/r0825_piano_10a_500.py | 153 +++++++++++++ 7 files changed, 647 insertions(+), 7 deletions(-) create mode 100644 scripts/research/r0825_capitale_fermo.py create mode 100644 scripts/research/r0825_piano_10a_500.py diff --git a/CLAUDE.md b/CLAUDE.md index 85814bb..cd5b486 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,6 +93,22 @@ sono all'ancora fortunata; le stime oneste sono la **mediana della banda d'ancor | P(≥50 €/g), canale funded, 36 mesi | 42% | **2,6%** [1,5 – 4,7], P(zero) 40,3% | | soffitto direzionale BTC/ETH | ~1,3 | **~1,15** col funding dentro | +📌 **Il libro a k=1 rende MENO dell'S&P 500** (15,19% contro 17,40%, stessa finestra): il vantaggio +sta nello **Sharpe** (1,35 vs 0,89), e **senza leva non si converte in rendimento**. A iso-rischio +farebbe +9,0 punti sopra l'indice; a k=1,40 (max difendibile) +3,9 con meno vol. ⇒ **senza leva il +libro non si giustifica come veicolo di ACCUMULO, si giustifica come veicolo di RENDITA** — dove +serve **2,5× meno capitale** ($254k contro $646k) perche' la perpetua vive sul drawdown (misura +2026-08-25, `r0825_piano_10a_500.py`). E **tutta la leva autorizzabile vale quanto €100-150/mese**: +k=1,25 (+15,6% a 10 anni) vale **meno** di €100/mese in piu' (+19,1%). + +📌 **L'equivalenza che ordina il piano:** **€100/mese in piu' == +4,07%/anno di drift** (+27% su +tutto il drift del libro). A 10 anni i bonifici fanno il **59%** del risultato. + +📌 **Il libro e' FLAT il 28,0% dei giorni** sull'intera storia (2,7% nel 2021, 77,0% nel 2022, +75,6% nel 2026); live, esposto **14 giorni su 64**, leva lorda mediana **0,00x** e **max 0,52x** su +un tetto di 1,0x. **Il cap non ha mai morso: il vincolo e' il segnale.** Riempire quel vuoto con +l'ETF e' stato **misurato e SCARTATO** il 25/08 (`20-ondate`). + 🚨 **Tre baseline diverse girano sotto lo stesso nome "libro 75/25"**, e lo spread fra loro (0,12 di Sharpe, 1,7pp di maxDD) e' **piu' grande di quasi tutti gli effetti che il progetto misura**: `hourly` **1,683 / 11,22%** (la lente di **ogni muro e ogni traiettoria**) · `canonical` 1,800 / 9,56% diff --git a/docs/diary/2026-08-25-manutenzione-deribit-e-resilienza-venue.md b/docs/diary/2026-08-25-manutenzione-deribit-e-resilienza-venue.md index 92814f3..96da659 100644 --- a/docs/diary/2026-08-25-manutenzione-deribit-e-resilienza-venue.md +++ b/docs/diary/2026-08-25-manutenzione-deribit-e-resilienza-venue.md @@ -194,3 +194,78 @@ Un **effetto collaterale su file** trasforma un test puro in un attore sul siste test non menzionava il watermark, non lo importava, non lo asseriva: lo scriveva passando per una funzione di produzione tre livelli più in basso. **Il perimetro di un test non è quello che il test dice di toccare: è quello che tocca il codice che chiama.** + +--- + +## Seconda parte della giornata: il versamento, e le tre misure che ne sono uscite + +Alle **11:03:35Z** sono atterrati **1.400 USDC**: equity **$667,88 → $2.066,88**. Il giro delle +11:47 ha ribilanciato correttamente (cap/asset $1.033, BTC +$238, ETH +$319, disaster-SL `placed` +su entrambi alla taglia nuova) — la prima volta che il ramo riparato al mattino gira sul serio. + +Poi l'operatore ha chiesto tre cose in fila, e ognuna ha rotto qualcosa. + +### (1) «€500/mese per 10 anni» + +Rifatto sul conto vero invece che citare la tabella da $635. **Non centra**: capitale mediano +$114.934, rendita 18,35 €/g, **P(≥50 €/g) = 0,0%** su 3.000 traiettorie. Il bersaglio con €500/m +arriva al 17° anno; per averlo a 10 servono €1.718/mese. + +Il numero che ordina tutto: **€100/mese in più valgono +4,07%/anno di drift**, cioè +27% su tutto +il drift del libro. Dettaglio in `30-piano-capitale-fisco.md`. + +### (2) «A cosa serve XSR01, allora?» → e poi: «l'abbiamo fatto per la leva» + +Due osservazioni dell'operatore, entrambe centrate, che hanno spostato la conversazione dal +candidato al **disegno**. + +XSR01 ha già fallito `weights_tilt_null` (peso ottimo ~0). Ma la risposta vera è che **anche un +uplift di strategia riuscito vale meno di un bonifico modesto**, e questo si applica a qualunque +sleeve, non solo a XSR01. + +Poi la misura che ha ribaltato la discussione sulla leva: **il libro non è poco levato, è FERMO**. +Esposizione lorda realizzata in 64 giorni live: mediana **0,00x**, media 0,05x, **max 0,52x** su un +tetto strutturale di 1,0x, esposto in **14 giorni su 64**. Il cap non ha mai morso: il vincolo è il +segnale. *Il basso drawdown di TP01 non viene dall'essere piccolo nel mercato — viene dallo stare +fuori dal mercato.* + +⚠️ Correzione che mi sono fatto da solo: quel 78% è il **regime attuale**, non la media. Sull'intera +storia i giorni flat sono il **28,0%** — 2,7% nel 2021, 77,0% nel 2022, 75,6% nel 2026. + +### (3) «Allora usiamo l'ETF quando il libro è flat» → SCARTATO + +Test in `r0825_capitale_fermo.py`. **Perde** a iso-rischio (Sharpe 1,01 contro 1,40 del 50/50 e +1,35 del libro), e il null a maschera casuale lo mette al **30° percentile**: fa peggio di +commutare a caso. + +E qui la parte che vale più del risultato. Stavo per scrivere un meccanismo pulito — *«il libro va +flat quando anche l'azionario soffre»* — sostenuto da un aggregato netto (SPY 4,91% nei giorni +flat contro 21,48% negli altri, −16,57%). **La scomposizione per anno lo ha demolito**: il segno +alterna, 3 anni su e 4 giù, e il **2022** — l'anno che doveva reggerlo — ha il segno **opposto**. +Artefatto di composizione: i giorni flat stanno negli **anni** brutti per l'azionario, non nei +**giorni** brutti. M9 ha fatto esattamente il suo lavoro, su di me. + +### I due difetti di misurazione trovati addosso + +1. **Maschera vuota vestita da risultato.** La prima versione prendeva i giorni flat dalla serie + **de-luckata**; ma `deluck` sottrae una costante, quindi lo zero esatto sparisce. Maschera + vuota → dinamico identico al book → e lo script ha stampato tabelle piene e un p-value. Lo ha + rivelato solo la riga che stampava `0 = 0.0%`. + **Regola: stampare la CARDINALITÀ di una maschera prima di usarla.** +2. **`r0822_slip_audit.py` aveva l'equity CABLATA a `636.0`** con un commento che diceva di leggerla + dal watermark — P1 in piena regola. Il giorno del versamento avrebbe continuato a stampare $636, + sbagliando di 3,3x proprio la sezione che esiste per dire *quando la misura scade*. Riparato + leggendo il watermark — e poi riparato **di nuovo**, perché la prima riparazione divideva tutti i + fill per l'equity di oggi mentre i fill del campione sono stati eseguiti a conti diversi + ($597–$2.067). Ora la normalizzazione è **per fill**, e a $600 riproduce il 22,0% originale. + +### La misura di slippage, rifatta alla taglia vera + +26 fill (erano 18), taglia $6–$319. I due più grandi mai eseguiti sono di oggi e hanno preso +**1,01%** e **0,54%** della loro barra 5m, con il print **a metà del range** (q=0,50). Il caso +peggiore resta un fill da **$74 del 18/07 al 21,9%** — un sabato, nastro inesistente. + +📌 **L'attrito non è funzione della taglia: è funzione di `taglia / volume della barra`.** Un fill +4,3× più grande in un'ora liquida ha fatto 20× meno partecipazione di uno piccolo in un'ora morta. +⚠️ Ma non è «assente», è **«non ancora testato»**: nel campione non c'è ancora un fill grande in una +barra sottile, e il weekend è dove TP01 fa il 38% del proprio gross. diff --git a/docs/memory/20-ondate-e-scartati.md b/docs/memory/20-ondate-e-scartati.md index 25ac6ec..18845b2 100644 --- a/docs/memory/20-ondate-e-scartati.md +++ b/docs/memory/20-ondate-e-scartati.md @@ -1399,3 +1399,56 @@ chi lo riapre deve battere il motivo, non ripetere l'esperimento. lo Sharpe **MARGINALE vs baseline TP01** (non assoluto) + esigere plateau e jackknife drop-one-month sull'hold-out prima di PASS (hanno ucciso 13/14 falsi positivi). Diario `2026-06-20-alt-strategies-100agent-sweep.md`. + +--- + +## 2026-08-25 — «Mettere l'ETF nei giorni in cui il libro è flat»: SCARTATO + +Script `scripts/research/r0825_capitale_fermo.py` (calcola il proprio verdetto a runtime). +Nasce da un'osservazione dell'operatore: il libro live sta **flat il 28,0% dei giorni** sull'intera +storia (**75,6% nel 2026**, 77,0% nel 2022) → quel capitale fermo è un'occasione? + +### L'esito, a ISO-RISCHIO (vol 11%, M6) + +| linea | drift | Sharpe | maxDD | +|---|---:|---:|---:| +| BOOK da solo | 14,80% | **1,35** | **−12,1%** | +| 50/50 statico | 15,41% | **1,40** | −19,6% | +| **DINAMICO (ETF se flat)** | 11,13% | **1,01** | −14,7% | +| ETF da solo | 9,53% | 0,87 | −20,2% | + +**Il dinamico perde contro entrambi.** E il null a maschera casuale (400 estrazioni, stessa quota +28,0%, stessa struttura a blocchi di 20g) lo mette al **30° percentile**, p 0,698: **fa peggio di +una maschera a caso della stessa frequenza**. + +### I motivi — cioè quello che chi riapre il tema deve battere + +- 🚨 **A ISO-NOZIONALE il dinamico SEMBRAVA vincere**: drift **17,09%**, il più alto di tutti. Ma + aveva anche la vol più alta (16,90%). È esattamente la trappola di **M6**: a iso-nozionale un + veicolo a vol bassa perde per costruzione e uno a vol alta vince per costruzione. *Il de-levering + è il PRIMO test, non l'ultimo.* +- 🚨 **IL MECCANISMO CHE SEMBRAVA OVVIO NON ESISTE.** Aggregato: nei giorni flat l'SPY rende + **4,91%**, negli altri **21,48%** → differenza **−16,57%**, e la storia si scrive da sola («il + libro va flat quando anche l'azionario soffre»). **Falsa.** Scomposta per anno il segno + **ALTERNA — 3 anni su, 4 giù** — e il **2022**, l'anno che doveva reggerla (77% flat, SPY −18,2%), + ha il segno **OPPOSTO** (+39,9%: nei giorni flat l'azionario andava *meglio*). + L'aggregato è un **artefatto di composizione**: i giorni flat si concentrano negli **anni** a + basso rendimento azionario, non nei **giorni** a basso rendimento. È **M9** che fa il suo lavoro. +- **La spiegazione vera è meccanica e non serve una storia:** scambiare un asset a Sharpe 1,35 con + uno a Sharpe 0,87 per il 28% del tempo abbassa lo Sharpe della miscela. Punto. +- 📌 **La flat-ness del libro NON porta informazione sull'azionario, in nessuna direzione.** + +### Due cose utili cadute fuori dal test + +1. **Il 50/50 statico batte il libro sullo Sharpe (1,40 vs 1,35) ma PEGGIORA il drawdown + (−19,6% vs −12,1%).** Non è pasto gratis: siccome la rendita perpetua **vive sul drawdown**, + per l'obiettivo €50/giorno **il libro da solo resta preferibile** nonostante lo Sharpe più basso + — coerente col capitale-rendita $254k contro $646k. Il 50/50 conviene per *capitale a 10 anni*, + non per *rendita*. +2. ⚠️ **Difetto del misuratore, catturato da una riga di stampa.** La prima stesura prendeva la + maschera flat dalla serie **de-luckata** — ma `deluck` sottrae una COSTANTE, quindi un giorno a + rendimento esattamente zero non è più zero e `abs(r) < 1e-12` **non trova niente**. Maschera + vuota, dinamico identico al book, e lo script ha stampato **tabelle complete e un p-value** + dall'aria seria. Se ne è accorta solo la riga `giorni in cui il LIBRO e' flat: 0 = 0.0%`. + **REGOLA: stampare sempre la CARDINALITÀ di una maschera prima di usarla** — un filtro vuoto non + solleva, produce il caso degenere e lo veste da risultato. diff --git a/docs/memory/30-piano-capitale-fisco.md b/docs/memory/30-piano-capitale-fisco.md index 60d883f..5d0ff40 100644 --- a/docs/memory/30-piano-capitale-fisco.md +++ b/docs/memory/30-piano-capitale-fisco.md @@ -696,3 +696,72 @@ La tabella che comanda e' quella al netto di TUTTO (fisco + funding). tempo. La strategia che *guadagna* esiste, ma a ~+€1.5/giorno su 2000. Script ricerca: `scripts/research/track{A,B,C,D,E}_*.py` + `trackD_timing.py`. + +--- + +## 2026-08-25 — €500/mese per 10 anni dal conto vero, e l'ordine delle leve rimisurato + +Versamento di **1.400 USDC** atterrato alle 11:03:35Z: equity **$667,88 → $2.066,88**. Le tabelle +pubblicate partono da $600/$635, quindi sono state rifatte sul conto reale. +Script `scripts/research/r0825_piano_10a_500.py`, lente **L3 CONGIUNTA** (funding esatto + fisco +d'accumulo + ancora ×0,89). + +**Controllo di replica superato:** da $2.067 lo script chiede **€1.718/m** a 10 anni dove la +tabella pubblicata da $635 chiedeva €1.733 — lo scarto è esattamente il capitale iniziale più alto. + +### €500/mese da $2.067 + +| orizzonte | capitale mediano | p10 | p90 | €/g | P(≥50 €/g) | versato | fin/vers | +|---|---:|---:|---:|---:|---:|---:|---:| +| 5a | $44.925 | $38.015 | $54.453 | 7,17 | 0,0% | $34.767 | 1,29x | +| **10a** | **$114.934** | $89.461 | $152.072 | **18,35** | **0,0%** | $68.012 | 1,69x | +| 15a | $228.778 | $163.333 | $324.376 | 36,53 | 12,3% | $101.257 | 2,26x | +| 20a | $408.117 | $276.623 | $621.664 | 65,16 | 79,4% | $134.502 | 3,03x | + +**A 10 anni €500/mese NON centra €50/giorno: centra ~18 €/g, P(≥50) = 0,0% su 3.000 traiettorie.** +Con €500/m il bersaglio arriva al **17,0° anno** (P(20a) 87%). Per €50/g **in 10 anni** servono +**€1.718/m** (P=90%, totale $228.688) o €1.305/m a P=50%. +Quando arriva, per versamento: €0 **mai** · €250 22,9a (arriva il 73%) · **€500 17,0a** · +€800 13,5a · €1.000 11,8a. + +### 🚨 L'EQUIVALENZA CHE ORDINA TUTTO IL PIANO + +> **€100/mese in più ($13.189 in dieci anni) vale quanto +4,07%/anno di drift** — cioè portare il +> libro da 15,19% a 19,26%, un **+27% su tutto il drift**. + +| leva | mediana 10a | guadagno | +|---|---:|---:| +| base €500/m | $114.934 | — | +| **€600/m** | **$136.877** | **+19,1%** | +| €800/m | $180.864 | +57,4% | +| uplift drift +1%/anno | $119.850 | +4,3% | +| uplift drift +3%/anno | $130.605 | +13,6% | +| **leva k=1,25 sul libro** | $132.846 | +15,6% | +| **leva k=1,40 (max difendibile)** | $145.146 | +26,3% | + +📌 **Tutta la leva autorizzabile vale quanto €100-150/mese** — e k=1,25 vale **meno** di €100/mese +in più, portandosi dietro il peggior giorno strutturale al 21,48%. Il bonifico non porta rischio. + +### Il versamento di oggi, e quanto conta un lump + +I €1.400 di stamattina spostano la mediana a 10 anni solo del **+3,3%** (da $111.297 a $114.934). +Conferma l'ordine delle leve: **versare > quando > quanto presto si smette > piatto vs crescente > +frequenza**. A 10 anni il rendimento fa il **41%** del risultato, i bonifici il **59%**. + +### Book vs ETF, rimisurato a iso-rischio (lente L3) + +| | rendimento | vol | Sharpe | +|---|---:|---:|---:| +| book k=1 (oggi) | **15,19%** | 11,29% | **1,35** | +| S&P 500 (stessa finestra) | 17,40% | 19,60% | 0,89 | +| book a iso-rischio (k=1,74) | **26,38%** | 19,60% | 1,35 | +| book a k=1,40 (max difendibile) | 21,27% | 15,80% | 1,35 | + +🚨 **A k=1 il libro rende 2,2 punti MENO dell'S&P.** Il vantaggio di Sharpe (1,35 vs 0,89) resta +**rischio risparmiato, non rendimento incassato** — e senza leva non si converte. A iso-rischio il +libro farebbe **+9,0 punti** sopra l'indice; a k=1,40 **+3,9 punti con meno vol**. +📌 **Quindi: senza leva il libro NON si giustifica come veicolo di ACCUMULO. Si giustifica come +veicolo di RENDITA**, dove serve **2,5× meno capitale** ($254k contro $646k) perché la perpetua +vive sul drawdown. La scelta del veicolo dipende da quale dei due è l'obiettivo (N6). +⚠️ E il fisco favorisce l'ETF: UCITS ad accumulazione paga **26% alla vendita**, il book **33% +ogni anno**. Il differimento è un vantaggio strutturale reale. diff --git a/scripts/research/r0822_slip_audit.py b/scripts/research/r0822_slip_audit.py index 02cf8c5..adb44da 100644 --- a/scripts/research/r0822_slip_audit.py +++ b/scripts/research/r0822_slip_audit.py @@ -212,6 +212,54 @@ def _containing(bars: pd.DataFrame, t) -> pd.Series | None: # ----------------------------------------------------------------------------- stats +def _equity_per_fill(ts_list) -> "pd.Series": + """Equity del conto AL MOMENTO di ciascun fill, dal libro di bordo (`data/live/trades.db`). + + ⚠️ PERCHE' SERVE (2026-08-25). La sezione SCALA divideva TUTTE le partecipazioni per UNA + equity sola. Andava bene finche' il conto stava fermo a ~$636; il giorno del versamento + ($667 -> $2.067) qualunque scelta di quel singolo numero diventa sbagliata, perche' i fill + del campione sono stati eseguiti a taglie di conto DIVERSE. Il caso peggiore osservato + (22% di una barra 5m) e' un fill da $74 fatto a ~$650: normalizzarlo sull'equity di oggi lo + sottostima di 3,2x, sull'equity di allora sovrastima gli altri. + La grandezza invariante e' **partecipazione per dollaro di equity**, e si ottiene solo + appaiando ogni fill alla SUA equity. + """ + import sqlite3 + import pandas as _pd + f = ROOT / "data" / "live" / "trades.db" + ts = _pd.DatetimeIndex(_pd.to_datetime(list(ts_list), utc=True)) + try: + with sqlite3.connect(f"file:{f}?mode=ro", uri=True) as con: + e = _pd.read_sql("SELECT ts_utc, equity FROM equity ORDER BY ts_utc", con) + e["ts_utc"] = _pd.to_datetime(e["ts_utc"], utc=True) + e = e.dropna().set_index("ts_utc")["equity"].sort_index() + # BACKWARD: l'equity in vigore quando il fill e' avvenuto, mai una lettura futura. + out = _pd.Series(e.reindex(ts, method="ffill").values, index=ts) + if out.notna().any(): + return out + except Exception as ex: # noqa: BLE001 + print(f" ⚠️ equity per fill non leggibile ({type(ex).__name__}): " + f"ricado sull'equity unica") + return _pd.Series([float("nan")] * len(ts), index=ts) + + +def _equity_osservata(default: float = 636.0) -> float: + """L'equity reale dal watermark del libro live — DERIVATA, non ridichiarata (P1). + + Se il file non c'e' o non e' leggibile si ricade sul valore storico e **lo si dice**: un + default silenzioso qui rimetterebbe esattamente il difetto che questa funzione ripara. + """ + import json + f = ROOT / "data" / "live" / "equity_seen.json" + try: + v = float(json.loads(f.read_text())["real_equity"]) + if v > 0: + return v + except Exception as e: # noqa: BLE001 + print(f" ⚠️ watermark non leggibile ({type(e).__name__}): uso il default ${default:,.0f}") + return default + + def ci_mean(x, boot=True): x = np.asarray(x, float) x = x[np.isfinite(x)] @@ -515,18 +563,34 @@ def main(): # -- COSA CAMBIA CON IL CAPITALE: la partecipazione scala LINEARE, il conto no. print(f"\n SCALA — l'unica parte di questa misura che ha una data di scadenza:") - eq = 636.0 # equity reale osservata (data/live/equity_seen.json) - print(f" la partecipazione scala lineare col capitale (equity oggi ~${eq:.0f}).") + # ⚠️ 2026-08-25: era CABLATA a 636.0 con un commento che diceva "equity reale osservata + # (data/live/equity_seen.json)" — cioe' DICHIARAVA la fonte senza leggerla. E' P1: un + # misuratore deve DERIVARE il proprio bersaglio, mai ridichiararlo. Il giorno del versamento + # (equity $667 -> $2.067) la riga avrebbe continuato a stampare $636 e l'intera colonna + # "scala" sarebbe stata sbagliata di 3,3x senza che nulla lo segnalasse — proprio nella + # sezione che esiste per dire quando la misura scade. + eq = _equity_osservata() + eq_fill = _equity_per_fill(R.loc[pr.index, "fill_ts"]) + if eq_fill.notna().all(): + # partecipazione PER DOLLARO di equity: invariante alla taglia del conto al momento + ppd = pr.values / eq_fill.values + base = "per-fill (ogni fill sulla SUA equity)" + else: + ppd = pr.values / eq + base = f"equity unica ${eq:,.0f} — RIPIEGO, i fill hanno taglie di conto diverse" + print(f" la partecipazione scala lineare col capitale. Normalizzazione: {base}.") + print(f" equity al momento dei fill: ${np.nanmin(eq_fill):,.0f} - ${np.nanmax(eq_fill):,.0f}" + if eq_fill.notna().any() else "") print(f" {'capitale':>10} {'partecip. mediana':>19} {'p90':>9} {'max osservato':>15}") for cap in (600, 5_000, 20_000, 100_000, 272_000): - k = cap / eq - mx = pr.max() * k * 100 - print(f" {'$' + format(cap, ','):>10} {pr.median() * k * 100:>18.2f}% " - f"{pr.quantile(.9) * k * 100:>8.2f}% " + mx = float(np.nanmax(ppd)) * cap * 100 + print(f" {'$' + format(cap, ','):>10} {float(np.nanmedian(ppd)) * cap * 100:>18.2f}% " + f"{float(np.nanpercentile(ppd, 90)) * cap * 100:>8.2f}% " + (f"{mx:>14.1f}%" if mx <= 100 else f"{'>tutta la barra':>15}")) print(f" => a $600 l'impatto e' strutturalmente impossibile e questa misura lo conferma.") + _mx = float(np.nanmax(ppd)) print(f" Ma il caso peggiore osservato e' gia' {pr.max() * 100:.0f}% di una barra 5m: a $5k") - print(f" diventa {pr.max() * 5000 / eq * 100:.0f}%, a $20k {pr.max() * 20000 / eq * 100:.0f}%. " + print(f" diventa {_mx * 5000 * 100:.0f}%, a $20k {_mx * 20000 * 100:.0f}%. " f"**La misura di oggi NON si estrapola**") print(f" al capitale del piano: va RIFATTA a ogni salto di taglia. E' la stessa") print(f" forma del muro di eseguibilita' gia' noto (XS01 ~$20k, XSR01 ~$5k), ma") diff --git a/scripts/research/r0825_capitale_fermo.py b/scripts/research/r0825_capitale_fermo.py new file mode 100644 index 0000000..819ed9e --- /dev/null +++ b/scripts/research/r0825_capitale_fermo.py @@ -0,0 +1,210 @@ +"""r0825_capitale_fermo.py — il capitale che il libro NON usa: occasione o illusione? + +DOMANDA (operatore, 2026-08-25). Il libro live sta **flat** buona parte del tempo (28,0% dei giorni +sull'intera storia; **75,6% nel 2026** e 77,0% nel 2022). Il 07/08 il progetto aveva gia' scelto un +**50/50 book/ETF** per *rimpianto minimo*, su base statistica. La domanda nuova e' meccanica: +**mettere l'ETF proprio nei giorni in cui il libro e' flat** batte il 50/50 statico? + +⚠️ ATTENZIONE A COSA SI STA MISURANDO. "ETF quando TP01 e' flat" e' a tutti gli effetti una +**strategia di timing sull'azionario guidata da un segnale cripto**. Non c'e' nessuna ragione a +priori perche' funzioni, e il rischio e' di misurare due cose diverse credendo di misurarne una: + + (a) **UTILIZZO DEL CAPITALE** — stare nell'ETF invece che fermi aggiunge rendimento a + prescindere da QUANDO lo si fa. E' aritmetica, non informazione. + (b) **TIMING** — la flat-ness di TP01 predice i rendimenti azionari. + +Il confronto ingenuo (dinamico vs 50/50) li somma. Per separarli serve il **null a maschera +casuale**: si rifa' la stessa strategia con una maschera flat FINTA della stessa lunghezza e della +stessa struttura a blocchi. Se il dinamico vero non batte quella nuvola, di (b) non c'e' niente e +resta solo (a) — che e' comunque un risultato, ma **un altro** risultato. + +REGOLE APPLICATE: + * **M6/M5** — il confronto e' a **ISO-RISCHIO** (ogni linea riscalata alla stessa vol bersaglio), + mai a iso-nozionale: un veicolo a CAGR piu' basso ma vol piu' bassa perde sempre a iso-nozionale + per costruzione. Il de-levering e' il PRIMO test, non l'ultimo. + * **M14** — il null e' a **struttura preservata** (blocchi), non i.i.d.: la flat-ness del libro e' + fortemente autocorrelata (interi mesi), e un null i.i.d. sarebbe banale da battere. + * **⚠️ finestra comune** — un mix esiste solo dove esistono ENTRAMBE le serie. L'errore gia' + commesso e corretto il 07/08 (dichiarava 30 anni e ne usava 7) qui non si ripete: si taglia + sull'**intersezione** e la si stampa. + * **convenzione GTAA01** per l'azionario: ritorni su griglia di calendario con **0.0 a borsa + chiusa** (senza, lo Sharpe azionario sale del 20% — lezione 25/07). + + uv run python scripts/research/r0825_capitale_fermo.py +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import numpy as np +import pandas as pd + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) +sys.path.insert(0, str(ROOT / "scripts" / "research")) + +import eqlib as EQ # noqa: E402 +import r0807_asset_compare as AC # noqa: E402 +import r0822d_piano_vero as PV # noqa: E402 + +VOL_TARGET = 0.11 # ~la vol del libro: la lente iso-rischio del confronto +N_NULL = 400 # estrazioni della maschera finta +BLOCK = 20 # giorni: la stessa taglia di blocco del resto del progetto +SEED = 20260825 + + +def metriche(r: pd.Series | np.ndarray, nome: str = "") -> dict: + r = np.asarray(r, float) + mu, sd = r.mean() * 365.0, r.std() * 365.0 ** 0.5 + eq = np.cumprod(1.0 + r) + dd = float((eq / np.maximum.accumulate(eq) - 1.0).min()) + return dict(nome=nome, drift=mu, vol=sd, sharpe=mu / sd if sd > 0 else np.nan, maxdd=dd) + + +def iso(r: np.ndarray, vol_target: float = VOL_TARGET) -> np.ndarray: + """Riscala la serie alla vol bersaglio. E' il de-levering di M6: senza, un veicolo a vol + bassa perde per costruzione e il confronto non dice niente.""" + sd = r.std() * 365.0 ** 0.5 + return r * (vol_target / sd) if sd > 0 else r + + +def maschera_a_blocchi(n: int, quota: float, rng: np.random.Generator, + block: int = BLOCK) -> np.ndarray: + """Maschera booleana casuale con ~`quota` di True, in BLOCCHI di `block` giorni. + + La flat-ness vera del libro dura mesi interi: un null i.i.d. avrebbe una struttura temporale + completamente diversa e sarebbe facile da battere per la ragione sbagliata.""" + nb = int(np.ceil(n / block)) + scelti = rng.random(nb) < quota + return np.repeat(scelti, block)[:n] + + +def main() -> None: + print("=" * 100) + print(" r0825 — IL CAPITALE FERMO: l'ETF nei giorni in cui il libro e' flat") + print("=" * 100) + + # --- le due serie, sulla FINESTRA COMUNE ------------------------------------------------- + B = PV.costruisci_serie() + ix = pd.to_datetime(B.index).tz_localize(None).normalize() + # ⚠️ LA MASCHERA FLAT SI PRENDE DALLA SERIE GREZZA, MAI DA QUELLA DE-LUCKATA. + # `deluck` sottrae una COSTANTE (r - (1-0.89)*r.mean()): un giorno a rendimento esattamente + # zero diventa un numero piccolo ma non nullo, e `abs(r) < 1e-12` non trova piu' NIENTE. + # Prima versione di questo script: maschera VUOTA, dinamico identico al book, e un p-value + # stampato lo stesso. Se ne e' accorta solo la riga che stampa la quota — che e' il motivo + # per cui la si stampa. + grezzo = pd.Series(B["fund"].values.astype(float), index=ix) + book = pd.Series(PV.deluck(B["fund"].values.astype(float)), index=ix) + grezzo = grezzo[~grezzo.index.duplicated(keep="last")] + book = book[~book.index.duplicated(keep="last")] + spy = AC._calendar_daily(EQ.load_eq("SPY")["close"]) + + idx = book.index.intersection(spy.index) + book, spy, grezzo = book.reindex(idx), spy.reindex(idx), grezzo.reindex(idx) + flat = (grezzo.abs() < 1e-12).values + assert flat.sum() > 0, "maschera flat vuota: e' stata presa dalla serie de-luckata?" + print(f"\n finestra COMUNE: {idx[0].date()} -> {idx[-1].date()} ({len(idx)} giorni, " + f"{len(idx)/365.25:.1f} anni)") + print(f" giorni in cui il LIBRO e' flat: {flat.sum()} = {flat.mean():.1%}") + print(f" quota per anno: " + " · ".join( + f"{y}: {g.mean():.0%}" for y, g in pd.Series(flat, index=idx).groupby(idx.year))) + + b, s = book.values, spy.values + linee = { + "BOOK da solo": b, + "ETF da solo": s, + "50/50 statico": 0.5 * b + 0.5 * s, + "DINAMICO (ETF se il libro e' flat)": np.where(flat, s, b), + } + + # --- (1) confronto a ISO-NOZIONALE (come lo si guarderebbe d'istinto) -------------------- + print(f"\n{'='*100}\n (1) ISO-NOZIONALE — come lo si guarda d'istinto. ⚠️ NON e' il test." + f"\n{'='*100}") + print(f" {'linea':<38}{'drift':>9}{'vol':>9}{'Sharpe':>9}{'maxDD':>9}") + for nome, r in linee.items(): + m = metriche(r, nome) + print(f" {nome:<38}{m['drift']:>8.2%}{m['vol']:>9.2%}{m['sharpe']:>9.2f}{m['maxdd']:>9.1%}") + + # --- (2) ISO-RISCHIO: il test vero (M6) -------------------------------------------------- + print(f"\n{'='*100}\n (2) ISO-RISCHIO a vol {VOL_TARGET:.0%} — il confronto che conta (M6)" + f"\n{'='*100}") + print(f" {'linea':<38}{'drift':>9}{'vol':>9}{'Sharpe':>9}{'maxDD':>9}{'leva impl.':>12}") + iso_m = {} + for nome, r in linee.items(): + k = VOL_TARGET / (r.std() * 365 ** 0.5) + m = metriche(iso(r), nome) + iso_m[nome] = m + print(f" {nome:<38}{m['drift']:>8.2%}{m['vol']:>9.2%}{m['sharpe']:>9.2f}" + f"{m['maxdd']:>9.1%}{k:>11.2f}x") + + # --- (3) IL NULL: timing o solo utilizzo del capitale? ----------------------------------- + print(f"\n{'='*100}\n (3) NULL A MASCHERA CASUALE — separa il TIMING dall'UTILIZZO" + f"\n{'='*100}") + print(f" {N_NULL} maschere finte, stessa quota ({flat.mean():.1%}) e stessa struttura " + f"a blocchi di {BLOCK}g.") + rng = np.random.default_rng(SEED) + nulli = [] + for _ in range(N_NULL): + fk = maschera_a_blocchi(len(b), flat.mean(), rng) + nulli.append(metriche(iso(np.where(fk, s, b)))["sharpe"]) + nulli = np.array(nulli) + vero = iso_m["DINAMICO (ETF se il libro e' flat)"]["sharpe"] + pct = float((nulli < vero).mean()) + print(f" Sharpe iso-rischio del DINAMICO vero : {vero:.3f}") + print(f" nuvola del null: mediana {np.median(nulli):.3f} · " + f"p5 {np.percentile(nulli,5):.3f} · p95 {np.percentile(nulli,95):.3f}") + print(f" percentile del vero dentro il null : {pct:.1%}") + print(f" p-value one-sided (vero <= null) : {1-pct:.3f}") + + # --- (3b) IL "MECCANISMO" SI SCOMPONE PER ANNO PRIMA DI CREDERCI (M9) -------------------- + print(f"\n{'='*100}\n (3b) L'azionario nei giorni flat — e la scomposizione che la smonta" + f"\n{'='*100}") + sf, sn = spy.values[flat], spy.values[~flat] + print(f" aggregato: SPY|flat {sf.mean()*365:>7.2%} (vol {sf.std()*365**0.5:.2%}) · " + f"SPY|dentro {sn.mean()*365:>7.2%} (vol {sn.std()*365**0.5:.2%}) · " + f"differenza {(sf.mean()-sn.mean())*365:>+7.2%}") + print(f" ⚠️ Sembra un meccanismo («il libro va flat quando l'azionario soffre»). Non lo e':") + print(f" {'anno':>6}{'gg flat':>9}{'differenza SPY (flat - dentro)':>34}") + segni = [] + for y, m in spy.groupby(spy.index.year): + mk = flat[spy.index.year == y] + a, b = m.values[mk], m.values[~mk] + if len(a) < 15 or len(b) < 15: + print(f" {y:>6}{len(a):>9}{'campione insufficiente':>34}") + continue + d = (a.mean() - b.mean()) * 365 + segni.append(np.sign(d)) + print(f" {y:>6}{len(a):>9}{d:>+33.1%}") + su, giu = int(sum(1 for x in segni if x > 0)), int(sum(1 for x in segni if x < 0)) + print(f"\n => il segno ALTERNA: {su} anni 'flat meglio', {giu} anni 'flat peggio'.") + print(f" L'aggregato e' un artefatto di COMPOSIZIONE: i giorni flat sono concentrati") + print(f" negli anni a basso rendimento azionario, non nei GIORNI a basso rendimento.") + print(f" Dentro ogni anno l'effetto non c'e'. (M9: un contributo si scompone per anno") + print(f" prima di crederci — '24/24 ancore positive' puo' essere un anno solo.)") + + # --- (4) VERDETTO calcolato a runtime (N11) ---------------------------------------------- + print(f"\n{'='*100}\n VERDETTO (calcolato, non scritto a mano)\n{'='*100}") + din = iso_m["DINAMICO (ETF se il libro e' flat)"]["sharpe"] + sta = iso_m["50/50 statico"]["sharpe"] + bk = iso_m["BOOK da solo"]["sharpe"] + print(f" a iso-rischio: BOOK {bk:.3f} · 50/50 {sta:.3f} · DINAMICO {din:.3f}") + if din <= sta: + print(f" (a) Il dinamico NON batte il 50/50 statico ({din:.3f} <= {sta:.3f}): " + f"la commutazione non aggiunge nulla che il mix non dia gia'.") + else: + print(f" (a) Il dinamico batte il 50/50 statico di {din-sta:+.3f} di Sharpe.") + if pct < 0.95: + print(f" (b) E il TIMING non e' dimostrato: il vero sta al {pct:.0%} del null, " + f"sotto la soglia del 95%.") + print(f" Quel che si vede e' UTILIZZO DEL CAPITALE, non informazione: qualunque") + print(f" maschera con la stessa quota fa altrettanto.") + else: + print(f" (b) Il timing SOPRAVVIVE al null ({pct:.0%} >= 95%): la flat-ness del libro") + print(f" porta informazione sull'azionario. Da riprovare fuori campione.") + print(f"\n NB: 'battere a iso-rischio' qui vuol dire leva implicita diversa per ogni linea") + print(f" (colonna 'leva impl.'), e la leva NON e' autorizzata oggi (§3, k max 1.40).") + + +if __name__ == "__main__": + main() diff --git a/scripts/research/r0825_piano_10a_500.py b/scripts/research/r0825_piano_10a_500.py new file mode 100644 index 0000000..e720cfb --- /dev/null +++ b/scripts/research/r0825_piano_10a_500.py @@ -0,0 +1,153 @@ +"""r0825_piano_10a_500.py — €500/mese per 10 anni, dal conto VERO di oggi. + +DOMANDA (operatore, 2026-08-25): *"dammi strategia a 10 anni con 500 al mese"*. + +PERCHE' NON BASTAVA CITARE LA TABELLA. Le tabelle pubblicate del piano partono da **$600/$635**. +Oggi il conto e' **$2.067** (versamento di 1.400 USDC atterrato alle 11:03:35Z). Il lump e' la +leva piu' grande misurata dopo il versamento mensile stesso (*"€10.000 oggi + €250/m porta P(20a) +da 14% a 45%"*), quindi la riga vecchia sottostima il caso di oggi e non si puo' riusare. + +LENTE: **L3 CONGIUNTA** di `r0822d_piano_vero` — libro live 75/25, **funding esatto dentro la +serie**, ancora de-luckata x0.89, **fisco d'accumulo** ogni anno dentro il portafoglio (33% sulla +variazione annua di valore, minusvalenze in carry 4 anni, patrimoniale 0,2%). E' la piu' severa +che il progetto abbia, ed e' quella che ha prodotto muro $313k e *"€1.733/mese per 10 anni"*. + +⚠️ DIFETTI EREDITATI, dichiarati perche' vanno **a favore del piano** (registrati il 23/08): + 1. `PN.accumula` versa ogni **30 giorni** -> a 10 anni sono **121** versamenti, non 120 (+0,83%); + 2. il contatore `versato` e' uno **scalare che non si ferma al traguardo** -> il "versato" a + denominatore e' quello dell'INTERO orizzonte, non quello fino all'arrivo. +Non li ho riparati qui: ripararli cambierebbe i numeri di riferimento e li renderebbe non +confrontabili con tutto il resto della memoria. Vanno LETTI, non dimenticati. + +⚠️ E IL NUMERO DA NON DIMENTICARE MAI accanto a questi: la SE del drift L3 e' **5,151%/anno**, e +il muro e' `prelievo/perpetua` = un **1/x su una quantita' che va a zero** -> la banda e' +esplosiva e **asimmetrica verso l'alto**: $187k (p90) · **$313k (punto)** · $1,14M (p10) · +a **−2 SE il traguardo non esiste a nessun capitale**. Percio' la regola del progetto: +**il piano si dimensiona sul VERSAMENTO, che e' certo, non sul muro** (N1). + + uv run python scripts/research/r0825_piano_10a_500.py +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import numpy as np + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) +sys.path.insert(0, str(ROOT / "scripts" / "research")) + +import r0725_capcurve as CC # noqa: E402 +import r0807_piano_netto as PN # noqa: E402 +import r0822d_piano_vero as PV # noqa: E402 +import r0823_deposit_timing as DT # noqa: E402 + +# --- lo stato VERO di oggi, non un nominale ------------------------------------------------- +START_OGGI = 2066.88 # equity Deribit letta alle 11:47:01Z del 2026-08-25 +START_PUBBL = PV.START # 635.0 — la base delle tabelle pubblicate, per il confronto +DEP = 500.0 # EUR/mese +ORIZZONTI = (5, 10, 15, 20) +N_PATHS = 3000 +SEED = PV.SEED_TRAJ # 725: stesso seme delle traiettorie pubblicate +BLOCK = PV.BLOCK # 20 giorni + + +def rendita_eur_giorno(cap: np.ndarray, perp: float) -> np.ndarray: + """€/giorno NETTI da un capitale, alla perpetua della lente. + + ⚠️ La perpetua L3 (6,35%) ha gia' le imposte dentro: **non si lordizza una seconda volta** + (errore corretto il 07/08).""" + return cap * perp / 365.0 / CC.EURUSD + + +def main() -> None: + print("=" * 100) + print(" r0825 — €500/MESE PER 10 ANNI, dal conto vero di oggi ($2.067)") + print("=" * 100) + + B = PV.costruisci_serie() + r = PV.deluck(B["fund"].values.astype(float)) # L3 CONGIUNTA, ancora x0.89 + lente = PV.Lente("L3 congiunta", r, lordizza=False) + lente.perp, lente.muro = PV.muro_di(lente) + + print(f"\n serie: {len(r)} giorni · drift {lente.drift:.2%}/anno · vol {lente.vol:.2%}/anno") + print(f" perpetua {lente.perp:.2%} · MURO ${lente.muro:,.0f} " + f"[banda del progetto: $187k (p90) — $1,14M (p10); a −2 SE non esiste]") + print(f" bersaglio di rendita: €{CC.TARGET_EUR_DAY:.0f}/giorno netti") + + print(f"\n{'='*100}\n (1) €{DEP:.0f}/MESE DA ${START_OGGI:,.0f} — cosa consegna, per orizzonte" + f"\n{'='*100}") + print(f" {'oriz.':>6} {'capitale mediano':>18} {'p10':>12} {'p90':>13} " + f"{'€/g mediani':>13} {'P(≥50 €/g)':>12} {'versato':>11} {'fin/vers':>9}") + print(" " + "-" * 98) + righe = {} + for anni in ORIZZONTI: + rng = np.random.default_rng(SEED) + ii = DT.boot_idx(len(r), N_PATHS, anni * 365, BLOCK, rng) + paths = r[ii] + out = PN.accumula(paths, DEP, lente.muro, lente.aliq, lente.patr, start=START_OGGI) + cap, versato = out["cap"], out["versato"] + eur = rendita_eur_giorno(cap, lente.perp) + p50 = float((eur >= CC.TARGET_EUR_DAY).mean()) + righe[anni] = dict(med=np.median(cap), eur=np.median(eur), p50=p50, versato=versato) + print(f" {anni:>4}a ${np.median(cap):>16,.0f} ${np.percentile(cap,10):>10,.0f} " + f"${np.percentile(cap,90):>11,.0f} {np.median(eur):>12.2f} {p50:>11.1%} " + f"${versato:>9,.0f} {np.median(cap)/versato:>8.2f}x") + + print(f"\n{'='*100}\n (2) COSA HA COMPRATO IL VERSAMENTO DI OGGI — $635 (tabelle pubbl.) vs " + f"$2.067 (reale)\n{'='*100}") + print(f" {'oriz.':>6} {'da $635':>14} {'da $2.067':>14} {'guadagno':>11} " + f"{'€/g da $635':>13} {'€/g da $2.067':>15}") + print(" " + "-" * 78) + for anni in ORIZZONTI: + rng = np.random.default_rng(SEED) + ii = DT.boot_idx(len(r), N_PATHS, anni * 365, BLOCK, rng) + paths = r[ii] + a = PN.accumula(paths, DEP, lente.muro, lente.aliq, lente.patr, start=START_PUBBL)["cap"] + b = PN.accumula(paths, DEP, lente.muro, lente.aliq, lente.patr, start=START_OGGI)["cap"] + ea, eb = rendita_eur_giorno(a, lente.perp), rendita_eur_giorno(b, lente.perp) + print(f" {anni:>4}a ${np.median(a):>12,.0f} ${np.median(b):>12,.0f} " + f"{np.median(b)/np.median(a)-1:>10.1%} {np.median(ea):>12.2f} {np.median(eb):>14.2f}") + + print(f"\n{'='*100}\n (3) QUANDO ARRIVA IL BERSAGLIO (€50/g = ${lente.muro:,.0f})" + f"\n{'='*100}") + rng = np.random.default_rng(SEED) + ii = DT.boot_idx(len(r), N_PATHS, 25 * 365, BLOCK, rng) + paths25 = r[ii] + for dep in (0, 250, 500, 800, 1_000): + out = PN.accumula(paths25, float(dep), lente.muro, lente.aliq, lente.patr, + start=START_OGGI) + d = PV.leggi(out["colpito"], anni_sim=25) + med = "mai" if not np.isfinite(d["med"]) else f"{d['med']:.1f}a" + cond = "n/d" if not np.isfinite(d["med_cond"]) else f"{d['med_cond']:.1f}a" + print(f" €{dep:>5}/m -> mediana {med:>6} (condizionata all'arrivo {cond:>6}, " + f"arriva il {d['p_arr']:.0%}) · P(entro 20a) {d['p20']:>5.0%}") + + print(f"\n{'='*100}\n (4) IL VERSAMENTO CHE SERVE DAVVERO PER €50/g, da ${START_OGGI:,.0f}" + f"\n{'='*100}") + for anni in (10, 15, 20): + rng = np.random.default_rng(PV.SEED_DEP) + ii = DT.boot_idx(len(r), PV.N_DEP, anni * 365, BLOCK, rng) + p = r[ii] + riga = [] + for conf in (0.50, 0.75, 0.90): + d, tot = PV.dep_per_conf(p, lente, conf, start=START_OGGI) + riga.append(f"P={conf:.0%}: €{d:,.0f}/m" + (f" (tot ${tot:,.0f})" if conf == 0.90 else "")) + print(f" {anni:>2} anni -> " + " · ".join(riga)) + + print(f"\n{'='*100}\n VERDETTO (calcolato a runtime, non scritto a mano)\n{'='*100}") + d10 = righe[10] + print(f" €500/mese per 10 anni da ${START_OGGI:,.0f}:") + print(f" capitale mediano ${d10['med']:,.0f} · rendita mediana {d10['eur']:.2f} €/giorno") + print(f" P(≥50 €/g a 10 anni) = {d10['p50']:.1%}") + print(f" versato ${d10['versato']:,.0f} -> il rendimento fa il " + f"{max(0.0, 1 - d10['versato']/d10['med']):.0%} del risultato, i bonifici il resto") + verdetto = ("CENTRA" if d10["p50"] >= 0.5 else "NON CENTRA") + print(f"\n => a 10 anni €500/mese {verdetto} il bersaglio €50/giorno.") + print(f" => quello che centra a 10 anni sta nella sezione (4); quello che €500/mese centra,") + print(f" e quando, sta nella (3).") + + +if __name__ == "__main__": + main()