Vai al contenuto
PAPER SIAT 2026Apri la pagina della ricerca
Cryptoverso

Calcolatore del libro · C06

C06 — Attrito fiscale, perdite riportate e perdite scadute

Calcolatore 6 — L'attrito fiscale

Quaderno del capitolo «L'attrito che nessun backtest include» di La matematica di chi perde.

Tre numeri sui tuoi risultati annuali: il capitale finale pagando ogni anno, quello pagando alla fine, e le perdite che scadono senza essere state usate.

Questo quaderno non è consulenza fiscale. L'aliquota e gli anni di riporto sono due caselle da riempire: mettici quelle che ti riguardano, verificate alla fonte. Il meccanismo che il quaderno mostra — l'imposta versata presto smette di comporre — non dipende da quei numeri.

Per orientarti, il quadro italiano verificato ad agosto 2026 — da ricontrollare, perché cambia:

Su che cosaAliquota
Azioni, obbligazioni societarie, ETF, fondi, derivati26%
Titoli di Stato italiani e di Paesi in white list12,5%
Investimenti in un PIR mantenuti almeno 5 anni0%
Cripto-attività, plusvalenze realizzate dal 1° gennaio 202633%

Attenzione all'asimmetria degli ETF armonizzati: i guadagni sono redditi di capitale, le perdite redditi diversi, e le due categorie non si compensano fra loro.

Le righe marcate PROVA sono quelle da cambiare: cambiale e riesegui per vedere l'effetto. Il resto — comprese le righe marcate NON TOCCARE — serve a mantenere il risultato confrontabile con quello stampato nel libro.

Espandi lo script di questo passo
calc_06_fisco.py
python
import matplotlib.pyplot as plt
import numpy as np

from cvbook.dati import carica
from cvbook.lingua import t
from cvbook.regole import esegui, rottura

ALIQUOTA = 0.26        # ← l'aliquota che ti riguarda (0.33 sulle cripto dal 2026)
                       # PROVA / TRY: 0,26 · 0,33 · quella in vigore quando leggi
ANNI_RIPORTO = 4       # ← per quanti anni si possono riportare le perdite
                       # PROVA / TRY: 0 (nessun riporto, vedi esercizio 3) · 4
CAPITALE = 100_000.0   # ← il capitale di partenza
                       # PROVA / TRY: il tuo capitale reale

1. Non è l'aliquota, è il momento

Due persone identiche, stesso rendimento, stessa aliquota. L'unica differenza: la prima realizza e paga ogni anno, la seconda lascia correre e paga alla fine.

Due pannelli affiancati su un orizzonte di 30 anni. A sinistra due curve del capitale netto partendo da 1.000 euro: pagando l'imposta solo alla fine si arriva sopra i 12.000, pagandola ogni anno si resta sotto, e l'area fra le due e' ombreggiata. A destra il capitale in piu' ottenuto differendo, per tre tassi lordi annui: con 5% resta sotto 20%, con 10% arriva a circa 55%, con 20% supera 175% a trent'anni.

Due persone identiche, stessa aliquota: una paga ogni anno, l'altra alla fine.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Capitalizzazione a tasso costante con aliquota del 26% applicata ogni anno oppure una sola volta alla scadenza, e differenza fra i due capitali netti.

Output

aliquota usata: 26%

 orizzonte               5% lordo             10% lordo             20% lordo
        5a                  0.4%                 1.6%                 5.4%
       10a                  1.9%                 6.7%                21.8%
       20a                  7.5%                25.6%                81.1%
       30a                 16.3%                54.7%               179.9%

Il divario cresce con l'orizzonte e soprattutto con il RENDIMENTO. Chi ha rendimenti alti e orizzonti lunghi — cioe' esattamente la situazione che ogni strategia attiva promette — e' chi paga di piu' questo attrito.
Espandi lo script di questo passo
calc_06_fisco.py
python
def confronto(rendimento: float, anni: int, aliquota: float = ALIQUOTA):
    annuale = (1 + rendimento * (1 - aliquota)) ** anni
    lordo = (1 + rendimento) ** anni
    differita = 1 + (lordo - 1) * (1 - aliquota)
    return annuale, differita


anni = np.arange(0, 31)
annuale = np.array([confronto(0.10, int(a))[0] for a in anni]) * 1000
differita = np.array([confronto(0.10, int(a))[1] for a in anni]) * 1000

