Tracking cost basis for crypto trades is a huge pain. I’ve been experimenting with ledger-cli to track trading history. It works well, so I’m sharing some notes here.

ledger is a developer-friendly bookkeeping tool. It’s basically a calculator for double-entry accounting. Data is kept in plain text files, and it offers a command line interface.

It’s a great tool. If there’s any shortcoming, for my purposes, it is ledger’s ability to track cost basis associated with trades. In this post, I describe how I structure accounts to better track cost basis, and gains/losses.

Some relevant background reading that I recommend are the ledger wiki article “Multiple Currencies with Currency Trading Accounts” and Peter Selinger’s “Tutorial on Multiple Currency Accounting”. The ideas in those two posts inspired the following approach.

Currencies in Ledger-CLI

In this example, I’m going use imaginary assets ABC and XYZ. For a base currency, I’ll use USD.

ledger learns how much decimal precision to associate with a currency the first time it sees a value. To avoid gotchas, start your ledger data file by explicitly specifying a precision. For this example…

; Decimal precision for various currencies.
D 0.000000001 ABC
D 0.000000001 XYZ
D 0.00 USD

Simple Trade Representation

Let’s say that back in 2016, I was given some ABC, each worth 2 cents at the time…

; Establish cost basis for a cryptocurrency.
2016-01-01 Received ABC
    Assets:Crypto                                100 ABC @ 0.02 USD
    Income:Air Drop                            

The ledger format (above) describes receiving ABC. The first line (called the “payee” line) has the date of the activity, and a free-form description. Each indented line (called a “split”) is a balance change:

  • My “Assets:Crypto” balance increased by 100 ABC. The “@ 0.02 USD” indicates that one ABC was worth two cents at the time. Knowing a price here allows me to calculate a cost basis for future trades.

  • There’s no amount next to “Income:Air Drop” in this example; ledger will automatically calculate an amount that offsets the other indented lines. In this case Income:Air Drop will tally -2.00 USD. In ledger double-entry accounting, income is negative (offsetting positive assets).

To continue this example, let’s say that one year has passed, the value of an ABC has grown to a dollar, and I sell one. In ledger, this looks like…

; Trade for dollars
2017-01-01 Sell an ABC for one dollar
    Assets:Exchange                                1 USD        
    Assets:Crypto                                 -1 ABC @ 1 USD

If I run ledger bal assets on our data so far, I see that our assets are what I expect (99 ABC, and one dollar on my exchange)…

    99.000000000 ABC
          1.0000 USD  Assets
    99.000000000 ABC    Crypto
          1.0000 USD    Exchange
--------------------
    99.000000000 ABC
          1.0000 USD

This simple representation produces the correct ballances, but does not capture the cost basis or gains from my trade. To do so, I’m going to add splits that track “lots”. (Note that ledger has some built-in support for automatically calculating lots, but I could not get the built-in feature to track all the details I want to see.)

Trade Represented with Lots

Let’s revisit my first transaction. Another version, shown below, creates a “lot”. The lot has an inventory of 100 ABC, and a cost basis of two cents (per ABC)…

; Establish cost basis for a cryptocurrency.
2016-01-01 Received ABC
    Assets:Crypto                                100 ABC 
    Income:Air Drop                            
    Trade:Lot:2016/01/01:100ABC@0.02USD		-100 ABC ; inventory
    Trade:Lot:2016/01/01:100ABC@0.02USD		2.00 USD ; basis

To be clear what is expressed:

  • Assets:Crypto increases by 100 ABC (I received in air drop).

  • Income will be calculated by ledger, exactly as before. This amount (minus $2) is negative, for double-entry accounting.

  • Trade:Lot accounts are for bookkeeping. These accounts names include the date created, the inventory and cost basis. Including this information makes each lot name unique (enough for my purposes), also these details are helpful to see when looking at ledger splits.

  • The lot’s starting inventory is 100 ABC, represented as a negative amount (like income, for purposes of double-entry bookkeeping).

  • The lot’s cost basis is 2 dollars, represented as a positive amount.

Note that the Trade:Lot:... accounts are tracking inventory and cost basis. They are not tracking assets held, or realized gains. They serve a purpose ledger calls “virtual” accounts. Although I am entering them as normal (non-virtual) splits, because I want ledger to enforce that the entire transaction remains balanced. Each positive increase is offset by a negative decrease.

Next let’s look at my first trade, with splits added to track the lot inventory…

; Trade for dollars
2017-01-01 Sell an ABC for one dollar
    Assets:Exchange                                1 USD        
    Assets:Crypto                                 -1 ABC 
    Trade:Lot:2016/01/01:100ABC@0.02USD		1 ABC 
    Trade:Lot:2016/01/01:100ABC@0.02USD		-0.02 USD 
    Income:Lot:long term gain			 

Here’s what is expressed in this transaction:

  • I traded 1 ABC for 1 USD (exactly as before).

  • I decreased the lot supply by 1 ABC (a positive value consumes the lot inventory).

  • The cost basis of 1 ABC from this lot is two cents (a negative value offsets the cost basis).

  • ledger will automatically calculate the Income:Lot:long term gain balance. Here, it will be -0.98 USD, the difference between the trade value and the cost basis (negated for double-entry bookkeeping).

