Finances with Ledger and Org

Posted on September 9, 2023

Managing finances has always been a bit painful, but I've recently finally become somewhat comfortable with Ledger to make a report that's made the whole process easier. I started with trying to stay strictly within ledger-mode in Emacs, which is definitely an upgrade from the CLI, but it was still hard to use the reports effectively, so I decided to give the reports more context by compiling them into an Org document that looks a little something like this:

#+STARTUP: hideblocks
* Goals
- $150/mo travel
- $100/mo home improvement
- =Luxury:Food:Dining= 1meal/wk, allowing for other smaller purchases
* The Month
#+begin_src sh :var period="June" journal=(ledger-master-file) :results output
ledger bal -f $journal --period "$period"
#+end_src
#+RESULTS:
#+begin_example
                 150  Assets:Checking
                 230  Expenses
                 180    Food
                  50      Dining
                 130      Grocery
                  50    Hobbies
                -475  Income:Job
                  95  Liabilities:Credit
--------------------
                   0
#+end_example
** Expenses Vs Income for Month
:LOGBOOK:
- Note taken on [2023-04-30 Sun 12:31] \\
  Maybe I can eat out less and put more money toward travel budget?
- Note taken on [2023-03-31 Fri 12:31] \\
  Spending less on hobbies wasn't bad, we can try to keep that up.
- Note taken on [2023-02-28 Tue 12:31] \\
  Spent extra money on Valentine's Day present, will try to save more next month.
:END:
#+begin_src sh :var period="June" journal=(ledger-master-file) :results output
ledger -f $journal bal ^Expenses ^Income --invert --monthly --period "$period"
#+end_src
#+RESULTS:
:                 -230  Expenses
:                 -180    Food
:                  -50      Dining
:                 -130      Grocery
:                  -50    Hobbies
:                  475  Income:Job
: --------------------
:                  245
#+begin_src sh :var journal=(ledger-master-file) textcolor=(face-attribute 'default :foreground) backgroundcolor=(face-attribute 'default :background) incomecolor=(face-attribute 'success :foreground) expensecolor=(face-attribute 'error :foreground) :results silent
ledger reg -f $journal -j --monthly --related ^Income > /tmp/ledger_income.tmp
ledger reg -f $journal -j --monthly --related ^Expenses --collapse --plot-amount-format="%(format_date(date, \"%Y-%m-%d\")) %(abs(quantity(scrub(display_amount))))\n" > /tmp/ledger_expenses.tmp
(cat <<EOF) | gnuplot
set terminal pngcairo background rgb "$backgroundcolor" font "/usr/share/fonts/TTF/ArimoNerdFont-Regular.ttf,14" size 768,680
set xdata time
set timefmt "%Y-%m-%d"
set output "expenses.png"
set title "Monthly Income and Expenses" textcolor "$textcolor"
set key textcolor "$textcolor" right center
set xlabel "Date" textcolor "$textcolor"
set xtics rotate by 45 right
set ylabel "Amount ($)" textcolor "$textcolor"
set border lc rgb "$textcolor"
set format x "%b '%y"
set bmargin 5
set style line 1 lc rgb "$incomecolor" lt 1 lw 2 pt 7 pi -1 ps 1.5
set style line 2 lc rgb "$expensecolor" lt 1 lw 2 pt 7 pi -1 ps 1.5
plot "/tmp/ledger_income.tmp" using 1:2 title "Income" with linespoints ls 1, "/tmp/ledger_expenses.tmp" using 1:2 title "Expenses" with linespoints ls 2, 
EOF
#+end_src
[[file:./expenses.png]]
** Expenses By Account
#+begin_src sh :var account="Expenses:Food:Dining" period="January" journal=(ledger-master-file) :results output
printf "$(ledger -f $journal reg ^$account --period "$period")
----------------------------------------------------
%37s transaction(s)" $(ledger -f $journal reg ^$account --period "$period" | wc -l)
#+end_src
#+RESULTS:
: 23-Jan-25 My Favorite Restaur.. Expenses:Food:Dining             20           20
: ----------------------------------------------------
:                                     1 transaction(s)
* The Year
#+begin_src sh :var journal=(ledger-master-file) :results output
ledger reg -f $journal --monthly --period "this year"
#+end_src
#+RESULTS:
#+begin_example
23-Jan-01 - 23-Jan-31           Assets:Checking                 575          575
                                Equity                         -200          375
                                Expenses:Food:Dining             20          395
                                Expenses:Food:Grocery           100          495
                                Income:Job                     -475           20
                                Liabilities:Credit              -20            0
