Skip to main content

Beancount Tools Collection

🧮 My personal collection of beancount tools including importers, price fetchers, plugins, and utilities for various financial institutions.

CI Python 3.11+ License: MIT GitHub issues

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:

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_account set 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. Set flag_unverified=False to turn that off.
  • Amounts are posted verbatim from Amount/Currency, which is always the settled amount in the card's currency. OriginalAmount is recorded as metadata but never used for arithmetic: it can differ from Amount even 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_accounts mapping) 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:

  1. Add new importers for financial institutions
  2. Improve existing importers with bug fixes and features
  3. Add price fetchers for different data sources
  4. Create plugins for common beancount workflows
  5. 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)

Source distribution for beancount-tools-collection 1.4.1
File Size Uploaded
beancount_tools_collection-1.4.1.tar.gz 44.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for beancount-tools-collection 1.4.1
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

1.5.0

2 release files

1.4.2

2 release files

This release

1.4.1 This release

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page