ibkr-report-parser
Interactive Brokers (IBKR) Report Parser for MyTax (vero.fi) - not affiliated with either service
Example
How to run locally
Option 1: pip
pip install ibkr-report-parser
ibkr-report-parser
Option 2: Docker
docker pull ghcr.io/oittaa/ibkr-report-parser
docker run --rm -d -p 8080:8080 --name ibkr-report-parser ghcr.io/oittaa/ibkr-report-parser
Use the app
Browse to http://127.0.0.1:8080/
Environment variables
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""
Testing and debugging
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
How to build yourself
Python
git clone https://github.com/oittaa/ibkr-report-parser.git
cd ibkr-report-parser
pip install .
ibkr-report-parser
Docker
git 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-parser
Multiple years and options
You 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.
MyTax field names (form 9A)
UI labels and the Python API follow Verohallinto form 9A terminology (English / Finnish):
| Report / API | Form 9A (EN sense) | Finnish |
|---|---|---|
acquired_on |
Acquisition date | Hankintapäivä |
acquisition_cost |
Acquisition cost / price | Hankintahinta |
disposed_on |
Disposal date | Luovutuspäivä |
proceeds |
Disposal price | Luovutushinta |
realized |
Gain or loss | Luovutusvoitto / -tappio |
used_deemed_acquisition_cost |
Deemed acquisition cost used | Hankintameno-olettama |
Totals: proceeds / gains / losses |
Total disposal prices / gains / losses | Luovutushinnat / -voitot / -tappiot yhteensä |
Python API
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"))
Metadata
Release files for ibkr-report-parser 2026.7.20
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ibkr_report_parser-2026.7.20.tar.gz | 204.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ibkr_report_parser-2026.7.20-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 413.1 kB
Release files / ibkr_report_parser-2026.7.20.tar.gz
| Download URL | ibkr_report_parser-2026.7.20.tar.gz |
|---|---|
| Size | 204.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bfc45e7cfd62b1836b854e52796cf8f9b59b5facc5a8f2e24755648c35973a61
|
|
BLAKE2b-256 checksum How to use checksums |
d1e7c22687d1cc0391ec668b5f05167bf33cf5e0df70d123c06ef695f67b3b79
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Release files / ibkr_report_parser-2026.7.20-py3-none-any.whl
| Download URL | ibkr_report_parser-2026.7.20-py3-none-any.whl |
|---|---|
| Size | 208.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
566e0bd61863645c7b63182bafde2c35bb783b763a1e751c7f8ce5bfbd5671eb
|
|
BLAKE2b-256 checksum How to use checksums |
71c443c9f8be10791418ec6f63d9e1de62a45bd745396c0244453644286c76f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|