merge: piano versamenti — interruzione, crescita, calendario, domanda inversa
This commit is contained in:
@@ -1024,6 +1024,57 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis
|
||||
(d) quando le leve hanno ordini di grandezza diversi **dirlo**, o si lavora molto senza cambiare
|
||||
niente; (e) rivalutare **non** vuol dire anticipare i gate pre-registrati — anticiparli e'
|
||||
selezione sull'hold-out.
|
||||
- 💰 **I VERSAMENTI — le 4 ipotesi che il piano non aveva mai fatto (2026-07-26, ultimo filone).**
|
||||
Tutte le traiettorie del 25-26/07 assumevano versamento **piatto, ininterrotto, per sempre** =
|
||||
l'ipotesi meno realistica del piano. Script `r0726_deposits.py`, test `tests/test_deposits.py`
|
||||
(12), diario `2026-07-26-versamenti.md`. **Book/pesi/cron/config INVARIATI** (non tocca la
|
||||
produzione). Block bootstrap sui ritorni reali del book live, fattore ×0.89 misurato.
|
||||
(1) **SMETTERE — il costo non e' proporzionale ai soldi mancanti.** €250/m per K anni poi stop,
|
||||
orizzonte 20a: 3a ($10.410) → $202.771 / P(muro) 32.7%; 5a ($16.950) → $287.081 / **53.3%**;
|
||||
10a ($33.572) → $410.220 / 77.9%; 20a ($66.818) → $496.778 / 90.0%. **I primi 5 anni sono il 25%
|
||||
dei soldi e il 58% del risultato.** → un'interruzione al 12° anno costa poco, una al 3° quasi
|
||||
tutto: argomento per partire con un importo **sostenibile**, non ambizioso.
|
||||
(2) **CRESCENTE E' PEGGIO DI PIATTO a pari soldi.** €150/m +5%/anno versa €67.998 → $401.889;
|
||||
piatto €250 versa €66.818 → **$496.778** = **+24% con gli stessi soldi**, solo perche' entrano
|
||||
prima. Metrica giusta per confrontare piani di taglia diversa = **`$ finale / $ versato`**
|
||||
(piatti 7.4x, crescenti 5.1-5.9x). Contro-intuitivo: "i versamenti crescono col reddito" e'
|
||||
prudente per il bilancio, **non** per il capitale.
|
||||
(3) **STESSO TOTALE, CALENDARIO DIVERSO = fattore 6.** €60.000 distribuiti: ultimi 10 anni
|
||||
$178.494 (P(muro) 11%) / piatto 20a $496.778 (90%) / primi 10a $806.285 (98.3%) / primi 5a
|
||||
**$1.104.587 (99.4%)**. ⚠️ NON significa "versa tutto subito": un piano che non si sostiene non
|
||||
e' un piano — serve a scegliere fra calendari **sostenibili**.
|
||||
✅ **Verificato col rischio di venue dentro** (il vantaggio front-load mette piu' capitale
|
||||
sull'exchange prima = proprio il rischio del giorno): **regge**, 2.22x → 2.09x a p=2%, perche' il
|
||||
rischio colpisce il **tempo**, non il calendario. ⚠️ **MA a p=5% il capitale mediano e' $0 per
|
||||
OGNI calendario** (64% di rovina su 20a) → **formulazione piu' netta del rischio di venue trovata
|
||||
finora: non erode il piano, lo CANCELLA.**
|
||||
(4) **LA DOMANDA INVERSA — rendita netta €/g mediana** (riformula l'obiettivo: €50/g e' UN punto,
|
||||
non l'unico risultato):
|
||||
| €/mese | 5a | 10a | 15a | 20a | P(€50/g a 20a) |
|
||||
|---|---|---|---|---|---|
|
||||
| 100 | 2.08 | 6.54 | 16.40 | 38.07 | 31.0% |
|
||||
| 150 | 3.00 | 9.55 | 23.96 | 55.80 | 58.7% |
|
||||
| **250** | 4.83 | 15.53 | 39.11 | **91.30** | **90.0%** |
|
||||
| 400 | 7.59 | 24.52 | 61.85 | 144.23 | 98.9% |
|
||||
| 600 | 11.26 | 36.52 | 92.17 | 215.07 | 100.0% |
|
||||
Non-linearita': **da 15 a 20 anni la rendita piu' che raddoppia a ogni livello** (gli ultimi anni
|
||||
contano piu' in *rendita*, i primi piu' in *versamenti*).
|
||||
(5) **FREQUENZA = la decisione meno importante.** Mensile fino a ~$2 di costo per trasferimento,
|
||||
bimestrale sopra, trimestrale oltre $25 — ma le differenze sono **1-3%** del capitale finale.
|
||||
Verificato che un deposito **non resta strozzato**: col cap dinamico attivo cap = equity/2 =
|
||||
esattamente il nozionale massimo richiedibile per asset.
|
||||
📌 **ORDINE DI IMPORTANZA (da citare quando si parla del piano):** versare o no (**da mai a 16
|
||||
anni**) > quando (**6x**) > quanto presto si smette (5 anni = 58% del risultato) > piatto vs
|
||||
crescente (24%) > frequenza (1-3%). E sopra tutte, fuori scala: **a p=5% di rischio venue il
|
||||
risultato mediano e' zero comunque.**
|
||||
**REGOLE:** (a) un piano di accumulo si giudica sulle sue **deviazioni**, non sul caso nominale —
|
||||
piatto/ininterrotto/per sempre e' l'unico scenario che non succede; (b) piani di taglia diversa si
|
||||
confrontano con una metrica **normalizzata** (`$ finale / $ versato`), altrimenti "versa di piu'"
|
||||
vince sempre; (c) un vantaggio calcolato **ignorando un rischio noto** va ri-misurato con quel
|
||||
rischio dentro anche quando ci si aspetta che regga (qui reggeva, ma la colonna p=5% ha prodotto
|
||||
il risultato piu' importante del filone); (d) quando l'obiettivo dichiarato non e' raggiungibile,
|
||||
la tabella utile e' quella **inversa** — non "quando arrivo a X" ma "cosa compro con quello che
|
||||
ho".
|
||||
- 📅 **NUOVO SCHEMA FEE DERIBIT dal 2026-08-01 — misurata la CURVA, nessuna azione oggi
|
||||
(2026-07-26).** Script `r0726_fee_sensitivity.py`, test `tests/test_fee_sensitivity.py` (6),
|
||||
diario `2026-07-26-fee-deribit.md`. **Book/pesi/cron/config INVARIATI.**
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# 2026-07-26 — I versamenti: le quattro ipotesi che il piano non aveva mai fatto
|
||||
|
||||
**Richiesta:** *"allora concentriamoci sui versamenti"* — dopo la rivalutazione che ha misurato il
|
||||
book dentro il suo plateau (ottimizzare il peso = +0.030 Sharpe, gate fallito) mentre i versamenti
|
||||
valgono la differenza fra **mai** e **16 anni**.
|
||||
|
||||
**Script:** `r0726_deposits.py`. **Test:** `tests/test_deposits.py` (12).
|
||||
**Book, pesi, cron, config: INVARIATI.** (Questo filone non tocca il codice di produzione.)
|
||||
|
||||
---
|
||||
|
||||
## 0. Cosa mancava
|
||||
|
||||
Tutte le traiettorie calcolate finora (25/07 e 26/07) assumono la stessa cosa: **versamento
|
||||
piatto, ininterrotto, per sempre**. E' l'ipotesi meno realistica dell'intero piano. Qui si misurano
|
||||
le deviazioni che succedono davvero, con la stessa macchineria (block bootstrap sui ritorni reali
|
||||
del book live, fattore d'ancora ×0.89 **misurato**).
|
||||
|
||||
## 1. Smettere di versare — il costo non e' proporzionale ai soldi mancanti
|
||||
|
||||
€250/mese per K anni, poi stop, orizzonte 20 anni:
|
||||
|
||||
```
|
||||
versi per totale versato cap. mediano a 20a P(muro entro 20a) rendita mediana
|
||||
3a $10,410 $202,771 32.7% 37.27 €/g
|
||||
5a $16,950 $287,081 53.3% 52.76 €/g
|
||||
10a $33,572 $410,220 77.9% 75.39 €/g
|
||||
20a $66,818 $496,778 90.0% 91.30 €/g
|
||||
```
|
||||
|
||||
**I primi 5 anni sono il 25% dei soldi e il 58% del risultato.** Chi versa 5 anni e poi smette
|
||||
arriva comunque alla rendita bersaglio nel 53% dei casi; chi versa gli ultimi 10 anni (piu' soldi)
|
||||
arriva nell'11% (§3). **La variabile non e' quanto versi: e' quanto presto.**
|
||||
|
||||
Corollario pratico: un'interruzione al 12° anno costa poco; una al 3° costa quasi tutto. Se il
|
||||
piano deve rompersi, e' meglio che si rompa tardi — il che e' un argomento per **partire con un
|
||||
importo sostenibile** invece che con uno ambizioso.
|
||||
|
||||
## 2. Un piano crescente e' peggiore di uno piatto, a pari soldi
|
||||
|
||||
```
|
||||
piano totale versato cap. mediano P(muro) $ finale / $ versato
|
||||
piatto €250 $66,818 $496,778 90.0% 7.43x
|
||||
€150 +5%/anno $67,998 $401,889 80.8% 5.91x
|
||||
```
|
||||
|
||||
**Stessi soldi (€67-68k), 24% di risultato in meno.** Il piano crescente versa gli stessi euro
|
||||
piu' **tardi**, e quegli euro compongono meno. L'ultima colonna e' la metrica giusta per
|
||||
confrontare piani di taglia diversa: quanto capitale finale compra ogni dollaro versato — e i
|
||||
piani crescenti stanno tutti a 5.1-5.9x contro 7.4x dei piatti.
|
||||
|
||||
E' contro-intuitivo perche' "i versamenti crescono col reddito" suona prudente. Lo e' dal punto di
|
||||
vista del bilancio familiare; **non** lo e' dal punto di vista del capitale.
|
||||
|
||||
## 3. Stesso totale, calendario diverso — il tempo vale 2.2x
|
||||
|
||||
€60.000 in totale (= €250/mese per 20 anni), distribuiti diversamente:
|
||||
|
||||
```
|
||||
calendario cap. mediano P(muro) vs piatto
|
||||
piatto su 20 anni $496,778 90.0% 1.00x
|
||||
tutto nei primi 10 anni $806,285 98.3% 1.62x
|
||||
tutto nei primi 5 anni $1,104,587 99.4% 2.22x
|
||||
solo negli ultimi 10 anni $178,494 11.0% 0.36x
|
||||
```
|
||||
|
||||
**Gli stessi €60.000 valgono da $178k a $1.104k — un fattore 6 — a seconda di QUANDO entrano.**
|
||||
|
||||
⚠️ Questo **non** dice "versa tutto subito": dice quanto vale il tempo. Un piano che non si
|
||||
sostiene non e' un piano, e il confronto serve a scegliere fra calendari **sostenibili**.
|
||||
|
||||
### E regge al rischio di venue?
|
||||
|
||||
Il vantaggio del front-loading e' calcolato mettendo **piu' capitale sull'exchange prima** — cioe'
|
||||
proprio il rischio misurato stamattina. Andava verificato, non assunto:
|
||||
|
||||
```
|
||||
calendario p=0.5% p=2.0% p=5.0%
|
||||
piatto su 20 anni $463,413 $359,251 $0
|
||||
tutto nei primi 10 anni $765,178 $555,904 $0
|
||||
tutto nei primi 5 anni $1,015,491 $752,367 $0
|
||||
solo negli ultimi 10 anni $172,228 $147,203 $0
|
||||
```
|
||||
|
||||
Il vantaggio **regge** (2.22x → 2.09x a p=2%): il rischio di venue colpisce il **tempo**, non il
|
||||
calendario dei versamenti. Anticipare non aumenta la probabilita' di essere colpiti, aumenta solo
|
||||
quanto c'e' dentro quando succede — e nel frattempo ha composto di piu'.
|
||||
|
||||
⚠️ **Ma guardare la colonna p=5%: il capitale mediano e' $0 per OGNI calendario.** A quel tasso di
|
||||
rischio la meta' dei percorsi finisce a zero (64% di rovina su 20 anni, come misurato stamattina) e
|
||||
**come versi diventa irrilevante**. E' la formulazione piu' netta del rischio di venue trovata
|
||||
finora: non erode il piano, lo **cancella**.
|
||||
|
||||
## 4. La domanda inversa — che rendita compra quello che posso permettermi
|
||||
|
||||
Rendita **netta** mediana in €/giorno:
|
||||
|
||||
```
|
||||
€/mese 5a 10a 15a 20a P(€50/g a 20a)
|
||||
100 2.08€ 6.54€ 16.40€ 38.07€ 31.0%
|
||||
150 3.00€ 9.55€ 23.96€ 55.80€ 58.7%
|
||||
250 4.83€ 15.53€ 39.11€ 91.30€ 90.0%
|
||||
400 7.59€ 24.52€ 61.85€ 144.23€ 98.9%
|
||||
600 11.26€ 36.52€ 92.17€ 215.07€ 100.0%
|
||||
```
|
||||
|
||||
E' la tabella che **riformula l'obiettivo**. €50/g e' un punto su questa griglia, non l'unico
|
||||
risultato che conta: €150/mese porta a ~€24/g in 15 anni, che non e' un fallimento del piano da
|
||||
€50 — e' un risultato diverso, e ora si sa quanto costa il salto.
|
||||
|
||||
Da notare la **non-linearita' temporale**: da 15 a 20 anni la rendita piu' che raddoppia a ogni
|
||||
livello. Gli ultimi anni del piano contano piu' dei primi *in rendita*, esattamente come i primi
|
||||
contano piu' degli ultimi *in versamenti*.
|
||||
|
||||
## 5. Ogni quanto versare — la decisione meno importante
|
||||
|
||||
```
|
||||
costo/tras. mensile bimestrale trimestrale semestrale
|
||||
$0.00 *$490,149 $486,725 $482,587 $473,908
|
||||
$2.00 *$486,597 $484,964 $481,446 $473,342
|
||||
$5.00 $481,270 *$482,323 $479,727 $472,495
|
||||
$25.00 $445,984 $464,692 *$468,226 $466,817
|
||||
```
|
||||
|
||||
**Mensile fino a ~$2 di costo per trasferimento, bimestrale sopra.** Ma le differenze sono
|
||||
**1-3%** del capitale finale: e' la meno importante delle cinque decisioni di questo diario, e non
|
||||
merita ottimizzazione oltre la regola in una riga.
|
||||
|
||||
Verificato che un deposito **non resta strozzato**: col cap dinamico attivo (`_cap`), cap =
|
||||
equity/2 = esattamente il nozionale massimo che il book puo' chiedere per asset.
|
||||
|
||||
## 6. Le cinque decisioni, in ordine di quanto pesano
|
||||
|
||||
| decisione | effetto misurato |
|
||||
|---|---|
|
||||
| **versare o no** | da **mai** a 16 anni |
|
||||
| **quando** (front vs back, stesso totale) | **6x** sul capitale finale |
|
||||
| **quanto presto smettere** | 5 anni = 25% dei soldi, 58% del risultato |
|
||||
| piatto vs crescente | 24% a pari soldi |
|
||||
| frequenza | 1-3% |
|
||||
|
||||
E sopra tutte, fuori scala: **a p=5% di rischio venue il risultato mediano e' zero comunque.**
|
||||
|
||||
## 7. Regole trasferibili
|
||||
|
||||
1. **Un piano di accumulo si giudica sulle sue deviazioni, non sul caso nominale.** Piatto,
|
||||
ininterrotto e per sempre e' l'unico scenario che non succede.
|
||||
2. **Confrontare piani di taglia diversa richiede una metrica normalizzata** (`$ finale / $
|
||||
versato`), altrimenti "versa di piu'" vince sempre e non si impara niente.
|
||||
3. **Un vantaggio calcolato ignorando un rischio noto va ri-misurato con quel rischio dentro**,
|
||||
anche quando ci si aspetta che regga — qui reggeva, ma la colonna p=5% ha prodotto il risultato
|
||||
piu' importante del filone.
|
||||
4. **Quando l'obiettivo dichiarato non e' raggiungibile, la tabella utile e' quella inversa**:
|
||||
non "quando arrivo a X" ma "cosa compro con quello che ho".
|
||||
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python
|
||||
"""r0726_deposits.py — il piano dei VERSAMENTI, sotto le ipotesi che il piano non ha mai fatto.
|
||||
|
||||
CONTESTO. La rivalutazione di fine giornata ha misurato che il book e' dentro il suo plateau su
|
||||
ogni asse (ottimizzare il peso = +0.030 Sharpe, gate fallito) mentre i versamenti valgono la
|
||||
differenza fra **mai** e **16 anni**. Quindi il vincolo binding non e' piu' la strategia.
|
||||
|
||||
Ma tutte le traiettorie calcolate finora (25/07 e 26/07) assumono la stessa cosa:
|
||||
**versamento PIATTO, ININTERROTTO, PER SEMPRE**. E' l'ipotesi meno realistica del piano intero.
|
||||
Qui si misurano le quattro deviazioni che contano davvero, tutte con la stessa macchineria
|
||||
(block bootstrap sui ritorni REALI del book live, fattore d'ancora ×0.89 misurato).
|
||||
|
||||
(A) INTERRUZIONE — si versa per K anni e poi si smette. E' la deviazione piu' probabile di
|
||||
tutte (lavoro, spese, vita). Quanto costa smettere, e quando smettere fa meno male?
|
||||
(B) CRESCITA — €150/mese che crescono del 5%/anno contro €250/mese piatti. Piu' realistico
|
||||
di un importo fisso per vent'anni, e la domanda e' se conviene A PARI TOTALE VERSATO.
|
||||
(C) FRONT vs BACK LOADING — stesso totale, calendario diverso. Dice se vale la pena
|
||||
sforzarsi ADESSO invece che dopo, e di quanto.
|
||||
(D) LA DOMANDA INVERSA — invece di "quando arrivo a €50/g", **"che rendita compro con quello
|
||||
che posso permettermi?"**. Se €50/g non e' raggiungibile, il numero utile e' l'altro.
|
||||
|
||||
uv run python scripts/research/r0726_deposits.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 r0726_capwall_refresh as WR # noqa: E402
|
||||
|
||||
DELUCK = 0.89 # fattore d'ancora MISURATO (r0726_deluck_factor), non scelto a occhio
|
||||
START = 600.0
|
||||
N_PATHS = 4000
|
||||
BLOCK = 20
|
||||
SEED = 20260726
|
||||
|
||||
|
||||
def deluck_returns() -> np.ndarray:
|
||||
"""Ritorni giornalieri del book live con il drift ridotto del fattore d'ancora misurato."""
|
||||
base = CC.book_series("hourly")
|
||||
mu = float(base.values.mean())
|
||||
return (base - (1.0 - DELUCK) * mu).values.astype(float)
|
||||
|
||||
|
||||
def simulate(paths: np.ndarray, schedule: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Capitale finale e totale versato, dato un CALENDARIO di versamenti giornaliero (USD/giorno).
|
||||
|
||||
`schedule[t]` = quanto entra il giorno t. Vettoriale su tutti i path insieme.
|
||||
Ritorna (capitale finale per path, totale versato — uguale per tutti, ma ritornato come
|
||||
scalare replicato per rendere impossibile confrontare per sbaglio grandezze diverse).
|
||||
"""
|
||||
n_paths, n_days = paths.shape
|
||||
cap = np.full(n_paths, START)
|
||||
for t in range(n_days):
|
||||
cap = cap * (1.0 + paths[:, t])
|
||||
if schedule[t]:
|
||||
cap += schedule[t]
|
||||
return cap, np.full(n_paths, START + float(schedule.sum()))
|
||||
|
||||
|
||||
def monthly_schedule(n_days: int, eur_month: float, stop_year: float | None = None,
|
||||
growth: float = 0.0) -> np.ndarray:
|
||||
"""Calendario: versamento ogni 30 giorni, opzionalmente crescente e/o interrotto."""
|
||||
s = np.zeros(n_days)
|
||||
for t in range(30, n_days, 30):
|
||||
if stop_year is not None and t >= stop_year * 365:
|
||||
break
|
||||
yrs = t / 365.0
|
||||
s[t] = eur_month * CC.EURUSD * ((1.0 + growth) ** yrs)
|
||||
return s
|
||||
|
||||
|
||||
def rent_from_capital(cap: np.ndarray, perp_rate: float) -> np.ndarray:
|
||||
"""Rendita NETTA in €/giorno da un capitale, al tasso di prelievo perpetuo misurato."""
|
||||
return cap * perp_rate * (1 - CC.TAX_RATE) / 365.0 / CC.EURUSD
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(" r0726 — I VERSAMENTI: interruzione, crescita, calendario, e la domanda inversa")
|
||||
print("=" * 100)
|
||||
|
||||
r = deluck_returns()
|
||||
gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE)
|
||||
_, perp, wall = WR.perp_and_wall(r, 1.0, gross)
|
||||
print(f" book live de-luckato ×{DELUCK:.2f} | rendita perpetua {perp:.2%} | "
|
||||
f"muro €{CC.TARGET_EUR_DAY:.0f}/g = ${wall:,.0f}")
|
||||
print(f" block bootstrap, {N_PATHS} path, blocchi da {BLOCK} giorni, partenza ${START:.0f}")
|
||||
|
||||
rng = np.random.default_rng(SEED)
|
||||
YEARS = 20
|
||||
n_days = 365 * YEARS
|
||||
paths = CC._boot_paths(r, N_PATHS, n_days, BLOCK, rng)
|
||||
|
||||
# ------------------------------------------------------------ (A) interruzione
|
||||
print("\n" + "=" * 100)
|
||||
print(" (A) SMETTERE DI VERSARE — €250/mese per K anni, poi stop. Orizzonte 20 anni.")
|
||||
print("=" * 100)
|
||||
print(f"\n {'versi per':>10}{'totale versato':>16}{'cap. mediano a 20a':>21}"
|
||||
f"{'P(muro entro 20a)':>19}{'rendita mediana':>17}")
|
||||
ref = None
|
||||
for k in (3, 5, 8, 10, 15, 20):
|
||||
sch = monthly_schedule(n_days, 250.0, stop_year=k)
|
||||
cap, paid = simulate(paths, sch)
|
||||
p_wall = float((cap >= wall).mean())
|
||||
rent = float(np.median(rent_from_capital(cap, perp)))
|
||||
if k == 20:
|
||||
ref = (float(np.median(cap)), p_wall)
|
||||
print(f" {k:>8}a {paid[0]:>15,.0f}${np.median(cap):>20,.0f}"
|
||||
f"{p_wall:>18.1%}{rent:>15.2f} €/g")
|
||||
print("\n Lettura: la curva NON e' proporzionale al totale versato — gli anni iniziali")
|
||||
print(" valgono di piu' perche' compongono piu' a lungo. Smettere presto costa il doppio")
|
||||
print(" di quanto suggerisca il totale mancante.")
|
||||
|
||||
# ------------------------------------------------------------ (B) crescita
|
||||
print("\n" + "=" * 100)
|
||||
print(" (B) VERSAMENTO CRESCENTE vs PIATTO — a confronto anche A PARI TOTALE VERSATO")
|
||||
print("=" * 100)
|
||||
plans = [("piatto €250", 250.0, 0.0), ("piatto €300", 300.0, 0.0),
|
||||
("€150 +5%/anno", 150.0, 0.05), ("€200 +5%/anno", 200.0, 0.05),
|
||||
("€150 +8%/anno", 150.0, 0.08)]
|
||||
print(f"\n {'piano':<16}{'totale versato':>16}{'cap. mediano':>15}"
|
||||
f"{'P(muro)':>10}{'$ finale / $ versato':>22}")
|
||||
for lab, eur, g in plans:
|
||||
sch = monthly_schedule(n_days, eur, growth=g)
|
||||
cap, paid = simulate(paths, sch)
|
||||
print(f" {lab:<16}{paid[0]:>15,.0f}${np.median(cap):>14,.0f}"
|
||||
f"{float((cap >= wall).mean()):>10.1%}{np.median(cap)/paid[0]:>21.2f}x")
|
||||
print("\n L'ultima colonna e' la METRICA GIUSTA per confrontare piani di taglia diversa:")
|
||||
print(" quanto capitale finale compra ogni dollaro versato. Un piano crescente versa")
|
||||
print(" di piu' TARDI, quindi quei dollari compongono meno.")
|
||||
|
||||
# ------------------------------------------------------------ (C) calendario
|
||||
print("\n" + "=" * 100)
|
||||
print(" (C) STESSO TOTALE, CALENDARIO DIVERSO — conviene sforzarsi adesso?")
|
||||
print("=" * 100)
|
||||
TOT = 250.0 * 12 * 20 # totale del piano di riferimento, in EUR
|
||||
print(f"\n Totale fissato: €{TOT:,.0f} (= €250/mese per 20 anni), distribuito diversamente:")
|
||||
print(f"\n {'calendario':<28}{'cap. mediano':>15}{'P(muro)':>10}{'vs piatto':>12}")
|
||||
layouts = [("piatto su 20 anni", 20, 0), ("tutto nei primi 10 anni", 10, 0),
|
||||
("tutto nei primi 5 anni", 5, 0), ("solo negli ultimi 10 anni", 10, 10)]
|
||||
base_med = None
|
||||
scheds = {}
|
||||
for lab, span, delay in layouts:
|
||||
eur_m = TOT / (span * 12)
|
||||
s = np.zeros(n_days)
|
||||
for t in range(30, n_days, 30):
|
||||
if delay * 365 <= t < (delay + span) * 365:
|
||||
s[t] = eur_m * CC.EURUSD
|
||||
scheds[lab] = s
|
||||
cap, paid = simulate(paths, s)
|
||||
med = float(np.median(cap))
|
||||
if base_med is None:
|
||||
base_med = med
|
||||
print(f" {lab:<28}${med:>14,.0f}{float((cap >= wall).mean()):>10.1%}"
|
||||
f"{med/base_med:>11.2f}x")
|
||||
print("\n ⚠️ Questo NON dice 'versa tutto subito': dice quanto vale il tempo. Un piano")
|
||||
print(" che non riesci a sostenere non e' un piano — il confronto serve a scegliere fra")
|
||||
print(" calendari SOSTENIBILI, non a giustificare uno sforzo che si rompe al terzo anno.")
|
||||
|
||||
# --- e il vantaggio del front-loading regge al RISCHIO DI VENUE? ---
|
||||
# Anticipare i versamenti mette PIU' capitale sull'exchange PRIMA: il numero sopra e'
|
||||
# calcolato senza il rischio che il conto sparisca, che e' proprio il rischio che cresce con
|
||||
# quanto tieni li' e per quanto tempo (r0726_venue_risk). Va misurato, non assunto.
|
||||
print(f"\n Lo stesso confronto CON il rischio di venue (salto a zero a probabilita' annua p,")
|
||||
print(f" 100% Deribit come da decisione del 26/07):")
|
||||
print(f"\n {'calendario':<28}" + "".join(f"{'p=' + f'{p:.1%}':>16}" for p in (0.005, 0.02, 0.05)))
|
||||
rng_v = np.random.default_rng(SEED + 1)
|
||||
for lab, span, delay in layouts:
|
||||
cells = []
|
||||
for p in (0.005, 0.02, 0.05):
|
||||
haz = 1.0 - (1.0 - p) ** (1.0 / 365.0) # hazard giornaliero -> p annua
|
||||
alive = rng_v.random((N_PATHS, n_days)) > haz
|
||||
surv = np.cumprod(alive, axis=1) # 1 finche' vivo, 0 dopo il fallimento
|
||||
cap = np.full(N_PATHS, START)
|
||||
s = scheds[lab]
|
||||
for t in range(n_days):
|
||||
cap = cap * (1.0 + paths[:, t])
|
||||
if s[t]:
|
||||
cap += s[t]
|
||||
cap = cap * surv[:, t] # il conto sparisce col saldo dentro
|
||||
cells.append(float(np.median(cap)))
|
||||
print(f" {lab:<28}" + "".join(f"${c:>15,.0f}" for c in cells))
|
||||
print("\n Il front-loading resta avanti perche' il rischio di venue colpisce il TEMPO, non")
|
||||
print(" il calendario dei versamenti: anticipare non aumenta la probabilita' di essere")
|
||||
print(" colpiti, aumenta solo quanto c'e' dentro quando succede — e nel frattempo ha")
|
||||
print(" composto di piu'. La conclusione non si ribalta, ma il margine si assottiglia.")
|
||||
|
||||
# ------------------------------------------------------------ (D) domanda inversa
|
||||
print("\n" + "=" * 100)
|
||||
print(" (D) LA DOMANDA INVERSA — che rendita compra quello che posso permettermi?")
|
||||
print("=" * 100)
|
||||
print("\n Rendita NETTA mediana in €/giorno, per versamento mensile e orizzonte:")
|
||||
horizons = (5, 10, 15, 20)
|
||||
print(f"\n {'€/mese':>8}" + "".join(f"{h:>10}a" for h in horizons) + f"{' (a 20a: P(€50/g))':>20}")
|
||||
for eur in (100, 150, 250, 400, 600, 1000):
|
||||
cells = []
|
||||
p50g = None
|
||||
for h in horizons:
|
||||
nd = 365 * h
|
||||
pp = paths[:, :nd]
|
||||
sch = monthly_schedule(nd, eur)
|
||||
cap, _ = simulate(pp, sch)
|
||||
rent = rent_from_capital(cap, perp)
|
||||
cells.append(f"{np.median(rent):>9.2f}€")
|
||||
if h == 20:
|
||||
p50g = float((rent >= CC.TARGET_EUR_DAY).mean())
|
||||
print(f" {eur:>8}" + "".join(cells) + f"{p50g:>19.1%}")
|
||||
print("\n E' la tabella che riformula l'obiettivo: €50/g e' UN punto su questa griglia,")
|
||||
print(" non l'unico risultato che conta. Un piano che porta a €10-15/g in 15 anni non")
|
||||
print(" e' un fallimento del piano da €50 — e' un risultato diverso, e va scelto sapendo")
|
||||
print(" quanto costa il salto.")
|
||||
|
||||
# ------------------------------------------------------------ (E) frequenza
|
||||
print("\n" + "=" * 100)
|
||||
print(" (E) OGNI QUANTO VERSARE — l'unica di queste decisioni che si prende ogni mese")
|
||||
print("=" * 100)
|
||||
print("\n Compromesso: versare spesso = piu' tempo a mercato ma piu' costi fissi di")
|
||||
print(" trasferimento; versare di rado = meno costi ma i soldi restano fermi.")
|
||||
print(" ⚠️ Il costo per trasferimento NON e' stimato da me (dipende da rete e metodo):")
|
||||
print(" e' una SENSIBILITA'. Cerchia la riga che corrisponde al tuo costo reale.")
|
||||
EUR_YEAR = 250.0 * 12
|
||||
cadences = [("mensile", 30), ("bimestrale", 60), ("trimestrale", 91), ("semestrale", 182)]
|
||||
print(f"\n {'costo/tras.':>12}" + "".join(f"{lab:>16}" for lab, _ in cadences))
|
||||
for fee in (0.0, 1.0, 2.0, 5.0, 10.0, 25.0):
|
||||
cells = []
|
||||
for lab, days in cadences:
|
||||
per_dep = EUR_YEAR * CC.EURUSD * days / 365.0
|
||||
s = np.zeros(n_days)
|
||||
for t in range(days, n_days, days):
|
||||
s[t] = per_dep - fee # il costo fisso si paga a OGNI trasferimento
|
||||
cap, _ = simulate(paths, s)
|
||||
cells.append(float(np.median(cap)))
|
||||
best = max(range(len(cells)), key=lambda i: cells[i])
|
||||
row = "".join(f"{('*' if i == best else ' ') + '$' + format(c, ',.0f'):>16}"
|
||||
for i, c in enumerate(cells))
|
||||
print(f" {'$' + format(fee, '.2f'):>12}" + row)
|
||||
print("\n (* = migliore della riga). Il ribaltamento avviene dove il costo fisso supera")
|
||||
print(" il valore del tempo a mercato del versamento medio.")
|
||||
print(" ⚠️ Le differenze sono ~1-3% del capitale finale: e' la meno importante di queste")
|
||||
print(" cinque decisioni. Non vale la pena ottimizzarla oltre 'mensile, salvo costi alti'.")
|
||||
print("\n NB: a $600 il min-order del book e' $5 e il cap e' equity/2 — un deposito NON")
|
||||
print(" resta strozzato (vedi la guardia in `_cap`: col cap dinamico attivo, cap =")
|
||||
print(" equity/2 = esattamente il nozionale massimo che il book puo' chiedere).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,133 @@
|
||||
"""Test del piano versamenti (scripts/research/r0726_deposits.py).
|
||||
|
||||
Il test piu' importante e' `test_la_rendita_al_muro_e_esattamente_il_bersaglio`: lega la tabella
|
||||
della "domanda inversa" al muro calcolato altrove. Se le due macchinerie divergono, una delle due
|
||||
tabelle sta mentendo e nessun altro controllo se ne accorgerebbe.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0725_capcurve as CC # noqa: E402
|
||||
import r0726_deposits as DP # noqa: E402
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# calendario dei versamenti
|
||||
# ===========================================================================
|
||||
def test_versamento_mensile_conta_i_periodi_giusti():
|
||||
s = DP.monthly_schedule(365, 100.0)
|
||||
assert (s > 0).sum() == 12, "12 versamenti in un anno di 365 giorni a passo 30"
|
||||
assert s[0] == 0.0, "il primo versamento non e' al giorno 0 (li' c'e' il capitale iniziale)"
|
||||
|
||||
|
||||
def test_stop_year_interrompe_davvero():
|
||||
s = DP.monthly_schedule(365 * 10, 100.0, stop_year=3)
|
||||
ultimo = np.nonzero(s)[0].max()
|
||||
assert ultimo < 3 * 365
|
||||
assert (s > 0).sum() == pytest.approx(36, abs=1)
|
||||
|
||||
|
||||
def test_la_crescita_aumenta_gli_importi_nel_tempo():
|
||||
s = DP.monthly_schedule(365 * 10, 100.0, growth=0.05)
|
||||
nz = s[s > 0]
|
||||
assert nz[-1] > nz[0] * 1.5, "dopo ~10 anni al 5% l'importo deve essere >1.5x"
|
||||
piatto = DP.monthly_schedule(365 * 10, 100.0)
|
||||
assert s.sum() > piatto.sum()
|
||||
|
||||
|
||||
def test_crescita_zero_equivale_al_piatto():
|
||||
a = DP.monthly_schedule(365 * 5, 200.0, growth=0.0)
|
||||
b = DP.monthly_schedule(365 * 5, 200.0)
|
||||
assert np.allclose(a, b)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# simulazione
|
||||
# ===========================================================================
|
||||
def test_senza_versamenti_e_solo_capitalizzazione():
|
||||
paths = np.full((3, 100), 0.001)
|
||||
cap, paid = DP.simulate(paths, np.zeros(100))
|
||||
atteso = DP.START * (1.001 ** 100)
|
||||
assert cap[0] == pytest.approx(atteso, rel=1e-9)
|
||||
assert paid[0] == pytest.approx(DP.START)
|
||||
|
||||
|
||||
def test_a_rendimento_zero_il_capitale_e_la_somma_dei_versamenti():
|
||||
paths = np.zeros((2, 365))
|
||||
s = DP.monthly_schedule(365, 100.0)
|
||||
cap, paid = DP.simulate(paths, s)
|
||||
assert cap[0] == pytest.approx(DP.START + s.sum())
|
||||
assert paid[0] == pytest.approx(DP.START + s.sum())
|
||||
|
||||
|
||||
def test_il_totale_versato_include_il_capitale_iniziale():
|
||||
"""Confrontare un capitale finale con un 'versato' che dimentica i $600 iniziali gonfia il
|
||||
rapporto capitale/versato — errore gia' fatto una volta oggi nel contatore `paid`."""
|
||||
s = DP.monthly_schedule(365, 50.0)
|
||||
_, paid = DP.simulate(np.zeros((2, 365)), s)
|
||||
assert paid[0] == pytest.approx(DP.START + s.sum())
|
||||
assert paid[0] > s.sum()
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# il legame con il muro — il test che lega le due macchinerie
|
||||
# ===========================================================================
|
||||
def test_la_rendita_al_muro_e_esattamente_il_bersaglio():
|
||||
"""Un capitale pari al muro deve dare, per costruzione, esattamente TARGET_EUR_DAY.
|
||||
Se questa identita' si rompe, la tabella (D) e la tabella dei muri non parlano piu' della
|
||||
stessa cosa."""
|
||||
import r0726_capwall_refresh as WR
|
||||
|
||||
r = DP.deluck_returns()
|
||||
gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE)
|
||||
_, perp, wall = WR.perp_and_wall(r, 1.0, gross)
|
||||
rent = DP.rent_from_capital(np.array([wall]), perp)[0]
|
||||
assert rent == pytest.approx(CC.TARGET_EUR_DAY, rel=1e-6)
|
||||
|
||||
|
||||
def test_la_rendita_e_lineare_nel_capitale():
|
||||
r = DP.rent_from_capital(np.array([100_000.0, 200_000.0]), 0.10)
|
||||
assert r[1] == pytest.approx(2 * r[0])
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# i FINDING: se cambiano, e' una notizia
|
||||
# ===========================================================================
|
||||
def test_anticipare_batte_posticipare_a_pari_totale():
|
||||
"""Il finding di (C). Su ritorni a drift positivo deterministico e' un'identita' algebrica,
|
||||
e serve come controllo di direzione: se uscisse il contrario, la simulazione e' rotta."""
|
||||
n = 365 * 20
|
||||
paths = np.full((1, n), 0.0004)
|
||||
tot = 60_000.0 * CC.EURUSD
|
||||
presto, tardi = np.zeros(n), np.zeros(n)
|
||||
k = len(range(30, 365 * 5, 30))
|
||||
for t in range(30, 365 * 5, 30):
|
||||
presto[t] = tot / k
|
||||
k2 = len(range(365 * 10, n, 30))
|
||||
for t in range(365 * 10, n, 30):
|
||||
tardi[t] = tot / k2
|
||||
assert presto.sum() == pytest.approx(tardi.sum(), rel=1e-9)
|
||||
cap_p, _ = DP.simulate(paths, presto)
|
||||
cap_t, _ = DP.simulate(paths, tardi)
|
||||
assert cap_p[0] > cap_t[0] * 1.5
|
||||
|
||||
|
||||
def test_il_fattore_deluck_e_quello_misurato_non_quello_a_occhio():
|
||||
"""Il ×0.6 del 25/07 era scelto a occhio ed e' stato misurato ×0.87-0.91 il 26/07."""
|
||||
assert DP.DELUCK == 0.89
|
||||
|
||||
|
||||
def test_hazard_giornaliero_ricompone_alla_probabilita_annua():
|
||||
"""Il salto di venue: (1-haz)^365 deve dare (1-p). Sbagliare qui cambia tutte le colonne."""
|
||||
for p in (0.005, 0.01, 0.02, 0.05):
|
||||
haz = 1.0 - (1.0 - p) ** (1.0 / 365.0)
|
||||
assert (1.0 - haz) ** 365 == pytest.approx(1.0 - p, rel=1e-9)
|
||||
Reference in New Issue
Block a user