Disclaimer

While I endeavour to present accurate and useful information here, I am not an accountant, lawyer, or tax expert. This should not be taken as legal advice or investment advice. Use the information here at your own risk. If in doubt, contact your local accountant or tax agent.

Intro

Plain text accounting is awesome. I use hledger, which is a modern rewrite of the original ledger-cli. It's awesome.

One thing that it doesn't do out of the box though is tracking lots, capital gains, and capital losses.

The concept of capital gains/losses, and how to model them in a ledger file, was something I spent some time trying to understand when dabbling in cryptocurrency. So, I'm sharing a method, tutorial, and example ledger file for tracking transactions to fulfil tax requirements, in the hope that it may be useful to others who are interested in doing something similar.

Method / Backstory

So basically, I had two issues:

  1. how to use hledger to model capital gains/losses,
  2. and how to record lots as well as physical locations of assets.

To model capital gains/losses, we need to ensure cost basis is recorded whenever a cryptocurrency (or other asset subject to capital gains) is purchased. Then when disposing of the asset, the cost basis is subtracted from the sell price, and the difference determines your profit or loss (usually directly capital gain or capital loss). It turns out that hledger already supports transaction prices. We can use them for neatly calculating capital gains/losses, by recording the cost basis as the transaction price on the asset being sold, and the current exchange rate (or full sell price) on the asset being purchased. The capital gain/loss will be the difference, and hledger can auto calculate and enforce that.

As for lots versus physical locations, the motivation for this split may not be immediately apparent. Consider this situation:

  • You purchase 1 COIN on Exchange One for 100 AUD, and 1 COIN on Exchange Two for 110 AUD.
  • The next day you purchase 1 COIN on Exchange Two for 120 AUD.
  • Now you have 1 COIN on Exchange One, and 2 COIN on Exchange Two, but you need to record the 2 COIN on Exchange Two as two lots, since they were purchased at different prices.
  • Later if you sell the 2 COIN on Exchange Two, and your country uses FIFO rules for capital gains, you need to sell the 1 COIN lot from Exchange One at 100 AUD cost basis, and the 1 COIN lot from Exchange Two at 110 AUD cost basis.
  • Now you have 1 COIN lot at 120 AUD (purchased on Exchange Two), but that 1 COIN is physically on Exchange One.

So I ended up recording the physical locations with the real postings (ie. sell 2 COIN on Exchange Two), and virtual postings for lots. It involves a bit of manual work to ensure everything is balanced, but has been working well for me so far.

Tutorial

Here we'll step through an example sequence of transactions and events. This will build up a complete valid ledger file, which is included in full after this tutorial.

Step 1 is to establish the starting state. 1000 AUD in the bank, and exists an asset COIN that is currently worth 100 AUD per unit:

; Add as many decimal points as you want to have precision to.
; 2 is usually standard for fiat,
; but you may need more for high value commodities.
commodity AUD
    format 100,000.00 AUD

commodity COIN
    format 100,000.00 COIN

2020-01-01 * opening balances
  assets:bank  1000 AUD
  equity:opening balances

P 2020-01-02 00:00:00 COIN 100 AUD

Now we're ready to invest in some COIN. We need to record the exchange rate when it was purchased, to calculate capital gain/loss later. We also use the real account assets:exchange with the COIN subaccount, which makes sense because we've just bought COIN and it's on the exchange.

We also add a virtual posting to record the "lot". This simply mirrors the COIN purchase, but is credited to an account that specifies which lot this is part of. For cryptocurrency in Australia, the use case that this tutorial is assuming, lots are taxed by FIFO (first in, first out), and everything in the lot must have been purchased at the same price. This means that the simplest method of naming the lot is the date of purchase.

2021-02-03 EDIT: I've been informed that FIFO is not necessarily required in Australia; please do your own research to confirm for your use case.

2020-01-02 * buy COIN
  assets:exchange:COIN  2 COIN @ 100 AUD
  assets:bank  -200 AUD
  (virtual:lots:2020-01-02)  2 COIN @ 100 AUD

Now the price of COIN versus AUD increases. We purchase some more, using a new lot because the price has changed since the last lot, and record everything similarly to last time. The price then increases again the next day, and then we move 1 COIN off the exchange to a hardware wallet.

P 2020-01-03 00:00:00 COIN 150 AUD

2020-01-03 * buy more COIN
  assets:exchange:COIN  1 COIN @ 150 AUD
  assets:bank  -150 AUD
  (virtual:lots:2020-01-03)  1 COIN @ 150 AUD

P 2020-01-04 00:00:00 COIN 175 AUD

2020-01-04 * move COIN to hardware wallet
  assets:hardware wallet  1 COIN
  assets:exchange:COIN

We go into town and by a coffee at a fancy hipster café that accepts COIN. It's an expensive coffee to make the maths simpler, don't judge.

2020-01-04 * use COIN on hardware wallet
  assets:hardware wallet  -0.5 COIN @ 100 AUD
  expenses:coffee  0.5 COIN @ 175 AUD
  income:capital gains  -37.5 AUD
  (virtual:lots:2020-01-02)  -0.5 COIN @ 100 AUD

