Manage your accounts on the command line

dollar and command line

As the years go by, the chil­dren grow up and the time comes for high­er ed­u­ca­tion (and as­so­ci­at­ed cost­s). This is an op­por­tu­ni­ty to take back con­trol of my per­son­al fi­nances a lit­tle more se­ri­ous­ly.

I tried gnu­cash that I have al­ready used a few years ago, but I met some dif­fi­cul­ties:

  • I had a lot of sta­­bil­i­­ty prob­lems (seg­­fault­­),

  • syn­chro­niza­­­tion with OFX files re­trieved from the bank re­quired far too many man­u­al ac­­tion­s.

Af­ter some re­search on the we­b, I de­cid­ed to try ledger-­cli and I think I have now adopt­ed it. My con­fig­u­ra­tion now al­lows:

  • re­­cov­­ery of bank­ing tran­s­ac­­tions via we­­boob,

  • au­­to­­mat­ic syn­chro­niza­­­tion with my ac­­count book via legder-au­­tosync,

  • anal­y­sis of syn­chro­niza­tion-re­lat­ed ad­di­tions us­ing ver­sion man­age­ment like mer­cu­ri­al or git,

  • edit­ing var­i­ous text re­ports us­ing the com­mand line ledger,

  • easy ad­di­tion of trans­ac­tions to vim us­ing the vim-ledger’ plug­in,

  • mul­ti­-de­vice syn­chro­niza­­­tion (an­­droid, lin­ux) via sync­thing or the ver­­sion man­age­­ment soft­­ware,

  • the mod­i­fi­ca­tion of ledger files and edi­tion of re­ports on the phone thanks to the use of ledger tool in the ter­mux ter­mi­nal.

It’s not easy for Mrs. Jones to set up, but what a plea­sure to work on the com­mand line with tools that work so well.

Books of accounts of the tailor Louis-Hippolyte Leroy. "General Ledger - No. 5"

Books of ac­counts of the tai­lor Louis-Hip­poly­te Leroy. “Gen­er­al Ledger - No. 5” (Gal­li­ca)

Reference documentation

The web­site http­s://­plain­tex­tac­count­ing.org/ is a mine of in­for­ma­tion, as well as of course the on­line doc­u­men­ta­tion of ledger.

This “ledger CLI cheat sheet” is al­so very use­ful.

Double-entry accounting

Double-entry accounting is the base principle of the accounting system used by ledger. It sounds complicated at first, but it just means that each transaction shall concerned at least two accounts, one which is debited and one which is credited, and that the sum of the credits and debits of this transaction is null.

For ex­am­ple, we can have the fol­low­ing trans­ac­tion­s:

2019/03/08 Doctor
   assets:bank:current           -30 €
   expenses:health                30 €

2019/03/12 Amazon
   assets:bank:current          -118.84 €
   expenses:house:maintenance    114.99 €
   expenses:house:supply           3.85 €

This means that on March 8, I deb­it­ed €30 from my cur­rent ac­count to pay the doc­tor which is record­ed as a health ex­pense. And on March 12, I pur­chased main­te­nance equip­ment and sup­plies on Ama­zon for an amount of €118.84.

What you see here above is exactly the content of the ledger file. A simple ascii text file. Easy to type and to manage. A simple hg diff (or git diff) allows you to see the current changes.

The ledger tool can then give the balance of accounts:

ledger -f test.ledger bal
           -148.84 € assets:bank:current
            148.84 € expenses
            118.84 €   house
            114.99 €     maintenance
              3.85 €     supply
             30.00 €   health
--------------------
                   0

Note that in a transaction you can leave one of line without amount in which case ledger computes the missing value in order to ensure a balanced transaction.

File organization

I have several accounts in different banks and for the ease of synchronisationņ I have chosen to separate the transactions in several files, one per bank account. For example, I have one main file perso.ledger which includes all the other files:

include bank1-current.ledger
include bank2-current.ledger
include bank2-livret.ledger

Data import

OXF downloading

I wrote the fol­low­ing lit­tle script that re­cov­ers ac­counts in files OXF :

#!/bin/bash

backends="-b bppc,boursorama"
accountsdir="/$HOME/banques/OXF"
format="${1:-oxf}"

function accountsList()
{
      boobank --auto-update $backends list --formatter simple --no-header\
              --no-keys --select id --condition 'type=1 OR type=2'
}