Given the data with lots, ledger calculates the following balances:

  • The asset tally (ledger bal assets) is unchanged:

    99.000000000 ABC
          1.0000 USD  Assets
    99.000000000 ABC    Crypto
          1.0000 USD    Exchange
    --------------------
    99.000000000 ABC
          1.0000 USD
    
  • The income tally (ledger bal income --invert) confirms what I expect:

          2.9800 USD  Income
          2.0000 USD    Air Drop
          0.9800 USD    Lot:long term gain
    --------------------
          2.9800 USD
    
  • The lot tally (ledger bal trade --invert) shows that 99 ABC remains in my first lot. The cost basis of this remaining amount is $1.98. (Recall that 1 ABC of the original 100 was consumed in the first trade, as was $0.02 of cost basis.)

    99.000000000 ABC
         -1.9800 USD  Trade:Lot:2016/01/01:100ABC@0.02USD
    

Trade with Deferred Gains

We’ve seen the basics of how I use lots to tally gains. Let’s dive a little deeper and see how my lots can represent a deferred gain. This is something U.S. residents may take advantage of in a “like-kind” exchange. I am not a lawyer, and nothing I write is financial or legal advice. Consider a listen to an episode of “Unconfirmed” podcast about IRS policies if you pay taxes in the U.S.

To illustrate a deferred gain, let’s say I trade ABC for another fictional currency, XYZ. A simple ledger representation would be…

; Trade cryptocurrency for cryptocurrency (without lots)
2017-02-01 Trade an ABC for XYZ
    Assets:Crypto                               1000 XYZ @ 0.01 ABC
    Assets:Crypto                                -10 ABC

Note that here I’m trading 10 ABC for 1000 XYZ and the cost is represented in ABC (not my base currency, USD).

Here’s the same trade, with lots expressed, and gains deferred…

; Trade cryptocurrency for cryptocurrency (deferred gain)
2017-02-01 Trade an ABC for XYZ
    Assets:Crypto                               1000 XYZ
    Assets:Crypto                                -10 ABC
    Trade:Lot:2016/01/01:100ABC@0.02USD			10.00 ABC ; inventory (traded)
    Trade:Lot:2016/01/01:100ABC@0.02USD			-0.2000 USD ; deferredBasis (deferred)
    Trade:Lot:2017/02/01:1000XYZ@0.01ABC@0.02USD	-1000 XYZ ; inventory
    Trade:Lot:2017/02/01:1000XYZ@0.01ABC@0.02USD	0.2000 USD ; basis

Notice what is expressed in the splits:

  • The trade is as before, 10 ABC for 1000 XYZ.

  • Inventory (10 ABC) is consumed from our first lot.

  • A new lot is created. Notice the naming convention, “1000XYZ” is the inventory of the lot, and “0.01ABC@0.02USD” is a convention representing the cost basis carried forward.

  • The cost basis of the new lot ($0.20) is exactly the cost basis of the 10 ABC used to purchase XYZ. This basis is extracted from the first lot and now associated with the second lot.

Importantly, notice that every split in the transaction is offset by another. The net total of the entire transaction is zero; as it must be in double-entry accounting.

Trade with Realized Gains

What if I want to express a trade, but realize the gains immediately instead of deferring?

Let’s look at a trade of the same currencies at the same prices. To realize the gains at the time of the trade, I value both assets in their value in USD…

; Trade cryptocurrency for cryptocurrency, realize gains immediately
2018-02-01 Trade an ABC for XYZ
    Assets:Crypto                               1000 XYZ @ 0.01 USD
    Assets:Crypto                                -10 ABC @ 1 USD

And here’s that transaction, with lot inventory and basis tracked…

; Trade cryptocurrency for cryptocurrency, realize gains immediately
2018-02-01 Trade an ABC for XYZ
    Assets:Crypto                               1000 XYZ
    Assets:Crypto                                -10 ABC
    Trade:Lot:2018/02/01:1000XYZ@0.01USD	-1000 XYZ ; inventory
    Trade:Lot:2018/02/01:1000XYZ@0.01USD	10.00 USD ; basis
    Trade:Lot:2016/01/01:100ABC@0.02USD		10 ABC ; inventory (sold)
    Trade:Lot:2016/01/01:100ABC@0.02USD		-0.20 USD ; basis (when bought)
    Income:Lot:long term gain

Notice the splits in this example…

  • The trade (10 ABC for 1000 XYZ) is unchanged.

  • The inventory and basis consumed from the first lot is unchanged.

  • A new lot is created with 1000 XYZ. It’s cost basis is determined by prices on the day of the trade (10 USD total).

  • The final split has no value, so ledger will calculate the long term gain. In this example, it offsets the basis splits ($9.80).

Calculating Lots Automatically

I have a work-in progress tool that adds “Trade:Lot:…” splits to ledger files. So I manually create the simple representation of trades, and my tool adds the lots automatically. I’ll update this post when that code is stable enough to share.

Comments or Questions?

I’m experimenting with a Google group as a way to continue discussion. I welcome your feedback there: d10.dev blog discussion.