Interactive Brokers (IBKR) Report Parser for MyTax (vero.fi) - not affiliated with either service
pip install ibkr-report-parser
ibkr-report-parserdocker pull ghcr.io/oittaa/ibkr-report-parser
docker run --rm -d -p 8080:8080 --name ibkr-report-parser ghcr.io/oittaa/ibkr-report-parserBrowse to http://127.0.0.1:8080/
The web UI is available in English and Finnish. The language is detected from the browser (Accept-Language) and defaults to English; you can switch with the EN | FI control (preference is stored in a cookie).
TITLEThe title of the website. DefaultIBKR Report ParserCURRENCYThe currency used in the report output. DefaultEURUSE_DEEMED_ACQUISITION_COSTWhether to use the deemed acquisition cost, if it benefits you. DefaultTRUESTORAGE_TYPEThe storage to save the fetched daily Euro exchange rates, if set to anything other thanDISABLED. Currently supported types areLOCAL,AWS, andGCP. DefaultDISABLEDSTORAGE_DIRThe directory used whenSTORAGE_TYPEis set toLOCAL. Default.ibkr_storageBUCKET_IDThe storage bucket used whenSTORAGE_TYPEis set toAWSorGCP. Default""
DEBUGFlask debug. DefaultFALSELOGGING_LEVELPython logging level. DefaultINFOEXCHANGE_RATES_URLURL for the Euro exchange rates from European Central Bank. Defaulthttps://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip
git clone https://github.com/oittaa/ibkr-report-parser.git
cd ibkr-report-parser
pip install .
ibkr-report-parsergit clone https://github.com/oittaa/ibkr-report-parser.git
cd ibkr-report-parser
docker build -t ibkr-report-parser:latest .
docker run --rm -d -p 8080:8080 --name ibkr-report-parser ibkr-report-parserYou can upload several CSV files (or one multi-year custom statement). The app:
- Merges all trades so option premiums from earlier years can adjust stock lots sold later.
- Reports only disposals from the latest calendar year present in the data (for MyTax). The year of each row is the later of acquisition and disposal dates (for shorts that is the cover year).
This matters for short puts that get assigned: the premium is not taxed on the option; it reduces the acquisition cost of the shares. A later-year IBKR statement alone does not include that premium—you need the assignment year in the upload as well.
Option exercise/assignment (IBKR codes A / Ex) is folded into the stock leg:
| Position | Event | Effect on stock |
|---|---|---|
| Short call | Assigned | Premium increases disposal price (luovutushinta) |
| Long call | Exercised | Premium increases acquisition cost (hankintahinta) |
| Short put | Assigned | Premium decreases acquisition cost (hankintahinta) |
| Long put | Exercised | Premium decreases disposal price (luovutushinta) |
Expired options and cash closes are still reported as option disposals.
UI labels and the Python API follow Verohallinto form 9A terminology. The UI shows one language at a time (EN or FI); the table below lists the official wording used:
| Report / API | Form 9A (English) | Finnish |
|---|---|---|
acquired_on |
Date when acquired | Hankintapäivä |
acquisition_cost |
Acquisition price | Hankintahinta |
disposed_on |
Selling date | Luovutuspäivä |
proceeds |
Selling price | Luovutushinta |
realized |
Capital gain / capital loss | Luovutusvoitto / luovutustappio |
used_deemed_acquisition_cost |
Deemed acquisition cost | Hankintameno-olettama |
Totals: proceeds / gains / losses |
Total selling prices / capital gains / capital losses | Luovutushinnat / -voitot / -tappiot yhteensä |
from ibkr_report import Report
FILE_1 = "tests/test-data/data_single_account.csv"
FILE_2 = "tests/test-data/data_multi_account.csv"
with open(FILE_1, "rb") as file:
report = Report(file=file, report_currency="EUR", use_deemed_acquisition_cost=True)
with open(FILE_2, "rb") as file:
report.add_trades(file=file)
print(f"Report year: {report.report_year} ({report.file_count} file(s))")
# Totals → form 9A: Luovutushinnat / Luovutusvoitot / Luovutustappiot yhteensä
print(f"Total disposal prices (luovutushinnat): {report.proceeds}")
print(f"Total capital gains (luovutusvoitot): {report.gains}")
print(f"Total capital losses (luovutustappiot): {report.losses}")
for item in report.disposals:
# Hankintapäivä / hankintahinta / luovutuspäivä / luovutushinta / voitto tai tappio
print(
f"{item.symbol=}, {item.quantity=}, "
f"hankintapäivä={item.acquired_on}, hankintahinta={item.acquisition_cost}, "
f"luovutuspäivä={item.disposed_on}, luovutushinta={item.proceeds}, "
f"realized={item.realized}"
)from ibkr_report import ExchangeRates, StorageType
rates = ExchangeRates(storage_type=StorageType.LOCAL, storage_dir="/tmp/my_storage")
print(rates.get_rate("EUR", "USD", "2020-06-20"))
print(rates.get_rate("GBP", "SEK", "2015-12-31"))