echo "Get accounts list..."
accountsList | while read account
do
      filename="$accountsdir/$account"-$(date +%Y%m%d)".$format"
      printf "Dump $account to $filename..."
      boobank history "$account" -f "$format" -n 999 > "$filename" &&\
        printf "        ok!\n" || printf "      failed!\n"
done

The boobank backends are configured in the file ~/.­con­fig/we­boob/back­ends. For example:

[bppc]
module = banquepopulaire
password = `runcached pass show weboob/bppc`
website = www.ibps.mediterranee.banquepopulaire.fr
login = XXXXXXXXXX

[boursorama]
module = boursorama
enable_twofactors = True
device = weboob
pin_code =
login = XXXXXXXXXXXX
password = `runcached pass show weboob/boursorama`

My passwords are managed by the pass application and encrypted via a yubikey. runcached is a script that allows me to cache the passwords a few minutes so that I don’t have to re-enter it for all requests carried out by boobank.

Synchronization with ledger

I then use ledger-au­tosync to synchronize OXF files with the corresponding ledger file. It’s done with a script, which basically does for each account:

ledger-autosync -a "actif:banque:bppc" --assertions --fid "40618" \
     -l "bppc-courant.ledger" "$OXFDIR/CPTYYYYYY@bppc.oxf" \
     | sed -e 's/Expenses:Misc/expenses:Unknown/' -e 's/\<EUR\>/€/' \
       >>  bppc-courant.ledger

The problem you then have to deal with, is to assign the right expense or income account to transactions in place of the default expenses:Unknown account.

Fortunately, ledger allows you to associate an account with several payees, and replace the Unknown account with the one associated with the transaction payee.

Thus, the fol­low­ing ledger file:

account expenses:health
   paid Doctor


2019/03/08 Doctor
   assets:bank:current -30 €
   Expenses:Unknown

is in­ter­pret­ed as:

ledger -f test.ledger bal
              -30 € assets:bank:current
               30 € expenses:health
-------------------
                  0

For each file accountname.ledger, I have a file accountname.account which is included and defines the payees associated with each ledger account.

Daily use

Account hierarchy

You are free to de­fine the ac­count hi­er­ar­chy your in­ter­est­ed in. As far as I’m con­cerned, I have some­thing like:

assets:bank1:current
assets:bank1:livret
assets:bank2:current
assets:initial balance
expenses:administrative
expenses:food
expenses:food:canteen
expenses:food:restaurant
expenses:bank:fees
expenses:gift
expenses:communication
expenses:miscellaneous
expenses:donation
expenses:housing:insurance
expenses:housing:DIY
expenses:housing:construction
expenses:housing:water
expenses:housing:maintenance
expenses:housing:supply
expenses:housing:garden
expenses:housing:furniture
expenses:housing:household appliances
expenses:housing:energy
expenses:tax
expenses:tax:property tax
expenses:tax:housing
expenses:tax:income
expenses:cash
expenses:leisure
expenses:leisure:art
expenses:leisure:computer
expenses:leisure:book
expenses:leisure:software
expenses:leisure:music
expenses:leisure:photo
expenses:leisure:press
expenses:leisure:exhibition
expenses:leisure:sport
expenses:leisure:travel
expenses:mobility
expenses:mobility:airplane
expenses:mobility:rail
expenses:mobility:car
expenses:mobility:car:fine
expenses:mobility:car:insurance
expenses:mobility:car:fuel
expenses:mobility:car:maintenance
expenses:mobility:car:toll
expenses:mobility:car:parking
expenses:mobility:car:electricity
expenses:mobility:bicycle
expenses:reimbursement
expenses:health
expenses:service:web
expenses:clothing
liabilities:loan
income:insurance:family
income:insurance:health
income:bank
income:bank:interest
income:reimbursement
income:salary

Example of requests

  • State of my fi­nances:

    ledger bal assets liabilities
  • Bal­ance sheet of gains or loss­es over 2018:

    ledger bal income expenses -p 2018
  • Av­er­age month­ly dis­tri­bu­tion of ex­pens­es for 2018:

    ledger bal -E -p 2018 expenses --display-total "display_total/12"
  • To­tal month­ly ex­pens­es:

    ledger --group-by 'format_date(date, "%Y/%m")' bal expenses --depth 1

    which gives for ex­am­ple:

    2018/11
             2078.02 € expenses
    
    2018/12
             1775.98 € expenses
    
    2019/01
             2973.27 € expenses
    
    2019/02
             2988.48 € expenses
  • Bal­ance per month over 2019:

    ledger --group-by : format_date(date, "%Y/%m")' bal -p 2019

