Files
Adriano Dal Pastro 1227b2baab research: il piano dichiarato EUR 5.000 + EUR 500/mese, e la strozzatura del cap
L'operatore ha dato i numeri veri: EUR 5.000 subito + EUR 500/mese. Cambia la scala
(conto da $597 a $6.047 = 10.1x), quindi ricalcolato in dedicata invece che estrapolato.

QUANDO: traguardo EUR 50/g ($272.061) a p10 9.4a / MEDIANA 11.6a / p90 14.4a;
P(entro 15a) 94%, P(entro 20a) 100%. Rendita mediana: EUR 6.37/g a 3 anni, EUR 11.57/g a
5, EUR 35.18/g a 10, EUR 87.63/g a 15.

VALIDAZIONE INCROCIATA: la variante "solo EUR 500/mese da $600" da' 12.4 anni = esattamente
il numero pubblicato il 25/07 con macchineria diversa.

IL LUMP VALE 0.8 ANNI (12.4 -> 11.6), MENO di quanto suggerisse il "fattore 6" del
calendario, e la ragione e' aritmetica: EUR 5.000 sono ~10 mesi di versamenti a EUR 500,
quindi comprano ~10 mesi. Anticipare vale in proporzione a quanto si anticipa. La mia
aspettativa era piu' alta ed e' corretta.

SOGLIE: $3k e $5k superate il giorno 1 (GTAA01 e XSR01 diventano eseguibili); $13k a ~0.9
anni (GTAA01 entra nel book deployable); $20k a ~1.6 anni = LA DECISIONE VENUE DEL 26/07
SMETTE DI ESSERE UN'IPOTESI E DIVENTA UNA DATA (~19 mesi); $117k a ~7.6 anni (XS01).
Le soglie superate subito NON autorizzano ad anticipare il gate XSR01 del 23/10.

CON IL RISCHIO DI VENUE: P(traguardo) 100% -> 88% a p=1% (P(perso tutto) 23.2%); a p=5% il
capitale mediano e' ZERO. Il piano regge fino a p=2%.

⚠️ AZIONE OPERATIVA TROVATA (non eseguita, e' config su soldi veri): `book._cap` ripiega su
max_notional_per_asset_usd=$300 quando l'equity reale non e' leggibile. A $597 il fallback
era INERTE (equity/2 = $298 ~ $300); dopo il versamento equity/2 vale ~$3.023, quindi un
fallback strozzerebbe il book al ~10% del target, in silenzio. E' l'azione gia'
pre-registrata il 2026-07-02 ("al deposito alzare il cap a equity/2"). Cablato
test_il_cap_fisso_diventa_una_strozzatura_dopo_un_deposito, che FALLISCE se si deposita
senza adeguare il cap.

Aggiunta anche la sezione (F) frontiera di sostenibilita' a r0726_deposits.py: capitale e
rendita per (importo x anni sostenuti), e il confronto "tirare poco tempo vs comodo a
lungo" — EUR 400/m per 5a batte EUR 200/m per 20a, ma EUR 600/m per 3a perde contro
EUR 250/m per 15a.

Book, pesi, cron, config INVARIATI. 380 test verdi (+3).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 20:34:00 +00:00

299 lines
16 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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).")
# ------------------------------------------------------------ (F) frontiera di sostenibilita'
# "Quanto posso versare?" non ha una risposta da modello: dipende dal bilancio di chi versa.
# Ma la domanda DIETRO ("conviene tirare?") si': si misura come iso-risultato fra un importo
# alto tenuto poco e uno basso tenuto a lungo.
print("\n" + "=" * 100)
print(" (F) FRONTIERA DI SOSTENIBILITA' — conviene tirare, o restare comodi piu' a lungo?")
print("=" * 100)
print("\n Capitale mediano a 20 anni per (importo mensile × anni per cui riesci a tenerlo):")
amounts = (100, 150, 200, 250, 400, 600)
spans = (3, 5, 10, 15, 20)
print(f"\n {'€/mese':>8}" + "".join(f"{s:>12}a" for s in spans))
grid = {}
for eur in amounts:
cells = []
for sp in spans:
sch = monthly_schedule(n_days, float(eur), stop_year=sp)
cap, _ = simulate(paths, sch)
grid[(eur, sp)] = float(np.median(cap))
cells.append(f"${grid[(eur, sp)]:>11,.0f}")
print(f" {eur:>8}" + "".join(cells))
print("\n Rendita netta mediana corrispondente (€/giorno a 20 anni):")
print(f"\n {'€/mese':>8}" + "".join(f"{s:>12}a" for s in spans))
for eur in amounts:
cells = [f"{rent_from_capital(np.array([grid[(eur, sp)]]), perp)[0]:>11.2f}€"
for sp in spans]
print(f" {eur:>8}" + "".join(cells))
# il confronto che risponde alla domanda: tirare poco tempo vs comodo a lungo
print("\n IL CONFRONTO CHE DECIDE — stessa fatica percepita, esiti opposti:")
for hi, hi_yrs, lo, lo_yrs in ((400, 5, 200, 20), (600, 3, 250, 15), (400, 10, 250, 20)):
a, b = grid[(hi, hi_yrs)], grid[(lo, lo_yrs)]
tot_a = monthly_schedule(n_days, float(hi), stop_year=hi_yrs).sum()
tot_b = monthly_schedule(n_days, float(lo), stop_year=lo_yrs).sum()
win = "il PRIMO" if a > b else "il SECONDO"
print(f" €{hi}/m per {hi_yrs:>2}a (${tot_a:>7,.0f} versati) -> ${a:>9,.0f} vs "
f"€{lo}/m per {lo_yrs:>2}a (${tot_b:>7,.0f}) -> ${b:>9,.0f} vince {win}")
print("\n REGOLA CHE NE ESCE: l'importo massimo NON e' l'obiettivo. L'obiettivo e' il")
print(" massimo importo che sopravvive a un anno brutto — perche' l'unico modo di")
print(" perdere davvero e' smettere presto (§A: i primi 5 anni sono il 58% del")
print(" risultato), e si smette presto quando si e' versato piu' del sostenibile.")
if __name__ == "__main__":
main()