Shing Lyu

hledger and AI: Managing Your Finances in Plain Text

By Shing Lyu    

Disclaimer: This content reflects my personal opinions, not those of any organizations I am or have been affiliated with. Code samples are provided for illustration purposes only, use with caution and test thoroughly before deployment.

I was scrolling through YouTube late one evening when a video on plain text accounting caught my eye. It immediately clicked.

I’ve been managing my finances with a patchwork of Excel spreadsheets for years — one quarterly balance sheet, one income statement, and a few ad hoc sheets for tax calculations. They work, until they don’t. Formulas drift. Unbalanced accounts go unnoticed. And importing CSVs downloaded from my bank apps involves a tedious amount of manual cleanup every time.

Then there’s the Dutch tax system. If you live in the Netherlands, you’re probably familiar with Box 3 — the wealth tax on savings and investments. In the past, it was based on fictional flat returns, calculated from your balance on January 1st. The new system asks for your actual return, which requires a lot more detailed numbers — especially if you hold foreign currency accounts (say, a savings account in Taiwan while living in Amsterdam). You need the EUR-equivalent balance on January 1st for the declaration, and you need to track currency gains and losses for the actual-return calculation. In Excel, this is painful. I needed something better.

That’s where hledger came in.

What is plain text accounting?

Plain text accounting applies the double-entry bookkeeping idea to a simple, human-readable text file. The format is open, version-controllable, and doesn’t require any proprietary software to read.

The core idea is the same as traditional accounting: every transaction affects at least two accounts, and debits always equal credits. But instead of a spreadsheet or dedicated accounting software, you write it like this:

2024-01-25 Employer
    assets:checking              3000.00 EUR
    income:salary               -3000.00 EUR  ; you can omit this line — hledger infers it

That’s it. hledger reads these journal files and can generate balance sheets, income statements, and all sorts of reports. The text file is the source of truth. You can read it in any text editor, diff it with git, and process it with any tool you want.

hledger always checks that every transaction is balanced — debits must equal credits. This catches mistakes before they compound. You can also declare an expected balance at any point in time (a “balance assertion”) to cross-check against a real bank statement. For example, if your January statement says the account ended at 7,549.20 EUR, you write = 7549.20 EUR after the posting amount:

2024-01-25 Employer
    assets:checking              3000.00 EUR = 7549.20 EUR  ; balance assertion
    income:salary               -3000.00 EUR

hledger verifies the running total matches that number when it processes the transaction. If anything is off, it tells you immediately.

There’s also the --strict flag, which goes further: it checks that every account name you’ve used has been explicitly declared. If you mistype an account name — expneses:food instead of expenses:food — hledger will catch it instead of silently creating a phantom account.

Example 1: the basics

Let me show you what a month of personal finances looks like. Here’s a simple journal file:

; Opening balance
2024-01-01 Opening balance
    assets:checking              5000.00 EUR
    equity:opening-balances

; Salary income
2024-01-25 Employer
    assets:checking              3000.00 EUR
    income:salary               -3000.00 EUR  ; you can omit this line — hledger infers it

; Groceries
2024-01-10 Albert Heijn
    expenses:food:groceries       120.50 EUR
    assets:checking

2024-01-17 Albert Heijn
    expenses:food:groceries        95.30 EUR
    assets:checking

2024-01-24 Albert Heijn
    expenses:food:groceries       110.00 EUR
    assets:checking

; Utilities
2024-01-15 Power Company
    expenses:utilities:electricity  80.00 EUR
    assets:checking

; Dining out
2024-01-20 Local restaurant
    expenses:food:dining-out       45.00 EUR
    assets:checking

From this, I can generate a balance sheet:

$ hledger -f example1.journal bs
Balance Sheet 2024-01-25

                 ||  2024-01-25
=================++=============
 Assets          ||
-----------------++-------------
 assets:checking || 7549.20 EUR