with avvio.figura("schermo"):
    fig, (sx, dx) = plt.subplots(1, 2, figsize=(12, 4.5))
    sx.plot(anni, differita, linewidth=2, label=t("imposta alla fine", "tax at the end"))
    sx.plot(anni, annuale, linewidth=2, linestyle="--", label=t("imposta ogni anno", "tax every year"))
    sx.fill_between(anni, annuale, differita, alpha=0.2)
    sx.set_xlabel(t("Anni", "Years"))
    sx.set_ylabel(t("Capitale netto (euro, da 1.000)", "Net capital (euros, starting from 1,000)"))
    sx.legend()

    for r in (0.05, 0.10, 0.20):
        divario = [(confronto(r, int(a))[1] / confronto(r, int(a))[0] - 1) * 100
                   for a in anni[1:]]
        dx.plot(anni[1:], divario, linewidth=2, label=t(f"{r:.0%} lordo annuo", f"{r:.0%} gross a year"))
    dx.set_xlabel(t("Orizzonte (anni)", "Horizon (years)"))
    dx.set_ylabel(t("Capitale in piu' differendo (%)", "Extra capital by deferring (%)"))
    dx.legend()
    plt.show()

print(t(f"aliquota usata: {ALIQUOTA:.0%}\n", f"tax rate used: {ALIQUOTA:.0%}\n"))
print(f"{t('orizzonte', 'horizon'):>10s} " + "".join(
    f"{r:>16.0%}" + t(" lordo", " gross") for r in (0.05, 0.10, 0.20)))
for a in (5, 10, 20, 30):
    valori = "".join(f"{confronto(r, a)[1] / confronto(r, a)[0] - 1:21.1%}"
                     for r in (0.05, 0.10, 0.20))
    print(f"{a:9d}" + t("a", "y") + f" {valori}")

print(t("\nIl divario cresce con l'orizzonte e soprattutto con il RENDIMENTO. Chi "
        "ha rendimenti alti e orizzonti lunghi — cioe' esattamente la situazione "
        "che ogni strategia attiva promette — e' chi paga di piu' questo attrito.",
        "\nThe gap grows with the horizon and above all with the RETURN. Whoever "
        "has high returns and long horizons — exactly the situation every active "
        "strategy promises — pays the most for this friction."))

2. Il guadagno tassabile non è il guadagno

Anno per anno, con le perdite riportabili e la loro scadenza.

Barre appaiate per ogni anno dal 2017 al 2026, in migliaia di euro a partire da 100.000: sopra lo zero il risultato lordo dell'anno, sotto l'imposta versata. Il 2020 e' l'anno piu' alto, con oltre 1.000 migliaia di euro di risultato lordo; il 2018, il 2022, il 2025 e il 2026 chiudono in perdita e non versano nulla, e il 2022 arriva a circa 750 sotto lo zero.

Anno per anno, il risultato lordo e l'imposta che ne segue.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Risultato annuo del capitale con imposta al 26% sull'imponibile, perdite riportate e scadenza del riporto applicate anno per anno.

Output

  anno          lordo     imponibile      imposta       capitale
  2017        183,434        183,434       47,693        235,741
  2018        -90,871              0            0        144,870
  2019        161,681         70,811       18,411        288,141
  2020      1,134,546      1,134,546      294,982      1,127,705
  2021        455,166        455,166      118,343      1,464,528
  2022       -675,768              0            0        788,760
  2023        685,611          9,843        2,559      1,471,811
  2024      1,015,488      1,015,488      264,027      2,223,272
  2025       -209,113              0            0      2,014,159
  2026       -189,684              0            0      1,824,475

capitale finale pagando ogni anno:       1,824,475
capitale finale pagando alla fine:       2,664,982
differenza:                                  46.1%

imposte versate:                           746,015
guadagno lordo complessivo:              3,466,193
guadagno realizzato da chi ha pagato:    2,470,490
aliquota EFFETTIVA sul guadagno:             30.2%
perdite mai usate (scadute):               398,797

