De-CAF — Generatore di report fiscale per investimenti esteri. Niente commercialista.
Scarica i dati dai tuoi broker esteri e i tassi BCE, poi calcola tutto il necessario per il Modello Redditi PF:
- Quadro RW — Monitoraggio attività finanziarie estere + IVAFE
- Quadro RT — Plusvalenze di natura finanziaria (26%)
- Quadro RL — Redditi di capitale (interessi, dividendi, ritenute estere)
- Soglia valutaria — Analisi art. 67(1)(c-ter) TUIR
Output: tabelle colorate nel terminale, Excel (un foglio per quadro), PDF e YAML.
📝 Articolo di presentazione: sindro.me — decaf: Modello Redditi PF su investimenti esteri (🇬🇧 EN) — perché l'ho scritto, cosa fa in concreto, e il punto sulle plusvalenze valutarie che i broker non ti danno.
📖 Manuale completo: doc/decaf_manual.pdf — guida fiscale, normativa con riferimenti alla Gazzetta Ufficiale, architettura, internals per broker, setup Flex Query. Rigenerato ad ogni cambio in doc/ via pre-commit hook.
🎬 Guarda un esempio di output — fixture sintetica mascetti (anno 2025, stress test con soglia forex superata, multi-broker, 4 ritenute estere):
📄 PDF ·
📊 Excel ·
📋 YAML
Altri output in examples/.
⚠️ Disclaimer. Questo strumento automatizza i calcoli ma non sostituisce un commercialista. Le leggi fiscali cambiano, i tuoi dati e la tua situazione sono tuoi — verifica sempre i numeri prima di firmare il Modello Redditi. Gli autori non si assumono responsabilità per errori, omissioni, o interpretazioni della normativa. Usalo come punto di partenza, non come oracolo.
| Broker | Sorgente dati | Note |
|---|---|---|
| Interactive Brokers (Irlanda) | Flex Query API o file XML | Automatico |
| Charles Schwab (account EAC/RSU) | 3 file: PDF Year-End Summary + PDF Withholding + JSON transazioni | Manuale da schwab.com |
Linux (Debian/Ubuntu):
sudo apt install python3 python3-venv poppler-utils gitmacOS:
brew install python poppler gitpoppler-utils (pdftotext) serve al parsing dei PDF Schwab. Windows non testato.
pip install --user decaf-tax # pacchetto: decaf-tax · comando: decaf
mkdir ~/decaf
decaf --helpInstallazione isolata con pipx (alternativa, un venv dedicato al tool):
pipx install decaf-taxIl comando decaf sarà disponibile nel tuo PATH. Tutti i comandi di questo README (decaf load, decaf report, decaf backtest, decaf manual) funzionano identici.
git clone https://github.com/vjt/decaf.git
cd decaf
mkdir private # qui metterai i tuoi file broker (gitignored)
./decaf.sh --help./decaf.sh crea .venv/ alla prima invocazione e aggiorna le dipendenze automaticamente quando pyproject.toml cambia (utile dopo un git pull). Le due librerie vendor (ibkr-flex-client, ecb-fx-rates) sono pubblicate su PyPI, quindi non serve --recursive per l'uso normale — vedi la sezione Sviluppo se vuoi modificarle localmente.
Da qui in poi il comando decaf si riferisce sia al binario installato via pip/pipx sia a ./decaf.sh dal sorgente — funzionano identici. Scegli tu dove tenere i file broker (~/decaf/ se hai installato via PyPI, ./private/ dal sorgente — dir già gitignored).
Se hai solo un conto Schwab con RSU vestate dal datore di lavoro (caso tipico di dipendenti italiani di Meta, Google, Apple, &co.) non devi toccare IBKR né Flex Query. Ti servono tre file che scarichi dal sito di Schwab:
- Transaction history JSON — Accounts → History → Export → formato JSON. Copre tutte le transazioni del periodo di imposta (vest, vendite, bonifici, eventuali dividendi). Se ti servono gli anni precedenti, esporta un periodo più lungo; i caricamenti sono idempotenti.
- Year-End Summary PDF — Statements → Tax Documents → Year-End Summary dell'anno di imposta. Contiene le plusvalenze per lotto (Quadro RT). Serve solo se nell'anno hai venduto azioni.
- Annual Withholding Statement PDF — Equity Award Center → Documents → Annual Withholding Statement dell'anno. Contiene il FMV per data di vest per giurisdizione ITA/IRL (base per IVAFE, Quadro RW). Serve anche se non hai venduto niente — le RSU ricevute nell'anno vanno comunque dichiarate nel monitoraggio RW.
Mettili in una cartella a tuo piacimento (esempio: ~/decaf) e lancia:
pip install decaf-tax # oppure: pipx install decaf-tax
mkdir -p ~/decaf && cd ~/decaf
# copia qui i tre file scaricati da Schwab
decaf load --broker schwab \
--file Individual_*_Transactions_*.json \
--gains-pdfs "Year-End Summary*.PDF" \
--vest-pdfs "Annual Withholding Statement*.PDF"
decaf report --year 2025 --output-dir ~/decafOutput: decaf_2025.{yaml,xlsx,pdf} nella cartella corrente + tabelle colorate nel terminale con totali per quadro, etichette AdE (RW/RT/RL) e riferimenti normativi. Apri l'Excel e copia i numeri nei righi corrispondenti del Modello Redditi PF; il PDF serve come prova documentale ordinabile per eventuali controlli. La Guida Fiscale ti dice rigo per rigo dove va ciascun numero.
Nessuna Flex Query, nessuna API key, nessuna configurazione di credenziali. Gli unici segreti (file con il tuo nome, codice fiscale, importi) restano sul tuo disco in ~/decaf/ — decaf non chiama in rete niente oltre al cambio BCE ufficiale e (se hai titoli senza FMV Schwab) Yahoo Finance per il prezzo di fine anno.
~/decaf/
├── flexquery.xml # IBKR — esportato da Flex Query
├── Individual_XXX_Transactions_*.json # Schwab — Accounts → History → Export (JSON)
├── Year-End Summary*.PDF # Schwab — Statements → Tax Documents
└── Annual Withholding Statement*.PDF # Schwab — Equity Award Center → Documents
Prima volta con IBKR? Devi configurare una Flex Query dal portale Interactive Brokers — serve sia per il download via API sia per esportare l'XML. Guida completa con screenshot: doc/QUERY_SETUP.md. Una volta configurata, puoi saltare il file e usare l'API mettendo IBKR_TOKEN + IBKR_QUERY_ID in .env nella directory corrente (gitignored).
Per Schwab i tre file contengono dati diversi e servono tutti:
| File | Cosa contiene |
|---|---|
Individual_*.json |
Dividendi, ritenute (RL), vendite, bonifici (forex LIFO) |
Year-End Summary*.PDF |
Plusvalenze per lotto (RT) |
Annual Withholding*.PDF |
FMV al vest per IVAFE (RW) |
cd ~/decaf
# IBKR — da file
decaf load --file flexquery.xml
# IBKR — da API (richiede .env)
decaf load
# Schwab
decaf load --broker schwab \
--file Individual_*_Transactions_*.json \
--gains-pdfs "Year-End Summary*.PDF" \
--vest-pdfs "Annual Withholding Statement*.PDF"I caricamenti sono idempotenti — puoi rieseguirli senza duplicare. Il DB sta in ~/.cache/decaf/.
decaf report --year 2025 --output-dir ~/decafProduce decaf_2025.yaml + .xlsx + .pdf in ~/decaf/, e stampa tabelle colorate nel terminale con totali per quadro, etichette AdE, e riferimenti normativi.
examples/ contiene gli output reali generati su tre fixture sintetiche:
| Fixture | Anni | Copre |
|---|---|---|
magnotta/ |
2024 | IBKR-only, caso base |
mosconi/ |
2023-2024 | IBKR + Schwab, RSU, stesso ticker a 2 broker |
mascetti/ |
2024-2025 | Stress — soglia forex, LIFO multi-lotto USD, 4 ritenute diverse |
Ogni sotto-directory contiene decaf_<year>.{yaml,xlsx,pdf}. Input corrispondenti in tests/reference/.
| File | Formato | Uso | Esempio |
|---|---|---|---|
decaf_<year>.xlsx |
Excel | Un foglio per quadro + riepilogo | xlsx |
decaf_<year>.pdf |
Prospetto con tabelle e totali | ||
decaf_<year>.yaml |
YAML | Dump completo del TaxReport — diffabile, stabile tra run |
yaml |
- Load — Scarica dati dal broker (API o file) e tassi BCE. Salva tutto in SQLite.
- Report — Carica da SQLite, converte USD→EUR al cambio BCE, calcola:
- Soglia valutaria: ricostruisce il saldo giornaliero in valuta estera, verifica 7+ giorni lavorativi consecutivi sopra €51.645,69
- IVAFE: 0.2% annuo sul valore di mercato dei titoli (pro-rata per giorni), €34.20 fisso per depositi
- Plusvalenze titoli: converte il P/L del broker in EUR al tasso BCE alla data di regolamento
- Plusvalenze valutarie: se soglia superata, calcola i guadagni forex con LIFO per singolo conto sui lotti USD (acquisti da vendite titoli, dividendi, interessi → cessioni tramite conversioni EUR.USD e bonifici)
- Redditi di capitale: abbina interessi lordi con ritenute estere
- Output — Genera i file e il report terminale
| Regola | Riferimento | Implementazione |
|---|---|---|
| IVAFE titoli | D.L. 201/2011, art. 19 | 0.2% su valore di mercato, pro-rata giorni |
| IVAFE depositi | D.L. 201/2011 | €34.20 fisso annuo |
| Plusvalenze titoli | Art. 67(1)(c-bis) TUIR | 26% imposta sostitutiva |
| Plusvalenze valutarie | Art. 67(1)(c-ter) TUIR + 1-bis + risposta 204/2023 | LIFO per singolo conto su lotti USD, 26% se soglia superata |
| Soglia valutaria | Art. 67(1)(c-ter) TUIR | €51.645,69 per 7+ giorni lavorativi |
| Cambio | D.P.R. 917/1986 | Tassi BCE (cambio ufficiale AdE) |
| Quadro RW | Modello Redditi PF, Sez. II-A | Cod. 20 titoli, Cod. 1 depositi |
| Quadro RT | Modello Redditi PF, righi RT21+ | Sez. II-A, imposta sostitutiva 26% |
| Quadro RL | Modello Redditi PF, rigo RL2 | Sez. I, redditi di capitale esteri |
Le scelte sotto si discostano dalla lettera della norma o lasciano fuori scope alcuni casi. Sono documentate per trasparenza — dettagli completi con citazioni in doc/NORMATIVA.md.
| Area | Cosa fa decaf oggi | Cosa richiederebbe la norma |
|---|---|---|
| Obbligazioni e titoli non partecipativi | Fuori scope. Decaf non e' back-testato su portafogli obbligazionari; applica la stessa logica delle partecipazioni. | Circ. 165/E/1998 §2.3.2 richiede LIFO esplicito sui titoli non partecipativi. Chi detiene obbligazioni estere deve rettificare manualmente. |
| IVAFE 0,4% black-list | Applica sempre 0,2%. Nessuna rilevazione automatica della giurisdizione. | L. 213/2023 art. 1 c. 91: 0,4% dal FY2024 per attivita' in Stati a fiscalita' privilegiata (D.M. 04/05/1999). Chi detiene posizioni presso intermediari black-list rettifica manualmente. |
| RSU trasferite Schwab → IBKR | Su IBKR decaf usa come costo il Lot@cost dell'XML, che eredita il basis fiscale USA (FMV del giorno di vest). IBKR non espone il Valore Normale italiano. |
Art. 68 c. 6 + art. 9 c. 4 lett. a) TUIR: il costo fiscalmente riconosciuto e' il Valore Normale (media mensile pre-vest) tassato come reddito di lavoro. Su Schwab decaf lo sostituisce automaticamente via Annual Withholding Statement; su IBKR non c'e' una fonte equivalente — chi trasferisce RSU fuori da EAC deve rettificare manualmente. |
Il comando decaf backtest <dir> riesegue l'intera pipeline su una directory di file broker e confronta l'output con oracoli YAML committati. Utile per:
- verificare che un cambio di codice non alteri output storici;
- congelare i risultati dell'anno N come regressione per l'anno N+1;
- condividere casi di test senza toccare dati sensibili.
Guida approfondita: doc/BACKTEST.md.
tests/reference/mascetti/
├── ibkr_flex_2024.xml # IBKR XML per anno
├── ibkr_flex_2025.xml
├── Individual_XXX066_Transactions_*.json # Schwab JSON per anno
├── Year-End Summary*.PDF # Schwab YES PDF per anno
├── Annual Withholding*.PDF # Schwab AWH PDF per anno
├── prices.yaml # opzionale — override prezzi
├── decaf_2024.yaml # oracolo per anno
└── decaf_2025.yaml
L'anno fiscale di ogni file si ricava dal nome: ibkr_flex_<year>.xml per l'XML, le date nei nomi Schwab per JSON/PDF. Gli oracoli sono obbligatori solo per gli anni che vuoi verificare.
# Rigenera oracoli (uso iniziale o dopo modifiche volute)
./decaf.sh backtest tests/reference/mascetti --update
# Verifica regressione (exit 0 = match, 1 = diff)
./decaf.sh backtest tests/reference/mascettiIl comando:
- crea un DB SQLite temporaneo in
/tmp/decaf_bt_<pid>.db; - ingestisce tutti i file broker trovati nella directory;
- calcola il report per ogni anno con oracolo;
- confronta il dump YAML completo contro l'oracolo (
--updatelo sovrascrive invece).
Exit code: 0 = tutti gli anni matchano, 1 = almeno un anno diverge.
Pinna i prezzi di fine anno per simboli che yfinance non risolve (ticker sintetici, delistati, esteri) o che vuoi controllare esplicitamente:
2024:
MSCT: 14.00
SPKZ: 18.00
2025:
ANTN: 6.00Il dizionario è consultato due volte per ogni anno fiscale:
- blocco
<year>→ prezzo a fine anno (IVAFE al 31/12); - blocco
<year-1>→ prezzo a fine anno precedente (usato comeinitial_valuenel calcolo pro-rata IVAFE per titoli portati dall'anno precedente).
Senza override, entrambi i lookup passano a yfinance.
| Fixture | Anni | Copertura |
|---|---|---|
magnotta/ |
2024 | IBKR singolo, caso base — IVAFE pro-rata, loss RT, dividendo con ritenuta |
mosconi/ |
2023-2024 | IBKR + Schwab, vendita parziale titoli multi-lotto (cost basis per lotto dal broker), RSU vest, multi-anno |
mascetti/ |
2024-2025 | Stress test — soglia forex superata 2 anni, LIFO multi-lotto USD (forex), RSU multi-anno, dividendi con 4 ritenute diverse (US 30%, UK 0%, DE 26.375%, IT 26%) |
Nomi dei personaggi:
mascetti/— Il Conte Raffaello Mascetti, personaggio immaginario del film Amici Mieimosconi/— Germano Mosconi, leggendario giornalista veronesemagnotta/— Mario Magnotta, icona internet ante-litteram di L'Aquila
Account IDs contengono 666 per distinguerli visivamente da account reali.
source .venv/bin/activate
scripts/lint.sh # ruff + pyright
scripts/test.sh # pytest -xTest suite: holidays, XML parsing, FX service, forex threshold, forex LIFO gains, statement store, Schwab PDF parsing, end-to-end regression su tre fixture sintetiche.
Richiede Python 3.12+. Le dipendenze sono gestite da ./decaf.sh (primo avvio crea .venv/ + installa, run successivi aggiornano solo se pyproject.toml è cambiato).
Per rigenerare il manuale PDF (scripts/manual.sh, lanciato anche dal pre-commit hook quando cambia doc/) serve pandoc + xelatex:
# Linux (Debian/Ubuntu)
sudo apt install pandoc texlive-xetex texlive-latex-recommended texlive-latex-extra
# macOS
brew install pandoc
brew install --cask mactex-no-guiSe vuoi modificare le due librerie vendor (ibkr-flex-client, ecb-fx-rates), clona con i submodule:
git submodule update --init --recursive./decaf.sh rileva automaticamente vendor/<dep>/pyproject.toml e installa quelle versioni in modalità editable, sovrascrivendo le pin PyPI. Fai le tue modifiche in vendor/<dep>/, i test di decaf le useranno subito.
I submodule sono via HTTPS. Se hai accesso push e preferisci SSH, scopi la riscrittura alle sole due repo dei submodule:
git config --global url."git@github.com:vjt/ibkr-flex-client.git".insteadOf "https://github.com/vjt/ibkr-flex-client.git"
git config --global url."git@github.com:vjt/ecb-fx-rates.git".insteadOf "https://github.com/vjt/ecb-fx-rates.git"Nessun altro repo (nemmeno altri di vjt/) viene toccato.
# 1. Bump version, pin delle URL jsdelivr al nuovo tag, aggiorna CHANGELOG.
# Le URL nel README devono puntare a @vX.Y.Z (non @master) così la
# pagina PyPI serve asset coerenti con la release: jsdelivr cache-a
# @master 7 giorni → un pin al tag elimina ogni staleness.
NEW=X.Y.Z
sed -i "s|^version = .*|version = \"$NEW\"|" pyproject.toml
sed -i "s|cdn.jsdelivr.net/gh/vjt/decaf@v[0-9.]\+|cdn.jsdelivr.net/gh/vjt/decaf@v$NEW|g" README.md
vim CHANGELOG.md # sposta [Unreleased] in una sezione [X.Y.Z] — YYYY-MM-DD
# 2. Build wheel + sdist
source .venv/bin/activate
rm -rf dist && python -m build
twine check dist/*
# 3. Upload a PyPI (richiede PYPI_TOKEN in .env con scope account)
set -a && source .env && set +a
TWINE_USERNAME=__token__ TWINE_PASSWORD="$PYPI_TOKEN" twine upload dist/*
# 4. Commit + tag + push
git add pyproject.toml CHANGELOG.md README.md
git commit -m "Release $NEW: <riassunto>"
git tag v$NEW
git push origin master --tagsIl pre-commit hook rigenera automaticamente doc/decaf_manual.pdf se hai toccato doc/, quindi non c'è niente da fare a mano per il manuale.
MIT

