Notes about investment lot tracking in [hledger](https://hledger.org) and other [plain text accounting](https://plaintextaccounting.org) apps.\
Last updated: 2025-08-13
Discussion:
[mastodon](https://fosstodon.org/@simonmic/113970313986816653),
[PTA forum](https://forum.plaintextaccounting.org/t/lot-tracking-in-pta-joyful-systems/492),
[hledger chat](https://matrix.hledger.org/)...
See also:
[[hledger lot tracking]]
## Introduction
Tracking investment lots is often necessary to calculate capital gains accurately and to comply with tax law.
It's a tricky, increasingly widespread bookkeeping need.
This page aims to be a useful guide to the topic, for PTA users and PTA developers.
Here are gathered related links and FAQs;
then a sort of test suite of common tasks involving lots;
then reproducible examples of solving these with each of the lot tracking methods available in PTA.
Finally, there are some dev notes on design and implementation of lot tracking in PTA.
(How best to do this in a robust way, is a big question that's hard to answer.)
General links:
- https://en.wikipedia.org/wiki/Cost_basis
- https://www.costbasis.com
- https://www.irs.gov/forms-pubs/about-publication-550 Investment Income and Expenses
- https://www.irs.gov/forms-pubs/about-publication-551 Basis of Assets
Which non-PTA apps or sites do this job well ?
- Most mature accounting apps which advertise investments support, probably.
Eg [GnuCash](https://wiki.gnucash.org/wiki/Lots_GUI), [2](https://wiki.gnucash.org/wiki/Concept_of_Lots).
- Stock brokers and cryptocurrency exchanges provide detailed lot reports.
- Online and offline apps built for this job, often aimed at cryptocurrency users.
Eg [BittyTax](https://github.com/BittyTax/BittyTax).
What solutions are there in the PTA world ?
- hledger users currently do it [manually with subaccounts](https://hledger.org/investments.html#what-about-lots-).
This method can be used in any double entry system.
It is intuitive but also verbose and tedious, requiring advanced text-editing skills.
- Ledger has [lot notation](https://hledger.org/dev/hledger.html#other-costlot-notations) which helps automate this.
[2](https://www.d10.dev/blog/ledger-cli-trade/)
- Beancount has [simpler lot notation](https://hledger.org/dev/hledger.html#other-costlot-notations) which helps automate this to a greater extent, and with more validation.
Also:
- The [Lotter](https://src.d10.dev/lotter/doc/tip/README.md) addon tool adds limited automated lot tracking to Ledger or hledger. It does not support custom lot dates, moving lots between accounts, etc.
- The [hledger-lots](https://hledger.org/scripts.html#hledger-lots) tool adds limited automated lot tracking to hledger. It does not support custom lot dates, moving lots between accounts, etc.
- The [hledger-move](https://hledger.org/scripts.html#hledger-move) tool helps generate journal entries for the manual subaccounts method (as does the [hledger close](https://hledger.org/dev/hledger.html#close) command).
PTA links:
- https://hledger.org/dev/hledger.html#other-costlot-notations
- https://github.com/simonmichael/hledger/issues/1015 overview of related PTA docs and hledger issues
- [2015 hledger discussion](https://groups.google.com/g/hledger/c/e8Ss7ZL4ADI) beginning of hledger's costs/investment support, & a description of Beancount's from Martin B
- https://plaintextaccounting.org/Investing-and-trading
- https://hledger.org/cookbook.html#investing-and-trading
- [Full-fledged hledger: Manual lot tracking](https://github.com/adept/full-fledged-hledger/wiki/Manual-lot-tracking) discussion of UK disposal rules
What is the scope of disposal methods like FIFO - per account, per broker, per commodity ?
It's unclear. No obvious IRS guidance found. LLMs disagree.
Discussion suggests the IRS will consider it per commodity, across all brokers and accounts (as it is in the wash sale rule).
But in practice brokers' cost basis reports are specific to them and even to each account held with them.
Accounting software may ideally need to support different scopes. Assume per account for now.
## Tests
Here are a few tasks/scenarios we'd like a lot tracking system to handle, as conveniently as possible.
The following market prices can be assumed, if needed for context:
```
P 2025-01-01 AAA $1.10 ; morning
P 2025-01-01 AAA $1.20 ; afternoon
P 2025-02-01 AAA $1.21
P 2025-02-02 AAA $1.22
P 2025-03-01 AAA $1.31
P 2025-03-02 AAA $1.32
P 2025-03-03 AAA $1.33
```
### Acquire lots
The first task is to record the acquisition of some lots. To make things more challenging, some have the same date and/or cost.
```
2025-01-01 buy 10 AAA at 1.10
2025-01-01 buy 10 AAA at 1.20 (same day)
2025-01-01 buy 10 AAA at 1.20 (same day and cost)
```
### Acquire lots with custom date/cost basis
Next is to record the acquisition of lots with a custom prior acquisition date and cost basis, as when receiving lots as a gift.
```
2025-02-01 receive 10 AAA acquired on 2021-01-01 at $0.40
2025-02-02 receive 10 AAA acquired on 2022-01-01 at $0.50
```
### Show lots
Then show the lots currently held. They should be:
```
10 AAA @ $0.40 acquired 2021-01-01
10 AAA @ $0.50 acquired 2022-01-01
10 AAA @ $1.10 acquired 2025-01-01 (1)
10 AAA @ $1.20 acquired 2025-01-01 (2)
10 AAA @ $1.20 acquired 2025-01-01 (3)
```
### Dispose of lots, FIFO
Then dispose of some lots in FIFO order.
First 5 AAA (should come from the 2021 lot).\
Then 24 AAA (should come from the 2021, 2022, and 2025 (1) lots).
### Dispose of lots, Spec ID
Then withdraw from two specifically identified lot(s):\
a total of 10 AAA from the 2022 lot and the 2025 (2) lot.
*(TODO: adjust this to run in sequence after the test above)*
### Move lots
Move a whole or fractional number of lots to a new account or broker, by each of the disposal methods.
This combines Dispose and Acquire, and could provide additional automation.
### Enforce or check disposal method
Specify and implement/enforce disposal methods, or allow them to be checked as a separate step.
### Assert lots
Assert that given lots exist in an account (like balance assertions).
### Show lots 2
Show the remaining lots. At this point they should be:
```
1 AAA @ $1.20 acquired 2025-01-01 (2)
10 AAA @ $1.20 acquired 2025-01-01 (3)
```
### Show average cost
Show the lots' average cost basis, both unweighted and weighted.
### Show unrealised gains
Show current unrealised gains/losses, per lot.
### Show realised gains
Record and show the gains/losses realised as part of a lot disposal (per lot ?)
### Show transactions
Show all the transactions in explicit form, if possible.
### Transition to a new file
Begin a new journal file, bringing forward all lots and balances from the previous period, and possibly closing the balances in the old file. An important task for some, eg when data gets large.
## PTA app examples
Here are examples / results of implementing the above Tests in various PTA apps.
Make your browser wide enough, or font small enough, so that the examples don't line-wrap.
Some of the journal entries below may be incomplete (eg lacking real world details like fees).
### Manual subaccounts
Using manual subaccounts to track lots.
This method works in hledger, Ledger, Beancount, or any double entry system.
#### Acquire
```
2025-01-01
assets:broker:usd
assets:broker:aaa:2025-01-01-1_$1.10 10 AAA @ $1.10
2025-01-01
assets:broker:usd
assets:broker:aaa:2025-01-01-2_$1.20 10 AAA @ $1.20
2025-01-01
assets:broker:usd
assets:broker:aaa:2025-01-01-3_$1.20 10 AAA @ $1.20
```
#### Acquire custom
```
2025-02-01
revenues:gifts
assets:broker:aaa:2021-01-01_$0.40 10 AAA @ $0.40
2025-02-02
revenues:gifts
assets:broker:aaa:2022-01-01_$0.50 10 AAA @ $0.50
```
#### Show lots
```
$ hledger bal aaa -YE
Balance changes in 2025:
|| 2025
======================================++========
assets:broker:aaa:2021-01-01_$0.40 || 10 AAA
assets:broker:aaa:2022-01-01_$0.50 || 10 AAA
assets:broker:aaa:2025-01-01-1_$1.10 || 10 AAA
assets:broker:aaa:2025-01-01-2_$1.20 || 10 AAA
assets:broker:aaa:2025-01-01-3_$1.20 || 10 AAA
--------------------------------------++--------
|| 50 AAA
```
#### Dispose FIFO
```
2025-03-01
assets:broker:aaa:2021-01-01_$0.40 -5 AAA @ $0.40
assets:broker:usd $6.55
revenues:gains
2025-03-02
assets:broker:aaa:2021-01-01_$0.40 -5 AAA @ $0.40
assets:broker:aaa:2022-01-01_$0.50 -10 AAA @ $0.50
assets:broker:aaa:2025-01-01-1_$1.10 -9 AAA @ $1.10
assets:broker:usd $31.68
revenues:gains
```
#### Dispose Spec ID
```
2025-03-03
assets:broker:aaa:2025-01-01-1_$1.10 -1 AAA @ $1.10
assets:broker:aaa:2025-01-01-2_$1.20 -9 AAA @ $1.20
assets:broker:usd $13.30
revenues:gains
```
#### Show lots 2
```
$ hledger bal aaa -YE
Balance changes in 2025:
|| 2025
======================================++========
assets:broker:aaa:2021-01-01_$0.40 || 0
assets:broker:aaa:2022-01-01_$0.50 || 0
assets:broker:aaa:2025-01-01-1_$1.10 || 0
assets:broker:aaa:2025-01-01-2_$1.20 || 1 AAA
assets:broker:aaa:2025-01-01-3_$1.20 || 10 AAA
--------------------------------------++--------
|| 11 AAA
```
The above also works with Ledger:
```
$ ledger bal aaa -E --flat
0 assets:broker:aaa:2021-01-01_$0.40
0 assets:broker:aaa:2022-01-01_$0.50
0 assets:broker:aaa:2025-01-01-1_$1.10
1 AAA assets:broker:aaa:2025-01-01-2_$1.20
10 AAA assets:broker:aaa:2025-01-01-3_$1.20
--------------------
11 AAA
```
And here it is in Beancount:
```beancount
option "operating_currency" "USD"
2025-01-01 open Assets:Broker:Aaa:2021-01-01-0-40
2025-01-01 open Assets:Broker:Aaa:2022-01-01-0-50
2025-01-01 open Assets:Broker:Aaa:2025-01-01-1-1-10
2025-01-01 open Assets:Broker:Aaa:2025-01-01-2-1-20
2025-01-01 open Assets:Broker:Aaa:2025-01-01-3-1-20
2025-01-01 open Assets:Broker:Usd
2025-01-01 open Income:Gains
2025-01-01 open Income:Gifts
2025-01-01 *
Assets:Broker:Usd
Assets:Broker:Aaa:2025-01-01-1-1-10 10 AAA @ 1.10 USD
2025-01-01 *
Assets:Broker:Usd
Assets:Broker:Aaa:2025-01-01-2-1-20 10 AAA @ 1.20 USD
2025-01-01 *
Assets:Broker:Usd
Assets:Broker:Aaa:2025-01-01-3-1-20 10 AAA @ 1.20 USD
2025-02-01 *
Income:Gifts
Assets:Broker:Aaa:2021-01-01-0-40 10 AAA @ 0.40 USD
2025-02-02 *
Income:Gifts
Assets:Broker:Aaa:2022-01-01-0-50 10 AAA @ 0.50 USD
2025-03-01 *
Assets:Broker:Aaa:2021-01-01-0-40 -5 AAA @ 0.40 USD
Assets:Broker:Usd 6.55 USD
Income:Gains
2025-03-02 *
Assets:Broker:Aaa:2021-01-01-0-40 -5 AAA @ 0.40 USD
Assets:Broker:Aaa:2022-01-01-0-50 -10 AAA @ 0.50 USD
Assets:Broker:Aaa:2025-01-01-1-1-10 -9 AAA @ 1.10 USD
Assets:Broker:Usd 31.68 USD
Income:Gains
2025-03-03 *
Assets:Broker:Aaa:2025-01-01-1-1-10 -1 AAA @ 1.10 USD
Assets:Broker:Aaa:2025-01-01-2-1-20 -9 AAA @ 1.20 USD
Assets:Broker:Usd 13.30 USD
Income:Gains
```
```
$ bean-query subaccounts.beancount "SELECT account, units(sum(position)), cost(sum(position)) WHERE account ~ 'Aaa' GROUP BY 1 ORDER BY account;"
account units( cost(s
----------------------------------- ------ ------
Assets:Broker:Aaa:2021-01-01-0-40
Assets:Broker:Aaa:2022-01-01-0-50
Assets:Broker:Aaa:2025-01-01-1-1-10
Assets:Broker:Aaa:2025-01-01-2-1-20 1 AAA 1 AAA
Assets:Broker:Aaa:2025-01-01-3-1-20 10 AAA 10 AAA
```
#### Show realised gains
```
$ hledger reg gains --invert
2025-03-01 revenues:gains $4.55 $4.55
2025-03-02 revenues:gains $14.78 $19.33
2025-03-03 revenues:gains $1.40 $20.73
```
```
$ ledger reg gains --invert
25-Mar-01 <Unspecifie.. revenues:gains $4.55 $4.55
25-Mar-02 <Unspecifie.. revenues:gains $14.78 $19.33
25-Mar-03 <Unspecifie.. revenues:gains $1.40 $20.73
```
#### Show transactions
```
$ hledger print -x
2025-01-01
assets:broker:usd $-11.00
assets:broker:aaa:2025-01-01-1_$1.10 10 AAA @ $1.10
2025-01-01
assets:broker:usd $-12.00
assets:broker:aaa:2025-01-01-2_$1.20 10 AAA @ $1.20
2025-01-01
assets:broker:usd $-12.00
assets:broker:aaa:2025-01-01-3_$1.20 10 AAA @ $1.20
2025-02-01
revenues:gifts $-4.00
assets:broker:aaa:2021-01-01_$0.40 10 AAA @ $0.40
2025-02-02
revenues:gifts $-5.00
assets:broker:aaa:2022-01-01_$0.50 10 AAA @ $0.50
2025-03-01
assets:broker:aaa:2021-01-01_$0.40 -5 AAA @ $0.40
assets:broker:usd $6.55
revenues:gains $-4.55
2025-03-02
assets:broker:aaa:2021-01-01_$0.40 -5 AAA @ $0.40
assets:broker:aaa:2022-01-01_$0.50 -10 AAA @ $0.50
assets:broker:aaa:2025-01-01-1_$1.10 -9 AAA @ $1.10
assets:broker:usd $31.68
revenues:gains $-14.78
2025-03-03
assets:broker:aaa:2025-01-01-1_$1.10 -1 AAA @ $1.10
assets:broker:aaa:2025-01-01-2_$1.20 -9 AAA @ $1.20
assets:broker:usd $13.30
revenues:gains $-1.40
```
#### Transition to a new file
hledger's `close` command helps with this.
You copy the opening balances transaction into the new file,
and (if using [clopen method](#clopen+method)) the closing balances transaction to the old file.
```
$ hledger close --clopen --show-costs -e 2026
2025-12-31 closing balances ; clopen:
assets:broker:aaa:2025-01-01-2_$1.20 -1 AAA @ $1.20 = 0 AAA
assets:broker:aaa:2025-01-01-3_$1.20 -10 AAA @ $1.20 = 0 AAA
assets:broker:usd $-16.53 = $0.00
equity:opening/closing balances
2026-01-01 opening balances ; clopen:
assets:broker:aaa:2025-01-01-2_$1.20 1 AAA @ $1.20 = 1 AAA
assets:broker:aaa:2025-01-01-3_$1.20 10 AAA @ $1.20 = 10 AAA
assets:broker:usd $16.53 = $16.53
equity:opening/closing balances
```
As a matter of style you might want to add the `@ COST`s to the opening balances transaction's balance assertions,
which must be done manually for now. hledger ignores costs there, but they make the situation clearer.
```
2026-01-01 opening balances ; clopen:
assets:broker:aaa:2025-01-01-2_$1.20 1 AAA @ $1.20 = 1 AAA @ $1.20
assets:broker:aaa:2025-01-01-3_$1.20 10 AAA @ $1.20 = 10 AAA @ $1.20
assets:broker:usd $16.53 = $16.53
equity:opening/closing balances
```
### Ledger lot notation
#### Acquire
`@ COST` creates lots:
```
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ $1.10
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ $1.20
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ $1.20
```
```
$ ledger bal --lots --flat -E --no-total aaa
10 AAA {$1.1} [2025/01/01] (1)
10 AAA {$1.2} [2025/01/01] (2)
10 AAA {$1.2} [2025/01/01] (3) assets:broker:aaa
```
But it doesn't allow adding a custom `[LOT DATE]` or `(LOT NOTE)`.
So it's better to use the Beancount-like `{COST}` syntax, which does:
```
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA {$1.10} (1)
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA {$1.20} (2)
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA {$1.20} (3)
```
Although this adds whitespace to the report (a display bug):
```
$ ledger -f ledger.ledger bal --lots --flat -E --no-total aaa
10 AAA {$1.1} (1)
10 AAA {$1.2} (2)
10 AAA {$1.2} (3) assets:broker:aaa
```
#### Acquire custom
```
2025-02-01
revenues:gifts
assets:broker:aaa 10 AAA {$0.40} [2021-01-01]
2025-02-02
revenues:gifts
assets:broker:aaa 10 AAA {$0.50} [2022-01-01]
```
#### Show lots
```
$ ledger -f ledger.ledger bal --lots --flat -E --no-total aaa
10 AAA {$0.4} [2021/01/01]
10 AAA {$0.5} [2022/01/01]
10 AAA {$1.1} (1)
10 AAA {$1.2} (2)
10 AAA {$1.2} (3) assets:broker:aaa
```
#### Dispose FIFO
Ledger doesn't apply FIFO automatically; you must specify the lots manually.
And if you added a custom lot date or lot note when acquiring,
you must also specify those when disposing.
Ledger won't report an error if you dispose of a non-existent lot.
```
2025-03-01
assets:broker:aaa -5 AAA {$0.40} [2021-01-01]
assets:broker:usd $6.55
revenues:gains
2025-03-02
assets:broker:aaa -5 AAA {$0.40} [2021-01-01]
assets:broker:aaa -10 AAA {$0.50} [2022-01-01]
assets:broker:aaa -9 AAA {$1.10} (1)
assets:broker:usd $31.68
revenues:gains
```
#### Dispose Spec ID
```
2025-03-03
assets:broker:aaa -1 AAA {$1.10} (1)
assets:broker:aaa -9 AAA {$1.20} (2)
assets:broker:usd $13.30
revenues:gains
```
#### Show lots 2
```
$ ledger -f ledger.ledger bal --lots --flat -E --no-total aaa
1 AAA {$1.20} (2)
10 AAA {$1.20} (3) assets:broker:aaa
```
#### Show transactions
```
$ ledger print
2025/01/01 <Unspecified payee>
assets:broker:usd
assets:broker:aaa 10 AAA {$1.10} (1)
2025/01/01 <Unspecified payee>
assets:broker:usd
assets:broker:aaa 10 AAA {$1.20} (2)
2025/01/01 <Unspecified payee>
assets:broker:usd
assets:broker:aaa 10 AAA {$1.20} (3)
2025/02/01 <Unspecified payee>
revenues:gifts
assets:broker:aaa 10 AAA {$0.40} [2021/01/01]
2025/02/02 <Unspecified payee>
revenues:gifts
assets:broker:aaa 10 AAA {$0.50} [2022/01/01]
2025/03/01 <Unspecified payee>
assets:broker:aaa -5 AAA {$0.40} [2021/01/01]
assets:broker:usd $6.55
revenues:gains
2025/03/02 <Unspecified payee>
assets:broker:aaa -5 AAA {$0.40} [2021/01/01]
assets:broker:aaa -10 AAA {$0.50} [2022/01/01]
assets:broker:aaa -9 AAA {$1.10} (1)
assets:broker:usd $31.68
revenues:gains
2025/03/03 <Unspecified payee>
assets:broker:aaa -1 AAA {$1.10} (1)
assets:broker:aaa -9 AAA {$1.20} (2)
assets:broker:usd $13.30
revenues:gains
```
#### Transition to a new file
?
### Beancount lot notation
#### Acquire
```
option "operating_currency" "USD"
2025-01-01 open Assets:Broker:Aaa AAA "FIFO"
2025-01-01 open Assets:Broker:Usd
2025-01-01 open Income:Gains
2025-01-01 open Income:Gifts
2025-01-01 *
Assets:Broker:Usd
Assets:Broker:Aaa 10 AAA {1.10 USD}
2025-01-01 *
Assets:Broker:Usd
Assets:Broker:Aaa 10 AAA {1.20 USD}
2025-01-01 *
Assets:Broker:Usd
Assets:Broker:Aaa 10 AAA {1.20 USD}
```
#### Acquire custom
```
2025-02-01 *
Income:Gifts
Assets:Broker:Aaa 10 AAA {0.40 USD, 2021-01-01}
2025-02-02 *
Income:Gifts
Assets:Broker:Aaa 10 AAA {0.50 USD, 2022-01-01}
```
#### Show lots
```
$ bean-query beancount.beancount "SELECT account, units(sum(position)) as units, cost_number as cost, cost_date as acquisition_date WHERE account ~ 'Aaa' GROUP BY account, cost_date, currency, cost_currency, cost_number, account_sortkey(account) ORDER BY account_sortkey(account), currency, cost_date"
account units cost acquisitio
----------------- ------ ---- ----------
Assets:Broker:Aaa 10 AAA 0.40 2021-01-01
Assets:Broker:Aaa 10 AAA 0.50 2022-01-01
Assets:Broker:Aaa 10 AAA 1.10 2025-01-01
Assets:Broker:Aaa 20 AAA 1.20 2025-01-01
```
More detail:
```
$ bean-query beancount.beancount "SELECT account, units(sum(position)) as units, cost_number as cost, first(getprice(currency, cost_currency)) as price, cost(sum(position)) as book_value, value(sum(position)) as market_value, safediv((abs(sum(number(value(position)))) - abs(sum(number(cost(position))))), sum(number(cost(position)))) * 100 as unrealized_profit_pct, cost_date as acquisition_date WHERE account ~ 'Aaa' GROUP BY account, cost_date, currency, cost_currency, cost_number, account_sortkey(account) ORDER BY account_sortkey(account), currency, cost_date"
account units cost p book_valu market unrealized_profit_pct acquisitio
----------------- ------ ---- - --------- ------ ------------------------------- ----------
Assets:Broker:Aaa 10 AAA 0.40 4.00 USD 10 AAA 150.0 2021-01-01
Assets:Broker:Aaa 10 AAA 0.50 5.00 USD 10 AAA 100 2022-01-01
Assets:Broker:Aaa 10 AAA 1.10 11.00 USD 10 AAA -9.090909090909090909090909091 2025-01-01
Assets:Broker:Aaa 20 AAA 1.20 24.00 USD 20 AAA -16.66666666666666666666666667 2025-01-01
```
#### Dispose FIFO
With the "FIFO" declaration for the Assets:Broker:Aaa account, above,
you can write just `{}` to dispose in FIFO order. This is very convenient.
"LIFO" and "STRICT" orders are also supported.
```
2025-03-01 *
Assets:Broker:Aaa -5 AAA {}
Assets:Broker:Usd 6.55 USD
Income:Gains
2025-03-02 *
Assets:Broker:Aaa -24 AAA {}
Assets:Broker:Usd 31.68 USD
Income:Gains
```
#### Dispose Spec ID
```
2025-03-03 *
Assets:Broker:Aaa -1 AAA {1.10 USD}
Assets:Broker:Aaa -9 AAA {1.20 USD}
Assets:Broker:Usd 13.30 USD
Income:Gains
```
#### Show lots 2
```
$ bean-query beancount.beancount "SELECT account, units(sum(position)) as units, cost_number as cost, cost_date as acquisition_date WHERE account ~ 'Aaa' GROUP BY account, cost_date, currency, cost_currency, cost_number, account_sortkey(account) ORDER BY account_sortkey(account), currency, cost_date"
account units cost acquisitio
----------------- ------ ---- ----------
Assets:Broker:Aaa 0.40 2021-01-01
Assets:Broker:Aaa 0.50 2022-01-01
Assets:Broker:Aaa 1.10 2025-01-01
Assets:Broker:Aaa 11 AAA 1.20 2025-01-01
```
More detail:
```
$ bean-query beancount.beancount "SELECT account, units(sum(position)) as units, cost_number as cost, first(getprice(currency, cost_currency)) as price, cost(sum(position)) as book_value, value(sum(position)) as market_value, safediv((abs(sum(number(value(position)))) - abs(sum(number(cost(position))))), sum(number(cost(position)))) * 100 as unrealized_profit_pct, cost_date as acquisition_date WHERE account ~ 'Aaa' GROUP BY account, cost_date, currency, cost_currency, cost_number, account_sortkey(account) ORDER BY account_sortkey(account), currency, cost_date"
account units cost p book_valu market unrealized_profit_pct acquisitio
----------------- ------ ---- - --------- ------ ------------------------------ ----------
Assets:Broker:Aaa 0.40 0 2021-01-01
Assets:Broker:Aaa 0.50 0 2022-01-01
Assets:Broker:Aaa 1.10 0 2025-01-01
Assets:Broker:Aaa 11 AAA 1.20 13.20 USD 11 AAA -16.66666666666666666666666667 2025-01-01
```
#### Show transactions
?
#### Transition to a new file
?
### Lotter
Install:
```
$ fossil clone https://src.d10.dev/lotter && cd lotter && go install
```
Lotter reads a subset of Ledger/hledger journal syntax.
Commodity symbols must be text and on the right.
Costs should be written with `@ COST`, not `{COST}`.
Generate lot postings (extra postings which update special accounts representing lots):
```
$ lotter -f a.journal > a.lotter.journal
```
The entries it generates have wrong signs to my eyes; you can use `--invert` to flip them.
Lots purchased on the same day at the same cost will be combined.
#### Acquire
```
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ 1.10 USD
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ 1.20 USD
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ 1.20 USD
```
Generates:
```
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA ; @ 1.10 USD
[Lot::2025/01/01:
[email protected]] -10 AAA ; :BUY: (inventory)
[Lot::2025/01/01:
[email protected]] 11 USD ; :BUY: (basis)
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA ; @ 1.20 USD
[Lot::2025/01/01:
[email protected]] -10 AAA ; :BUY: (inventory)
[Lot::2025/01/01:
[email protected]] 12 USD ; :BUY: (basis)
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA ; @ 1.20 USD
[Lot::2025/01/01:
[email protected]] -10 AAA ; :BUY: (inventory)
[Lot::2025/01/01:
[email protected]] 12 USD ; :BUY: (basis)
```
#### Acquire custom
Custom lot dates, or differing lot cost and transaction cost, are not supported.
```
2025-02-01
revenues:gifts
assets:broker:aaa 10 AAA @ 0.40 USD
2025-02-02
revenues:gifts
assets:broker:aaa 10 AAA @ 0.50 USD
```
Generates:
```
2025-02-01
revenues:gifts
assets:broker:aaa 10 AAA ; @ 0.40 USD
[Lot::2025/02/01:
[email protected]] -10 AAA ; :BUY: (inventory)
[Lot::2025/02/01:
[email protected]] 4 USD ; :BUY: (basis)
2025-02-02
revenues:gifts
assets:broker:aaa 10 AAA ; @ 0.50 USD
[Lot::2025/02/02:
[email protected]] -10 AAA ; :BUY: (inventory)
[Lot::2025/02/02:
[email protected]] 5 USD ; :BUY: (basis)
```
#### Show lots
```
$ hledger -f lotter.lotter bal -YE --invert Lot cur:AAA
Balance changes in 2025:
|| 2025
==============================++========
Lot::2025/01/01:
[email protected] || 10 AAA
Lot::2025/01/01:
[email protected] || 20 AAA
Lot::2025/02/01:
[email protected] || 10 AAA
Lot::2025/02/02:
[email protected] || 10 AAA
------------------------------++--------
|| 50 AAA
```
#### Dispose FIFO
The cost of lots being disposed must be specified.
The lack of support for custom lot dates means you can't implement FIFO in that case.
Eg here, FIFO would require disposing of the 0.40 lot with the 2021 acquisition date first.
```
2025-03-01
assets:broker:aaa -5 AAA @ 0.40 USD
assets:broker:usd 6.55 USD
revenues:gains
2025-03-02
assets:broker:aaa -5 AAA @ 0.40 USD
assets:broker:aaa -10 AAA @ 0.50 USD
assets:broker:aaa -9 AAA @ 1.10 USD
assets:broker:usd 31.68 USD
revenues:gains
```
Generates:
```
2025-03-01
assets:broker:aaa -5 AAA ; @ 0.40 USD
assets:broker:usd 6.55 USD
revenues:gains
[Lot::2025/01/01:
[email protected]] 5 AAA ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -5.5 USD ; :SELL: (basis consumed)
[Lot:Income:short term gain] 3.5 USD ; :GAIN:SHORTTERM: (loss)
2025-03-02
assets:broker:aaa -5 AAA ; @ 0.40 USD
assets:broker:aaa -10 AAA ; @ 0.50 USD
assets:broker:aaa -9 AAA ; @ 1.10 USD
assets:broker:usd 31.68 USD
revenues:gains
[Lot::2025/01/01:
[email protected]] 5 AAA ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -5.5 USD ; :SELL: (basis consumed)
[Lot::2025/01/01:
[email protected]] 10 AAA ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -12 USD ; :SELL: (basis consumed)
[Lot::2025/01/01:
[email protected]] 9 AAA ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -10.8 USD ; :SELL: (basis consumed)
[Lot:Income:short term gain] 11.4 USD ; :GAIN:SHORTTERM: (loss)
```
#### Dispose Spec ID
This too is not really supported in general.
```
2025-03-03
assets:broker:aaa -1 AAA @ 1.10 USD
assets:broker:aaa -9 AAA @ 1.20 USD
assets:broker:usd 13.30 USD
revenues:gains
```
Generates:
```
2025-03-03
assets:broker:aaa -1 AAA ; @ 1.10 USD
assets:broker:aaa -9 AAA ; @ 1.20 USD
assets:broker:usd 13.30 USD
revenues:gains
[Lot::2025/01/01:
[email protected]] 1 AAA ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -1.2 USD ; :SELL: (basis consumed)
[Lot::2025/02/01:
[email protected]] 9 AAA ; :SELL: (inventory consumed)
[Lot::2025/02/01:
[email protected]] -3.6 USD ; :SELL: (basis consumed)
[Lot:Income:short term gain] -7.1 USD ; :GAIN:SHORTTERM: (gain)
```
#### Show lots 2
The quantity of lots remaining is right but their cost bases are wrong:
```
$ hledger -f lotter.lotter bal -YE --invert Lot cur:AAA
Balance changes in 2025:
|| 2025
==============================++========
Lot::2025/01/01:
[email protected] || 0
Lot::2025/01/01:
[email protected] || 0
Lot::2025/02/01:
[email protected] || 1 AAA
Lot::2025/02/02:
[email protected] || 10 AAA
------------------------------++--------
|| 11 AAA
```
#### Show realised gains
Lotter adds extra currency noise that isn't very helpful:
```
$ hledger reg gains --invert
2025-03-01 revenues:gains -5 AAA -5 AAA
6.55 USD 6.55 USD
2025-03-02 revenues:gains -24 AAA -29 AAA
31.68 USD 38.23 USD
2025-03-03 revenues:gains -10 AAA -39 AAA
13.30 USD 51.53 USD
```
And the gains are wrong, the total should be 20.73 not 51.53:
```
$ hledger reg gains --invert cur:USD
2025-03-01 revenues:gains 6.55 USD 6.55 USD
2025-03-02 revenues:gains 31.68 USD 38.23 USD
2025-03-03 revenues:gains 13.30 USD 51.53 USD
```
#### Show transactions
```
$ hledger print -x
2025-01-01
assets:broker:usd -10 AAA
assets:broker:aaa 10 AAA ; @ 1.10 USD
[Lot::2025/01/01:
[email protected]] -10 AAA @@ 11 USD ; :BUY: (inventory)
[Lot::2025/01/01:
[email protected]] 11 USD ; :BUY: (basis)
2025-01-01
assets:broker:usd -10 AAA
assets:broker:aaa 10 AAA ; @ 1.20 USD
[Lot::2025/01/01:
[email protected]] -10 AAA @@ 12 USD ; :BUY: (inventory)
[Lot::2025/01/01:
[email protected]] 12 USD ; :BUY: (basis)
2025-01-01
assets:broker:usd -10 AAA
assets:broker:aaa 10 AAA ; @ 1.20 USD
[Lot::2025/01/01:
[email protected]] -10 AAA @@ 12 USD ; :BUY: (inventory)
[Lot::2025/01/01:
[email protected]] 12 USD ; :BUY: (basis)
2025-02-01
revenues:gifts -10 AAA
assets:broker:aaa 10 AAA ; @ 0.40 USD
[Lot::2025/02/01:
[email protected]] -10 AAA @@ 4 USD ; :BUY: (inventory)
[Lot::2025/02/01:
[email protected]] 4 USD ; :BUY: (basis)
2025-02-02
revenues:gifts -10 AAA
assets:broker:aaa 10 AAA ; @ 0.50 USD
[Lot::2025/02/02:
[email protected]] -10 AAA @@ 5 USD ; :BUY: (inventory)
[Lot::2025/02/02:
[email protected]] 5 USD ; :BUY: (basis)
2025-03-01
assets:broker:aaa -5 AAA ; @ 0.40 USD
assets:broker:usd 6.55 USD
revenues:gains 5 AAA
revenues:gains -6.55 USD
[Lot::2025/01/01:
[email protected]] 5 AAA @@ 2.0 USD ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -5.5 USD ; :SELL: (basis consumed)
[Lot:Income:short term gain] 3.5 USD ; :GAIN:SHORTTERM: (loss)
2025-03-02
assets:broker:aaa -5 AAA ; @ 0.40 USD
assets:broker:aaa -10 AAA ; @ 0.50 USD
assets:broker:aaa -9 AAA ; @ 1.10 USD
assets:broker:usd 31.68 USD
revenues:gains 24 AAA
revenues:gains -31.68 USD
[Lot::2025/01/01:
[email protected]] 5 AAA @ 0.70 USD ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -5.5 USD ; :SELL: (basis consumed)
[Lot::2025/01/01:
[email protected]] 10 AAA @ 0.70 USD ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -12 USD ; :SELL: (basis consumed)
[Lot::2025/01/01:
[email protected]] 9 AAA @ 0.70 USD ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -10.8 USD ; :SELL: (basis consumed)
[Lot:Income:short term gain] 11.4 USD ; :GAIN:SHORTTERM: (loss)
2025-03-03
assets:broker:aaa -1 AAA ; @ 1.10 USD
assets:broker:aaa -9 AAA ; @ 1.20 USD
assets:broker:usd 13.30 USD
revenues:gains 10 AAA
revenues:gains -13.30 USD
[Lot::2025/01/01:
[email protected]] 1 AAA @ 1.19 USD ; :SELL: (inventory consumed)
[Lot::2025/01/01:
[email protected]] -1.2 USD ; :SELL: (basis consumed)
[Lot::2025/02/01:
[email protected]] 9 AAA @ 1.19 USD ; :SELL: (inventory consumed)
[Lot::2025/02/01:
[email protected]] -3.6 USD ; :SELL: (basis consumed)
[Lot:Income:short term gain] -7.1 USD ; :GAIN:SHORTTERM: (gain)
```
### hledger-lots
hledger-lots provides:
- Interactive `buy` and `sell` commands that help add valid
lot acquisition and disposal entries to your journal.
- A `list` command that analyses the journal and prints the current lots.
This expects the disposal entries (at least) to be in the format generated by `sell`;
if you have created some yourself, they will probably break `list`.
Install:\
Current hledger-lots (1.4.2) requires an older hledger version (>=1.18 && <=1.32) in PATH.
```
$ pip install -U hledger-lots
```
The journal file needs to contain metadata like this:
```
#+hledger-lots avg_cost:false, check:true
#+hledger-lots no_desc:
```
Non-text currency symbols are supported, but in generated entries they will be on the right.
#### Acquire
The `buy` command can help generate entries like these, though they are simple.
```
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ 1.10 USD
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ 1.20 USD
2025-01-01
assets:broker:usd
assets:broker:aaa 10 AAA @ 1.20 USD
```
#### Acquire custom
Like Lotter, hledger-lots doesn't support custom lot dates, or differing lot and transaction costs.
```
2025-02-01
revenues:gifts
assets:broker:aaa 10 AAA @ 0.40 USD
2025-02-02
revenues:gifts
assets:broker:aaa 10 AAA @ 0.50 USD
```
#### Show lots
The list command lists lots. Here it aggregates them for some reason:
```
$ hledger-lots view AAA
date price base_cur qtty acct
---------- ------- ---------- ------ -----------------
2025-01-01 1.1000 USD 10 assets:broker:aaa
2025-01-01 1.2000 USD 10 assets:broker:aaa
2025-01-01 1.2000 USD 10 assets:broker:aaa
2025-02-01 0.4000 USD 10 assets:broker:aaa
2025-02-02 0.5000 USD 10 assets:broker:aaa
Info
----
Commodity: AAA
Quantity: 50
Amount: 44.00
Average Cost: 0.8800
Market Data not available
```
#### Dispose FIFO
Entries generated with the `sell` command.
The lack of custom date support means they are not correct FIFO.
```
2025-03-01 Sold AAA ; cost_method:fifo
; commodity:AAA, qtty:5.00, price:1.31
; avg_cost:1.1000, xirr:1.85% annual percent rate 30/360US
assets:broker:usd 6.55 USD
assets:broker:aaa -5.0 AAA @ 1.1 USD ; buy_date:2025-01-01, base_cur:USD
revenues:gains -1.05 USD
2025-03-02 Sold AAA ; cost_method:fifo
; commodity:AAA, qtty:24.00, price:1.32
; avg_cost:1.1792, xirr:0.95% annual percent rate 30/360US
assets:broker:usd 31.68 USD
assets:broker:aaa -5 AAA @ 1.1 USD ; buy_date:2025-01-01, base_cur:USD
assets:broker:aaa -10 AAA @ 1.2 USD ; buy_date:2025-01-01, base_cur:USD
assets:broker:aaa -9.0 AAA @ 1.2 USD ; buy_date:2025-01-01, base_cur:USD
revenues:gains -3.38 USD
```
#### Dispose Spec ID
The `sell` command doesn't support specific identification of lots.
Here's the third disposal, but using the default (pseudo) FIFO:
```
2025-03-03 Sold AAA ; cost_method:fifo
; commodity:AAA, qtty:10.00, price:1.33
; avg_cost:0.4800, xirr:6500.49% annual percent rate 30/360US
assets:broker:usd 13.30 USD
assets:broker:aaa -1 AAA @ 1.2 USD ; buy_date:2025-01-01, base_cur:USD
assets:broker:aaa -9.0 AAA @ 0.4 USD ; buy_date:2025-02-01, base_cur:USD
revenues:gains -8.50 USD
```
#### Show lots 2
```
$ hledger-lots view AAA
date price base_cur qtty acct
---------- ------- ---------- ------ -----------------
2025-01-01 1.1000 USD 0 assets:broker:aaa
2025-01-01 1.2000 USD 0 assets:broker:aaa
2025-01-01 1.2000 USD 0 assets:broker:aaa
2025-02-01 0.4000 USD 1 assets:broker:aaa
2025-02-02 0.5000 USD 10 assets:broker:aaa
Info
----
Commodity: AAA
Quantity: 11
Amount: 5.40
Average Cost: 0.4909
Market Data not available
```
#### Show realised gains
If the test scenario was supported, the total should be 20.73 not 12.93.
```
$ hledger -f hledger-lots.hledger reg --invert -w80 gains
2025-03-01 Sold AAA revenues:gains 1.05 USD 1.05 USD
2025-03-02 Sold AAA revenues:gains 3.38 USD 4.43 USD
2025-03-03 Sold AAA revenues:gains 8.50 USD 12.93 USD
```
#### Show transactions
```
$ hledger print -x
2025-01-01
assets:broker:usd -11.00 USD
assets:broker:aaa 10 AAA @ 1.10 USD
2025-01-01
assets:broker:usd -12.00 USD
assets:broker:aaa 10 AAA @ 1.20 USD
2025-01-01
assets:broker:usd -12.00 USD
assets:broker:aaa 10 AAA @ 1.20 USD
2025-02-01
revenues:gifts -4.00 USD
assets:broker:aaa 10 AAA @ 0.40 USD
2025-02-02
revenues:gifts -5.00 USD
assets:broker:aaa 10 AAA @ 0.50 USD
2025-03-01 Sold AAA ; cost_method:fifo
; commodity:AAA, qtty:5.00, price:1.31
; avg_cost:1.1000, xirr:1.85% annual percent rate 30/360US
assets:broker:usd 6.55 USD
assets:broker:aaa -5.0 AAA @ 1.1 USD ; buy_date:2025-01-01, base_cur:USD
revenues:gains -1.05 USD
2025-03-02 Sold AAA ; cost_method:fifo
; commodity:AAA, qtty:24.00, price:1.32
; avg_cost:1.1792, xirr:0.95% annual percent rate 30/360US
assets:broker:usd 31.68 USD
assets:broker:aaa -5 AAA @ 1.1 USD ; buy_date:2025-01-01, base_cur:USD
assets:broker:aaa -10 AAA @ 1.2 USD ; buy_date:2025-01-01, base_cur:USD
assets:broker:aaa -9.0 AAA @ 1.2 USD ; buy_date:2025-01-01, base_cur:USD
revenues:gains -3.38 USD
2025-03-03 Sold AAA ; cost_method:fifo
; commodity:AAA, qtty:10.00, price:1.33
; avg_cost:0.4800, xirr:6500.49% annual percent rate 30/360US
assets:broker:usd 13.30 USD
assets:broker:aaa -1 AAA @ 1.2 USD ; buy_date:2025-01-01, base_cur:USD
assets:broker:aaa -9.0 AAA @ 0.4 USD ; buy_date:2025-02-01, base_cur:USD
revenues:gains -8.50 USD
```