Guarda l'aliquota effettiva: e' PIU' ALTA di quella nominale, e non per un'aliquota diversa. E' l'effetto delle perdite scadute — anni in rosso che non hanno mai trovato un anno positivo entro il termine — sommato al fatto che l'imposta versata presto smette di comporre. Il danno non viene dall'aliquota: viene dal momento.
Espandi lo script di questo passo
calc_06_fisco.py
python
def simula_imposta(rendimenti_annui, capitale=CAPITALE, aliquota=ALIQUOTA,
                   anni_riporto=ANNI_RIPORTO):
    """Imposta anno per anno, con riporto delle perdite e loro scadenza."""
    crediti: list[list[float]] = []
    righe, imposte_totali = [], 0.0

    for anno, r in rendimenti_annui:
        lordo = capitale * r
        if lordo >= 0:
            usato = 0.0
            for c in crediti:
                if anno - c[0] <= anni_riporto:
                    quota = min(c[1], lordo - usato)
                    c[1] -= quota
                    usato += quota
                    if usato >= lordo:
                        break
            imponibile = max(lordo - usato, 0.0)
            imposta = imponibile * aliquota
        else:
            crediti.append([anno, -lordo])
            imponibile, imposta = 0.0, 0.0

        crediti = [c for c in crediti if c[1] > 1e-9 and anno - c[0] < anni_riporto]
        imposte_totali += imposta
        capitale += lordo - imposta
        righe.append({"anno": anno, "lordo": lordo, "imponibile": imponibile,
                      "imposta": imposta, "capitale": capitale})

    return righe, imposte_totali, sum(c[1] for c in crediti)


# I risultati annuali di una regola meccanica su Bitcoin. Sostituiscili con i TUOI.
# Annual results of a mechanical rule on Bitcoin. Replace them with YOUR OWN.
# PROVA / TRY: sostituisci l'intero blocco RENDIMENTI_ANNUI qui sotto con le
# tue coppie (anno, rendimento) — vedi l'esercizio 1
df = carica("btcusdt").sort("data")
prezzi = df["chiusura"].to_numpy()
anni_serie = np.array([d.year for d in df["data"].to_list()])
curva = esegui(prezzi, rottura(prezzi, 20))["curva"]

# Il rendimento di un anno si misura dalla chiusura dell'anno PRECEDENTE, non
# dalla sua prima seduta. Partendo dalla prima seduta si perde il movimento del
# passaggio d'anno — dieci passaggi in dieci anni — e i rendimenti annuali non
# ricompongono piu' il risultato della regola: il prodotto usciva 33,66 dove la
# curva vale 35,66, e con esso tutte le cifre del capitolo (1.740.075 invece di
# 1.824.475). Con questa base il prodotto degli anni coincide con la curva.
# A year's return is measured from the PREVIOUS year's close, not from its own
# first session: otherwise the turn-of-year move is lost and the annual returns
# no longer multiply back to the rule's result.
RENDIMENTI_ANNUI = []
base_anno = float(curva[0])
for a in sorted(set(anni_serie.tolist())):
    ultimo = int(np.where(anni_serie == a)[0][-1])
    RENDIMENTI_ANNUI.append((int(a), float(curva[ultimo]) / base_anno - 1.0))
    base_anno = float(curva[ultimo])

righe, imposte, mai_usate = simula_imposta(RENDIMENTI_ANNUI)

lordo_composto = CAPITALE
for _, r in RENDIMENTI_ANNUI:
    lordo_composto *= 1 + r
differito = CAPITALE + (lordo_composto - CAPITALE) * (1 - ALIQUOTA)

with avvio.figura("schermo"):
    fig, ax = plt.subplots(figsize=(10, 4.5))
    x = np.arange(len(righe))
    ax.bar(x - 0.2, [r["lordo"] / 1000 for r in righe], width=0.4,
           label=t("risultato lordo", "gross result"))
    ax.bar(x + 0.2, [-r["imposta"] / 1000 for r in righe], width=0.4,
           label=t("imposta versata", "tax paid"))
    ax.axhline(0, linewidth=1, color="black")
    ax.set_xticks(x)
    ax.set_xticklabels([str(r["anno"]) for r in righe], rotation=45)
    ax.set_ylabel(t(f"Migliaia di euro (da {CAPITALE / 1000:.0f}.000 iniziali)",
                     f"Thousands of euros (starting from {CAPITALE / 1000:.0f},000)"))
    ax.legend()
    plt.show()

print(f"{t('anno', 'year'):>6s} {t('lordo', 'gross'):>14s} {t('imponibile', 'taxable'):>14s} "
      f"{t('imposta', 'tax'):>12s} {t('capitale', 'capital'):>14s}")