23-Feb-01 - 23-Feb-28           Assets:Checking                 475          475
                                Expenses:Food:Dining             30          505
                                Income:Job                     -475           30
                                Liabilities:Credit              -30            0
#+end_example
# Local Variables:
# eval: (require 'ledger-mode)
# End:

I made all the data up, but other than that, this is what I'm using for now. One of the cool things about Org is that Ledger support is baked in to src blocks. By specifying the language as ledger and supplying the necessary inputs and options1, you can throw your postings into the source block and use org-ctrl-c-ctrl-c to run a report on the block.

Or, if you (like me) prefer to have all of your postings in a backing file, you can use src blocks with bash as the language to run ledger commands. My Org file has a few reports, and I wasn't sure how to run them in ledger src blocks without duplicating the data, so I opted for bash. I also went this route because the syntax highlighting in a ledger src block gets pretty slow when you put all of your postings for the year in there. One solution is to just not cram so much data into a src block, but for my brain, it made more sense to use bash src blocks.

In addition to Org being so accommodating to Ledger, Ledger is also pretty friendly towards Gnuplot and has options to format its data with Gnuplot in mind! The Expenses Vs Income for Month section of the report demonstrates how Ledger's output can work with Gnuplot without any fuss, and also how the output can require tuning. My understanding is that when using --invert option with --collapse, the inversion is done twice, so it appears to not be having any effect. Ledger has a --plot-amount-format option to work around this limitation, and while it would be nice if the --invert option could just do what I want for this use case, we can work with it!

So the pieces are there, and the report is getting generated, but how does it get used? What I like about this setup is, it's kind of a living report. Instead of generating a new one for every month, I leverage org-add-note to create notes in logbooks below each heading (see "Expense Vs Income Per Month" heading) to track history and savings goal progress. The plot is displayed in this same section because it works really well with the notes: The notes kind of serve as strategies for the next month and the plot provides feedback on how those strategies are working. Since the notes are timestamped, they're easy to line up with the plot. To illustrate what I'm talking about, here are the notes and plot side by side:

** Expenses Vs Income for Month
:LOGBOOK:
- Note taken on [2023-04-30 Sun 12:31] \\
  Maybe I can eat out less and put more money toward travel budget?
- Note taken on [2023-03-31 Fri 12:31] \\
  Spending less on hobbies wasn't bad, we can try to keep that up.
- Note taken on [2023-02-28 Tue 12:31] \\
  Spent extra money on Valentine's Day present, will try to save more next month.
:END:

Spikes in expenses stress me out, so I note them as they come up. This saves me stress later when I inevitably forget why there was a spike three or four months down the line. I'm juggling just a few accounts with this demo data, but with more accounts come more complex goals. Having notes makes tracking goals easier, and it provides important emotional data points when I go back and evaluate budgets on the accounts I'm tracking. I tend to make my goals all about the numbers, but saving money is stressful, and having a place where that stress can be factored into goal progress is great. Before this report, I didn't even realize how stressful money is for me. Goals always seemed kind of nebulous because it was overwhelming to try to figure out a way to measure progress and I wanted a solution that worked "right now." I think about saving as more of an iterative process now, and it's easier to appreciate the progress I'm making.

Footnotes

1 "Ledger 3," Ledger: Command-Line Accounting, accessed September 06, 2023. https://ledger-cli.org/doc/ledger3.html.