-----------------++-------------
                 || 7549.20 EUR
=================++=============
 Liabilities     ||
-----------------++-------------
-----------------++-------------
                 ||
=================++=============
 Net:            || 7549.20 EUR

And an income statement:

$ hledger -f example1.journal is
Income Statement 2024-01-01..2024-01-25

                                || 2024-01-01..2024-01-25
================================++========================
 Revenues                       ||
--------------------------------++------------------------
 income:salary                  ||            3000.00 EUR
--------------------------------++------------------------
                                ||            3000.00 EUR
================================++========================
 Expenses                       ||
--------------------------------++------------------------
 expenses:food:dining-out       ||              45.00 EUR
 expenses:food:groceries        ||             325.80 EUR
 expenses:utilities:electricity ||              80.00 EUR
--------------------------------++------------------------
                                ||             450.80 EUR
================================++========================
 Net:                           ||            2549.20 EUR

The numbers balance — always. Not because I checked them manually, but because every transaction is double-entry. The math is structural.

Example 2: tracking investments with market prices

hledger has a P directive — a way to record market prices over time. This lets you track your investments not just by cost basis, but by current market value.

; Opening balance
2024-01-01 Opening balance
    assets:checking              10000.00 EUR
    equity:opening-balances

; Buy 10 shares of VWRL ETF at 100 EUR each
2024-01-15 Buy VWRL
    assets:investments:VWRL        10 VWRL @ 100.00 EUR
    assets:checking              -1000.00 EUR

; Buy 5 more shares in February
2024-02-15 Buy VWRL
    assets:investments:VWRL         5 VWRL @ 105.00 EUR
    assets:checking               -525.00 EUR

; Market prices throughout the year
P 2024-01-31 VWRL 100.00 EUR
P 2024-02-29 VWRL 105.00 EUR
P 2024-03-31 VWRL 108.00 EUR
P 2024-06-30 VWRL 115.00 EUR
P 2024-09-30 VWRL 118.00 EUR
P 2024-12-31 VWRL 120.00 EUR

What did I pay (cost basis)?

$ hledger -f example2.journal bal assets:investments --cost
         1525.00 EUR  assets:investments:VWRL

What is it worth at the end of the year?

$ hledger -f example2.journal bal assets:investments -V --end 2025-01-01
         1800.00 EUR  assets:investments:VWRL

I can also see how the portfolio value and full balance sheet change each quarter using the --quarterly flag:

$ hledger -f example2.journal bs --quarterly -V
Balance Sheet 2024-03-31..2024-12-31, valued at period ends

                         ||   2024-03-31    2024-06-30    2024-09-30    2024-12-31
=========================++========================================================
 Assets                  ||
-------------------------++--------------------------------------------------------
 assets:checking         ||  8475.00 EUR   8475.00 EUR   8475.00 EUR   8475.00 EUR
 assets:investments:VWRL ||  1620.00 EUR   1725.00 EUR   1770.00 EUR   1800.00 EUR
-------------------------++--------------------------------------------------------
                         || 10095.00 EUR  10200.00 EUR  10245.00 EUR  10275.00 EUR
=========================++========================================================
 Liabilities             ||
-------------------------++--------------------------------------------------------
-------------------------++--------------------------------------------------------
                         ||
=========================++========================================================
 Net:                    || 10095.00 EUR  10200.00 EUR  10245.00 EUR  10275.00 EUR

15 VWRL shares × 120 EUR = 1800 EUR. My cost was 1525 EUR, so I’m up 275 EUR. That took a handful of commands. In Excel, I would have needed a running price table and careful VLOOKUP formulas — and a mistake in any cell would silently corrupt the result.

Example 3: Dutch Box 3 — foreign currency savings account

This is the example that made me fully commit to hledger. For Box 3, I need to report the EUR-equivalent balance of my foreign currency savings account on January 1st each year, and also track currency gains and losses for the actual-return calculation.