Things to note here:

assets:hardware wallet -0.5 COIN @ 100 AUD shows COIN at cost basis. expenses:coffee 0.5 COIN @ 175 AUD shows COIN at the current exchange rate. Since these two are different, the transaction will not be balanced, and hledger will require an extra posting to balance it out - this is the capital gains posting. Note that -37.5 AUD is the difference between 0.5 COIN @ 100 AUD and 0.5 COIN @ 175 AUD. Hledger will calculate this part automatically if you leave the value out, but I prefer to enter it to sanity check the value.

We're using the real posting for the physical location (hardware wallet), and the independent virtual posting for the lot (FIFO, so first lot that still has a balance which is 2020-01-02). The physical location of COIN being spent is tracked, and so is the lot for tax purposes. This was easy to calculate find the lot (the first lot is just above and we know it had 1 COIN), and calculate the cost basis (one lot at 100 AUD, so 100 AUD). We'll see a more complex example later.

Just a side note here, If you don't need to track capital gains on this purchase, then we can make a simpler transaction like the one below. This may or may not be the case depending on local tax laws, the type of COIN, why you purchased COIN, etc.... you'll need to do you're own research. This tutorial just covers the basic case and assumes capital gains for everything. For anything less complicated, you can just drop the AUD prices as in the transaction below. For anything more complicated you'll need to be a bit more fancy about naming lots and such (for example if you have some lots that aren't subject to capital gains, or need to be sold in a different order for whatever reason).

2020-01-04 * use COIN on hardware wallet
  assets:hardware wallet  -0.5 COIN
  expenses:coffee  0.5 COIN
  (virtual:lots:2020-01-02)  -0.5 COIN

Now, just to make things more interesting and to show a more complex transaction later, let's purchase more COIN, which of course is still increasing in worth.

P 2020-01-05 00:00:00 COIN 200 AUD

2020-01-05 * and buy more COIN
  assets:exchange:COIN  1 COIN @ 200 AUD
  assets:bank  -200 AUD
  (virtual:lots:2020-01-05)  1 COIN @ 200 AUD

P 2020-01-06 00:00:00 COIN 300 AUD

Let's see what our balances currently are:

❯ hledger bal -R
          450.00 AUD  assets:bank
           3.00 COIN  assets:exchange:COIN
           0.50 COIN  assets:hardware wallet
       -1,000.00 AUD  equity:opening balances
           0.50 COIN  expenses:coffee
          -37.50 AUD  income:capital gains
--------------------
         -587.50 AUD
           4.00 COIN

So there are 3 COIN on the exchange, and 0.5 COIN on the hardware wallet - two locations.

❯ hledger bal lots
           1.50 COIN  virtual:lots:2020-01-02
           1.00 COIN  virtual:lots:2020-01-03
           1.00 COIN  virtual:lots:2020-01-05
--------------------
           3.50 COIN

But we have 3 virtual lots of COIN. Note that if we weren't using virtual lots here, we would have difficulty calculating lot prices after moving COIN between the exchange and a hardware wallet, and it would be difficult balancing transactions if we sold from the exchange, but needed to dispose of the lot recorded with the hardware wallet first... Conversely, if we only used lots, we couldn't check the balance of the hardware wallet or exchange from hledger.

Now let's sell some COIN and make some profit!

2020-01-06 * sell COIN on exchange
  assets:exchange:COIN  -2 COIN @ 112.5 AUD  ;(1.5*100 + 0.5*150) / 2 = 112.5
  assets:exchange:AUD  600 AUD
  income:capital gains  -375 AUD  ;(2*112.5 - 600)
  (virtual:lots:2020-01-02)  -1.5 COIN @ 100 AUD
  (virtual:lots:2020-01-03)  -0.5 COIN @ 150 AUD

So this is a little more complex. We're selling 2 COIN on the exchange, but this now spans 2 lots. To write this transaction, we need to follow some manual steps.

First we need to work out which lots to use, and how much from each. If we run the hledger bal lots command from above, we see a neat output conveniently ordered by date.

So we can take all of lot 2020-01-02 (1.5 COIN), and 0.5 COIN from the next lot, 2020-01-03, to sum to the 2 COIN we want. We can find the cost basis of these lots, from the first time they are mentioned in the ledger file, and enter the virtual postings in the new transaction.

We then find the cost basis of the entire transaction by multiplying the amount of COIN by the lot price for each lot, and dividing by the amount of COIN being sold: (1.5*100 + 0.5*150) / 2 = 112.5 Then the total sell price is entered on the other side (600 AUD), and the capital gains calculated by the cost basis multiplied by the amount of COIN, minus the total sell price (2*112.5 - 600) Note that hledger will enforce that these balance, which confirms the calculations.

If the price of COIN drops and we sell, we can use the same recording method to show capital loss:

P 2020-01-07 00:00:00 COIN 50 AUD

2020-01-07 * sell COIN on exchange
  assets:exchange:COIN  -0.5 COIN @ 150 AUD
  assets:exchange:AUD  25 AUD
  expenses:capital loss  50 AUD  ;(0.5*150 - 25)
  (virtual:lots:2020-01-03)  -0.5 COIN @ 150 AUD

2020-01-08 * move COIN to exchange
  assets:exchange:COIN  1 COIN
  assets:hardware wallet

P 2020-01-09 00:00:00 COIN 150 AUD

2020-01-09 * sell COIN on exchange
  assets:exchange:COIN  -1 COIN @ 200 AUD
  assets:exchange:AUD  150 AUD
  expenses:capital loss  50 AUD  ;(1*200 - 150)
  (virtual:lots:2020-01-05)  -1 COIN @ 200 AUD

Note that capital loss are always positive values, while capital gains are always negative.

And that is essentially it.

Hopefully some of the manual steps in tracking investments will be automated by hledger in the future. Hledger is in active development, and there is tracking issue simonmichael/hledger#1015 for an overview of plans and progress in that area.

Example ledger file

Here is the complete example ledger file used in the tutorial above for reference.

commodity AUD
    format 100,000.00 AUD

commodity COIN
    format 100,000.00 COIN

2020-01-01 * opening balances
  assets:bank  1000 AUD
  equity:opening balances

P 2020-01-02 00:00:00 COIN 100 AUD

2020-01-02 * buy COIN
  assets:exchange:COIN  2 COIN @ 100 AUD
  assets:bank  -200 AUD
  (virtual:lots:2020-01-02)  2 COIN @ 100 AUD

P 2020-01-03 00:00:00 COIN 150 AUD

2020-01-03 * buy more COIN
  assets:exchange:COIN  1 COIN @ 150 AUD
  assets:bank  -150 AUD
  (virtual:lots:2020-01-03)  1 COIN @ 150 AUD

P 2020-01-04 00:00:00 COIN 175 AUD

2020-01-04 * move COIN to hardware wallet
  assets:hardware wallet  1 COIN
  assets:exchange:COIN

2020-01-04 * use COIN on hardware wallet
  assets:hardware wallet  -0.5 COIN @ 100 AUD
  expenses:coffee  0.5 COIN @ 175 AUD
  income:capital gains  -37.5 AUD
  (virtual:lots:2020-01-02)  -0.5 COIN @ 100 AUD

; or this if you don't need to track capital gains on the purchase
; 2020-01-04 * use COIN on hardware wallet
;   assets:hardware wallet  -0.5 COIN
;   expenses:coffee  0.5 COIN
;   (virtual:lots:2020-01-02)  -0.5 COIN

P 2020-01-05 00:00:00 COIN 200 AUD

2020-01-05 * and buy more COIN
  assets:exchange:COIN  1 COIN @ 200 AUD
  assets:bank  -200 AUD
  (virtual:lots:2020-01-05)  1 COIN @ 200 AUD

P 2020-01-06 00:00:00 COIN 300 AUD

2020-01-06 * sell COIN on exchange
  assets:exchange:COIN  -2 COIN @ 112.5 AUD  ;(1.5*100 + 0.5*150) / 2 = 112.5
  assets:exchange:AUD  600 AUD
  income:capital gains  -375 AUD  ;(2*112.5 - 600)
  (virtual:lots:2020-01-02)  -1.5 COIN @ 100 AUD
  (virtual:lots:2020-01-03)  -0.5 COIN @ 150 AUD

P 2020-01-07 00:00:00 COIN 50 AUD

2020-01-07 * sell COIN on exchange
  assets:exchange:COIN  -0.5 COIN @ 150 AUD
  assets:exchange:AUD  25 AUD
  expenses:capital loss  50 AUD  ;(0.5*150 - 25)
  (virtual:lots:2020-01-03)  -0.5 COIN @ 150 AUD

2020-01-08 * move COIN to exchange
  assets:exchange:COIN  1 COIN
  assets:hardware wallet

P 2020-01-09 00:00:00 COIN 150 AUD

2020-01-09 * sell COIN on exchange
  assets:exchange:COIN  -1 COIN @ 200 AUD
  assets:exchange:AUD  150 AUD
  expenses:capital loss  50 AUD  ;(1*200 - 150)
  (virtual:lots:2020-01-05)  -1 COIN @ 200 AUD
  • Plain text accounting is an epic info site.
  • pages on the hledger docs:
  • tracking issue for investment tracking in hledger: simonmichael/hledger#1015
  • hacker news comment with suggestions for tracking capital gains with ledger (2015)
  • Related tax laws for your country, especially capital gains laws. Common capital gains rules can be summarised as FIFO (first in first out) and LIFO (last in first out), and will inform the order to sell from lots - important to find out before spending too much time writing out transactions!
  • an accountant (they can answer your tax and reporting questions best)
  • local cryptocurrency user groups (these are great and let you talk to real people with similar accounting needs)
  • #hledger channel on Freenode IRC.