Gérer ses comptes en ligne de commande

dollar and command line

As the years go by, the chil­dren grow up and the time is about to come 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 con­trol of my per­son­al fi­nance man­age­ment a lit­tle more se­ri­ous­ly.

I tried to go back to gnu­cash that I al­ready have used a few years ago, but I was con­front­ed with some dif­fi­cul­ties:

  • a lot of sta­bil­i­ty prob­lem­s,
  • 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 adopt­ed it. I have now a con­fig­u­ra­tion that al­lows:

  • re­cov­ery of bank­ing trans­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 or edi­tion of re­ports on the phone thanks to the use of com­mand line “ledger” in a ter­mi­nal shel­l <http­s://ter­mux.­com/>`_.

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.

Sys­tem Mes­sage: ER­ROR/3 (<string>, line 44)

Er­ror in “fig­ure” di­rec­tive: in­valid op­tion block.

.. figure:: /images/ledger/account_books_of_the_tailor.jpeg
   :class: "thumbnail img-responsive"
   :width: 700px
   :align: center
   :alt: Books of accounts of the tailor Louis-Hippolyte Leroy. "General Ledger - No. 5"
   Target: /images/ledger/account_books_of_the_tailor.jpeg

   Books of accounts of the tailor Louis-Hippolyte Leroy. "General Ledger - No. 5"
   (`Gallica <https://gallica.bnf.fr/ark:/12148/btv1b530841242>`_)

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

This is the ba­sic prin­ci­ple of the ac­count­ing sys­tem used by “ledger”. This one ­sounds com­pli­cat­ed, but it just means that for ev­ery trans­ac­tion, there is at least two ac­count con­cerned, one which is deb­it­ed and one which is cred­it­ed, and that the sum of the ­cred­its and deb­its of this trans­ac­tion is nul­l.

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 and that this ex­pense is record­ed as an ex­pense of health. 9n 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 ex­act­ly the con­tent of the ac­count book. It is a sim­ple ascii file. Easy to type and to man­age in con­fig­u­ra­tion. A sim­ple hg diff (or git diff) al­lows you to see the cur­rent changes.

The “ledger” tool can then give the bal­ance of ac­counts:

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 trans­ac­tion one can leave one of the cred­it or deb­it with­out amount in which case “ledger” cal­cu­lates the amount re­quired ­to en­sure a trans­ac­tion with a ze­ro bal­ance.

File organization

I have sev­er­al ac­counts in dif­fer­ent banks and I have cho­sen to put the trans­ac­tions in a file as­so­ci­at­ed with each ac­coun­t. I for ex­am­ple have the main file “per­so.ledger” which in­cludes the oth­er 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” back­ends are con­fig­ured in the file ~/.­­con­­fig/we­­boob/back­­ends. For ex­am­ple:

[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 pass­words are man­aged by the pass ap­pli­ca­tion and en­crypt­ed via a yu­bikey key. run­cached is a script that al­lows me to cache the pass­word­s a few min­utes so that you don’t have to re-en­ter it for all re­quest­s ­car­ried out by boobank.

Synchronization with ledger

I then use ledger-au­­tosync to syn­chro­nize OXF files with the ledger file ­cor­re­spond­ing. Again it’s done in a scrip­t, but ba­si­cal­ly it gives ­for each ac­coun­t:

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

The prob­lem you then have to deal with is to as­sign the right ex­pense or in­come ac­count to ­trans­ac­tions in place of the de­fault ex­pense:Un­known.

For­tu­nate­ly, ledger al­lows you to as­so­ciate an ac­count with sev­er­al pay­ees when the ac­count of a trans­ac­tion con­tains Un­known.

Thus, the fol­low­ing ac­count book:

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 ac­count­name.ledger, I have a file ac­count­name.ac­count which is in­clud­ed and de­fines the pay­ees as­so­ci­at­ed with each ledger ac­coun­t.

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
expense:gift
expenses:communication
expenses:miscellaneous
expenses:donation
expense: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
expense:tax
expenses:tax:property tax
expenses:tax:housing
expense: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
expense:leisure:travel
expenses:mobility
expenses:mobility:airplane
expenses:mobility:rail
expense:mobility:car
expense:mobility:car:fine
expense:mobility:car:insurance
expenses:mobility:car:fuel
expenses:mobility:car:maintenance
expenses:mobility:car:toll
expense:mobility:car:parking
expenses:mobility:car:electricity
expenses:mobility:bicycle
expense:reimbursement
expenses:health
expense:service:web
expense: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

This 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 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 trans­ac­tion as­so­ciate with the ac­coun­t Ex­pense:­hous­ing:en­er­gy and whose pay­ee is En­er­coop, I de­crease the mo­bil­i­ty:­car:­elec­tric­i­ty ac­count by €45 and I cred­it back­ the ex­pense ac­count hous­ing:en­er­gy 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 trans­ac­tions are record­ed in the file bank1.ledger as ­fol­lows:

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

and in the file bank2.ledger as fol­lows:

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 sim­ple and el­e­gant so­lu­tion is to in­form ledger that ­ex­pens­es:­trans­fer to bank2“ and in­come:­trans­fer from bank1 are in­deed the same ac­coun­t. This is done through the alias di­rec­tive:

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 al­so pos­si­ble to de­fine a bud­get. For ex­am­ple, I have at the top the per­so.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 doc­u­men­ta­tion al­so talks about the pos­si­bil­i­ty of mak­ing fore­casts (with­ the --­­fore­­cast op­tion), but it does­n’t work and many bugs re­port­s re­lat­ed to this fea­ture 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, VC­S ­tool, com­mand line, au­to­mat­ic com­ple­tion,…).

Many fea­tures have not been cov­ered in this ar­ti­cle. I will prob­a­bly do an ad­di­tion­al ar­ti­cle to track the evo­lu­tion of my way of work­ing with ledger-­­cli.

Leave a comment
The name you want to show others

Comment by: Mathieu Clabaut

Je ne me suis pas encore vraiment penché sur les graphiques dans la mesure ou ces informations sont disponibles en ligne de commande. Ceci dit, certaines personnes ont fait des choses :

  • utilisation de gnuplot : https://www.sundialdreams.com/report-scripts-for-ledger-cli-with-gnuplot/
  • application fournissant une page web permettant de produire des graphiques : https://kndrck.co/posts/ledger-analytics/

Je pense que je ferais un jour quelque chose avec gnuplot… Si j’aboutis à quelque chose de satisfaisant, je ferais un deuxième billet.