← Files italian-investorARCHIVED FILE
skills/italian-investor/scripts/event_tax.py
11 KB · Oct 5, 2026 · 18:32 UTC
#!/usr/bin/env python3
"""Motore deterministico per proventi periodici e altri flussi finanziari.
Separa gli eventi fiscali dalla vendita: una stessa obbligazione, per esempio,
può generare un reddito di capitale con la cedola e un reddito diverso con il
capital gain da cessione.
Il modulo copre solo casi semplici e verificabili. Se il flusso è di fonte
estera, esistono ritenute estere, strumenti ibridi o qualificazioni dipendenti
dal prospetto restituisce hard-stop.
Esempi:
python scripts/event_tax.py classifica --tipo obbligazione --evento cedola
python scripts/event_tax.py provento --tipo azione --evento dividendo --lordo 100
python scripts/event_tax.py provento --tipo etf --evento distribuzione \
--lordo 100 --quota-stato 0.30
"""
import argparse
import json
import sys
ALIQUOTA_ORDINARIA = 0.26
FRAZIONE_IMPONIBILE_AGEVOLATA = 0.4808
ALIQUOTA_TITOLI_PUBBLICI = 0.125
CAPITALE = "reddito_di_capitale"
DIVERSO = "reddito_diverso"
EVENTI = {
("azione", "dividendo"): {
"categoria": CAPITALE,
"aliquota": "ordinaria",
"nota": "Dividendo percepito da persona fisica residente fuori dall'impresa.",
},
("obbligazione", "cedola"): {
"categoria": CAPITALE,
"aliquota": "ordinaria",
"nota": "Interessi/premi/altri proventi di obbligazione corporate.",
},
("obbligazione", "interesse"): {
"categoria": CAPITALE,
"aliquota": "ordinaria",
"nota": "Interessi/premi/altri proventi di obbligazione corporate.",
},
("titolo_stato", "cedola"): {
"categoria": CAPITALE,
"aliquota": "titolo_pubblico",
"nota": "Provento di titolo pubblico agevolato; verificare l'ammissibilita' del titolo.",
},
("titolo_stato", "interesse"): {
"categoria": CAPITALE,
"aliquota": "titolo_pubblico",
"nota": "Provento di titolo pubblico agevolato; verificare l'ammissibilita' del titolo.",
},
("etf", "distribuzione"): {
"categoria": CAPITALE,
"aliquota": "oicr_mista",
"nota": "Provento distribuito da OICR/ETF; la quota titoli pubblici puo' ridurre l'aliquota effettiva.",
},
("oicr", "distribuzione"): {
"categoria": CAPITALE,
"aliquota": "oicr_mista",
"nota": "Provento distribuito da OICR; la quota titoli pubblici puo' ridurre l'aliquota effettiva.",
},
("liquidita", "interesse"): {
"categoria": CAPITALE,
"aliquota": "ordinaria",
"nota": "Interesse su liquidita/deposito nel caso ordinario coperto.",
},
}
DA_ACCERTARE = {
"certificate": "Il trattamento del flusso periodico di un certificate dipende dalla struttura del prodotto: verificare prospetto/term sheet.",
"etc_etn": "ETC/ETN: qualificazione fiscale da verificare sul prospetto specifico.",
"etf_non_armonizzato": "OICR non armonizzato: il regime puo' differire; verificare caso per caso.",
"cripto": "Cripto-attivita': regime autonomo e variabile nel tempo, fuori da questo helper.",
"pir": "PIR: eventuale esenzione dipende dai requisiti del piano e non viene calcolata qui.",
"fondo_pensione": "Previdenza complementare: regime autonomo, fuori da questo helper.",
}
FONTI = [
"DL 66/2014 art. 3: aliquota ordinaria del 26% per interessi, premi, altri proventi ex art. 44 TUIR e redditi diversi finanziari, con eccezioni per titoli pubblici agevolati.",
"DPR 600/1973 art. 27: ritenuta del 26% sui dividendi corrisposti a persone fisiche residenti fuori dall'impresa.",
"DPR 600/1973 art. 26-quinquies / disciplina OICR: proventi da partecipazione a OICR come redditi di capitale; verificare testo vigente e prodotto.",
"Agenzia delle Entrate, Circolare 19/E del 27/06/2014: componente di OICR riferibile a titoli pubblici agevolati.",
"Istruzioni dichiarative correnti: per redditi di capitale di fonte estera e imposte estere verificare quadro, convenzione e modalita' di incasso applicabili.",
]
ITALIA_ALIASES = {"IT", "ITA", "ITALIA", "ITALY"}
def _quota_valida(quota_stato):
if quota_stato is None:
return None
try:
q = float(quota_stato)
except (TypeError, ValueError):
raise ValueError("quota_stato deve essere un numero tra 0 e 1")
if not 0.0 <= q <= 1.0:
raise ValueError("quota_stato deve essere compresa tra 0 e 1")
return q
def _paese_estero(paese_fonte):
raw = str(paese_fonte or "").strip().upper()
return bool(raw and raw not in ITALIA_ALIASES)
def classifica_evento(tipo, evento):
tipo = str(tipo).strip().lower()
evento = str(evento).strip().lower()
if tipo in DA_ACCERTARE:
return {
"tipo": tipo,
"evento": evento,
"calcolabile": False,
"motivo": DA_ACCERTARE[tipo],
"fonti": FONTI,
}
regola = EVENTI.get((tipo, evento))
if not regola:
return {
"tipo": tipo,
"evento": evento,
"calcolabile": False,
"motivo": (
"Combinazione tipo/evento non coperta. Non dedurre la categoria fiscale "
"dalla sola natura dello strumento."
),
"eventi_coperti": ["%s:%s" % k for k in sorted(EVENTI)],
"fonti": FONTI,
}
return {
"tipo": tipo,
"evento": evento,
"calcolabile": True,
"categoria_reddito": regola["categoria"],
"compensabile_con_minus": regola["categoria"] == DIVERSO,
"schema_aliquota": regola["aliquota"],
"nota": regola["nota"],
"fonti": FONTI,
}
def simula_provento(tipo, evento, lordo, quota_stato=None,
ritenuta_estera=0.0, paese_fonte=None):
info = classifica_evento(tipo, evento)
if not info["calcolabile"]:
return {
"errore": info["motivo"],
"tipo": tipo,
"evento": evento,
"imposta_stimata": None,
"fonti": info["fonti"],
}
try:
lordo = float(lordo)
ritenuta_estera = float(ritenuta_estera or 0.0)
quota_stato = _quota_valida(quota_stato)
except ValueError as exc:
return {"errore": str(exc), "imposta_stimata": None, "fonti": info["fonti"]}
if lordo < 0 or ritenuta_estera < 0:
return {
"errore": "lordo e ritenuta_estera non possono essere negativi",
"imposta_stimata": None,
"fonti": info["fonti"],
}
# Il solo fatto che il flusso sia estero basta a rendere insufficiente la
# formula domestica, anche se l'utente non ha ancora indicato una ritenuta.
# Evita il bug 'ritenuta_estera omessa -> tratta come Italia'.
if _paese_estero(paese_fonte) or ritenuta_estera > 0:
return {
"tipo": tipo,
"evento": evento,
"categoria_reddito": info["categoria_reddito"],
"lordo": round(lordo, 2),
"ritenuta_estera": round(ritenuta_estera, 2),
"paese_fonte": paese_fonte,
"imposta_stimata": None,
"netto_stimato": None,
"dato_mancante": "trattamento_doppia_imposizione_estera",
"verificare": [
"Verificare Paese fonte, convenzione, aliquota/ritenuta estera, eventuale credito d'imposta e modalita' di incasso/intermediario.",
],
"fonti": info["fonti"],
}
schema = info["schema_aliquota"]
verificare = []
if schema == "ordinaria":
imposta = lordo * ALIQUOTA_ORDINARIA
aliquota_effettiva = ALIQUOTA_ORDINARIA
elif schema == "titolo_pubblico":
imposta = lordo * ALIQUOTA_TITOLI_PUBBLICI
aliquota_effettiva = ALIQUOTA_TITOLI_PUBBLICI
verificare.append(
"Confermare che il titolo rientri tra i titoli pubblici/White List agevolati."
)
elif schema == "oicr_mista":
if quota_stato is None:
imposta_max = lordo * ALIQUOTA_ORDINARIA
imposta_min = lordo * ALIQUOTA_ORDINARIA * FRAZIONE_IMPONIBILE_AGEVOLATA
return {
"tipo": tipo,
"evento": evento,
"categoria_reddito": info["categoria_reddito"],
"lordo": round(lordo, 2),
"quota_stato": None,
"imposta_stimata": None,
"imposta_scenario": {
"quota_agevolata_0": round(imposta_max, 2),
"quota_agevolata_100": round(imposta_min, 2),
},
"netto_stimato": None,
"dato_mancante": "quota_stato",
"compensabile_con_minus": False,
"verificare": [
"Recuperare la quota fiscalmente agevolata comunicata/applicabile al fondo; non usare le holdings correnti come sostituto automatico."
],
"fonti": info["fonti"],
}
base_factor = (1.0 - quota_stato) + quota_stato * FRAZIONE_IMPONIBILE_AGEVOLATA
imposta = lordo * ALIQUOTA_ORDINARIA * base_factor
aliquota_effettiva = imposta / lordo if lordo else 0.0
verificare.append(
"Confermare la quota agevolata sulla comunicazione dell'emittente/intermediario."
)
else:
return {"errore": "schema aliquota non gestito", "imposta_stimata": None}
return {
"tipo": tipo,
"evento": evento,
"categoria_reddito": info["categoria_reddito"],
"lordo": round(lordo, 2),
"quota_stato": quota_stato,
"imposta_stimata": round(imposta, 2),
"netto_stimato": round(lordo - imposta, 2),
"aliquota_effettiva": round(aliquota_effettiva, 6),
"compensabile_con_minus": False,
"verificare": verificare,
"fonti": info["fonti"],
}
def main(argv=None):
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
sub = p.add_subparsers(dest="cmd", required=True)
c = sub.add_parser("classifica", help="classifica uno specifico evento fiscale")
c.add_argument("--tipo", required=True)
c.add_argument("--evento", required=True)
f = sub.add_parser("provento", help="simula un provento periodico semplice")
f.add_argument("--tipo", required=True)
f.add_argument("--evento", required=True)
f.add_argument("--lordo", required=True, type=float)
f.add_argument("--quota-stato", type=float)
f.add_argument("--ritenuta-estera", type=float, default=0.0)
f.add_argument("--paese-fonte")
args = p.parse_args(argv)
if args.cmd == "classifica":
out = classifica_evento(args.tipo, args.evento)
else:
out = simula_provento(
args.tipo, args.evento, args.lordo,
args.quota_stato, args.ritenuta_estera, args.paese_fonte,
)
print(json.dumps(out, ensure_ascii=False, indent=2))
return 1 if out.get("errore") else 0
if __name__ == "__main__":
sys.exit(main())
SHA-256: a31ea8296cdc46ef17e82c8ca41f2e2fd33aedeec2cd8413bba455387a3e217f