From 0a772906363e5c85d4a40d7b37a8003c7b03e78a Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Mon, 27 Jul 2026 19:51:42 +0000 Subject: [PATCH] =?UTF-8?q?research(capitale):=20il=20fisco=20durante=20l'?= =?UTF-8?q?ACCUMULO=20non=20era=20mai=20stato=20contato=20=E2=80=94=20vale?= =?UTF-8?q?=20il=2031%=20a=2010=20anni?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit r0727_tasse.py. TAX_RATE=0.33 compare in tutto il progetto in UN SOLO punto: la lordizzazione del bersaglio (fase di RENDITA). L'accumulo compone al lordo per dieci o vent'anni. 10.000 EUR + 500/mese, 10 anni, capitale mediano: nessuna imposta (modello pubblicato) 229.638$ P(entro 10a) 34.5% plus 26% 173.284$ 8.5% plus 33% + patrimoniale 0.2% 158.401$ 3.8% = -31% Versamento necessario a 10 anni (lump 10k): P=50% da 602 a 880 EUR/m; P=75% da 790 a 1.051. Cioe' +33%: il numero dato stamattina (790-870 EUR/m) era al lordo del fisco. L'errore e' COMPOSTO, non una tantum: -16.8% a 5 anni, -31% a 10, -44% a 15, -55% a 20. Assunzioni dichiarate (NON un parere fiscale): 33% cripto da L.199/2025 (misurato anche a 26%, perche' e' aperto se i derivati di sede estera seguano quel regime), minusvalenze riportabili 4 anni, 0.2% annuo sul valore. Il modello tassa la variazione ANNUA di valore, quindi anche la parte non realizzata a cavallo del 31/12: e' un LIMITE SUPERIORE rispetto alla pura realizzazione, ma il turnover del book lo rende stretto. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/research/r0727_tasse.py | 189 ++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 scripts/research/r0727_tasse.py diff --git a/scripts/research/r0727_tasse.py b/scripts/research/r0727_tasse.py new file mode 100644 index 0000000..fec1407 --- /dev/null +++ b/scripts/research/r0727_tasse.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python +"""r0727_tasse.py — il fisco durante l'ACCUMULO: la voce che nessuna traiettoria ha mai contato. + +IL BUCO. `TAX_RATE = 0.33` compare in tutto il progetto in UN SOLO punto: la lordizzazione del +bersaglio (per netto €50/giorno servono €74.6/giorno lordi, da cui il muro di $272.061) e la +conversione capitale->rendita. **L'accumulo compone al LORDO**: `cap *= (1 + r)` ogni giorno, per +dieci o vent'anni, senza che il fisco tocchi mai il capitale in crescita. + +Non e' un dettaglio contabile. Un book che apre e chiude posizioni ogni giorno **realizza** i +guadagni in continuazione: in Italia le plusvalenze si tassano per CASSA, anno per anno, non +alla fine. Quindi il modello sta capitalizzando soldi che ogni anno andrebbero versati, e +l'effetto composto di quell'errore cresce col tempo — proprio sull'orizzonte in cui si sta +decidendo. + +COSA SI MISURA QUI + (1) quanto costa la tassazione annua sulle plusvalenze realizzate; + (2) quanto costa in piu' l'imposta patrimoniale sul valore delle cripto-attivita'; + (3) di quanto si sposta il versamento mensile necessario per l'obiettivo a 10 anni. + +⚠️ ASSUNZIONI FISCALI DICHIARATE — NON SONO UN PARERE FISCALE, e vanno confermate: + * aliquota **33%** sulle plusvalenze cripto (l'assunzione gia' cablata nel progetto: + "L.199/2025, esenzione abolita dal 2026"). Si misura anche a **26%**, perche' e' aperta la + questione se i DERIVATI su cripto di una sede estera seguano il regime cripto o quello dei + redditi diversi di natura finanziaria: la differenza vale piu' di molte decisioni prese qui. + * **minusvalenze compensabili nei 4 periodi d'imposta successivi** (modellato: buffer a 4 anni). + * imposta **0.2% annua sul valore** delle cripto-attivita' detenute (analoga all'IVAFE). + * ⚠️ **il modello tassa la variazione ANNUA di valore** (fine anno − inizio anno − versamenti), + cioe' anche la parte NON realizzata di una posizione aperta a cavallo del 31 dicembre. E' + quindi un **LIMITE SUPERIORE** rispetto a una tassazione per pura realizzazione. Quanto sia + superiore dipende dal turnover: TP01 ribilancia ogni giorno e SKH01 chiude round-trip + discreti, quindi la quasi totalita' del risultato annuo E' realizzata entro l'anno — il vero + numero sta fra la riga senza imposte e questa, molto piu' vicino a questa. + + uv run python scripts/research/r0727_tasse.py +""" +from __future__ import annotations + +import sys +from pathlib import Path + +import numpy as np +import pandas as pd + +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 + +ALIQUOTE = (0.0, 0.26, 0.33) +PATRIMONIALE = 0.002 # 0.2% annuo sul valore (analoga IVAFE) +CARRY_ANNI = 4 # minusvalenze riportabili +N_PATHS = 4000 +BLOCK = 20 +SEED = 727 + + +def accumula(r: np.ndarray, lump_eur: float, dep_eur: float, years: int, + aliquota: float, patrimoniale: float = 0.0, + n_paths: int = N_PATHS, seed: int = SEED) -> dict: + """Accumulo con imposta ANNUA sulle plusvalenze realizzate + patrimoniale sul valore. + + La plusvalenza dell'anno e' `capitale_fine - capitale_inizio - versamenti_dell_anno` + (i versamenti non sono reddito). Le minusvalenze vanno in un buffer che scade dopo + `CARRY_ANNI` anni, come le minusvalenze da redditi diversi. + """ + 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) + hit = np.zeros(n_paths, bool) # traguardo toccato in QUALUNQUE momento + carry = np.zeros((n_paths, CARRY_ANNI)) # minusvalenze per anno di formazione + imposte = np.zeros(n_paths) + anno_start = cap.copy() + versato_anno = np.zeros(n_paths) + + 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_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 + # consuma il carry dal piu' vecchio + resid = usate.copy() + for k in range(CARRY_ANNI - 1, -1, -1): # dalla PIU' VECCHIA, che scade prima + 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) # invecchia: l'ultimo scade + 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 + + # stessa definizione delle altre tabelle: traguardo toccato in qualunque momento + return dict(median_end=float(np.median(cap)), p_reach=float(hit.mean()), + imposte=float(np.median(imposte)), + versato=LS.START + lump_eur * CC.EURUSD + dep * (n_days // 30)) + + +def dep_necessario(r: np.ndarray, lump_eur: float, years: int, aliquota: float, + patrimoniale: float, p_target: float = 0.50) -> float: + lo, hi = 0.0, 15_000.0 + for _ in range(14): + mid = 0.5 * (lo + hi) + if accumula(r, lump_eur, mid, years, aliquota, patrimoniale)["p_reach"] >= p_target: + hi = mid + else: + lo = mid + return 0.5 * (lo + hi) + + +def main() -> None: + series = VR.venue_series() + r = series["Deribit"].values.astype(float) + gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE) + + print("=" * 100) + print(" r0727 — IL FISCO DURANTE L'ACCUMULO (mai contato in nessuna traiettoria)") + print("=" * 100) + print(f" bersaglio ${LS.TARGET:,.0f} (gia' lordizzato al {CC.TAX_RATE:.0%} per il PRELIEVO) · " + f"book de-luckato ×{LS.DELUCK}") + print(f" ⚠️ il {CC.TAX_RATE:.0%} nel bersaglio riguarda la fase di RENDITA; qui si misura " + "cio' che manca: l'imposta") + print(" annua durante i 10 anni di crescita. Le due NON si sovrappongono.") + + # ------------------------------------------------------ (1) il costo, a 10 anni + print("\n" + "=" * 100) + print(" (1) QUANTO COSTA — €10.000 + €500/mese, orizzonte 10 anni") + print("=" * 100) + print(f"\n {'regime':<44}{'cap. mediano':>16}{'P(entro 10a)':>15}{'imposte pagate':>18}") + base = None + for al in ALIQUOTE: + for patr in ((0.0, PATRIMONIALE) if al > 0 else (0.0,)): + x = accumula(r, 10_000.0, 500.0, 10, al, patr) + base = base if base is not None else x["median_end"] + et = "nessuna imposta (il modello pubblicato)" if al == 0 else ( + f"plus {al:.0%}" + (f" + patrimoniale {patr:.1%}" if patr else "")) + print(f" {et:<44}{x['median_end']:>15,.0f}${x['p_reach']:>15.1%}" + f"{x['imposte']:>17,.0f}$") + x33 = accumula(r, 10_000.0, 500.0, 10, 0.33, PATRIMONIALE) + print(f"\n Costo del regime piu' severo vs il modello pubblicato: " + f"{x33['median_end'] / base - 1:+.1%} di capitale a 10 anni") + + # ------------------------------------------------------ (2) il versamento necessario + print("\n" + "=" * 100) + print(" (2) DI QUANTO SI SPOSTA IL VERSAMENTO NECESSARIO (P=50%, 10 anni)") + print("=" * 100) + for pt in (0.50, 0.75): + print(f"\n a P={pt:.0%}") + print(f" {'lump':>9}{'senza imposte':>16}{'plus 26%':>13}{'plus 33%':>13}" + f"{'33% + patrim.':>16}") + for lump in (5_000.0, 10_000.0): + cells = [dep_necessario(r, lump, 10, 0.0, 0.0, pt), + dep_necessario(r, lump, 10, 0.26, PATRIMONIALE, pt), + dep_necessario(r, lump, 10, 0.33, 0.0, pt), + dep_necessario(r, lump, 10, 0.33, PATRIMONIALE, pt)] + print(f" €{lump:>8,.0f}" + "".join(f"{c:>12,.0f}€" for c in cells)) + + # ------------------------------------------------------ (3) l'orizzonte + print("\n" + "=" * 100) + print(" (3) LO STESSO PIANO, PIU' AVANTI NEL TEMPO (€10.000 + €500/mese)") + print("=" * 100) + print(f"\n {'anni':>6}{'senza imposte':>17}{'con 33% + patrim.':>21}{'differenza':>14}") + for yrs in (5, 10, 15, 20): + a = accumula(r, 10_000.0, 500.0, yrs, 0.0, 0.0)["median_end"] + b = accumula(r, 10_000.0, 500.0, yrs, 0.33, PATRIMONIALE)["median_end"] + print(f" {yrs:>5}a{a:>16,.0f}${b:>20,.0f}${b / a - 1:>13.1%}") + print("\n ⚠️ Il divario si allarga col tempo: e' un errore COMPOSTO, non una tassa una tantum.") + print(" Ed e' il motivo per cui va corretto adesso e non 'quando ci arriviamo'.") + + +if __name__ == "__main__": + main()