research(capitale): il fisco durante l'ACCUMULO non era mai stato contato
r0807_growth_yearly.py: crescita anno per anno separando versamenti e guadagno, con e senza l'imposta d'accumulo. Nasce da "grafico della crescita per anno con versamento e con guadagno". TAX_RATE compariva in UN SOLO punto del progetto: la lordizzazione del bersaglio in fase di PRELIEVO. L'accumulo componeva al lordo per dieci o vent'anni. Costo (lump 5k + 500/mese): -15.7% a 5 anni, -29.8% a 10, -42.6% a 15 — replica coerente col 27/07 su lump 10k (-16.8/-31/-44%). L'errore e' COMPOSTO. Conseguenza: tutte le tabelle a 15-20 anni pubblicate in CLAUDE.md sono al lordo. Contro-intuitivo e misurato: versare di piu' RITARDA il sorpasso (l'anno in cui il guadagno cumulato supera il versato) — 7o anno a 500/mese, 8o a 800 — perche' alza l'asticella. I 300 in piu' comprano il traguardo (12o anno invece del 15o, P(bersaglio) a 15a da 73.2% a 99.0%), non il sorpasso. Riusa senza riscriverle la contabilita' fiscale di r0727_tasse.accumula e il block bootstrap di r0725_capcurve. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""r0807_growth_yearly.py — la crescita anno per anno, separando VERSAMENTI e GUADAGNO.
|
||||||
|
|
||||||
|
DOMANDA DELL'OPERATORE: *"grafico della crescita per anno con versamento e con guadagno"*.
|
||||||
|
|
||||||
|
COSA FA: prende il piano come sta oggi (€5.000 di versamento iniziale + €500/mese sul conto
|
||||||
|
Deribit) e per ogni anno riporta le due componenti del capitale:
|
||||||
|
|
||||||
|
capitale(t) = [START + lump + versamenti cumulati fino a t] + [guadagno cumulato]
|
||||||
|
\_______________ deterministico ______________/ \___ dal book ___/
|
||||||
|
|
||||||
|
La prima e' esatta (i bonifici sono decisi, non estratti); la seconda e' la MEDIANA fra i path
|
||||||
|
del bootstrap. ⚠️ Quindi la riga "guadagno" NON e' il guadagno di un percorso singolo: e'
|
||||||
|
`mediana(capitale) - versato`, cioe' quanto ha reso il book nel percorso mediano di ogni anno.
|
||||||
|
La banda p25-p75 dice quanto quella riga puo' spostarsi.
|
||||||
|
|
||||||
|
DUE REGIMI, SUGLI STESSI PATH (confronto appaiato):
|
||||||
|
* `lordo` — il modello pubblicato in CLAUDE.md: l'accumulo compone senza imposte;
|
||||||
|
* `netto` — con l'imposta annua sulle plusvalenze (33%) + patrimoniale 0.2% sul valore,
|
||||||
|
cioe' la correzione misurata il 2026-07-27 (`r0727_tasse.py`), che vale -31%
|
||||||
|
di capitale a 10 anni e -44% a 15.
|
||||||
|
|
||||||
|
Riusa senza riscrivere: `r0725_capcurve._boot_paths` (block bootstrap), `r0726_venue_risk.
|
||||||
|
venue_series` (serie del book live alle ancore canoniche, gia' de-luckata x0.89) e la
|
||||||
|
contabilita' fiscale di `r0727_tasse.accumula` (stesse assunzioni, stesso carry a 4 anni).
|
||||||
|
|
||||||
|
⚠️ Le assunzioni fiscali sono quelle dichiarate in r0727_tasse.py e NON sono un parere fiscale.
|
||||||
|
|
||||||
|
uv run python scripts/research/r0807_growth_yearly.py [--json PATH]
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
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_venue_risk as VR # noqa: E402
|
||||||
|
import r0727_lumpsum_split as LS # noqa: E402
|
||||||
|
import r0727_tasse as TX # noqa: E402
|
||||||
|
|
||||||
|
LUMP_EUR = 5_000.0 # il versamento iniziale previsto (config/live.json, nota del 26/07)
|
||||||
|
DEP_EUR = 500.0 # il versamento mensile previsto
|
||||||
|
YEARS = 15
|
||||||
|
N_PATHS = 4000
|
||||||
|
BLOCK = 20
|
||||||
|
SEED = 807
|
||||||
|
|
||||||
|
|
||||||
|
def traiettoria(r: np.ndarray, lump_eur: float, dep_eur: float, years: int,
|
||||||
|
aliquota: float, patrimoniale: float,
|
||||||
|
n_paths: int = N_PATHS, seed: int = SEED) -> list[dict]:
|
||||||
|
"""Accumulo giorno per giorno, con fotografia a fine di ogni anno.
|
||||||
|
|
||||||
|
Stessa meccanica di `r0727_tasse.accumula` (deposito ogni 30 giorni, imposta annua sulla
|
||||||
|
variazione di valore al netto dei versamenti, minusvalenze in carry 4 anni), ma qui
|
||||||
|
interessa la SERIE annuale, non il solo valore finale.
|
||||||
|
"""
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
n_days = years * 365
|
||||||
|
paths = CC._boot_paths(r, n_paths, n_days, BLOCK, rng)
|
||||||
|
|
||||||
|
dep = dep_eur * CC.EURUSD
|
||||||
|
cap = np.full(n_paths, LS.START + lump_eur * CC.EURUSD)
|
||||||
|
versato = np.full(n_paths, LS.START + lump_eur * CC.EURUSD) # deterministico, ma tenuto
|
||||||
|
hit = np.zeros(n_paths, bool) # traguardo toccato mai
|
||||||
|
carry = np.zeros((n_paths, TX.CARRY_ANNI))
|
||||||
|
imposte = np.zeros(n_paths)
|
||||||
|
anno_start = cap.copy()
|
||||||
|
versato_anno = np.zeros(n_paths)
|
||||||
|
|
||||||
|
out: list[dict] = []
|
||||||
|
for t in range(n_days):
|
||||||
|
cap *= (1.0 + paths[:, t])
|
||||||
|
hit |= cap >= LS.TARGET
|
||||||
|
if t % 30 == 0 and t > 0 and dep > 0:
|
||||||
|
cap += dep
|
||||||
|
versato += dep
|
||||||
|
versato_anno += dep
|
||||||
|
if (t + 1) % 365 == 0:
|
||||||
|
plus = cap - anno_start - versato_anno
|
||||||
|
if aliquota > 0:
|
||||||
|
disponibili = carry.sum(axis=1)
|
||||||
|
usate = np.minimum(np.maximum(plus, 0.0), disponibili)
|
||||||
|
imponibile = np.maximum(plus, 0.0) - usate
|
||||||
|
resid = usate.copy()
|
||||||
|
for k in range(TX.CARRY_ANNI - 1, -1, -1):
|
||||||
|
presa = np.minimum(carry[:, k], resid)
|
||||||
|
carry[:, k] -= presa
|
||||||
|
resid -= presa
|
||||||
|
dovuta = imponibile * aliquota
|
||||||
|
cap -= np.minimum(dovuta, cap)
|
||||||
|
imposte += dovuta
|
||||||
|
carry = np.roll(carry, 1, axis=1)
|
||||||
|
carry[:, 0] = np.maximum(-plus, 0.0)
|
||||||
|
if patrimoniale > 0:
|
||||||
|
p = cap * patrimoniale
|
||||||
|
cap -= np.minimum(p, cap)
|
||||||
|
imposte += p
|
||||||
|
anno_start = cap.copy()
|
||||||
|
versato_anno[:] = 0.0
|
||||||
|
|
||||||
|
anno = (t + 1) // 365
|
||||||
|
vers = float(versato[0]) # identico su ogni path: bonifici decisi
|
||||||
|
med = float(np.median(cap))
|
||||||
|
out.append(dict(
|
||||||
|
anno=anno,
|
||||||
|
versato=vers,
|
||||||
|
capitale_p25=float(np.percentile(cap, 25)),
|
||||||
|
capitale_med=med,
|
||||||
|
capitale_p75=float(np.percentile(cap, 75)),
|
||||||
|
guadagno_med=med - vers,
|
||||||
|
imposte_med=float(np.median(imposte)),
|
||||||
|
p_traguardo=float(hit.mean()),
|
||||||
|
))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def _riga(x: dict) -> str:
|
||||||
|
return (f" {x['anno']:>4}{x['versato']:>14,.0f}${x['guadagno_med']:>16,.0f}$"
|
||||||
|
f"{x['capitale_med']:>16,.0f}${x['capitale_p25']:>15,.0f}$"
|
||||||
|
f"{x['capitale_p75']:>15,.0f}${x['p_traguardo']:>13.1%}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--json", type=str, default="")
|
||||||
|
ap.add_argument("--lump", type=float, default=LUMP_EUR, help="versamento iniziale in EUR")
|
||||||
|
ap.add_argument("--dep", type=float, default=DEP_EUR, help="versamento mensile in EUR")
|
||||||
|
ap.add_argument("--anni", type=int, default=YEARS)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
lump_eur, dep_eur, years = args.lump, args.dep, args.anni
|
||||||
|
r = VR.venue_series()["Deribit"].values.astype(float)
|
||||||
|
|
||||||
|
print("=" * 108)
|
||||||
|
print(" r0807 — CRESCITA ANNO PER ANNO: quanto e' versamento e quanto e' guadagno")
|
||||||
|
print("=" * 108)
|
||||||
|
print(f" piano: START ${LS.START:,.0f} + lump €{lump_eur:,.0f} + €{dep_eur:,.0f}/mese · "
|
||||||
|
f"orizzonte {years} anni · {N_PATHS:,} path, blocchi {BLOCK}g")
|
||||||
|
print(f" book live TP01+SKH01 75/25 alle ancore canoniche, de-luckato ×{VR.DELUCK} · "
|
||||||
|
f"bersaglio ${LS.TARGET:,.0f}")
|
||||||
|
|
||||||
|
lordo = traiettoria(r, lump_eur, dep_eur, years, 0.0, 0.0)
|
||||||
|
netto = traiettoria(r, lump_eur, dep_eur, years, 0.33, TX.PATRIMONIALE)
|
||||||
|
|
||||||
|
for nome, serie in (("LORDO — il modello pubblicato (nessuna imposta durante l'accumulo)", lordo),
|
||||||
|
("NETTO — plusvalenze 33% annue + patrimoniale 0.2% (r0727_tasse)", netto)):
|
||||||
|
print("\n" + "-" * 108)
|
||||||
|
print(f" {nome}")
|
||||||
|
print("-" * 108)
|
||||||
|
print(f" {'anno':>4}{'versato':>15}{'guadagno med':>17}{'capitale med':>17}"
|
||||||
|
f"{'p25':>16}{'p75':>16}{'P(traguardo)':>14}")
|
||||||
|
for x in serie:
|
||||||
|
print(_riga(x))
|
||||||
|
|
||||||
|
print("\n" + "=" * 108)
|
||||||
|
print(" LETTURA")
|
||||||
|
print("=" * 108)
|
||||||
|
for a in [x for x in (5, 10, 15) if x <= years]:
|
||||||
|
lo = next(x for x in lordo if x["anno"] == a)
|
||||||
|
ne = next(x for x in netto if x["anno"] == a)
|
||||||
|
quota_lo = lo["guadagno_med"] / lo["capitale_med"] if lo["capitale_med"] > 0 else 0.0
|
||||||
|
quota_ne = ne["guadagno_med"] / ne["capitale_med"] if ne["capitale_med"] > 0 else 0.0
|
||||||
|
print(f" a {a:>2} anni · versato ${lo['versato']:,.0f} → capitale mediano "
|
||||||
|
f"${lo['capitale_med']:,.0f} lordo (guadagno = {quota_lo:.0%}) · "
|
||||||
|
f"${ne['capitale_med']:,.0f} netto fisco (guadagno = {quota_ne:.0%}) · "
|
||||||
|
f"costo del fisco {ne['capitale_med'] / lo['capitale_med'] - 1:+.1%}")
|
||||||
|
print("\n ⚠️ 'guadagno' = mediana(capitale) − versato: e' il rendimento del percorso MEDIANO,")
|
||||||
|
print(" non di un percorso singolo. La banda p25-p75 dice di quanto puo' spostarsi.")
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
Path(args.json).write_text(json.dumps(
|
||||||
|
dict(piano=dict(start=LS.START, lump_eur=lump_eur, dep_eur=dep_eur,
|
||||||
|
eurusd=CC.EURUSD, anni=years, n_paths=N_PATHS,
|
||||||
|
deluck=VR.DELUCK, target=LS.TARGET,
|
||||||
|
aliquota=0.33, patrimoniale=TX.PATRIMONIALE),
|
||||||
|
lordo=lordo, netto=netto), indent=1))
|
||||||
|
print(f"\n scritto {args.json}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user