Automatic transactions

Au­to­mat­ic transat­cions is a fea­ture I use to au­to­mat­i­cal­ly ad­just some trans­ac­tion­s. For ex­am­ple, I don’t want to en­ter the de­tails of my cash ex­pens­es, but I es­ti­mate that 70% of my cash with­drawals are used to buy food.

Thus I added the fol­low­ing au­to­mat­ic trans­ac­tion:

= expenses:cash
      expenses:food     0.7 ; 70% for food (estimated)
      expenses:cash    -0.7 ;

In the same way, I es­ti­mate that for each elec­tric­i­ty bil­l, I have about €45 ded­i­cat­ed to the elec­tric car. So I added the fol­low­ing au­to­mat­ic trans­ac­tion:

= /expenses:housing:energy/ and (expr payee =~ /Enercoop/)
      expenses:mobility:car:electricity   45 €; estimated zoé monthly cost
      expenses:housing:energy            -45€

Thus, for each transaction associate with the account Expense:housing:energy and whose payee is Enercoop, I decrease the mobility:car:electricity account by €45 and I credit back the expense account housing:energy with €45.

Account to account transfers

I have a month­ly mon­ey trans­fer from the bank that re­ceives my salary to the bank who hosts my ev­ery­day ac­coun­t.

Those transactions are recorded in the file bank1.ledger as follows:

2019/03/01 * PERMANENT TRSFR. Salary
   assets:bank1:current          -1000.00 €
   expenses:transfer to bank2     1000.00 €

and in the file bank2.ledger as follows:

2019/03/04 * Salary
   assets:bank2:current           1000.00 €
   income:transfer from bank1    -1000.00 €

The prob­lem is that the bal­ance dis­plays both trans­fer ac­counts, which pol­lutes all the re­port­s:

ledger -f test.ledger bal
                 0 assets
        -1000.00 € bank1:current
         1000.00 € bank2:current
         1000.00 € expenses:transfer to bank2
        -1000.00 € income:transfer from bank1
------------------
                 0

The simple and elegant solution is to inform ledger that expenses:transfer to bank2 and income:transfer from bank1 are indeed the same account. This is done through the alias directive:

alias expenses:transfer to bank2=income:transfer from bank1

This gives the ex­pect­ed re­sult:

ledger -f test.ledger bal
                 0 assets
        -1000.00 € bank1:current
         1000.00 € bank2:current
------------------
                 0

Budget

It is also possible to define a budget. For example, I have at the top the perso.ledger file:

~  Monthly
   expenses:food          444 €
   expenses:communication 56  €
   expenses:miscellaneous 29  €
   expenses:housing:DIY   35  €

~ Yearly
   expenses:housing:insurance      380 €
   expenses:mobility:car:insurance 330 €

It then al­lows me to keep an eye on un­bud­get­ed ex­pens­es:

ledger bal --unbudgeted expenses -p "this month"
            27.35 € expenses
             0.06 €  bank:fees
            23.90 €  gift
             3.39 €  service:web
-------------------
            27.35 €

Or to re­view the bud­get for the year (the re­port gives a progress per­cent­age of the ac­counts with re­spect to the bud­get):

ledger budget -p 2019 expenses income

The documentation also talks about the possibility of making forecasts (with the --­fore­cast option), but it doesn’t work and many bugs reports related to this feature are open on the project github.

Conclusion

Ledger-­cli is a re­al­ly pow­er­ful tool. Once con­fig­ured, I ap­pre­ci­ate the op­por­tu­ni­ty to work with ev­ery­day tools (text ed­i­tor, VCS tool, com­mand line, au­to­mat­ic com­ple­tion,…).

Many features have not been covered in this article. I will probably do an additional article to track the evolution of my way of working with ledger-­cli.

Comments

With an account on the Fediverse or Mastodon, you can respond to this post. Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don’t have an account on this one. Known non-private replies are displayed below.