Skip to content

vjt/decaf

Repository files navigation

decaf logo

decaf

De-CAF — Generatore di report fiscale per investimenti esteri. Niente commercialista.

Mascetti, Mosconi e Magnotta alle prese con la dichiarazione dei redditi

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 Supportati

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

Prerequisiti

Linux (Debian/Ubuntu):

sudo apt install python3 python3-venv poppler-utils git

macOS:

brew install python poppler git

poppler-utils (pdftotext) serve al parsing dei PDF Schwab. Windows non testato.

Installazione

Opzione 1 — da PyPI (consigliata)

pip install --user decaf-tax    # pacchetto: decaf-tax · comando: decaf
mkdir ~/decaf
decaf --help

Installazione isolata con pipx (alternativa, un venv dedicato al tool):

pipx install decaf-tax

Il comando decaf sarà disponibile nel tuo PATH. Tutti i comandi di questo README (decaf fetch, decaf report, decaf backtest) funzionano identici.

Opzione 2 — dal sorgente (per hackare o leggere il codice)

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.

Primo utilizzo

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).

1. Prepara i file broker

~/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)

2. Carica i dati nel DB locale

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/.

3. Genera il report

decaf report --year 2025 --output-dir ~/decaf

Produce decaf_2025.yaml + .xlsx + .pdf in ~/decaf/, e stampa tabelle colorate nel terminale con totali per quadro, etichette AdE, e riferimenti normativi.

Esempi

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 di Output

File Formato Uso Esempio
decaf_<year>.xlsx Excel Un foglio per quadro + riepilogo xlsx
decaf_<year>.pdf PDF Prospetto con tabelle e totali pdf
decaf_<year>.yaml YAML Dump completo del TaxReport — diffabile, stabile tra run yaml

Come Funziona

  1. Fetch — Scarica dati dal broker (API o file) e tassi BCE. Salva tutto in SQLite.
  2. 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
  3. Output — Genera i file e il report terminale

Regole Fiscali Implementate

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

Bring Your Own Data — Backtesting

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.

Layout della directory

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.

Comandi

# 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/mascetti

Il comando:

  1. crea un DB SQLite temporaneo in /tmp/decaf_bt_<pid>.db;
  2. ingestisce tutti i file broker trovati nella directory;
  3. calcola il report per ogni anno con oracolo;
  4. confronta il dump YAML completo contro l'oracolo (--update lo sovrascrive invece).

Exit code: 0 = tutti gli anni matchano, 1 = almeno un anno diverge.

Override di prezzo (prices.yaml)

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.00

Il 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 come initial_value nel calcolo pro-rata IVAFE per titoli portati dall'anno precedente).

Senza override, entrambi i lookup passano a yfinance.

Fixture sintetiche incluse

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:

Account IDs contengono 666 per distinguerli visivamente da account reali.

Sviluppo

source .venv/bin/activate
scripts/lint.sh     # ruff + pyright
scripts/test.sh     # pytest -x

143 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-gui

Se 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.

Rilasciare una nuova versione

# 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 --tags

Il pre-commit hook rigenera automaticamente doc/decaf_manual.pdf se hai toccato doc/, quindi non c'è niente da fare a mano per il manuale.

Licenza

MIT