for r in righe:
    print(f"{r['anno']:6d} {r['lordo']:14,.0f} {r['imponibile']:14,.0f} "
          f"{r['imposta']:12,.0f} {r['capitale']:14,.0f}")

print(t(f"\ncapitale finale pagando ogni anno:  {righe[-1]['capitale']:14,.0f}",
        f"\nfinal capital paying every year:    {righe[-1]['capitale']:14,.0f}"))
print(t(f"capitale finale pagando alla fine:  {differito:14,.0f}",
        f"final capital paying at the end:    {differito:14,.0f}"))
print(t(f"differenza:                         {differito / righe[-1]['capitale'] - 1:14.1%}",
        f"difference:                         {differito / righe[-1]['capitale'] - 1:14.1%}"))
print(t(f"\nimposte versate:                    {imposte:14,.0f}",
        f"\ntax paid:                           {imposte:14,.0f}"))
# ALIQUOTA EFFETTIVA: numeratore e denominatore devono venire dallo STESSO
# percorso. Dividere le imposte del percorso TASSATO per il guadagno del
# percorso LORDO significa mettere insieme due capitali diversi — chi paga ogni
# anno, da li' in poi, capitalizza su meno — e il rapporto usciva 21,7%, cioe'
# SOTTO l'aliquota nominale. Il guadagno lordo di chi paga lungo la strada e'
# quello che gli resta piu' quello che ha versato: su quello si misura quanta
# parte se n'e' andata, e viene il 30,2%, cioe' SOPRA la nominale.
# Numerator and denominator must come from the SAME path: the gross gain of
# whoever pays along the way is what they keep plus what they paid.
guadagno_realizzato = righe[-1]["capitale"] - CAPITALE + imposte

print(t(f"guadagno lordo complessivo:         {lordo_composto - CAPITALE:14,.0f}",
        f"total gross gain:                   {lordo_composto - CAPITALE:14,.0f}"))
print(t(f"guadagno realizzato da chi ha pagato:{guadagno_realizzato:13,.0f}",
        f"gain realized by whoever paid:      {guadagno_realizzato:14,.0f}"))
print(t(f"aliquota EFFETTIVA sul guadagno:    {imposte / guadagno_realizzato:14.1%}",
        f"EFFECTIVE rate on the gain:         {imposte / guadagno_realizzato:14.1%}"))
print(t(f"perdite mai usate (scadute):        {mai_usate:14,.0f}",
        f"losses never used (expired):        {mai_usate:14,.0f}"))

print(t("\nGuarda l'aliquota effettiva: e' PIU' ALTA di quella nominale, e non "
        "per un'aliquota diversa. E' l'effetto delle perdite scadute — anni in "
        "rosso che non hanno mai trovato un anno positivo entro il termine — "
        "sommato al fatto che l'imposta versata presto smette di comporre. "
        "Il danno non viene dall'aliquota: viene dal momento.",
        "\nLook at the effective rate: it is HIGHER than the nominal one, and "
        "not because of a different rate. It is the effect of expired losses — "
        "red years that never met a positive year within the window — plus the "
        "fact that tax paid early stops compounding. The damage doesn't come "
        "from the rate: it comes from the timing."))

3. L'esercizio più utile: mescola l'ordine

Stessa sequenza di risultati annuali, ordine diverso. Il risultato lordo complessivo non cambia — la moltiplicazione è commutativa — ma l'imposta sì.

Istogramma delle imposte versate su 2.000 ordini diversi degli stessi risultati annuali, con l'asse orizzontale in migliaia di euro da circa 500 a 1.800 e il conteggio fino a poco oltre 400. La massa sta fra 550 e 800; una riga verticale nera segna l'ordine realmente accaduto, a 710 migliaia, mentre il minimo e' 535 e il massimo 1.785.

Stessi risultati annuali, ordine diverso: l'imposta cambia, il guadagno lordo no.Fonte: Binance Data Vision · Periodo: 2017-08-17 … 2026-06-30 · Metodo: Duemila permutazioni della sequenza dei risultati annuali, ognuna ripassata dalla stessa regola fiscale; la riga nera e' l'ordine realmente accaduto.

Output

imposte versate nell'ordine reale:      746,015
mescolando l'ordine — minimo:           563,378
                      mediana:          635,853
                      massimo:        1,872,862

capitale finale: da 1,474,935 a 1,939,241

