merge: piano 5k+500/mese + strozzatura cap post-deposito

This commit is contained in:
Adriano Dal Pastro
2026-07-26 20:34:00 +00:00
4 changed files with 382 additions and 0 deletions
+83
View File
@@ -152,3 +152,86 @@ E sopra tutte, fuori scala: **a p=5% di rischio venue il risultato mediano e' ze
piu' importante del filone. piu' importante del filone.
4. **Quando l'obiettivo dichiarato non e' raggiungibile, la tabella utile e' quella inversa**: 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". non "quando arrivo a X" ma "cosa compro con quello che ho".
---
# Addendum — il piano dichiarato: €5.000 subito + €500/mese
L'operatore ha dato i numeri veri. Cambiano la scala: €5.000 portano il conto da **$597 a $6.047
(10.1x)** e €500/mese e' il doppio del livello tabulato sopra. Ricalcolato in dedicata
(`r0726_piano_5k500.py`), non estrapolato.
## Quando
```
traguardo ($272.061): p10 9.4a MEDIANA 11.6a p90 14.4a
P(entro 10a) 18% P(entro 12a) 57% P(entro 15a) 94% P(entro 20a) 100%
```
```
anno capitale p10 MEDIANO p90 rendita mediana
1$ 12,408$ 14,057$ 16,405 2.58 €/g
3$ 28,401$ 34,674$ 44,000 6.37 €/g
5$ 48,306$ 62,952$ 86,121 11.57 €/g
10$ 130,089$ 191,398$ 299,555 35.18 €/g
15$ 288,993$ 476,825$ 847,266 87.63 €/g
```
✅ **Validazione incrociata:** la variante "solo €500/mese da $600" da' **12.4 anni**, che e'
*esattamente* il numero pubblicato il 25/07 con macchineria diversa. Replica indipendente.
## Quanto vale il versamento iniziale: 0.8 anni
```
€5.000 subito (il piano) 11.6a
€5.000 spalmati sul 1° anno 11.7a
€5.000 al 5° anno 12.1a
niente lump, solo €500/mese 12.4a
```
⚠️ **Meno di quanto suggerisse il "fattore 6" del §3**, e la ragione e' aritmetica: €5.000 sono
**~10 mesi** di versamenti a €500, quindi comprano ~10 mesi. Il fattore 6 riguardava lo spostamento
di €60.000 su vent'anni. **Anticipare vale in proporzione a quanto si anticipa** — ovvio a
posteriori, ma la mia aspettativa era piu' alta e va corretta.
## Le soglie, e la data che compare
```
$ 3,000 GIA' SUPERATA il giorno 1 GTAA01 eseguibile
$ 5,000 GIA' SUPERATA il giorno 1 XSR01 eseguibile (gate pre-registrato 23/10)
$ 13,000 ~0.9 anni GTAA01 entra nel book deployable
$ 20,000 ~1.6 anni SOGLIA DELLA DECISIONE VENUE
$117,000 ~7.6 anni XS01 eseguibile
```
**La decisione "niente split fino a $20k" smette di essere un'ipotesi e diventa una data: ~19
mesi.** E le soglie $3k/$5k sono superate il primo giorno — il che NON autorizza ad anticipare il
gate XSR01 del 23/10 (anticiparlo e' selezione sull'hold-out), ma rende viva la sua domanda di
eseguibilita'.
## Con il rischio di venue dentro
```
p annua P(traguardo) P(perso TUTTO) cap. mediano 25a
0.0% 100.0% 0.0% $2,540,508
0.5% 94.3% 11.7% $2,304,596
1.0% 88.0% 23.2% $2,018,727
2.0% 78.8% 39.6% $1,464,013
5.0% 55.4% 72.0% $0
```
Il piano regge bene fino a p=2%. A p=5% il capitale mediano e' **zero**: e' la stessa conclusione
del §3, e vale a maggior ragione ora che il capitale in gioco e' 10x.
## ⚠️ AZIONE OPERATIVA PRIMA DEL VERSAMENTO — il cap fisso diventa una strozzatura
`src/live/book._cap` usa il cap **dinamico** (equity/2) solo quando l'equity reale e' leggibile;
altrimenti ripiega su `max_notional_per_asset_usd = $300`. **A $597 il fallback era INERTE**
(equity/2 = $298 ≈ $300). **Dopo il versamento l'equity/2 vale ~$3.023**, quindi un fallback
strozzerebbe il book a **~10% del target** — silenziosamente, con la sola allerta `eq_fallback`.
E' esattamente 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`: **fallisce** se
si deposita senza adeguare `max_notional_per_asset_usd`.
**Non eseguita**: e' un cambio di config su soldi veri, decisione dell'operatore.
+43
View File
@@ -250,6 +250,49 @@ def main() -> None:
print(" resta strozzato (vedi la guardia in `_cap`: col cap dinamico attivo, cap =") 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).") 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__": if __name__ == "__main__":
main() main()
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python
"""r0726_piano_5k500.py — il piano DICHIARATO dall'operatore: €5.000 subito + €500/mese.
Non e' una griglia di scenari: e' il calcolo del piano vero, perche' cambia la scala del problema.
Il conto oggi e' $597; €5.000 lo portano a ~$6.050 = **9x**, e €500/mese e' il doppio del livello
tabulato finora. Tre cose vanno ricalcolate e non estrapolate:
1. la TRAIETTORIA (quando si tocca il capitale-rendita, con che dispersione);
2. quanto vale il VERSAMENTO INIZIALE rispetto agli stessi soldi versati piu' tardi — il 26/07
ha misurato che il calendario vale un fattore 6, quindi un anticipo di €5.000 non e'
"un mese in piu'";
3. le SOGLIE OPERATIVE che si attraversano (GTAA01 a ~$13k, XS01 a ~$117k, XSR01 a ~$5k con
gate 23/10), perche' cambiano il book eseguibile.
Ipotesi dichiarate: fattore d'ancora ×0.89 MISURATO, book live TP01+SKH01 (i backtest sono a
0.10% RT), fisco 33%, EURUSD 1.09, bersaglio €50/g netti = $272.061.
uv run python scripts/research/r0726_piano_5k500.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
import r0726_deposits as DP # noqa: E402
CONTO_OGGI = 596.92 # equity reale letta dal conto Deribit
LUMP_EUR = 5_000.0
MENSILE_EUR = 500.0
N_PATHS = 6000
BLOCK = 20
YEARS = 25
SEED = 20260726
def run(paths: np.ndarray, start: float, dep_usd: float, wall: float,
lump_at_day: int | None = 0, lump: float = 0.0) -> dict:
"""Traiettoria con capitale iniziale, versamento mensile e un eventuale versamento una-tantum."""
n_paths, n_days = paths.shape
cap = np.full(n_paths, start)
hit = np.full(n_paths, -1, int)
snaps = {}
for t in range(n_days):
if lump and lump_at_day == t:
cap += lump
cap = cap * (1.0 + paths[:, t])
if t % 30 == 0 and t > 0:
cap += dep_usd
newly = (hit < 0) & (cap >= wall)
hit[newly] = t
if (t + 1) % 365 == 0:
snaps[(t + 1) // 365] = cap.copy()
yrs = np.where(hit >= 0, hit / 365.0, np.nan)
return dict(yrs=yrs, snaps=snaps, cap=cap)
def main() -> None:
print("=" * 100)
print(" IL PIANO: €5.000 subito + €500/mese — calcolo dedicato")
print("=" * 100)
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)
lump = LUMP_EUR * CC.EURUSD
dep = MENSILE_EUR * CC.EURUSD
start = CONTO_OGGI + lump
print(f" conto oggi ${CONTO_OGGI:,.2f}")
print(f" versamento subito €{LUMP_EUR:,.0f} = ${lump:,.0f} -> partenza ${start:,.0f} "
f"({start/CONTO_OGGI:.1f}x il conto attuale)")
print(f" versamento mensile€{MENSILE_EUR:,.0f} = ${dep:,.0f}")
print(f" bersaglio €{CC.TARGET_EUR_DAY:.0f}/g netti = ${wall:,.0f} "
f"(rendita perpetua {perp:.2%}, fattore d'ancora ×{DP.DELUCK})")
rng = np.random.default_rng(SEED)
n_days = 365 * YEARS
paths = CC._boot_paths(r, N_PATHS, n_days, BLOCK, rng)
# ------------------------------------------------------------------ traiettoria
res = run(paths, start, dep, wall)
yrs = res["yrs"]
fin = yrs[~np.isnan(yrs)]
print("\n" + "=" * 100)
print(" QUANDO")
print("=" * 100)
print(f"\n traguardo (${wall:,.0f}): p10 {np.percentile(fin,10):.1f}a "
f"MEDIANA {np.percentile(fin,50):.1f}a p90 {np.percentile(fin,90):.1f}a")
print(f"\n {'entro':>8}{'probabilita':>14}")
for h in (5, 8, 10, 12, 15, 20):
print(f" {h:>6}a {float((yrs <= h).sum())/len(yrs):>13.1%}")
print(f"\n {'anno':>6}{'capitale p10':>16}{'MEDIANO':>14}{'p90':>16}{'rendita mediana':>18}")
for y in (1, 2, 3, 5, 8, 10, 15, 20):
s = res["snaps"][y]
rent = float(np.median(s)) * perp * (1 - CC.TAX_RATE) / 365.0 / CC.EURUSD
print(f" {y:>6}${np.percentile(s,10):>15,.0f}${np.median(s):>13,.0f}"
f"${np.percentile(s,90):>15,.0f}{rent:>16.2f} €/g")
# ------------------------------------------------------------------ quanto vale l'anticipo
print("\n" + "=" * 100)
print(" QUANTO VALE VERSARE I €5.000 SUBITO invece che spalmati")
print("=" * 100)
alt = {
"€5.000 subito (il piano)": (start, 0, 0.0),
"€5.000 spalmati sul 1° anno": (CONTO_OGGI, None, 0.0),
"€5.000 al 5° anno": (CONTO_OGGI, 365 * 5, lump),
"niente lump, solo €500/mese": (CONTO_OGGI, None, 0.0),
}
print(f"\n {'variante':<30}{'mediana traguardo':>20}{f'cap. mediano {YEARS}a':>20}")
for lab, (st, day, lp) in alt.items():
if lab == "€5.000 spalmati sul 1° anno":
# 12 rate extra da lump/12 nel primo anno
cap = np.full(N_PATHS, CONTO_OGGI)
hit = np.full(N_PATHS, -1, int)
for t in range(n_days):
cap = cap * (1.0 + paths[:, t])
if t % 30 == 0 and t > 0:
cap += dep + (lump / 12.0 if t <= 365 else 0.0)
newly = (hit < 0) & (cap >= wall)
hit[newly] = t
y = np.where(hit >= 0, hit / 365.0, np.nan)
f = y[~np.isnan(y)]
print(f" {lab:<30}{np.percentile(f,50):>19.1f}a${np.median(cap):>19,.0f}")
continue
rr = run(paths, st, dep, wall, lump_at_day=day, lump=lp)
f = rr["yrs"][~np.isnan(rr["yrs"])]
print(f" {lab:<30}{np.percentile(f,50):>19.1f}a${np.median(rr['cap']):>19,.0f}")
# ------------------------------------------------------------------ soglie
print("\n" + "=" * 100)
print(" SOGLIE OPERATIVE CHE SI ATTRAVERSANO (e quando, al mediano)")
print("=" * 100)
soglie = [(3_000, "GTAA01 eseguibile (GTAA_MIN_CAPITAL) — ma nel BOOK entra a ~$13k"),
(5_000, "XSR01 diventa eseguibile (gate pre-registrato 2026-10-23)"),
(13_000, "GTAA01 entra nel book deployable (23% x cap >= $3k)"),
(20_000, "SOGLIA DELLA DECISIONE VENUE: si riapre lo split (26/07)"),
(117_000, "XS01 eseguibile (~$20k sulla gamba = 19 gambe HL)")]
med_by_day = None
for lv, nota in soglie:
# primo giorno in cui il capitale MEDIANO supera la soglia
if med_by_day is None:
cap = np.full(N_PATHS, start)
med_by_day = np.empty(n_days)
for t in range(n_days):
cap = cap * (1.0 + paths[:, t])
if t % 30 == 0 and t > 0:
cap += dep
med_by_day[t] = np.median(cap)
idx = np.nonzero(med_by_day >= lv)[0]
when = f"{idx[0]/365.0:.1f} anni" if len(idx) else "oltre l'orizzonte"
gia = " (GIA' SUPERATA il giorno 1)" if lv <= start else ""
print(f" ${lv:>7,} {when:>18}{gia} {nota}")
print("\n ⚠️ Le soglie sono attraversate dal capitale MEDIANO: meta' dei percorsi ci arriva")
print(" prima, meta' dopo. Non sono date, sono ordini di grandezza.")
# ------------------------------------------------------------------ rischio di venue
# Tutto quanto sopra assume che il conto ESISTA ancora. Dopo il lavoro del 26/07 quella
# non e' un'ipotesi neutra: e' la modalita' dominante di perdere tutto, e va messa dentro.
print("\n" + "=" * 100)
print(" LO STESSO PIANO, CON IL RISCHIO CHE IL CONTO SPARISCA")
print("=" * 100)
print(" Le tabelle sopra assumono che Deribit ci sia ancora. Il 26/07 ha misurato che e' la")
print(" modalita' DOMINANTE di perdere tutto, non diversificabile dagli sleeve.")
print(f"\n {'p annua':>9}{'P(traguardo)':>15}{'P(perso TUTTO)':>17}{'cap. mediano ' + str(YEARS) + 'a':>22}")
rng_v = np.random.default_rng(SEED + 99)
for p in (0.0, 0.005, 0.01, 0.02, 0.05):
haz = 1.0 - (1.0 - p) ** (1.0 / 365.0)
alive = np.cumprod(rng_v.random((N_PATHS, n_days)) > haz, axis=1) if p > 0 \
else np.ones((N_PATHS, n_days))
cap = np.full(N_PATHS, start)
hit = np.full(N_PATHS, -1, int)
for t in range(n_days):
cap = cap * (1.0 + paths[:, t])
if t % 30 == 0 and t > 0:
cap += dep
cap = cap * alive[:, t]
newly = (hit < 0) & (cap >= wall)
hit[newly] = t
print(f" {p:>8.1%}{float((hit >= 0).mean()):>14.1%}"
f"{float((alive[:, -1] == 0).mean()):>16.1%}${np.median(cap):>21,.0f}")
print("\n Nota: 'perso TUTTO' vuol dire il capitale, non solo i versamenti mancati — e con")
print(" un conto solo 'almeno un fallimento' COINCIDE con 'perso tutto'. Il tripwire")
print(" cablato oggi copre la parte di questo rischio che ha una FINESTRA (venue gated),")
print(" non quella improvvisa (furto di chiavi, sequestro).")
print(" ⚠️ E il traguardo cade DOPO la soglia dei $20k: la decisione 'niente split fino a")
print(" $20k' ha ora una data, non un'ipotesi.")
if __name__ == "__main__":
main()
+56
View File
@@ -131,3 +131,59 @@ def test_hazard_giornaliero_ricompone_alla_probabilita_annua():
for p in (0.005, 0.01, 0.02, 0.05): for p in (0.005, 0.01, 0.02, 0.05):
haz = 1.0 - (1.0 - p) ** (1.0 / 365.0) haz = 1.0 - (1.0 - p) ** (1.0 / 365.0)
assert (1.0 - haz) ** 365 == pytest.approx(1.0 - p, rel=1e-9) assert (1.0 - haz) ** 365 == pytest.approx(1.0 - p, rel=1e-9)
# ===========================================================================
# il PIANO dichiarato (€5.000 subito + €500/mese) — r0726_piano_5k500.py
# ===========================================================================
def test_il_piano_riproduce_il_numero_gia_pubblicato_senza_lump():
"""Controllo incrociato: 'solo €500/mese da $600' deve dare ~12.4 anni, che e' il numero
pubblicato il 25/07 con macchineria DIVERSA. Se diverge, una delle due tabelle mente."""
import r0725_capcurve as CC2
import r0726_capwall_refresh as WR2
import r0726_piano_5k500 as PL
r = DP.deluck_returns()
gross = CC2.TARGET_EUR_DAY * 365 * CC2.EURUSD / (1 - CC2.TAX_RATE)
_, _, wall = WR2.perp_and_wall(r, 1.0, gross)
rng = np.random.default_rng(PL.SEED)
paths = CC2._boot_paths(r, 1500, 365 * 25, PL.BLOCK, rng)
res = PL.run(paths, PL.CONTO_OGGI, 500.0 * CC2.EURUSD, wall)
fin = res["yrs"][~np.isnan(res["yrs"])]
assert 11.5 < float(np.percentile(fin, 50)) < 13.5
def test_il_versamento_iniziale_anticipa_il_traguardo():
"""Direzione: partire da piu' in alto non puo' allungare il tempo."""
import r0725_capcurve as CC2
import r0726_capwall_refresh as WR2
import r0726_piano_5k500 as PL
r = DP.deluck_returns()
gross = CC2.TARGET_EUR_DAY * 365 * CC2.EURUSD / (1 - CC2.TAX_RATE)
_, _, wall = WR2.perp_and_wall(r, 1.0, gross)
rng = np.random.default_rng(PL.SEED)
paths = CC2._boot_paths(r, 1500, 365 * 25, PL.BLOCK, rng)
dep = 500.0 * CC2.EURUSD
senza = PL.run(paths, PL.CONTO_OGGI, dep, wall)["yrs"]
con = PL.run(paths, PL.CONTO_OGGI + 5_000 * CC2.EURUSD, dep, wall)["yrs"]
m_senza = float(np.nanpercentile(senza, 50))
m_con = float(np.nanpercentile(con, 50))
assert m_con < m_senza
def test_il_cap_fisso_diventa_una_strozzatura_dopo_un_deposito():
"""⚠️ Il punto operativo trovato col piano: `_cap` ripiega su max_notional_per_asset_usd
quando l'equity reale non e' leggibile. A $600 era INERTE (equity/2 = $300 = cap fisso);
dopo un deposito da €5.000 il fallback strozzerebbe il book a ~10% del target.
Questo test FALLISCE se qualcuno deposita senza adeguare il cap fisso — che e' l'azione
gia' pre-registrata il 2026-07-02 ('al deposito alzare il cap a equity/2')."""
import json
cfg = json.loads((ROOT / "config" / "live.json").read_text())
fisso = float(cfg["max_notional_per_asset_usd"])
frac = float(cfg["max_notional_per_asset_frac"])
equity_attuale = 596.92
assert fisso >= equity_attuale * frac * 0.95, (
f"cap fisso ${fisso:.0f} molto sotto equity/2 = ${equity_attuale*frac:.0f}: se l'equity "
"reale non e' leggibile il book gira strozzato. Alzare max_notional_per_asset_usd.")