<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed.css?v=208f42b55cff" type="text/css"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:site="https://lalitm.com/feed/ns#"><channel><title>Lalit Maganti (Tag: Beancount)</title><link>https://lalitm.com/tags/beancount/</link><description>Recent content tagged Beancount on Lalit Maganti</description><site:notice>This is a feed.
Feeds let you subscribe to updates from this site using a feed reader. Copy this page's URL from your address bar and paste it into your reader.
New to feeds? Read: https://aboutfeeds.com</site:notice><docs>https://aboutfeeds.com</docs><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Thu, 01 Jan 2026 10:09:00 +0100</lastBuildDate><atom:link href="https://lalitm.com/tags/beancount/index.xml" rel="self" type="application/rss+xml"/><item><title>One Number I Trust: Plain-Text Accounting for a Multi-Currency Household</title><link>https://lalitm.com/post/one-number-i-trust/</link><pubDate>Thu, 01 Jan 2026 10:09:00 +0100</pubDate><guid>https://lalitm.com/post/one-number-i-trust/</guid><description>Two people. Eighteen accounts spanning checking, savings, credit cards, investments. Three currencies. Twenty minutes of work every week.
One net worth number I actually trust.
The payoff: A single, trustworthy net worth number growing over time.
No app did exactly what I needed, so I built my own personal finance system using plain-text accounting principles and a powerful Python library called Beancount. This post shows you how I handle imports, investments, multi-currency, and a two-person view.
How I got here# It all started during the 2021 tax season. I had blocked out an entire weekend and was juggling statements, trying to compute capital gains, stressing about getting the numbers mixed up. “This is chaos”, I thought. “There must be a way to simplify this with automation”. Being a software engineer, I did what felt natural and hacked together a bunch of scripts on top of a database.</description><content:encoded>&lt;p&gt;Two people. Eighteen accounts spanning checking, savings, credit cards, investments. Three currencies. Twenty minutes of work every week.&lt;/p&gt;
&lt;p&gt;One net worth number I actually trust.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/01-payoff-net-worth.png" alt="Net Worth Chart growing over 2+ years" title="One net worth number to trust"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The payoff: A single, trustworthy net worth number growing over time.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;No app did exactly what I needed, so I built my own personal finance system using &lt;strong&gt;plain-text accounting&lt;/strong&gt; principles and a powerful Python library called &lt;strong&gt;Beancount&lt;/strong&gt;. This post shows you how I handle imports, investments, multi-currency, and a two-person view.&lt;/p&gt;
&lt;h2 id="how-i-got-here"&gt;How I got here&lt;a class="heading-anchor" href="#how-i-got-here" aria-label="Permalink to How I got here"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;It all started during the 2021 tax season. I had blocked out an entire weekend and was juggling statements, trying to compute capital gains, stressing about getting the numbers mixed up. &amp;ldquo;This is chaos&amp;rdquo;, I thought. &amp;ldquo;There must be a way to simplify this with automation&amp;rdquo;. Being a software engineer, I did what felt natural and hacked together a bunch of scripts on top of a database.&lt;/p&gt;
&lt;p&gt;Though it worked and I kept using it day-to-day, by the next tax season the cracks became obvious. The code was hard to debug, random transactions went missing, and worst of all, the balances the scripts computed didn&amp;rsquo;t match the balances on my statements. I tried to fix it but the more I tried, the more I felt lost about what the system was really doing. Eventually I just gave up.&lt;/p&gt;
&lt;p&gt;Why did I fail so spectacularly? My entire approach was flawed from the start! I&amp;rsquo;d ignored centuries of accounting wisdom and repeated fundamental mistakes humanity solved long ago. So I learned from my mistakes and did the research. And over time I incrementally discovered &lt;strong&gt;double-entry bookkeeping&lt;/strong&gt;, &lt;strong&gt;plain-text accounting&lt;/strong&gt; and &lt;strong&gt;Beancount&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Fast forward to today, and I have a flexible, powerful, and private system, fully customized to how my brain works. Most transactions import automatically from PDF statements (counterintuitively, it&amp;rsquo;s often more reliable than CSV!). Tax time is a simple matter of checking always-fresh reports and copying numbers over. The weekly ritual is simple: download statements, categorize transactions in a web UI, run a bunch of scripts to regenerate, commit (I walk through this in more detail later).&lt;/p&gt;
&lt;p&gt;However, I want to be realistic: building a system like this takes time and effort.&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; You will need to learn some basic accounting concepts, be comfortable with Python, and consistently spend time every week keeping things up-to-date. If your finances are simple or you just want day-to-day budgeting, this is almost certainly overkill. Apps like YNAB or even the humble spreadsheet work great.&lt;/p&gt;
&lt;p&gt;But if you want uncompromising control over how you look at your finances, read on.&lt;/p&gt;
&lt;h2 id="chapter-1-the-concepts"&gt;Chapter 1: The Concepts&lt;a class="heading-anchor" href="#chapter-1-the-concepts" aria-label="Permalink to Chapter 1: The Concepts"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h4 id="double-entry-bookkeeping"&gt;Double-entry bookkeeping&lt;a class="heading-anchor" href="#double-entry-bookkeeping" aria-label="Permalink to Double-entry bookkeeping"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Suppose on a Saturday, I transfer money from my checking account to a savings account. The money leaves on the same day but doesn&amp;rsquo;t show up on the other side until Monday. So where was it for those two days?&lt;/p&gt;
&lt;p&gt;In a &amp;ldquo;normal&amp;rdquo;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; personal finance system, the answer would be that it was just gone. That is, for those two days, there would be a drop in the total money in two accounts. But this is weird because in reality my &amp;ldquo;net worth&amp;rdquo; did not change, yet there&amp;rsquo;s no good way to represent this.&lt;/p&gt;
&lt;p&gt;Or suppose I pay $90 for a dinner for me and two friends. They pay me back a week later. Again, in this case the money is &amp;ldquo;gone&amp;rdquo; for that week. And even worse, the full $90 would be categorized as a &amp;ldquo;restaurant expense&amp;rdquo; while each $30 my friends paid would be &amp;ldquo;income&amp;rdquo;. But this is wrong. My expense is just $30 and the money they give me should be matched against the $60 they owe me.&lt;/p&gt;
&lt;p&gt;Both of these are fundamental problems with how so-called &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Single-entry_bookkeeping"&gt;single-entry bookkeeping&lt;/a&gt;&amp;rdquo; works: each account&amp;rsquo;s transactions and balance are tracked individually but without the context of the &amp;ldquo;whole&amp;rdquo;. In the case of the transfer, because we&amp;rsquo;re looking at each account in isolation, we lose the fact that even though the money has left one account, it&amp;rsquo;s really still part of the &amp;ldquo;pool of money&amp;rdquo; that belongs to us. Similarly, when our friends pay us back, we&amp;rsquo;re not tracking the fact that our friends owe us money when the original transaction happened and their payment later neutralizes the debt.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Double-entry_bookkeeping"&gt;Double-entry bookkeeping&lt;/a&gt; is the solution to both these problems. Businesses have been using it for hundreds of years&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt; to run their accounts, and it has powerful yet elegant ways to solve these problems and many others too.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s consider again the transfer. In double-entry bookkeeping, we would represent the initial move of money as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Bank-Checking -1000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Transfer-In-Flight +1000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And when it arrives:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Transfer-In-Flight -1000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Savings-Account +1000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In both cases, we see the &amp;ldquo;golden rules&amp;rdquo; of double-entry bookkeeping:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Every transaction has at least two sides and the sum of all the sides is zero&lt;/em&gt;. -1000 + 1000 = 0. That is, transactions always &amp;ldquo;balance&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Every side of a transaction is an account&lt;/em&gt;, whether it exists in the real world or not.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It should be clear that &amp;ldquo;Bank-Checking&amp;rdquo; and &amp;ldquo;Savings-Account&amp;rdquo; are labels for your checking and savings accounts, respectively. But what is &amp;ldquo;Transfer-In-Flight&amp;rdquo;?&lt;/p&gt;
&lt;p&gt;Well, it&amp;rsquo;s also an account! It&amp;rsquo;s not an account you&amp;rsquo;ll find on your bank&amp;rsquo;s website, but within the double-entry system, it&amp;rsquo;s &lt;em&gt;just as real&lt;/em&gt;. Concretely, accounts in double-entry are just labels for a &amp;ldquo;bucket of money&amp;rdquo;. So there&amp;rsquo;s no &amp;ldquo;category to put this transaction under&amp;rdquo;, no &amp;ldquo;expense tracking&amp;rdquo;, no special &amp;ldquo;transfer tag&amp;rdquo;. Everything is an account.&lt;/p&gt;
&lt;p&gt;In this specific case, &lt;code&gt;Bank-Checking&lt;/code&gt;, &lt;code&gt;Savings-Account&lt;/code&gt;, and &lt;code&gt;Transfer-In-Flight&lt;/code&gt; are all a specific type of account: they are &lt;strong&gt;Asset&lt;/strong&gt; accounts. Assets are stuff you own; these can be real accounts (bank account, savings, stocks, bonds) or conceptual accounts (money in transit between accounts).&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s consider the dinner example. There are 4 sides to the transaction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Credit-Card -90
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Restaurant-Expense +30
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Owes-Me:Alice +30
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Owes-Me:Bob +30
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Again, -90 + 30 + 30 + 30 = 0. It balances. All of &lt;code&gt;Credit-Card&lt;/code&gt;, &lt;code&gt;Restaurant-Expense&lt;/code&gt;, &lt;code&gt;Owes-Me:Alice&lt;/code&gt;, and &lt;code&gt;Owes-Me:Bob&lt;/code&gt; are just accounts.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Credit-Card&lt;/code&gt; is a different type of account though: it&amp;rsquo;s a &lt;strong&gt;Liability&lt;/strong&gt;. Liabilities are the opposite of assets: instead of stuff you own, they&amp;rsquo;re stuff you &lt;strong&gt;owe&lt;/strong&gt; to someone else. So for example, loans, credit cards, and mortgages are all liabilities.&lt;/p&gt;
&lt;p&gt;Why &lt;strong&gt;negative&lt;/strong&gt; 90? The rule is always: negative means money flowed &lt;em&gt;from&lt;/em&gt; this account and positive means it flowed &lt;em&gt;into&lt;/em&gt; this account. The credit card company fronted you $90, so that money flowed from your credit card to fund the purchase.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Restaurant-Expense&lt;/code&gt; is yet another type of account, an &lt;strong&gt;Expense&lt;/strong&gt; account. Expense accounts are money &amp;ldquo;leaving your world&amp;rdquo;. So any time you spend some money and you no longer have access to it, that&amp;rsquo;s an expense.&lt;/p&gt;
&lt;p&gt;Finally, &lt;code&gt;Owes-Me:Alice&lt;/code&gt; and &lt;code&gt;Owes-Me:Bob&lt;/code&gt; are also &lt;strong&gt;Assets&lt;/strong&gt;. Alice and Bob have promised to pay you back, and that promise has value, $30 each. It&amp;rsquo;s not cash in your pocket, but it&amp;rsquo;s money you have a claim on. In double-entry, anything with economic value you control is an asset, whether it&amp;rsquo;s a bank balance or an IOU.&lt;/p&gt;
&lt;p&gt;Later, when Alice pays you back:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Bank-Checking +30
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Owes-Me:Alice -30
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is just money moving from the &amp;ldquo;virtual&amp;rdquo; &lt;code&gt;Owes-Me:Alice&lt;/code&gt; to the &amp;ldquo;real&amp;rdquo; &lt;code&gt;Bank-Checking&lt;/code&gt; account. Both of these are still assets; it&amp;rsquo;s just the &lt;em&gt;type&lt;/em&gt; of asset that&amp;rsquo;s changing. So no money has &amp;ldquo;entered the system&amp;rdquo; at this point. You&amp;rsquo;re just settling the debt Alice owed you.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take one last example: a paycheck.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Bank-Checking +3000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Salary -3000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Bank-Checking&lt;/code&gt; is an &lt;strong&gt;Asset&lt;/strong&gt; as we&amp;rsquo;ve learned. But &lt;code&gt;Salary&lt;/code&gt; is a new account type, an &lt;strong&gt;Income&lt;/strong&gt; account. Just like &lt;strong&gt;Assets&lt;/strong&gt; and &lt;strong&gt;Liabilities&lt;/strong&gt; are opposites, so are &lt;strong&gt;Income&lt;/strong&gt; and &lt;strong&gt;Expenses&lt;/strong&gt;. Where &lt;strong&gt;Expenses&lt;/strong&gt; are money leaving your world, &lt;strong&gt;Income&lt;/strong&gt; is money entering it.&lt;/p&gt;
&lt;p&gt;Money flowed from Salary (source, negative) to Bank-Checking (destination, positive). The sign feels backwards: &amp;ldquo;I &lt;em&gt;received&lt;/em&gt; money, so why is Income &lt;em&gt;negative&lt;/em&gt;?&amp;rdquo; Because the sign shows direction of flow: income is where the money &lt;em&gt;came from&lt;/em&gt;, and your bank is where it &lt;em&gt;went to&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is the one part of double-entry that takes repetition.&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; Don&amp;rsquo;t try to make it intuitive; just trust the invariant: &lt;strong&gt;if your transaction sums to zero, you&amp;rsquo;ve got the signs right.&lt;/strong&gt; After a dozen transactions, the pattern becomes automatic.&lt;/p&gt;
&lt;p&gt;These four types of accounts cover 99% of what you&amp;rsquo;ll do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Assets&lt;/strong&gt;: stuff you own (bank accounts, cash, investments, money owed to you)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Liabilities&lt;/strong&gt;: stuff you owe (credit cards, loans)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Income&lt;/strong&gt;: money entering your world (salary, interest, dividends)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expenses&lt;/strong&gt;: money leaving your world (groceries, rent, restaurants)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s a fifth type, &lt;strong&gt;Equity&lt;/strong&gt;, which is a catch-all &amp;ldquo;this money doesn&amp;rsquo;t fit elsewhere&amp;rdquo; bucket.&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; Suppose you start tracking an account that already has $1000 in it; that money came from &lt;strong&gt;somewhere&lt;/strong&gt; but you don&amp;rsquo;t have a record of that. It can&amp;rsquo;t be income because you already had it, and the other types don&amp;rsquo;t fit. That&amp;rsquo;s a good sign it belongs in equity. The good news is that you rarely interact with Equity directly. Generally, the software handles it for you, but there are some exceptions that we&amp;rsquo;ll cover in Chapter 2.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s so much more that could be said about double-entry bookkeeping. For further reading, I particularly like the &lt;a href="https://beancount.github.io/docs/the_double_entry_counting_method.html"&gt;double entry explainer&lt;/a&gt; in the Beancount docs. It goes through some more examples and expands into a bunch of related topics.&lt;/p&gt;
&lt;p&gt;But we now have the foundation which ensures that every transaction balances, every dollar is accounted for, and nothing slips through the cracks. But we still need a way to actually record and store these transactions.&lt;/p&gt;
&lt;h4 id="plain-text-accounting"&gt;Plain text accounting&lt;a class="heading-anchor" href="#plain-text-accounting" aria-label="Permalink to Plain text accounting"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;One of the things I learned from writing my own finance system is that auditability is king. You need the ability to eyeball a transaction, ask yourself &amp;ldquo;does this look right,&amp;rdquo; and fix it if it doesn&amp;rsquo;t. And nothing beats being able to see and edit any transaction you&amp;rsquo;ve ever made in a text editor.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s one of the main things that drew me to the philosophy of &lt;a href="https://plaintextaccounting.org/"&gt;Plain Text Accounting&lt;/a&gt;. This is a set of principles on using plain text files as the &amp;ldquo;immutable source of truth&amp;rdquo; of your finances and then building scripts and tools on top of them to process, analyze, and visualize them.&lt;/p&gt;
&lt;p&gt;There are many other advantages to this I&amp;rsquo;ve come to appreciate over the years:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Everything is version controlled.&lt;/strong&gt; You can store these transaction files in a git repo, which has powerful effects. You can look at diffs to see what changed on any day. You can &lt;code&gt;git blame&lt;/code&gt; any transaction to see when and why it was added. You can &lt;code&gt;git tag&lt;/code&gt; important states of the repo (e.g. when taxes were filed, when a new job was started, when a big refactoring happened). You can &lt;code&gt;git checkout&lt;/code&gt; any previous state to see e.g. &amp;ldquo;how did my repo look last year&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s private&lt;/strong&gt; so you never have to trust any third party with all your financial details. Everything can be stored in locations that you fully control.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There&amp;rsquo;s no lock-in.&lt;/strong&gt; Because everything is just a plain text file, it&amp;rsquo;s trivially easy to change how you want things to be represented: you don&amp;rsquo;t have to deal with apps with broken or messy CSV exports making it difficult to take your data elsewhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s scriptable&lt;/strong&gt;. If you want to refactor something, compute a new breakdown or even rewrite your system entirely, all you need is to write a script. Whether you write it yourself or prompt an LLM to do it for you, the text-based format makes automation trivial.&lt;/p&gt;
&lt;p&gt;Plain text gives you the abstract idea of &amp;ldquo;storing transactions in text&amp;rdquo; but there&amp;rsquo;s still a bunch of questions. What&amp;rsquo;s the transaction syntax? How do you parse your files? How do you validate that everything balances, compute totals, and let you query the results? That&amp;rsquo;s where &lt;strong&gt;Beancount&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;h4 id="introducing-beancount"&gt;Introducing Beancount!&lt;a class="heading-anchor" href="#introducing-beancount" aria-label="Permalink to Introducing Beancount!"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Over the years, people have written many plain-text accounting tools which answer all the questions above. The main ones you&amp;rsquo;ll find which have gained a lot of popularity are &lt;strong&gt;Ledger&lt;/strong&gt;, &lt;strong&gt;hledger&lt;/strong&gt;, and &lt;strong&gt;Beancount&lt;/strong&gt;. I&amp;rsquo;ve used each of them at some point in my plain text journey and all are solid choices. But I ended up on Beancount for a few reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s a Python library&lt;/strong&gt;, not just a command-line tool. I can write importers that parse my bank&amp;rsquo;s PDF statements, generate transactions programmatically, and build custom reports, all in a language I already know.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strictness by default&lt;/strong&gt;. Accounts must be declared before use, so typos get caught immediately. Transactions must balance and there are immediate error messages if they don&amp;rsquo;t. The tool catches mistakes early rather than letting them propagate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plugin and tool ecosystem&lt;/strong&gt;. Beancount has a very rich set of libraries and tools which build on top of and integrate with it. Along with the core project, you get access to any and all of these projects you want to use. We&amp;rsquo;ll discuss this much more later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/beancount/fava"&gt;Fava&lt;/a&gt;&lt;/strong&gt;. The web UI which sits on top of the Beancount engine. It&amp;rsquo;s so good that people convert from other formats (using tools like &lt;a href="https://github.com/beancount/ledger2beancount"&gt;ledger2beancount&lt;/a&gt; or &lt;a href="https://github.com/shivanshu7/gnucash2beancount"&gt;gnucash2beancount&lt;/a&gt;) just to use it. Where Beancount gives you the reliable engine, Fava gives you the pretty yet powerful frontend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reports&lt;/strong&gt;: balance sheet, income statement, transaction journal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query editor&lt;/strong&gt;: SQL-like queries, exportable to CSV&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Charts&lt;/strong&gt;: spending breakdowns, net worth over time, holdings by currency&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error highlighting&lt;/strong&gt;: problems highlighted immediately&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;: plugins like &lt;a href="https://github.com/andreasgerstmayr/fava-dashboards"&gt;fava-dashboards&lt;/a&gt; and &lt;a href="https://github.com/andreasgerstmayr/fava-portfolio-returns"&gt;fava-portfolio-returns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With this, we now understand enough of the basic concepts for us to get started trying out Beancount!&lt;/p&gt;
&lt;h2 id="chapter-2-getting-started"&gt;Chapter 2: Getting Started&lt;a class="heading-anchor" href="#chapter-2-getting-started" aria-label="Permalink to Chapter 2: Getting Started"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The best way to learn plain-text accounting is to roll up your sleeves and try it out. So let&amp;rsquo;s pause the theory for a moment and get a real ledger running on your machine.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve built a companion repository, &lt;a href="https://github.com/LalitMaganti/beancount-blog-examples"&gt;LalitMaganti/beancount-blog-examples&lt;/a&gt;, which contains a cut-down version of the system I use day-to-day. The repo is organized into folders that match the chapters of this post (&lt;code&gt;chapter-2/&lt;/code&gt;, &lt;code&gt;chapter-3/&lt;/code&gt;, etc.), each building on the previous. It also includes a &lt;code&gt;demo/&lt;/code&gt; folder with 2+ years of synthetic history if you want to see the end result immediately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To get started, clone the repository:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/LalitMaganti/beancount-blog-examples.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; beancount-blog-examples
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Run the demo to see the end result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./scripts/quickstart.sh demo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Or start Chapter 2 to follow the guide&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./scripts/quickstart.sh chapter-2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will set up a Python environment, install dependencies, and open Fava at http://localhost:5000. Here&amp;rsquo;s what you should see.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/02-first-look-trial-balance.png" alt="Fava Trial Balance showing Assets, Income, and Expenses" title="First look at Fava"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;Fava's Trial Balance view shows assets, income, and expenses in one place.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/03-sunburst-expenses.png" alt="Sunburst chart breaking down expenses" title="Visualizing expenses"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;Sunburst charts make it easy to spot your biggest expense categories instantly.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Click around the different tabs. You&amp;rsquo;ll find that Fava gives you the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Balance sheet&lt;/strong&gt;: the state of your accounts at the current point in time. Basically think of it as an aggregate view of all your finances. You can answer questions like &amp;ldquo;what&amp;rsquo;s my net worth now?&amp;rdquo;, &amp;ldquo;how much money do I owe across my credit cards?&amp;rdquo; or &amp;ldquo;how much have I gained from investments?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Income statement&lt;/strong&gt;: the sum of all the money flows into/out of your accounts. Think of it as a sum of all the income and expenses across time. You can answer questions like &amp;ldquo;how much did I earn from my job?&amp;rdquo;, &amp;ldquo;how much did I spend on Amazon?&amp;rdquo; or &amp;ldquo;did I spend more or less than last year?&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transaction history&lt;/strong&gt;: a flat list of all transactions you have in your journal. A way to see and search any transaction you&amp;rsquo;ve made across any accounts in your system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/04-query-console.png" alt="Fava Query Console running a SQL query" title="SQL for your finances"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The Query Console lets you run SQL-like queries against your financial data.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s much more to Fava&amp;rsquo;s features (queries, multi-currency, plugins) as we&amp;rsquo;ll see later on in the post.&lt;/p&gt;
&lt;p&gt;You can also explore the text journal itself using a text editor. For example, here&amp;rsquo;s a grocery run and a payslip in the Beancount transaction format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-2/src/transactions.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; Beancount auto-fills the second amount when it can be inferred.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-15 * &amp;#34;Tesco&amp;#34; &amp;#34;Weekly groceries&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Expenses:Groceries 85.50 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:HSBC:Current:GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-25 * &amp;#34;Google&amp;#34; &amp;#34;January salary&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:HSBC:Current:GBP 3200.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Income:Lalit:UK:Google:Salary
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Go through the transactions and get a feel for the format. It might seem alien at first but trust me when I say soon it&amp;rsquo;ll feel like the most natural thing in the world!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/05-ide-experience.png" alt="VS Code with Beancount extension showing syntax highlighting" title="The IDE Experience"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;With a VS Code extension, you get syntax highlighting and auto-completion for your accounts.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now that you have a working system, I want to share the hard-won insights that aren&amp;rsquo;t in the official docs. This post isn&amp;rsquo;t going to be a full Beancount tutorial. The &lt;a href="https://beancount.github.io/docs/getting_started_with_beancount.html"&gt;official docs&lt;/a&gt; are excellent for that (Fava even has a &lt;a href="https://fava.pythonanywhere.com/example-beancount-file/income_statement/"&gt;demo&lt;/a&gt; that you can try without downloading anything!).&lt;/p&gt;
&lt;p&gt;Instead, I want to focus on the &lt;em&gt;architecture&lt;/em&gt;: the decisions I wish I&amp;rsquo;d made correctly from day one. I&amp;rsquo;m writing this as if I&amp;rsquo;m speaking to my past self.&lt;/p&gt;
&lt;h4 id="start-with-one-account"&gt;Start with one account&lt;a class="heading-anchor" href="#start-with-one-account" aria-label="Permalink to Start with one account"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;You don&amp;rsquo;t need to track everything on day one. Pick one account. Your main checking account is a good start. Get comfortable with the flow. Import statements, categorize transactions, check that balances match. Once that feels solid, add another account. Then another.&lt;/p&gt;
&lt;p&gt;I started with my HSBC current account. Now, I have my whole financial life inside the system and I trust it wholeheartedly. But this happened one account at a time. If I tried to do everything in one go, I would certainly have been overwhelmed and given up on the whole thing.&lt;/p&gt;
&lt;h4 id="opening-balances"&gt;Opening balances&lt;a class="heading-anchor" href="#opening-balances" aria-label="Permalink to Opening balances"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Once you&amp;rsquo;ve picked your first account, you face an immediate problem: you may have opened that account years ago and there might already be thousands of transactions over that time. Trying to import them all in one go is another sure path to being overwhelmed and giving up.&lt;/p&gt;
&lt;p&gt;Instead, a better idea is to pick a &amp;ldquo;starting date&amp;rdquo; at which you say &amp;ldquo;I will import everything from this day onwards&amp;rdquo;. But that poses its own problem: you already had money in that account, how do you tell Beancount it exists?&lt;/p&gt;
&lt;p&gt;Well, Beancount has a &lt;code&gt;pad&lt;/code&gt; directive that creates the balancing entry automatically:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-2/src/balance.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Equity:Opening-Balances
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 pad Assets:Lalit:UK:HSBC:Current:GBP Equity:Opening-Balances
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-02 balance Assets:Lalit:UK:HSBC:Current:GBP 1500.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;pad&lt;/code&gt; directive tells Beancount: &amp;ldquo;whatever amount is needed to make the balance assertion true, take it from &lt;code&gt;Equity:Opening-Balances&lt;/code&gt; and put it in this account&amp;rdquo;. This is one of the rare cases you actually have to think about equity accounts (though not much beyond blindly using &lt;code&gt;Equity:Opening-Balances&lt;/code&gt;!).&lt;/p&gt;
&lt;h3 id="structure-that-scales"&gt;Structure That Scales&lt;a class="heading-anchor" href="#structure-that-scales" aria-label="Permalink to Structure That Scales"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Adding a first account is easy and the second is straightforward, but adding a third, fourth, fifth&amp;hellip; and you can easily find that things start becoming jumbled and messy. Just like code, putting a little bit of thought into the organization upfront goes a long way. This part covers the architectural decisions you&amp;rsquo;ll thank yourself later for.&lt;/p&gt;
&lt;h4 id="naming-asset-and-liability-accounts"&gt;Naming Asset and Liability accounts&lt;a class="heading-anchor" href="#naming-asset-and-liability-accounts" aria-label="Permalink to Naming Asset and Liability accounts"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The structure of asset and liability account names is very important, much more than I initially gave them credit for. It&amp;rsquo;s a good idea to keep as much information in them as possible. Here&amp;rsquo;s what I&amp;rsquo;ve settled on:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-2/src/accounts.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Assets:Lalit:UK:HSBC:Current:GBP GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Assets:Lalit:UK:Barclays:Current:GBP GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Liabilities:Lalit:UK:AMEX:GBP GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The pattern is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Type:Person:Region:Institution:Account:Currency
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Why this structure? Because it&amp;rsquo;s a lot easier to &lt;em&gt;remove detail&lt;/em&gt; than add it in later! I initially started by not having the country, the currency or my name in the account. But over time, I wanted to understand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How much money I have in the UK vs the US?&lt;/li&gt;
&lt;li&gt;How much cash (i.e. not investments) is in a certain currency?&lt;/li&gt;
&lt;li&gt;How much of our household wealth was in my wife&amp;rsquo;s accounts vs my own (discussed in more detail in Chapter 6)?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having this information in the account name is great because Beancount&amp;rsquo;s SQL syntax makes it very easy to filter on account names. Want &amp;ldquo;all UK assets&amp;rdquo;? Filter on &lt;code&gt;:UK:&lt;/code&gt;. Want &amp;ldquo;all HSBC accounts&amp;rdquo;? Filter on &lt;code&gt;:HSBC:&lt;/code&gt;. Want &amp;ldquo;all GBP cash&amp;rdquo;? Filter on &lt;code&gt;:GBP&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="powering-up-with-plugins"&gt;Powering up with Plugins&lt;a class="heading-anchor" href="#powering-up-with-plugins" aria-label="Permalink to Powering up with Plugins"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Once you have a structure, you want to ensure it stays clean. This is where Beancount&amp;rsquo;s &lt;strong&gt;Plugins&lt;/strong&gt; come in.&lt;/p&gt;
&lt;p&gt;Plugins are Python scripts that run when your ledger loads. They can validate data, modify entries, or even generate new transactions automatically. You load them in your journal file like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;plugin &amp;#34;beancount.plugins.check_commodity&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Remember the transfer-in-flight pattern from Chapter 1? Here&amp;rsquo;s how it looks in Beancount:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-2/src/transactions.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; Money leaves on Saturday
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-03-16 * &amp;#34;Transfer to Barclays&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:HSBC:Current:GBP -1000.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:Transfers:Internal 1000.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; Money arrives on Monday
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-03-18 * &amp;#34;Transfer from HSBC&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:Transfers:Internal -1000.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:Barclays:Current:GBP 1000.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once both transactions are recorded, the transit account balance returns to zero, confirming the transfer is complete. If you record one leg of a transfer but forget the other, the transit account will simply show a non-zero balance.&lt;/p&gt;
&lt;p&gt;Understanding where a non-zero balance in transfers is coming from is handled by my absolute favorite plugin, &lt;strong&gt;&lt;a href="https://github.com/redstreet/beancount_reds_plugins"&gt;beancount_reds_plugins.zerosum&lt;/a&gt;&lt;/strong&gt;. It&amp;rsquo;s responsible for matching both sides of a transaction and moving it to a separate account, meaning my &lt;code&gt;Transfers:Internal&lt;/code&gt; account only contains the actual &amp;ldquo;pending&amp;rdquo; transactions. Making this account empty is a surprisingly satisfying little &amp;ldquo;minigame&amp;rdquo; during my weekly imports (though it never lasts for long!).&lt;/p&gt;
&lt;p&gt;There are also a couple more plugins that make handling closed accounts better:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/beancount/beancount/blob/master/beancount/plugins/close_tree.py"&gt;beancount.plugins.close_tree&lt;/a&gt;&lt;/strong&gt; - When you close an account, automatically closes all child accounts too. Useful as you move your banking between institutions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/daniel-wells/beancount_checkclosed"&gt;beancount_checkclosed.check_closed&lt;/a&gt;&lt;/strong&gt; - Validates that closed accounts have zero balance and no transactions after the close date.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="the-boundary-of-your-system"&gt;The boundary of your system&lt;a class="heading-anchor" href="#the-boundary-of-your-system" aria-label="Permalink to The boundary of your system"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;As you add accounts one by one, you&amp;rsquo;ll inevitably see money flowing to places you haven&amp;rsquo;t set up yet. Say you transfer £500 to a Natwest savings account you haven&amp;rsquo;t added to the system. Where does it go?&lt;/p&gt;
&lt;p&gt;Specifically, use a named placeholder account in &lt;code&gt;Equity:Transfers&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-2/src/transactions.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-03-15 * &amp;#34;Transfer to savings (not yet tracked)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:HSBC:Current:GBP -500.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Equity:Transfers:Natwest-Savings 500.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This essentially says: &amp;ldquo;£500 went to Natwest Savings, which I&amp;rsquo;m not tracking yet&amp;rdquo;. Putting it in an equity account means it doesn&amp;rsquo;t pollute your balance sheet with incomplete information &lt;em&gt;or&lt;/em&gt; your income statement with false expenses.&lt;/p&gt;
&lt;p&gt;Note also the best practice of using a &lt;em&gt;named&lt;/em&gt; equity account per untracked destination, not a generic bucket; this was a mistake I made when I did this initially. You&amp;rsquo;ll thank yourself when you import your Natwest savings account later, you can just do a search-replace to rename the accounts &lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h4 id="organizing-your-repo"&gt;Organizing your repo&lt;a class="heading-anchor" href="#organizing-your-repo" aria-label="Permalink to Organizing your repo"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;As you add these patterns (transit accounts, multiple institutions, liability accounts) your single &lt;code&gt;journal.beancount&lt;/code&gt; file will start to become unwieldy. Just like good software architecture, you want to organize upfront to be easy to maintain as the system continues to grow.&lt;/p&gt;
&lt;p&gt;This is what the structure looks like with the concepts we have right now (it&amp;rsquo;ll get more complicated as we go deeper!):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chapter-2/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── journal.beancount # Main entry point, includes everything
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── src/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── accounts.beancount # Account definitions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── transactions.beancount # Primary transaction ledger
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── balance.beancount # Balance assertions
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/&lt;/code&gt; is what you write and edit (your &amp;ldquo;code&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;journal.beancount&lt;/code&gt; is the entry point that includes everything&lt;/li&gt;
&lt;li&gt;Later, &lt;code&gt;data/&lt;/code&gt; will hold inputs from the outside world (raw statements: PDFs, OFX, CSVs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This organization is a small change but can make a big difference in your subconscious feeling about the state of your finances!&lt;/p&gt;
&lt;h4 id="exit-ramp"&gt;Exit ramp&lt;a class="heading-anchor" href="#exit-ramp" aria-label="Permalink to Exit ramp"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;At this point, you have a solid foundation for tracking multiple accounts. If you stop here, you have a robust, auditable system for manual bookkeeping. You could continue adding transactions by hand indefinitely, and you&amp;rsquo;d still be miles ahead of any spreadsheet in terms of correctness and visibility.&lt;/p&gt;
&lt;p&gt;But manual entry is a chore, and as your financial life grows, it becomes a bottleneck. In the next chapter, we&amp;rsquo;ll see how to automate the tedious part: getting transactions from your bank statements into your ledger without losing the control that plain-text accounting gives you.&lt;/p&gt;
&lt;h2 id="chapter-3-automated-import"&gt;Chapter 3: Automated Import&lt;a class="heading-anchor" href="#chapter-3-automated-import" aria-label="Permalink to Chapter 3: Automated Import"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h4 id="automation-is-king-but-harder-than-it-looks"&gt;Automation is king, but harder than it looks&lt;a class="heading-anchor" href="#automation-is-king-but-harder-than-it-looks" aria-label="Permalink to Automation is king, but harder than it looks"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Inputting transactions by hand works for some people, but I don&amp;rsquo;t have the patience for it. Ever since I was young, I&amp;rsquo;ve always wanted to automate everything: it&amp;rsquo;s the reason why I became a software engineer in the first place!&lt;/p&gt;
&lt;p&gt;But full automation is a dead end. Most banks don&amp;rsquo;t offer APIs, and scraping breaks constantly. 2FA flows change &lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;, websites get redesigned, sessions expire. I tried this route, and it wasn&amp;rsquo;t worth it. Even in the US where aggregators like Plaid exist, coverage is patchy.&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt; In the UK, it&amp;rsquo;s impossible.&lt;/p&gt;
&lt;h4 id="the-hierarchy-of-data-sources"&gt;The hierarchy of data sources&lt;a class="heading-anchor" href="#the-hierarchy-of-data-sources" aria-label="Permalink to The hierarchy of data sources"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;So what actually works? Well ideally your financial institution gives you something you can write a script against. But what that might be is non-obvious and counter-intuitive. Here&amp;rsquo;s the hierarchy I&amp;rsquo;ve landed on over time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OFX is the gold standard.&lt;/strong&gt; If your bank offers it, use it. The format is standardized, transactions have unique IDs, and deduplication is straightforward. Life is easy if you have good OFX.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSV is the deceptive runner-up.&lt;/strong&gt; It seems like the logical choice. Structured data, right? But in practice, bank CSVs are often afterthoughts. I&amp;rsquo;ve seen column formats change without notice, &amp;ldquo;CSVs&amp;rdquo; that are actually weird custom formats spread over multiple lines, and rows coalesced in ways that lose critical information (like cost basis).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So what do you do when OFX isn&amp;rsquo;t available and CSV isn&amp;rsquo;t trustworthy? You turn to an unlikely hero.&lt;/p&gt;
&lt;h4 id="why-pdfs-beat-csvs"&gt;Why PDFs beat CSVs&lt;a class="heading-anchor" href="#why-pdfs-beat-csvs" aria-label="Permalink to Why PDFs beat CSVs"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;It sounds backwards, but PDFs are often the most reliable data source available.&lt;/p&gt;
&lt;p&gt;Banks have a strong incentive to get PDFs right. Customers actually read them. They&amp;rsquo;re legal documents that get printed and filed. If a bank messes up a PDF statement, they hear about it immediately. If they break a CSV export, it might go months without anyone noticing.&lt;/p&gt;
&lt;p&gt;The key insight is that bank statement PDFs are almost always columnar. Of course, this relies on the PDF having a proper text layer; if your bank sends you scanned images, you&amp;rsquo;re out of luck (though I&amp;rsquo;ve yet to encounter one that does). When you convert them to text while preserving the layout, you get something that looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Date Details Paid out Paid in Balance
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;15 Jan 24 TESCO STORES 1234 42.50 1,457.50
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;16 Jan 24 TFL TRAVEL 6.80 1,450.70
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;25 Jan 24 GOOGLE SALARY 3,200.00 4,650.70
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The columns are aligned by spaces, which means you can parse them as fixed-width data. The approach works in three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Convert PDF to text&lt;/strong&gt;: Run &lt;code&gt;pdftotext -layout statement.pdf statement.txt&lt;/code&gt;. The &lt;code&gt;-layout&lt;/code&gt; flag preserves the original column alignment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Find the table boundaries&lt;/strong&gt;: Bank statements have predictable markers. HSBC uses &amp;ldquo;BALANCE BROUGHT FORWARD&amp;rdquo; at the start and &amp;ldquo;BALANCE CARRIED FORWARD&amp;rdquo; at the end. You extract just the transaction rows between these markers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Parse with fixed-width columns&lt;/strong&gt;: Pandas&amp;rsquo; &lt;code&gt;read_fwf&lt;/code&gt; function is designed exactly for this. You put this logic inside the &lt;code&gt;extract()&lt;/code&gt; method of your &lt;code&gt;beangulp&lt;/code&gt; importer class, where it converts the text into a DataFrame:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Inside your Importer class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_fwf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;colspecs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Date&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Details&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Paid out&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Paid in&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The column positions come from inspecting the header row. In practice, I detect them dynamically by finding keywords like &amp;ldquo;Paid out&amp;rdquo; and &amp;ldquo;Paid in&amp;rdquo; in the header and using their character positions.&lt;/p&gt;
&lt;p&gt;Once you have a DataFrame, generating Beancount transactions is straightforward. You write a small class that iterates through this DataFrame and maps each row to a Beancount &lt;code&gt;Transaction&lt;/code&gt; object, filling in the date, amount, and payee. See my &lt;a href="https://github.com/LalitMaganti/beancount-lalitm/blob/main/src/beancount_lalitm/importers/hsbc.py"&gt;HSBC importer&lt;/a&gt; for a working example.&lt;/p&gt;
&lt;p&gt;For 95% of my banks, this approach works great. However, there is one bank where the text spacing becomes very strange and so I need to use something else. That&amp;rsquo;s when I turn to &lt;a href="https://tabula.technology/"&gt;Tabula&lt;/a&gt;, a Java CLI that extracts data tables from PDFs, even very complex ones.&lt;/p&gt;
&lt;p&gt;The main reason I don&amp;rsquo;t use it all the time is that it&amp;rsquo;s much slower. But it also succeeds in the cases where &lt;code&gt;pdftotext&lt;/code&gt; fails. I run it to get a structured JSON output, from which I create transactions (see my &lt;a href="https://github.com/LalitMaganti/beancount-lalitm/blob/main/src/beancount_lalitm/importers/hsbc_us_cc.py"&gt;HSBC US credit card importer&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m sure some readers will have worries about the fragility of what I&amp;rsquo;ve described here. I can tell you from experience that in three years, neither my UK nor my US HSBC PDF importer has ever broken. Neither has my Schwab one, and my Aviva one has only needed a single change. So I can personally vouch that this approach works and works &lt;strong&gt;well&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id="from-raw-data-to-transactions"&gt;From raw data to transactions&lt;a class="heading-anchor" href="#from-raw-data-to-transactions" aria-label="Permalink to From raw data to transactions"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;OK, so now you have statements. What do you do then? There are two pieces to the puzzle: parsing statements into transactions, and categorizing those transactions.&lt;/p&gt;
&lt;p&gt;For parsing, Beancount has an official importer framework called &lt;a href="https://beancount.github.io/docs/beangulp.html"&gt;beangulp&lt;/a&gt;. You write a Python class that knows how to read a particular file format (the &lt;a href="https://github.com/LalitMaganti/beancount-blog-examples/blob/main/chapter-3/importers/hsbc.py"&gt;skeleton importer&lt;/a&gt; in &lt;code&gt;chapter-3/&lt;/code&gt; already uses this API). Beangulp handles the mechanics: identifying which importer handles which file, extracting transactions, and deduplicating against existing entries.&lt;/p&gt;
&lt;p&gt;But beangulp just extracts transactions without making any judgment on which account the transaction should be booked against. It doesn&amp;rsquo;t know that Tesco is groceries or that British Airways is an airline. I go to a new restaurant. How does the system know that &lt;em&gt;is&lt;/em&gt; a restaurant?&lt;/p&gt;
&lt;p&gt;This leads to a very important conclusion: we &lt;strong&gt;cannot&lt;/strong&gt; fully automate importing transactions for bank accounts and credit cards. However, that doesn&amp;rsquo;t mean we have to enter things manually either. There&amp;rsquo;s a middle ground.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="https://github.com/jbms/beancount-import"&gt;beancount-import&lt;/a&gt;&lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt; (Note: this is a standalone tool, distinct from Fava&amp;rsquo;s built-in import features). It&amp;rsquo;s a web UI that uses your beangulp importers and adds a categorization layer on top. You run it, it opens in your browser, and it presents pending transactions one by one for review. Think of it as a staging area where you approve or tweak before anything hits your ledger.&lt;/p&gt;
&lt;p&gt;For each transaction, it shows the raw data from your statement alongside a suggested categorization. You can accept the suggestion, override it with a different account, or skip it entirely. Once you decide, it moves to the next one. The interface is simple, mostly keyboard-driven and optimized for speed.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/06-import-ui.png" alt="Beancount Import Web UI" title="Visual Categorization"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The import UI allows you to manually categorize transactions, like this Tesco grocery run, while the system learns your preferences.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The categorization uses machine learning (old-school decision trees, running locally, no LLMs, no cloud). It learns from your previous choices: the first time you see &amp;ldquo;Tesco&amp;rdquo;, you pick &amp;ldquo;Expenses:Groceries&amp;rdquo;, and the second time it auto-suggests and you just hit Enter. After a few weeks, the system should know 90% of the types of transactions you make and it&amp;rsquo;s easy to correct the ones which it doesn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;re proficient, a month&amp;rsquo;s worth of transactions takes 5-10 minutes. Most are repeats and you&amp;rsquo;re just hitting Enter. You only pause on genuinely new merchants. This is the core of my weekly ritual, and why I&amp;rsquo;ve found categorization has not become a chore, even after doing it for years.&lt;/p&gt;
&lt;h4 id="exit-ramp-1"&gt;Exit ramp&lt;a class="heading-anchor" href="#exit-ramp-1" aria-label="Permalink to Exit ramp"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;With automated imports and semi-automated categorization, the &amp;ldquo;hard work&amp;rdquo; of bookkeeping is mostly solved. For many, this is the endgame: a perfect record of where every penny went, updated in minutes each week.&lt;/p&gt;
&lt;p&gt;But your net worth isn&amp;rsquo;t just cash in a bank account. It&amp;rsquo;s also the stocks, bonds, and funds that grow (or shrink) over time. Tracking these requires a few more tools to handle cost basis, dividends, and market prices. We&amp;rsquo;ll tackle those in Chapter 4.&lt;/p&gt;
&lt;h2 id="chapter-4-investments"&gt;Chapter 4: Investments&lt;a class="heading-anchor" href="#chapter-4-investments" aria-label="Permalink to Chapter 4: Investments"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Now for investments. It&amp;rsquo;s where things get more interesting, and I think there&amp;rsquo;s less material out there covering the nitty-gritty. Here are some lessons I&amp;rsquo;ve learned over the years.&lt;/p&gt;
&lt;h4 id="the-unified-mental-model"&gt;The Unified Mental Model&lt;a class="heading-anchor" href="#the-unified-mental-model" aria-label="Permalink to The Unified Mental Model"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;The most important thing to realize is that Beancount treats &lt;strong&gt;everything&lt;/strong&gt; as a commodity.&lt;/p&gt;
&lt;p&gt;A share of Apple (&lt;code&gt;AAPL&lt;/code&gt;) is a commodity. A US Dollar (&lt;code&gt;USD&lt;/code&gt;) is a commodity. A British Pound (&lt;code&gt;GBP&lt;/code&gt;) is a commodity. While Beancount &lt;em&gt;can&lt;/em&gt; infer these on the fly, you&amp;rsquo;ll typically declare them explicitly in your journal (e.g., &lt;code&gt;2024-01-01 commodity AAPL&lt;/code&gt;).&lt;sup id="fnref:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This explicit declaration is a small but critical architectural win: it prevents a simple typo from creating a phantom currency, and it provides the metadata that advanced reporting plugins (like those used to calculate your portfolio performance) rely on to work correctly.&lt;/p&gt;
&lt;p&gt;This means you don&amp;rsquo;t &amp;ldquo;buy stocks with money&amp;rdquo;. You simply exchange one commodity for another. The syntax for buying shares is identical to the syntax for exchanging currency.&lt;/p&gt;
&lt;h4 id="the-account-structure"&gt;The Account Structure&lt;a class="heading-anchor" href="#the-account-structure" aria-label="Permalink to The Account Structure"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Just like with bank accounts, I break investments down by institution and security.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-4/src/accounts.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Assets:Lalit:US:IB:Brokerage:USD USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Assets:Lalit:US:IB:Brokerage:AAPL AAPL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Assets:Lalit:UK:Vanguard:ISA:GBP GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Assets:Lalit:UK:Vanguard:ISA:VWRL VWRL
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cash in a brokerage is just another holding named by currency (&lt;code&gt;GBP&lt;/code&gt;, &lt;code&gt;USD&lt;/code&gt;), while stock holdings use their ticker (&lt;code&gt;AAPL&lt;/code&gt;, &lt;code&gt;VWRL&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;But you also need to track the flows &lt;em&gt;generated&lt;/em&gt; by these assets: capital gains, dividends, commissions, and withholding taxes. I create specific accounts for each security:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Income:Lalit:US:IB:Brokerage:AAPL:Dividends USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Income:Lalit:US:IB:Brokerage:AAPL:Capital-Gains USD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why so granular?&lt;/strong&gt; It&amp;rsquo;s the same reason I name assets fully: aggregation up the tree is trivial; disaggregation after the fact is impossible. If you track all your dividends in a single &lt;code&gt;Income:Dividends&lt;/code&gt; account, it&amp;rsquo;s easy to know &amp;ldquo;how much dividends did I earn total?&amp;rdquo;. But if you want to know &amp;ldquo;what was my AAPL dividend yield this year?&amp;rdquo;, you&amp;rsquo;re out of luck. Track at the leaf (&lt;code&gt;Income:IB:AAPL:Dividends&lt;/code&gt;), and you can answer both questions.&lt;/p&gt;
&lt;h4 id="the-notation"&gt;The Notation&lt;a class="heading-anchor" href="#the-notation" aria-label="Permalink to The Notation"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;With our accounts defined, we can now record the actual movement of assets. We use &lt;code&gt;{}&lt;/code&gt; to denote cost (what we paid per unit) and &lt;code&gt;@&lt;/code&gt; to denote price (what the unit is worth now).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 1: Buying Stock&lt;/strong&gt;
Exchanging 1850 USD for 10 shares of Apple.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-10 * &amp;#34;BUY AAPL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:US:IB:Brokerage:AAPL 10 AAPL {185.00 USD} @ 185.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:US:IB:Brokerage:USD -1850.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;{185.00 USD}&lt;/code&gt; is the &lt;strong&gt;cost basis&lt;/strong&gt; and the &lt;code&gt;@ 185.00 USD&lt;/code&gt; is the &lt;strong&gt;price&lt;/strong&gt;. Beancount uses the cost basis to track lots and calculate capital gains.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: Cost basis rules depend heavily on where you are. In the US, you track cost basis of individual lots. In the UK, we have special &amp;ldquo;Section 104&amp;rdquo; pooling rules.&lt;sup id="fnref:11"&gt;&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref"&gt;11&lt;/a&gt;&lt;/sup&gt; I discuss this more in Chapter 5.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 2: Buying Currency&lt;/strong&gt;
Exchanging 950 GBP for 98,000 INR.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-03-02 * &amp;#34;Wise&amp;#34; &amp;#34;GBP to INR&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:Wise:INR 98000.00 INR @@ 950.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:Wise:GBP -950.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the second example, &lt;code&gt;@@&lt;/code&gt; specifies the &lt;em&gt;total&lt;/em&gt; cost rather than the per-unit cost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example 3: Selling Stock&lt;/strong&gt;
Selling 5 shares of Apple at $190 (bought at $185).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-30 * &amp;#34;SELL AAPL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:US:IB:Brokerage:AAPL -5 AAPL {185.00 USD} @ 190.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:US:IB:Brokerage:USD 950.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Income:Lalit:US:IB:Brokerage:AAPL:Capital-Gains -25.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we specify the lot we&amp;rsquo;re selling (&lt;code&gt;{185.00 USD}&lt;/code&gt;) and the price we&amp;rsquo;re selling it at (&lt;code&gt;@ 190.00 USD&lt;/code&gt;). The difference is the capital gain (or loss).&lt;/p&gt;
&lt;p&gt;But the principle is identical: &lt;code&gt;Assets:Wise:INR&lt;/code&gt; and &lt;code&gt;Assets:Brokerage:AAPL&lt;/code&gt; are just accounts holding commodities.&lt;/p&gt;
&lt;h4 id="automation"&gt;Automation&lt;a class="heading-anchor" href="#automation" aria-label="Permalink to Automation"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Investments are very different from normal accounts in that they can be &lt;strong&gt;fully automated&lt;/strong&gt;. No categorization needed: a buy is a buy, a dividend is a dividend. You don&amp;rsquo;t have new merchants to worry about.&lt;/p&gt;
&lt;p&gt;This means you can skip beancount-import&amp;rsquo;s web UI and run your beangulp importers directly with output going straight to the ledger. To help inspire you, I&amp;rsquo;ve open-sourced my personal collection of importers (IB, Vanguard, Schwab, and more) in the &lt;a href="https://github.com/LalitMaganti/beancount-lalitm/tree/main/src/beancount_lalitm/importers"&gt;beancount-lalitm&lt;/a&gt; repo.&lt;/p&gt;
&lt;h4 id="handling-account-sprawl"&gt;Handling Account Sprawl&lt;a class="heading-anchor" href="#handling-account-sprawl" aria-label="Permalink to Handling Account Sprawl"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;However, one annoyance is that creating those granular accounts for every single stock (&lt;code&gt;...:AAPL:Dividends&lt;/code&gt;, &lt;code&gt;...:AAPL:Commissions&lt;/code&gt;, etc.) is tedious. To solve this, I wrote the &lt;a href="https://github.com/LalitMaganti/beancount-lalitm/blob/main/src/beancount_lalitm/plugins/ancillary_accounts.py"&gt;ancillary_accounts&lt;/a&gt; plugin. Instead of manual account creation, you just add metadata to the main holding account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2023-02-01 open Assets:Lalit:US:IB:Brokerage:BAC BAC
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ancillary_commission_currency: &amp;#34;USD&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ancillary_distribution_currency: &amp;#34;USD&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ancillary_withholding_tax_currency: &amp;#34;USD&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ancillary_capital_gains_currency: &amp;#34;USD&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The plugin automatically generates the corresponding income and expense accounts for you.&lt;/p&gt;
&lt;h4 id="corporate-actions"&gt;Corporate Actions&lt;a class="heading-anchor" href="#corporate-actions" aria-label="Permalink to Corporate Actions"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;I also use a plugin called &lt;a href="https://github.com/LalitMaganti/beancount-lalitm/blob/main/src/beancount_lalitm/plugins/stock_split.py"&gt;stock_split&lt;/a&gt; to handle corporate actions. It retroactively adjusts historical transactions when a stock splits, keeping quantities and prices consistent with post-split values so your charts don&amp;rsquo;t show a sudden, fake drop in value.&lt;/p&gt;
&lt;h4 id="the-value-of-things-prices"&gt;The Value of Things (Prices)&lt;a class="heading-anchor" href="#the-value-of-things-prices" aria-label="Permalink to The Value of Things (Prices)"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;We have the quantities (10 AAPL, 98,000 INR), but to calculate a single &amp;ldquo;Net Worth&amp;rdquo; number, we need to know what they are worth in your home currency. This requires prices for &lt;strong&gt;both&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-4/src/prices.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-10 price AAPL 185.50 USD ; Stock price in USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-10 price USD 0.79 GBP ; Currency price in GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I automate this using a daily CI job. A script fetches the latest stock prices and forex rates from &lt;a href="https://www.alphavantage.co/"&gt;AlphaVantage&lt;/a&gt; and commits them to &lt;code&gt;prices.beancount&lt;/code&gt;. You can find the script &lt;a href="https://github.com/LalitMaganti/beancount-lalitm/blob/main/hack/fetch_prices.py"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In practice, I actually have three price files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prices.beancount&lt;/code&gt; - auto-fetched daily for as many securities as possible.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prices-manual.beancount&lt;/code&gt; - for securities without automatic feeds (like some pension funds). I input these manually once a month.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prices-delisted.beancount&lt;/code&gt; - historical prices for securities no longer trading. This saves me from making API calls which would fail anyway.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="the-payoff"&gt;The Payoff&lt;a class="heading-anchor" href="#the-payoff" aria-label="Permalink to The Payoff"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;With this data, Fava comes alive. To see the full potential of these reports, I&amp;rsquo;ve included a &lt;code&gt;demo/&lt;/code&gt; folder in the companion repo with 2+ years of history. Run &lt;code&gt;./scripts/quickstart.sh demo&lt;/code&gt; and you&amp;rsquo;ll see the payoff.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Holdings&lt;/strong&gt; page now shows your positions with their cost basis and current market value.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/07-holdings-table.png" alt="Holdings view showing Cost Basis and Market Value" title="Tracking Investments"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The Holdings report automatically calculates the market value of your assets using live price data.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the demo environment, you can also see how plugins like &lt;a href="https://github.com/andreasgerstmayr/fava-dashboards"&gt;&lt;strong&gt;fava-dashboards&lt;/strong&gt;&lt;/a&gt; build custom visualizations. The plugin uses beanquery (Beancount&amp;rsquo;s SQL-like query language) to fetch data and renders interactive charts. It’s the best way to track long-term trends and asset allocation at a glance.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/08a-dashboard-scorecards.png" alt="Dashboard showing top-level scorecards and savings rate heatmap" title="Dashboard Scorecards"&gt;
&lt;img src="https://lalitm.com/img/one-number-i-trust/08b-dashboard-allocation.png" alt="Dashboard showing asset allocation charts" title="Asset Allocation Dashboard"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;Custom dashboards (shown here using the demo data) allow you to track long-term trends and asset allocation at a glance.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And with &lt;a href="https://github.com/andreasgerstmayr/fava-portfolio-returns"&gt;&lt;strong&gt;fava-portfolio-returns&lt;/strong&gt;&lt;/a&gt;, you can calculate your true Time-Weighted Return (TWR) and Internal Rate of Return (IRR) to see if you&amp;rsquo;re actually beating the market. It accounts for cash flows properly, so adding money mid-year doesn&amp;rsquo;t inflate your returns.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/09-portfolio-returns.png" alt="Portfolio returns showing TWR and IRR" title="Portfolio Returns"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The portfolio returns plugin (using the demo data) calculates your actual investment performance, net of cash flows.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id="exit-ramp-2"&gt;Exit ramp&lt;a class="heading-anchor" href="#exit-ramp-2" aria-label="Permalink to Exit ramp"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;You now have a system that tracks your entire financial world: from the coffee you bought this morning to the capital gains in your brokerage account. For most people, this is a complete solution.&lt;/p&gt;
&lt;p&gt;However, as you collect more data, you&amp;rsquo;ll find you want to look at it in different ways. Maybe you want a simplified view for daily use and a detailed one for tax season. Or maybe you&amp;rsquo;re not the only person in your household. In the final chapters, we&amp;rsquo;ll see how to scale this system to handle multiple views and multiple people.&lt;/p&gt;
&lt;h2 id="chapter-5-multiple-views"&gt;Chapter 5: Multiple Views&lt;a class="heading-anchor" href="#chapter-5-multiple-views" aria-label="Permalink to Chapter 5: Multiple Views"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Day-to-day, I want a simple view of my finances. Take-home pay as a single number, investments without tax calculations cluttering the screen. But at tax time, I need detail. Every payslip line item and capital gains calculated the way HMRC wants them. Recording the same transaction twice would be maintenance hell. So instead, I extract multiple views from a single journal.&lt;/p&gt;
&lt;p&gt;I think of these as &amp;ldquo;lenses&amp;rdquo; on the data. Some lenses &lt;strong&gt;aggregate&lt;/strong&gt;: rolling up transactions into balances, summaries, or dashboards. Others &lt;strong&gt;transform&lt;/strong&gt;: collapsing detail you don&amp;rsquo;t need day-to-day, or expanding it when you do. Both read from the same source files; nothing is duplicated.&lt;/p&gt;
&lt;h4 id="aggregated-views"&gt;Aggregated views&lt;a class="heading-anchor" href="#aggregated-views" aria-label="Permalink to Aggregated views"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Fava is great for interactive exploration, but I also want textual snapshots I can version control. I have a script that generates daily summaries showing account balances, and a CI workflow that commits them automatically. In my setup this runs on a self-hosted Gitea instance on hardware I control, so the raw ledger never leaves machines I own. If you prefer, you can keep everything local-only or push to an encrypted remote; GitHub Actions works the same way if you&amp;rsquo;re comfortable with that trade-off. This gives me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A record of how balances changed day to day&lt;/li&gt;
&lt;li&gt;An immutable snapshot at tax time of what the system showed&lt;/li&gt;
&lt;li&gt;Git as audit trail: &amp;ldquo;What was my net worth on March 15th 2023?&amp;rdquo; is answerable with &lt;code&gt;git checkout&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key script is &lt;a href="https://github.com/LalitMaganti/beancount-blog-examples/blob/main/chapter-5/scripts/archive.py"&gt;archive.py&lt;/a&gt;. It uses the &lt;a href="https://github.com/beancount/beanquery"&gt;beanquery&lt;/a&gt; library to write SQL scripts over your journal and generate textual reports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Generate balance sheet in GBP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; SELECT account,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; round(sum(number(convert(value(position, &amp;#39;2024-12-31&amp;#39;), &amp;#39;GBP&amp;#39;, &amp;#39;2024-12-31&amp;#39;))), 2) as value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; FROM OPEN ON 2024-01-01 CLOSE ON 2024-12-31 CLEAR
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; GROUP BY account
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; HAVING round(sum(number), 2) != 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; ORDER BY account;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I run this for each calendar year and tax year, generating files like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;networth.txt&lt;/code&gt; - Single-line net worth in each currency&lt;/li&gt;
&lt;li&gt;&lt;code&gt;balance-sheet.txt&lt;/code&gt; - Net worth breakdown by account&lt;/li&gt;
&lt;li&gt;&lt;code&gt;holdings.txt&lt;/code&gt; - Investment positions with cost basis and market value&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s what &lt;code&gt;networth.txt&lt;/code&gt; looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; gbp usd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;------------- -------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 15978.42 GBP 19973.02 USD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the concrete &amp;ldquo;one number I trust&amp;rdquo;: a single net-worth snapshot in my reporting currency, generated from the full ledger and price data.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;holdings.txt&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;account units curr avg_cost price book_val mkt_val
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--------------------------------- ----- ---- -------- ----- -------- -------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Assets:Lalit:UK:HSBC:Current:GBP 4914.50 GBP 1.00 1.00 4914.50 4914.50
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Assets:Lalit:UK:Vanguard:ISA:VWRL 20.00 VWRL 96.00 97.50 1920.00 1950.00
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Assets:Lalit:US:IB:Brokerage:AAPL 5.00 AAPL 146.15 150.40 730.75 752.00
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My Gitea workflow is very simple too:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;00 7 * * *&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Run daily at 7am&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;uv run scripts/archive.py outputs/ journal.beancount 2024-01-01 2024-12-31&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;git commit -am &amp;#34;Regen reports&amp;#34; &amp;amp;&amp;amp; git push&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="transformed-views"&gt;Transformed views&lt;a class="heading-anchor" href="#transformed-views" aria-label="Permalink to Transformed views"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Sometimes I want to change &lt;em&gt;how&lt;/em&gt; transactions work fundamentally. This is a more advanced technique: while Aggregated views &lt;em&gt;read&lt;/em&gt; data, Transformed views temporarily &lt;em&gt;rewrite&lt;/em&gt; it in memory to simplify reality.&lt;/p&gt;
&lt;p&gt;I have three transformed views, each for a different purpose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Net&lt;/strong&gt; - my daily driver. Collapses payslip details into a single take-home number.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gross&lt;/strong&gt; - breaks down payslip line items for tax time analysis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CGT&lt;/strong&gt; - a view that includes a &amp;ldquo;virtual currency&amp;rdquo; tracking capital gains the way my tax authority calculates them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The linchpin is the &lt;code&gt;rename_accounts&lt;/code&gt; plugin. It lets me keep one copy of all transactions and rename accounts on the fly to show or hide detail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gross vs net payslip&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let me start with the simpler example. In gross view, my payslip shows every line item:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-25 * &amp;#34;Google&amp;#34; &amp;#34;January salary&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:HSBC:Current:GBP 3500.00 GBP ; Take-home pay
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Income:Lalit:UK:Google:Salary -5000.00 GBP ; Gross salary
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Expenses:Lalit:UK:Google:Income-Tax 1000.00 GBP ; Tax withheld
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Expenses:Lalit:UK:Google:National-Insurance 400.00 GBP ; NI contribution
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Expenses:Lalit:UK:Google:Pension 100.00 GBP ; Pension contribution
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Useful for analyzing my tax situation. But day-to-day, I don&amp;rsquo;t care about the breakdown. In net view, the same transaction collapses:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-5/journal-net.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;include &amp;#34;journal.beancount&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;plugin &amp;#34;beancount_reds_plugins.rename_accounts.rename_accounts&amp;#34; &amp;#34;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Income:Lalit:UK:Google:Salary&amp;#39;: &amp;#39;Income:Lalit:UK:Google:Net-Income&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Income:Lalit:UK:Google:Bonus&amp;#39;: &amp;#39;Income:Lalit:UK:Google:Net-Income&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Expenses:Lalit:UK:Google:Income-Tax&amp;#39;: &amp;#39;Income:Lalit:UK:Google:Net-Income&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Expenses:Lalit:UK:Google:National-Insurance&amp;#39;: &amp;#39;Income:Lalit:UK:Google:Net-Income&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Expenses:Lalit:UK:Google:Pension&amp;#39;: &amp;#39;Income:Lalit:UK:Google:Net-Income&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because Income is stored as a negative number and Expenses as positive numbers, merging them into one account mathematically subtracts the tax from the gross pay, leaving just the net amount. The difference should be obvious if I compare the Income Statements on Fava:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/10-gross-view.png" alt="Gross Income Statement view showing all tax line items" title="Gross View"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The Gross view is essential for tax season, but the detailed line items for taxes and insurance often dwarf your actual spending data.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/11-net-view.png" alt="Net Income Statement view showing take-home pay" title="Net View"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The Net view collapses those details into a single take-home number, making your everyday expenses much easier to analyze.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id="tracking-capital-gains-for-tax"&gt;Tracking capital gains for tax&lt;a class="heading-anchor" href="#tracking-capital-gains-for-tax" aria-label="Permalink to Tracking capital gains for tax"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;We can use this same renaming technique to handle a much more complex beast: &lt;strong&gt;Capital Gains Tax&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Your broker reports one gain number, but your tax authority may calculate another. In the UK, where I live, we have specific rules like &amp;ldquo;Section 104 pooling&amp;rdquo; (averaging cost basis) and &amp;ldquo;bed-and-breakfasting&amp;rdquo; (wash sale rules).&lt;/p&gt;
&lt;p&gt;To handle this, I use a virtual currency called &lt;code&gt;CGT-GBP&lt;/code&gt; that represents &amp;ldquo;pounds of gain HMRC cares about&amp;rdquo;. My plugin, &lt;a href="https://github.com/LalitMaganti/beancount-lalitm/blob/main/src/beancount_lalitm/plugins/uk_cgt_lots.py"&gt;&lt;code&gt;uk_cgt_lots&lt;/code&gt;&lt;/a&gt;, calculates this number and automatically appends a &lt;strong&gt;self-balancing pair of Equity postings&lt;/strong&gt; to the original sale transaction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-06-15 * &amp;#34;SELL AAPL&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:US:IB:Brokerage:AAPL -10 AAPL {150.00 USD} @ 175.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:US:IB:Brokerage:USD 1750.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Income:Lalit:US:IB:Brokerage:AAPL:Capital-Gains -250.00 USD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ; The following postings are generated by the uk_cgt_lots plugin:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Equity:Taxable-Capital-Gains 195.00 CGT-GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Equity:Taxable-Capital-Gains-Placeholder -195.00 CGT-GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since both legs are in &lt;code&gt;Equity&lt;/code&gt;, they remain invisible on my Income Statement in my daily &amp;ldquo;Net&amp;rdquo; view. In fact, I use &lt;code&gt;rename_accounts&lt;/code&gt; to collapse them into a single account so they net to zero:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; journal-net.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;plugin &amp;#34;beancount_reds_plugins.rename_accounts.rename_accounts&amp;#34; &amp;#34;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Equity:Taxable-Capital-Gains-Placeholder&amp;#39; : &amp;#39;Equity:Taxable-Capital-Gains&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But when I want to see my tax liability, I switch to the &lt;strong&gt;CGT View&lt;/strong&gt;. This view renames the &amp;ldquo;Placeholder&amp;rdquo; to a visible Revenue account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; journal-cgt.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;plugin &amp;#34;beancount_reds_plugins.rename_accounts.rename_accounts&amp;#34; &amp;#34;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Equity:Taxable-Capital-Gains-Placeholder&amp;#39; : &amp;#39;Revenues:Taxable-Capital-Gains&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, the -195.00 becomes Revenue, which shows up as profit on my tax report. The matching +195.00 remains in Equity. This allows me to have &amp;ldquo;Schrödinger&amp;rsquo;s Capital Gains&amp;rdquo;: they exist for the taxman, but not for my daily budget, all controlled by which view I load.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t calculate the tax owed since that&amp;rsquo;s too complicated with allowances, rates, and bands; the system just tracks the gains. At tax time, I sum up the CGT-GBP balance and do the actual calculation on the tax form.&lt;/p&gt;
&lt;h4 id="exit-ramp-3"&gt;Exit ramp&lt;a class="heading-anchor" href="#exit-ramp-3" aria-label="Permalink to Exit ramp"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;By separating your &amp;ldquo;source of truth&amp;rdquo; from your &amp;ldquo;lenses,&amp;rdquo; you get a system that grows with you. You can add new plugins or virtual currencies to solve specific problems (like taxes) without ever touching the raw transactions you&amp;rsquo;ve already imported.&lt;/p&gt;
&lt;p&gt;In the final chapter, we&amp;rsquo;ll see the ultimate application of this: combining two people&amp;rsquo;s financial lives into one unified view.&lt;/p&gt;
&lt;h2 id="chapter-6-two-people-one-number"&gt;Chapter 6: Two People, One Number&lt;a class="heading-anchor" href="#chapter-6-two-people-one-number" aria-label="Permalink to Chapter 6: Two People, One Number"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I got married at the start of the year, which brought a fundamental change to how I manage my finances. While many couples use joint accounts, we prefer to keep our individual accounts and perform occasional &amp;ldquo;normalization&amp;rdquo; transfers. However, we view our combined resources as shared household wealth.&lt;/p&gt;
&lt;p&gt;This created a reporting paradox that I had to solve in Beancount.&lt;/p&gt;
&lt;h4 id="the-paradox"&gt;The Paradox&lt;a class="heading-anchor" href="#the-paradox" aria-label="Permalink to The Paradox"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;When I transfer £500 to my wife for my share of the bills, two things are true simultaneously:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Individual Truth:&lt;/strong&gt; From my perspective, £500 is &amp;ldquo;gone&amp;rdquo; (an expense). From her perspective, £500 has &amp;ldquo;arrived&amp;rdquo; (income).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Household Truth:&lt;/strong&gt; For the household, the net worth hasn&amp;rsquo;t changed. Money just moved from the left pocket to the right pocket.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In a traditional system, you usually have to pick one truth. In Beancount, we can have both.&lt;/p&gt;
&lt;h4 id="the-composable-architecture"&gt;The Composable Architecture&lt;a class="heading-anchor" href="#the-composable-architecture" aria-label="Permalink to The Composable Architecture"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;To solve this, I treat the household as a composable system of three distinct entities: &lt;strong&gt;Me&lt;/strong&gt;, &lt;strong&gt;Her&lt;/strong&gt;, and &lt;strong&gt;Shared Definitions&lt;/strong&gt;. We use Beancount’s &lt;code&gt;include&lt;/code&gt; feature to build the specific &amp;ldquo;lens&amp;rdquo; we need at any given moment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lalit&amp;rsquo;s View&lt;/strong&gt; = Shared Definitions + Lalit&amp;rsquo;s Transactions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wife&amp;rsquo;s View&lt;/strong&gt; = Shared Definitions + Wife&amp;rsquo;s Transactions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Household View&lt;/strong&gt; = Shared Definitions + Lalit&amp;rsquo;s Transactions + Wife&amp;rsquo;s Transactions + &lt;em&gt;Translation Logic&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, the household view literally just includes the other files (alongside the translation logic we&amp;rsquo;ll see in a moment):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-6/total/journal-net.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;include &amp;#34;../common/src/commodities.beancount&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;include &amp;#34;../common/src/accounts.beancount&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;include &amp;#34;../lalit/src/journal.beancount&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;include &amp;#34;../wife/src/journal.beancount&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; ... Translation Logic follows
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="implementation-the-directory-structure"&gt;Implementation: The Directory Structure&lt;a class="heading-anchor" href="#implementation-the-directory-structure" aria-label="Permalink to Implementation: The Directory Structure"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;This architecture is reflected directly in the repository structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chapter-6/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── common/ # Shared configuration
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── src/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── accounts.beancount # Shared expense accounts (e.g. Expenses:Groceries)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── commodities.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── lalit/ # My stuff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── src/ # My ledger
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── data/ # My statement PDFs/CSVs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── wife/ # Wife&amp;#39;s stuff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── src/ # Her ledger
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── data/ # Her statement PDFs/CSVs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── total/ # Combined household view
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── journal-net.beancount # Entry point with Translation Logic
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="the-rules-of-engagement"&gt;The Rules of Engagement&lt;a class="heading-anchor" href="#the-rules-of-engagement" aria-label="Permalink to The Rules of Engagement"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;For this to work without constant manual adjustment, we follow two simple rules:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 1: Assets and Liabilities are Private.&lt;/strong&gt;
Bank accounts always include the person&amp;rsquo;s name in the path (e.g., &lt;code&gt;Assets:Lalit:HSBC&lt;/code&gt; or &lt;code&gt;Assets:Wife:HSBC&lt;/code&gt;). We never use a generic &lt;code&gt;Assets:Checking&lt;/code&gt; account. Legal ownership of the cash always matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule 2: Expenses are Public.&lt;/strong&gt;
Shared expenses like groceries or electricity use a generic name without a person prefix.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-6/common/src/accounts.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-01 open Expenses:Groceries GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When I buy groceries, I record it in my ledger using the shared account. We don&amp;rsquo;t track &amp;ldquo;who owes what&amp;rdquo; for individual grocery runs; we just track that the household spent the money. We accept that we lose the ability to split shared expenses by person, but the gain in simplicity is worth it.&lt;/p&gt;
&lt;h4 id="the-magic-solving-the-transfer-paradox"&gt;The &amp;ldquo;Magic&amp;rdquo;: Solving the Transfer Paradox&lt;a class="heading-anchor" href="#the-magic-solving-the-transfer-paradox" aria-label="Permalink to The “Magic”: Solving the Transfer Paradox"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;Finally, we use the &lt;code&gt;rename_accounts&lt;/code&gt; plugin in the &lt;code&gt;total/&lt;/code&gt; folder to resolve the transfer paradox.&lt;/p&gt;
&lt;p&gt;In my ledger, a transfer looks like a simple expense:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-6/lalit/src/transactions.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-20 * &amp;#34;Transfer to Wife&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Expenses:Lalit:Transfers:Wife 500.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Lalit:UK:HSBC:Current:GBP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In her ledger, it looks like income:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-6/wife/src/transactions.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-01-20 * &amp;#34;Transfer from Lalit&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Assets:Wife:UK:HSBC:Current:GBP 500.00 GBP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Income:Wife:Transfers:Lalit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &amp;ldquo;Translation Logic&amp;rdquo; in the combined view renames these into a shared transit account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;; chapter-6/total/journal-net.beancount
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;plugin &amp;#34;beancount_reds_plugins.rename_accounts.rename_accounts&amp;#34; &amp;#34;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Expenses:Lalit:Transfers:Wife&amp;#39;: &amp;#39;Assets:Household:Transfers:Internal&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;Income:Wife:Transfers:Lalit&amp;#39;: &amp;#39;Assets:Household:Transfers:Internal&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, when Fava loads the combined view, it sees £500 leave my account and enter &lt;code&gt;Assets:Household:Transfers:Internal&lt;/code&gt;, and then £500 leave that same account and enter her bank account. The transit account nets to zero, and our household net worth remains unchanged.&lt;/p&gt;
&lt;h4 id="the-result"&gt;The Result&lt;a class="heading-anchor" href="#the-result" aria-label="Permalink to The Result"&gt;#&lt;/a&gt;
&lt;/h4&gt;
&lt;p&gt;This setup gives us the best of both worlds. I can maintain my own financial autonomy and see my personal &amp;ldquo;runway,&amp;rdquo; while we can simultaneously monitor our combined progress toward shared goals.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/one-number-i-trust/12-household-balance-sheet.png" alt="Combined Household Balance Sheet" title="Two people, one number"&gt;&lt;/p&gt;
&lt;p align="center"&gt;&lt;em&gt;The final result: a unified household view that tracks legal ownership without sacrificing the "One Number" net worth total.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the full system (see &lt;a href="https://github.com/LalitMaganti/beancount-blog-examples/tree/main/chapter-6"&gt;chapter-6&lt;/a&gt; for the complete multi-person structure). But how do I actually use it week to week?&lt;/p&gt;
&lt;h2 id="the-weekly-ritual"&gt;The weekly ritual&lt;a class="heading-anchor" href="#the-weekly-ritual" aria-label="Permalink to The weekly ritual"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Now here&amp;rsquo;s how I keep it current: the &amp;ldquo;20 minutes a week&amp;rdquo; I mentioned at the start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Getting statements&lt;/strong&gt;: During the week, banks email me saying a statement is available. Some attach PDFs directly; others require a login. Either way, I snooze the emails (I use inbox zero) until the weekend. For my wife&amp;rsquo;s accounts, I nudge her once a month and she drops stuff in a shared Drive folder.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Running imports&lt;/strong&gt;: On the weekend, I work through my snoozed emails. Note that I&amp;rsquo;m not updating every single account every week. I only download statements for the 3-4 accounts that saw activity; long-term investments often just get a monthly or quarterly check-in. I move each file to the correct &lt;code&gt;data/&lt;/code&gt; subfolder for that institution (e.g., &lt;code&gt;data/hsbc-uk-current/&lt;/code&gt;). My importer pipeline automatically runs &lt;code&gt;pdftotext&lt;/code&gt; to extract text so the parsers can read it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Categorizing&lt;/strong&gt;: I launch beancount-import, which opens a web UI in my browser:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m beancount_import.webserver --journal lalit/journal-net.beancount
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As I mentioned before, most transactions auto-categorize and I&amp;rsquo;m just hitting Enter: I buy groceries from the same place, pay my hosting costs to the same provider etc. New merchants do need some manual work, but it&amp;rsquo;s a matter of typing a few characters and again pressing Enter.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Formatting and checking&lt;/strong&gt;: I run &lt;code&gt;bean-format&lt;/code&gt; (a Beancount utility that normalizes indentation and aligns amounts) to keep my transactions file tidy. This makes git diffs cleaner. Then I open Fava for a quick sanity check:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fava lalit/journal-net.beancount
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Are transfer accounts zeroed out? Do expenses look legit? How are investments doing? If it all checks out, commit and push.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s it. I try to stick to doing this &lt;em&gt;every week&lt;/em&gt;, but sometimes I&amp;rsquo;m on holiday or just have other commitments. In that case, it&amp;rsquo;s 40 minutes every two weeks. The system is forgiving; I&amp;rsquo;m never behind for too long.&lt;/p&gt;
&lt;p&gt;I also have some automation helping me out: during the week, I have a CI workflow that runs daily, regenerates summaries, and commits them. Whenever I want, I can check the repo and see what the numbers look like. I particularly like this because I can easily see the before/after numbers in a single file, so I can spot check &amp;ldquo;does this make sense&amp;rdquo;. And of course at any time, I can also open Fava if I want to go a bit deeper.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="heading-anchor" href="#conclusion" aria-label="Permalink to Conclusion"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;That 2021 tax disaster feels like a lifetime ago. What started as &amp;ldquo;there must be a better way&amp;rdquo; became a system I actually trust. One number, always current, completely under my control.&lt;/p&gt;
&lt;p&gt;The specifics will evolve as life changes and tools improve, but three principles have held for years now. I expect them to hold for decades:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Double-entry everywhere.&lt;/strong&gt; Every transaction balances. Money never appears from nowhere or vanishes into nothing. When something doesn&amp;rsquo;t add up, you know immediately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plain text as the source of truth.&lt;/strong&gt; Your financial history lives in files you can read, diff, grep, and version control. No vendor lock-in, no opaque databases, no trusting a third party with your data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Track at the leaf.&lt;/strong&gt; Record transactions at the most granular level that makes sense. You can always aggregate up (&lt;code&gt;Income:Dividends&lt;/code&gt; from &lt;code&gt;Income:IB:AAPL:Dividends&lt;/code&gt;), but you can never disaggregate down. Capture the detail; collapse it later with views.&lt;/p&gt;
&lt;p&gt;Finance systems are deeply personal. This post isn&amp;rsquo;t meant to say this is &lt;em&gt;the&lt;/em&gt; system everyone should use, just what&amp;rsquo;s worked for me over several years.&lt;/p&gt;
&lt;p&gt;Each chapter here could be its own post, so if you want me to go deeper on imports, investments, or the multi-person setup, let me know!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;It took me dozens of hours spread over months. This post and companion repo should get you there faster.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;By &amp;ldquo;normal&amp;rdquo; I mean any personal finance system which works on the basis of just plain transactions and &amp;ldquo;tagging&amp;rdquo; or &amp;ldquo;categorizing them&amp;rdquo;. This includes spreadsheets and apps like Mint, Quicken, or Copilot.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Since 1494, when Luca Pacioli first codified the method in &lt;a href="https://en.wikipedia.org/wiki/Summa_de_arithmetica"&gt;&lt;em&gt;Summa de arithmetica&lt;/em&gt;&lt;/a&gt;. The practice existed among Italian merchants before that, but Pacioli was the first to publish it.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Real accountants use &amp;ldquo;Debits&amp;rdquo; and &amp;ldquo;Credits&amp;rdquo; to keep numbers positive. Beancount just uses plus and minus. It&amp;rsquo;s way more intuitive for coders, even if it makes accountants twitch.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Yes I know technically that Equity means &amp;lsquo;Ownership&amp;rsquo;. But I&amp;rsquo;m trying to keep things simple for an audience new to this stuff, and equity is confusing enough in this simplified model without the abstract ideas which come from business bookkeeping.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;As I say, I initially just had an &amp;ldquo;Equity:Transfers&amp;rdquo; account and I put 3 different &amp;ldquo;untracked accounts&amp;rdquo; in that same bucket. When I finally imported these, I had to go through and recategorize all these transfers manually! Not the most fun day I&amp;rsquo;ve had&amp;hellip;&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;Trust me, I tried for years to maintain Puppeteer scrapers for all my bank statements. It became a second job to keep those up to date and eventually I started dreading my finances because of it. The day I deleted all that code, I felt a real burden had been lifted.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;Plaid also has privacy tradeoffs. Banks with OAuth support let you authenticate directly, but many still require you to &lt;a href="https://support-my.plaid.com/hc/en-us/articles/8117349507095-What-are-the-different-ways-third-party-apps-using-Plaid-can-connect-to-my-financial-accounts"&gt;hand Plaid your credentials&lt;/a&gt;, which they store and use to access your data.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;The original beancount-import doesn&amp;rsquo;t support Beancount v3. I maintain a &lt;a href="https://github.com/LalitMaganti/beancount-import"&gt;fork&lt;/a&gt; with v3 compatibility.&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:10"&gt;
&lt;p&gt;The &lt;code&gt;beangrow&lt;/code&gt; plugin (used for performance reporting) specifically requires a &lt;code&gt;name&lt;/code&gt; metadata attribute on each commodity. For convenience, I typically set this to the symbol itself, but it can be any descriptive string like &amp;ldquo;Apple Inc.&amp;rdquo; or &amp;ldquo;Vanguard All-World ETF&amp;rdquo;.&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:11"&gt;
&lt;p&gt;US readers will be familiar with tracking individual lots instead. The principle of needing a tax-specific view applies regardless of jurisdiction.&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item></channel></rss>