Stesso guadagno lordo. Il fisco non tassa il tuo guadagno: tassa il modo in cui e' arrivato.
Espandi lo script di questo passo
calc_06_fisco.py
python
rng = np.random.default_rng(20260816)
# NON TOCCARE / DO NOT CHANGE: il seme fissa quali dei 2.000 rimescolamenti
# vengono disegnati; l'istogramma e il minimo/mediana/massimo citati nel testo
# restano quelli. Cambiare il seme non falsa la dimostrazione (qualunque
# rimescolamento mostra lo stesso fenomeno), ma sposta i numeri esatti stampati.
# The seed fixes which of the 2,000 shuffles get drawn; the histogram and the
# min/median/max quoted in the text stay those. Changing the seed doesn't
# invalidate the demonstration (any shuffle shows the same phenomenon), but
# it shifts the exact printed numbers.
valori = [r for _, r in RENDIMENTI_ANNUI]
anni_etichette = [a for a, _ in RENDIMENTI_ANNUI]

imposte_mescolate, finali_mescolati = [], []
for _ in range(2000):
    ordine = rng.permutation(len(valori))
    sequenza = [(anni_etichette[k], valori[ordine[k]]) for k in range(len(valori))]
    rr, ii, _ = simula_imposta(sequenza)
    imposte_mescolate.append(ii)
    finali_mescolati.append(rr[-1]["capitale"])

imposte_mescolate = np.array(imposte_mescolate)
finali_mescolati = np.array(finali_mescolati)

with avvio.figura("schermo"):
    fig, ax = plt.subplots()
    ax.hist(imposte_mescolate / 1000, bins=60)
    ax.axvline(imposte / 1000, linewidth=2.5, color="black")
    ax.set_xlabel(t("Imposte versate (migliaia di euro)", "Tax paid (thousands of euros)"))
    ax.set_ylabel(t("Su 2.000 ordini diversi", "Out of 2,000 different orderings"))
    plt.show()

print(t(f"imposte versate nell'ordine reale: {imposte:12,.0f}",
        f"tax paid in the real order:        {imposte:12,.0f}"))
print(t(f"mescolando l'ordine — minimo:      {imposte_mescolate.min():12,.0f}",
        f"shuffling the order — minimum:     {imposte_mescolate.min():12,.0f}"))
print(t(f"                      mediana:     {np.median(imposte_mescolate):12,.0f}",
        f"                      median:      {np.median(imposte_mescolate):12,.0f}"))
print(t(f"                      massimo:     {imposte_mescolate.max():12,.0f}",
        f"                      maximum:     {imposte_mescolate.max():12,.0f}"))
print(t(f"\ncapitale finale: da {finali_mescolati.min():,.0f} a {finali_mescolati.max():,.0f}",
        f"\nfinal capital: from {finali_mescolati.min():,.0f} to {finali_mescolati.max():,.0f}"))
print(t("\nStesso guadagno lordo. Il fisco non tassa il tuo guadagno: tassa il modo "
        "in cui e' arrivato.",
        "\nSame gross gain. The taxman doesn't tax your gain: it taxes how it "
        "arrived."))

Esercizi

  1. Sostituisci RENDIMENTI_ANNUI con i tuoi risultati annuali — bastano coppie (anno, rendimento). È il conto che nessun software di backtest fa.
  2. Cambia ALIQUOTA e ANNI_RIPORTO con quelli in vigore quando leggi. Le conclusioni cambiano di grado, non di segno.
  3. Metti ANNI_RIPORTO = 0 — cioè nessun riporto — e guarda quanto peggiora il caso di una strategia molto oscillante. È il costo fiscale della volatilità del risultato annuale, che è una cosa diversa dalla volatilità del mercato e non compare in nessuna metrica standard.

Riproducibilità e download

Eseguito il 2026-08-27 dal quaderno del repository

Il quaderno

  • calc_06_fisco.ipynb21,4 KB

    sha256 8eba3264b73332f4430935d516c6ccd7c930f443965a618c1135bd1b726541e6

  • calc_06_fisco.py17,2 KB

    sha256 cc85f8229130c75727bbbaf5863622f460edf50f9360b9d10426583bc2ef4f12

I dati

  • btcusdt.parquet93,2 KB

    sha256 ea75ad84e6e981507054df5c622c6b0ec3c8849c1f4dd007721878d4e4c8a329

    Fonte: Binance Data Vision · Periodo: 2017-08-17 → 2026-06-30 · 3240 righe · estratto 2026-08-16

Torna all’indice dei lab