Beancount Tools Collection
🧮 My personal collection of beancount tools including importers, price fetchers, plugins, and utilities for various financial institutions.
Features
📥 Data Importers
Swiss Institutions:
- Yuh - CSV exports
- Viseca - CSV bill exports (primary) and JSON transaction exports (archival), including Migros Cumulus Credit Card
- VIAC - JSON transaction exports (pillar 2 & 3a)
- Finpension - CSV transaction reports (pillar 3a)
International Institutions:
- Interactive Brokers - FlexQuery XML reports (global); fetch/credential failures surface as errors in Fava and exit non-zero in the CLI (a genuinely empty statement still shows "No entries to import")
- Revolut - CSV exports (multi-country)
Other Formats:
- Firefly III - CSV exports
💰 Price Fetchers
- Interactive Brokers - Real-time prices from FlexQuery
🔌 Beancount Plugins
- Crickets chirping 🦗 - This section is as empty as my wallet after buying crypto at the peak
🛠️ Utility Scripts
- Transaction Processor - Example transaction processing with ImporterProtocolAdapter and TransactionInspector for automatic categorization and payee standardization
Installation
From PyPI
pip install beancount-tools-collection
From Source
git clone https://github.com/mekanics/beancount-tools-collection.git
cd beancount-tools-collection
make install
uv run pre-commit install
make install syncs the lockfile into .venv. The pre-commit hooks format
staged files; pre-push runs make check (the same gate CI uses).
Quick Start
Basic Importer Configuration
from beancount_tools_collection.importers import (
finpension,
ibkr,
revolut,
viac,
viseca_csv,
yuh,
)
# Example configuration
CONFIG = [
# Swiss institutions
finpension.FinpensionImporter(
root_account='Assets:Pension:S3:Finpension:Portfolio1',
deposit_account='Assets:Checking',
isin_lookup={
'CH0132501898': 'CH0132501898', # Example ISIN mapping
# ... more ISINs
},
),
viac.ViacImporter(
root_account='Assets:Pension:S3a:Viac:Portfolio1',
deposit_account='Assets:Checking',
share_lookup={
'UBS SMI': {'isin': 'CH0033782431', 'symbol': 'CH0033782431'},
# ... more share mappings
},
),
yuh.YuhImporter(account='Assets:Cash:Yuh:CHF', goals_base_account='Assets:Savings:Yuh'),
viseca_csv.VisecaCsvImporter(
account='Liabilities:CreditCard:Viseca',
# The monthly "Ihre Zahlung - Danke" row settles the previous bill.
# Point it at the account you pay from and the liability returns to
# zero each cycle, so a dropped transaction shows up as a balance error.
settlement_account='Assets:Cash:Yuh:CHF',
# Optional. Unmapped merchants stay single-legged on purpose.
merchant_map={
'Coop': 'Expenses:Groceries',
'Migros': 'Expenses:Groceries',
'SBB CFF FFS': 'Expenses:Transport',
},
),
# International institutions
ibkr.IBKRImporter(
Mainaccount='Assets:Invest:InteractiveBrokers',
DivAccount='Income:Dividends:InteractiveBrokers',
WHTAccount='Expenses:Taxes:WithholdingTax',
PnLAccount='Income:Invest:Gains',
FeesAccount='Expenses:Invest:Fees',
configFile='ibkr.yaml', # Your IBKR FlexQuery config
),
revolut.RevolutImporter('revolut_chf', 'Assets:Cash:Revolut:CHF', 'CHF'),
]
Automatic Categorization
The Viseca CSV export carries no category, so VisecaCsvImporter emits
single-legged postings and leaves the expense account to you. Merchants in
merchant_map get a balanced expense leg; anything else stays incomplete
for you to fill in Fava.
Price Fetcher Configuration
# In your beancount price configuration
from beancount_tools_collection.prices import ibkr
# The IBKR price source will be available for bean-price
Documentation
Importer-Specific Setup
Each importer has specific requirements and configuration options:
- Finpension - CSV transaction reports
- Interactive Brokers - FlexQuery configuration
- VIAC - JSON export setup
- Viseca - CSV bill exports (primary) and JSON (archival)
- Yuh - CSV account-activities exports
Account Structure Examples
The importers work best with structured account hierarchies:
Assets:
Cash:
Yuh:
CHF
USD
Revolut:
CHF
EUR
Invest:
InteractiveBrokers:
Long-Term:
VTI
VXUS
USD
Pension:
S3a:
Finpension:
Portfolio1:
CHF
Viac:
Portfolio1:
CH0132501898
Income:
Dividends:
InteractiveBrokers:
Long-Term:
USD
Pension:
S3a:
Finpension:
Portfolio1:
Interest:
CHF
Expenses:
Invest:
Fees:
CHF
USD
Taxes:
WithholdingTax
Notes
Yuh CSV exports
YuhImporter reads the account-activities CSV the Yuh app exports. It recognises
the file by its column header, so the download works unrenamed; pass regex as
well if you import several Yuh accounts separately.
Viseca CSV bill exports
VisecaCsvImporter reads the CSV bill the Viseca One app exports. It recognises
the file by its column header, so the download works unrenamed; pass
filename_regex as well if you import several Viseca accounts separately.
A few behaviours worth knowing:
- Payments become transfers. The monthly "Ihre Zahlung - Danke" row settles
the previous bill. With
settlement_accountset it posts as a balanced transfer, so the liability returns to zero each cycle. The JSON importer drops these rows, which lets the liability grow without bound. - Refunds and foreign-currency rows are flagged
!for review, since their semantics have not yet been confirmed against real data. Setflag_unverified=Falseto turn that off. - Amounts are posted verbatim from
Amount/Currency, which is always the settled amount in the card's currency.OriginalAmountis recorded as metadata but never used for arithmetic: it can differ fromAmounteven at exchange rate 1.0 in the same currency. - Bad rows are skipped, not fatal, and the count is reported in the log so
the loss is never silent. Failures affecting the whole file (unreadable file,
unexpected header, several cards with no
card_accountsmapping) raise instead, so Fava shows a real error rather than "No entries to import". - Entries carry
transactionId, the same metadata key the JSON importer writes, so re-imports and overlapping bills deduplicate exactly rather than heuristically.
Interactive Brokers sells
Enable Execution and Closed Lots in the Trades section of your Flex
query. Each sell then closes the lots IBKR closed, matched by open date
(-3 ACME {2022-09-14} @ 212.35 USD). This works with Beancount's default
booking method.
IBKR's closed-lot price includes the buy commission, while the importer books
buys at the execution price and the commission as a fee. That's why sells never
write a lot price, and why the profit Beancount computes differs from IBKR's by
the original buy commission. IBKR's own cost basis is kept as ibkr_cost
metadata for comparison.
Without Closed Lots, a sell posts -8 ACME {} and your ledger's booking method
picks the lots. With several lots of one stock that needs
option "booking_method" "FIFO"; the default STRICT method reports an
"Ambiguous matches" error. Sells whose closed lots don't add up, and cancelled
sells, are flagged ! for review.
Interactive Brokers errors (v1.1.0+)
IBKR Flex fetch and credential failures (expired/invalid token, bad ibkr.yaml, network errors, unparseable statements) now raise typed errors instead of returning an empty entry list. In Fava this surfaces as an import/API error (rather than the yellow "No entries to import from this file." warning that used to appear on hard failures). The CLI exits non-zero with a short remediation message.
Exception messages, log lines, and raised tracebacks redact the Flex token (and do not chain secret-bearing upstream exceptions). If older logs were shared while a token was still live, rotate the token under Reports > Flex Web Service.
Publishing to PyPI
Requires Python 3.11+. Bump the version, commit, then push a matching tag:
uv version --bump minor # or patch / major
git add pyproject.toml uv.lock
git commit -m "Release 1.3.0"
git tag v1.3.0
git push origin main --tags
The tag must equal v plus the version in pyproject.toml. A mismatch fails
before any PyPI contact. That tag push is the only publish trigger: the
Release workflow tests, uploads to PyPI, then creates the matching GitHub
Release. Creating or editing a Release in the GitHub UI does not upload again.
Contributing
We welcome contributions! Here's how you can help:
- Add new importers for financial institutions
- Improve existing importers with bug fixes and features
- Add price fetchers for different data sources
- Create plugins for common beancount workflows
- Improve documentation and examples
Development Setup
Same as From Source: make install and
uv run pre-commit install.
make check # lint, format check, lockfile
make format # rewrite the tree
make test # pytest with a 30% coverage ratchet
make build # sdist + wheel, no local path sources
make audit # zizmor over the workflows
CI and pre-push call these targets. Do not invoke Ruff directly if you
want the same result as the pipeline.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- The beancount project for the excellent accounting framework
- Various open-source beancount importers that served as inspiration
Support
Made with ❤️ for personal finance tracking
Metadata
Release files for beancount-tools-collection 1.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| beancount_tools_collection-1.4.1.tar.gz | 44.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| beancount_tools_collection-1.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.9 kB
Release files / beancount_tools_collection-1.4.1.tar.gz
| Download URL | beancount_tools_collection-1.4.1.tar.gz |
|---|---|
| Size | 44.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee1fecd98bc8ad80ee75e33e0ab622593b0f78daa96d6e0a940c9b70e8569fd4
|
|
BLAKE2b-256 checksum How to use checksums |
cfcffde2dcebfa7e12232fa09aa4a7f0f28812e51acc93d7bca508969d88ca0d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / beancount_tools_collection-1.4.1-py3-none-any.whl
| Download URL | beancount_tools_collection-1.4.1-py3-none-any.whl |
|---|---|
| Size | 51.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a5f9e082cc51eb47f5c02601fe94e7ff2dafa84384fb8fe561c7403bc2481bd1
|
|
BLAKE2b-256 checksum How to use checksums |
91bd3805e4ff79c7cbff0c2baacff960327c0586100c19b5b5d15be3d3585427
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log