(Disclaimer: I’m not an accountant and this is not professional financial or tax advice. Always consult a qualified professional for your specific situation.)

I have a savings account in Taiwan (TWD). Here’s how I model it — I record exchange rates with P directives and then track all deposits and withdrawals:

; Exchange rate P directives (TWD to EUR, approximate rates for illustration)
P 2023-01-01 TWD 0.030 EUR
P 2023-06-01 TWD 0.028 EUR
P 2023-12-31 TWD 0.029 EUR
P 2024-01-01 TWD 0.029 EUR
P 2024-06-01 TWD 0.027 EUR
P 2024-12-31 TWD 0.026 EUR

; Opening balance
2023-01-01 Opening balance - Taiwan savings
    assets:savings:taiwan-bank      100000 TWD
    equity:opening-balances

; Savings deposit
2023-06-01 Savings deposit
    assets:savings:taiwan-bank       50000 TWD
    income:other

; Cash withdrawal for expenses
2023-09-01 Cash withdrawal
    expenses:other                   30000 TWD
    assets:savings:taiwan-bank

; Balance at 2024-01-01 is 120,000 TWD

; Close the account at end of 2024
2024-12-31 Close Taiwan savings account
    assets:savings:taiwan-bank      -120000 TWD
    equity:opening-balances

Balance on January 1st (for the Box 3 declaration)

$ hledger -f example3.journal bal assets:savings --end 2024-01-02 --value=then,EUR
        3560.000 EUR  assets:savings:taiwan-bank

The --value=then,EUR flag converts each TWD amount to EUR at the exchange rate in effect at the time of that transaction. So the 100,000 TWD opening at rate 0.030 = 3,000 EUR, plus the 50,000 TWD deposit at 0.028 = 1,400 EUR, minus the 30,000 TWD withdrawal at 0.028 = 840 EUR. Total: 3,560 EUR.

EUR value of the account at the end of the year (after closing)

To see the full picture of what each transaction was worth in EUR at the time, I use the account register:

$ hledger -f example3.journal areg assets:savings --value=then,EUR
Transactions in assets:savings and subaccounts:
2023-01-01 Opening balance - Taiwan savings  eq:opening-balances  3000.000 EUR  3000.000 EUR
2023-06-01 Savings deposit                   in:other             1400.000 EUR  4400.000 EUR
2023-09-01 Cash withdrawal                   ex:other             -840.000 EUR  3560.000 EUR
2024-12-31 Close Taiwan savings account      eq:opening-balances -3120.000 EUR   440.000 EUR

The account started with 3,000 EUR worth of TWD, received another 1,400 EUR worth, then withdrew 840 EUR worth. At the end of 2024, I closed it by withdrawing 120,000 TWD at the then-current rate of 0.026, which was worth 3,120 EUR. The “440 EUR” final balance in the register is the currency loss — we put in more EUR-equivalent than we got back because TWD depreciated against EUR. To make the sign intuitive (negative = loss), add --invert:

$ hledger -f example3.journal bal assets:savings --value=then,EUR --invert
        -440.000 EUR  assets:savings:taiwan-bank

The −440 EUR is the net currency loss over the account’s lifetime.

Inflows and outflows for the year

You can also break down total deposits and withdrawals by filtering on the amount sign:

$ hledger -f example3.journal bal assets:savings --value=then,EUR 'amt:>0'
        4400.000 EUR  assets:savings:taiwan-bank

$ hledger -f example3.journal bal assets:savings --value=then,EUR 'amt:<0'
       -3960.000 EUR  assets:savings:taiwan-bank

4,400 EUR went in (in EUR terms at the time of each deposit), 3,960 EUR came out. The 440 EUR difference is exactly the currency loss.

This would be excruciating to track correctly in Excel. With hledger, it’s a few flags.

Why plain text accounting works well with AI

