Update: I’ve published the tool described below as lotter
, a
companion tool to ledger
. Source code and instructions:
https://src.d10.dev/lotter.
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 oneABC
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 caseIncome:Air Drop
will tally -2.00 USD. Inledger
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 balances, 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. Update: the lotter
tool
now creates virtual splits for these, and ledger
still enforces
correctness.
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 theIncome: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’ve published the tool described in this post. Called lotter
, it
reads transactions in the ledger-cli
format, and adds virtual splits
that represent inventory and cost basis. The result can then be
passed to ledger
for calculation of gains.
Find source code for lotter
at https://src.d10.dev/lotter .
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.
Share this post
Twitter
Facebook
Reddit
LinkedIn
StumbleUpon
Email