Manage your accounts on the command line
As the years go by, the children grow up and the time comes for higher education (and associated costs). This is an opportunity to take back control of my personal finances a little more seriously.
I tried gnucash that I have already used a few years ago, but I met some difficulties:
I had a lot of stability problems (segfault),
synchronization with OFX files retrieved from the bank required far too many manual actions.
After some research on the web, I decided to try ledger-cli and I think I have now adopted it. My configuration now allows:
recovery of banking transactions via weboob,
automatic synchronization with my account book via legder-autosync,
analysis of synchronization-related additions using version management like
mercurial
orgit
,editing various text reports using the command line
ledger
,easy addition of transactions to
vim
using the vim-ledger’ plugin,multi-device synchronization (android, linux) via syncthing or the version management software,
the modification of ledger files and edition of reports on the phone thanks to the use of
ledger
tool in the termux terminal.
It’s not easy for Mrs. Jones to set up, but what a pleasure to work on the command line with tools that work so well.
Reference documentation¶
The website https://plaintextaccounting.org/ is a mine of information, as well as of course the online documentation of ledger.
This “ledger CLI cheat sheet” is also very useful.
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 example, we can have the following transactions:
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 debited €30 from my current account to pay the doctor which is recorded as a health expense. And on March 12, I purchased maintenance equipment and supplies on Amazon 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:
Data import¶
OXF downloading¶
I wrote the following little script that recovers accounts 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
~/.config/weboob/backends
. 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-autosync
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 following ledger file:
is interpreted as:
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 define the account hierarchy your interested in. As far as I’m concerned, I have something 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 finances:
ledger bal assets liabilities
-
Balance sheet of gains or losses over 2018:
ledger bal income expenses -p 2018
-
Average monthly distribution of expenses for 2018:
ledger bal -E -p 2018 expenses --display-total "display_total/12"
-
Total monthly expenses:
ledger --group-by 'format_date(date, "%Y/%m")' bal expenses --depth 1
which gives for example:
2018/11 2078.02 € expenses 2018/12 1775.98 € expenses 2019/01 2973.27 € expenses 2019/02 2988.48 € expenses
-
Balance per month over 2019:
ledger --group-by : format_date(date, "%Y/%m")' bal -p 2019
Automatic transactions¶
Automatic transatcions is a feature I use to automatically adjust some transactions. For example, I don’t want to enter the details of my cash expenses, but I estimate that 70% of my cash withdrawals are used to buy food.
Thus I added the following automatic transaction:
In the same way, I estimate that for each electricity bill, I have about €45 dedicated to the electric car. So I added the following automatic transaction:
= /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 monthly money transfer from the bank that receives my salary to the bank who hosts my everyday account.
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:
The problem is that the balance displays both transfer accounts, which pollutes all the reports:
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:
This gives the expected result:
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 allows me to keep an eye on unbudgeted expenses:
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 review the budget for the year (the report gives a progress percentage of the accounts with respect to the budget):
The documentation also talks about the possibility of making forecasts (with the --forecast
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 really powerful tool. Once configured, I appreciate the opportunity to work with everyday tools (text editor, VCS tool, command line, automatic completion,…).
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.