There’s a lot to talk about here, but let’s start with some fun numbers!
10 year old beancount ledger
10 years of financial transactions is a lot of data! In total, my ledger has over 45,000 lines of beancount entries spread across 16 plain text files. It’s all stored in one finances Directory on my laptop (version controlled). Here’s a snapshot:
❯ find . -name "*.beancount" | xargs wc -l
4037 ./includes/2020.beancount
3887 ./includes/2018.beancount
27 ./includes/cash.beancount
4398 ./includes/2021.beancount
5531 ./includes/2019.beancount
5267 ./includes/2022.beancount
3287 ./includes/2017.beancount
5506 ./includes/2024.beancount
5606 ./includes/2023.beancount
1454 ./includes/2016.beancount
1089 ./includes/open/04-expenses.beancount
66 ./includes/open/03-income.beancount
11 ./includes/open/05-liabilities.beancount
37 ./includes/open/02-assets.beancount
1 ./includes/open/01-equity.beancount
4807 ./main.beancount
45011 total
run bean-query But main.beancount Tells me that I have about 10,000 transactions in total, which in turn involve about 20,000 postings (in double-entry bookkeeping, a single transaction can have multiple postings).
❯ uv run bean-query main.beancount
Input file: "Goel"
Ready with 12466 directives (19743 postings in 9895 transactions).
beanquery>
There are total 1086 accounts.
beanquery> select count(*) from (select distinct(account));
coun
----
1086
…which does not mean that there are 1086 Edge accounting book. Accounts in BeanCount are virtual, and you can create as many as you want. Imagine one account for categorizing supermarket spending, one for tracking your income, one for your Netflix subscription, etc.
Subsequently, there are approximately 500 documents in the repository.
❯ find documents/ -name "*.pdf" | wc -l
507
Beancount lets you attach documents (like receipts or invoices) to transactions, which makes bookkeeping very efficient. I like the fact that whenever I need to file my tax return, I can simply take a look at my BeanCount ledger and find all the invoices next to the corresponding transactions.
Finally, in terms of postings, I started the year 2016 with 715, and the year 2023 was the busiest year ever in terms of total posting numbers.
beanquery> select year(date), count(*) where year(date) < 2025 group by year(date);
year coun
---- ----
2016 715
2017 1422
2018 1605
2019 2437
2020 1582
2021 2022
2022 2435
2023 2651
2024 2602
monthly ritual
I wrote earlier that every month it takes me about 30-45 minutes to import my financial transactions into BeanCount. What does that workflow look like? I wrote a more detailed blog post about this a few years ago, but here’s a gist.
It starts with logging into my bank account(s) to download my monthly statement in CSV (CSV because it’s more predictable to parse than PDF). I then run these CSV files through an “importer”, which converts this CSV data into data structures that BeanCount understands. Then I add all those extracted entries to my current .beancount file (which is the main file All My financial transactions in plain text). Then I go through each entry one by one and make sure it is balanced (in double-entry bookkeeping, the sum of all postings in a transaction must be zero, and not all postings/transactions with importer outputs balance). Some of this balancing is manual and some is automated (for example the importer code can look at the details of the transaction and decide which account it should go to, and balance automatically). This last part (balance) is where most of those 30-45 minutes go.
Whenever a new year starts, I transfer all the transactions of the previous year to it
file and add a include in active
main.beancountfile, mainly to keep the main file from getting too long. Not that it would be an issue for beancount, but just for readability.
With this kind of workflow, all my financial transactions from the beginning of time are contained in a few plain text files in a directory on my laptop.
Creation of beancount importers for German banks
BeanCount only provides the foundation for working with money, but it has no insight into what your bank statements look like. This is where the concept of an importer comes in. An importer is a (Python) class that takes a type of bank statement (e.g. a CSV export of your transactions) and converts them into something BeanCount can work with.
I live in Germany and my bank accounts are in German banks, so I had to write some importers for a few different banks, specifically beancount-dkb, beancount-ing, beancount-n26, and beancount-comerzbank. I closed my Commerzbank account a while ago, so I no longer maintain that integration, but the first three libraries are actively maintained (and used)!
From user to author
My start with BeanCount was a bit of a bumpy ride. The documentation is very comprehensive but as a newcomer, I found it difficult to gain a grasp on the overall workflow. It took me some trial and error to figure things out and get that “aha” moment.
I thought that if I find it difficult, it’s probably difficult for others too. So I wrote a short book to help newcomers easily get involved with BeanCount. If you’re interested, here’s a link: https://personalfinancespython.com.
Response to the book has been extremely positive. It got a mention on BeanCount’s external contributions page, and reader reviews have been very encouraging!
closing thoughts
Having all of my finances in a bunch of plain text files tracked in a Git repository seems invaluable to me. And hitting the 10-year mark at that almost feels like a milestone.
Perhaps the best part about all this is that this data exists my own machineNot in any data center anywhere else. It’s all in plain text files that I can open in my editor, and analyze using the tools that the BeanCount ecosystem gives me. All of this will surpass any app or service, and I think that’s why plaintext accounting is so powerful.
<a href