AI writes the import rules once; you run them forever. Most banks let you export transactions as CSV. hledger has a rule-based CSV import system. You show the AI a sample CSV from your bank, and it generates a rules file like this (this is an example, not an actual rules file):

# bank_export.csv.rules

source bank_export.csv

skip 1

fields date, description, amount, balance

date-format %Y-%m-%d
currency EUR
account1 assets:checking

if albert heijn
  account2 expenses:food:groceries

if power co
  account2 expenses:utilities:electricity

if restaurant
  account2 expenses:food:dining-out

if salary
  account2 income:salary

From then on, you run hledger import bank_export.csv and everything gets categorized automatically — no AI involved, no tokens spent, fully deterministic. The AI writes the pattern-matching rules once; you benefit from them forever.

Privacy-friendly AI use. If you care about your financial data going to cloud AI providers, you have options. You can run a local LLM (like Llama via Ollama). Or, since the AI only needs to understand the structure of your CSV to write import rules, you can replace real account names and numbers with dummy ones before sharing. The rules will work exactly the same on your real data.

Future-proof by design. hledger is open source. The file format is a documented plain text standard. If hledger disappeared tomorrow, the data is still just text — you can read it, write a parser, convert it, or ask an AI to migrate it to any other tool.

A caveat on AI and hledger. Most AI models haven’t seen much hledger in their training data, so they do hallucinate commands and flags. I’ve found that providing the relevant section of the hledger documentation in the prompt helps a lot. The hledger Matrix chat room is also friendly and helpful when you get stuck — the community there is small but active.

Ask AI questions, run hledger answers. The plain text journal is something any LLM can read and reason about directly. A nice workflow is to ask the AI a question about your finances, and instead of having it answer based on the journal directly (which risks hallucinating numbers), ask it to generate the hledger command that answers the question. Then you run the command yourself and get a deterministic, trustworthy result.

For a simple question like “How much did I spend on food in January?”, the AI would respond with:

Run: hledger -f example1.journal bal expenses:food -p 2024-01

$ hledger -f example1.journal bal expenses:food -p 2024-01
           45.00 EUR  expenses:food:dining-out
          325.80 EUR  expenses:food:groceries
--------------------
          370.80 EUR

But the AI really shines on multi-step questions that require reasoning across the data. For example: “My total expenses last month were 450.80 EUR. Which category is taking the biggest share, and where could I realistically cut spending?”

A good AI response would first ask for a category breakdown:

Run: hledger -f example1.journal bal expenses --depth 2 -p 2024-01

$ hledger -f example1.journal bal expenses --depth 2 -p 2024-01
          370.80 EUR  expenses:food
           80.00 EUR  expenses:utilities
--------------------
          450.80 EUR

Then, seeing that food is 370.80 out of 450.80 total (about 82%), and groceries dominate within that, it might suggest drilling into groceries:

Run: hledger -f example1.journal bal expenses:food:groceries -p 2024-01

$ hledger -f example1.journal bal expenses:food:groceries -p 2024-01
          325.80 EUR  expenses:food:groceries

325.80 EUR on groceries, across three trips. The AI now has enough data to say: “Groceries are 72% of total spending. If you want to cut 20% overall, reducing your grocery runs from 3 to 2 per month would get you most of the way there.” All numbers came from deterministic hledger commands, not AI guesswork.

Getting started

If you want to try this, the hledger website has good documentation and a quick start guide.

Start small. Create a journal file, record a week of transactions, and run hledger is to see your income statement. Once you see how clean the numbers are — and how easy it is to verify them — going back to spreadsheets feels like going back to a flip phone.

There’s a lot I haven’t touched on here: setting up budgets and tracking actual vs. planned spending, handling multiple currencies simultaneously, calculating investment ROI over time. hledger has features for all of these — worth exploring once you’re comfortable with the basics.

Plain text tools and AI are a natural fit. hledger is a way to move your finances into the AI age.

Want to learn Rust? Check out my book: