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.
📖 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 fetch, decaf report, decaf backtest) 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).
~/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 FIFO) |
Year-End Summary*.PDF |
Plusvalenze per lotto (RT) |
Annual Withholding*.PDF |
FMV al vest per IVAFE (RW) |
cd ~/decaf
# IBKR — da file
decaf fetch --file flexquery.xml
# IBKR — da API (richiede .env)
decaf fetch
# Schwab
decaf fetch --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, FIFO multi-lotto, 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 |
- Fetch — 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 FIFO 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 | FIFO 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 |
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, FIFO su vendita parziale, RSU vest, multi-anno |
mascetti/ |
2024-2025 | Stress test — soglia forex superata 2 anni, FIFO multi-lotto, 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 -x143 test: holidays, XML parsing, FX service, forex threshold, forex FIFO 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

