Finances with Ledger and Org
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 ledgersrc blocks without duplicating the data, so I opted for bash. I also went this route because the syntax highlighting in a ledgersrc 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 bashsrc 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. |