merge: piano versamenti — interruzione, crescita, calendario, domanda inversa

This commit is contained in:
Adriano Dal Pastro
2026-07-26 20:23:46 +00:00
4 changed files with 593 additions and 0 deletions
+51
View File
@@ -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.**
+154
View File
@@ -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".
+255
View File
@@ -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()
+133
View File
@@ -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)