Inventory Management in Beancount
Beancount's inventory system is a powerful feature for tracking assets that are bought and sold over time, such as stocks, mutual funds, or foreign currencies. It allows for precise tracking of cost basis, which is essential for calculating capital gains and understanding portfolio performance. This tutorial covers the core mechanics of managing inventories in your ledger.
Core Concepts
At its heart, inventory management revolves around tracking positions. A "position" is simply an amount of a commodity held in an account. Beancount distinguishes between two fundamental types of positions.
Position Types
-
Simple Position (No Cost): This is a standard balance posting. It represents an amount of a commodity without any associated acquisition cost. It's suitable for cash or simple balance assertions.
Assets:Bank:Checking 100.00 USD
-
Position with Cost Basis: This type of position includes not only the number of units and the commodity but also the cost at which it was acquired. This is the foundation of inventory tracking. The cost is specified within curly braces
{}
.Assets:Invest:VTSAX 10 VTSAX {100.00 USD, "lot-1"}
In this example, we hold 10 units of
VTSAX
. Each unit was acquired at a cost of $100.00 USD. This specific batch of shares is identified as a "lot."
Inventory Operations
There are two primary operations you can perform on an inventory:
-
Augmentations (Adding to inventory): When you buy a commodity, you augment your inventory. You create a new lot with a specific number of units and a cost basis.
2024-01-15 * "Buy shares"
Assets:Invest:STOCK 50 STOCK {25.00 USD, "lot-1"}
Assets:Bank:Checking -1250.00 USDHere, we buy 50 units of
STOCK
at a per-unit cost of $25.00 USD. This creates a lot in theAssets:Invest:STOCK
account. -
Reductions (Removing from inventory): When you sell a commodity, you reduce your inventory. You must specify which lot you are selling from. This is done by providing matching information in the curly braces.
2024-01-20 * "Sell shares"
Assets:Invest:STOCK -25 STOCK {25.00 USD}
Assets:Bank:Checking 625.00 USDIn this transaction, we are selling 25 units of
STOCK
from the lot that was purchased at $25.00 USD per unit.
Booking Methods
When you reduce an inventory, Beancount needs a rule to decide which specific lot to pull from if multiple lots match or if the match is ambiguous. This rule is called the "booking method." You can set a default method for your entire file or specify one for each account.
1. STRICT (Default)
The STRICT
method is the default and safest booking method. It enforces explicit and unambiguous matching.
2024-01-01 open Assets:Invest:STOCK "STRICT"
- Requires Exact Lot Match: You must provide enough information in the reduction posting (
{...}
) to uniquely identify the lot being sold. - Errors on Ambiguous Matches: If the information provided matches multiple lots, Beancount will raise an error, forcing you to be more specific.
- Exception: If a reduction posting exactly removes the total number of units held in an account, an empty cost specifier (
{}
) is allowed.
2. FIFO (First-In, First-Out)
The FIFO
method automatically books reductions against the oldest available lots first.
2024-01-01 open Assets:Invest:STOCK "FIFO"
- Automatic Resolution: It resolves ambiguity by selecting the oldest matching lots.
- Chronological Matching: This is a common accounting method where you assume you are selling the assets you have held the longest. This is the required method for tax purposes in many countries.
3. LIFO (Last-In, First-Out)
The LIFO
method is the opposite of FIFO. It books reductions against the newest available lots first.
2024-01-01 open Assets:Invest:STOCK "LIFO"
- Reverse Chronological Order: It selects the most recently acquired lots that match the reduction criteria.
- Tax Optimization: In some jurisdictions, this method can be used for tax optimization, for instance, by selling the shares with the highest cost basis first to minimize capital gains.
4. NONE
The NONE
method disables lot matching entirely.
2024-01-01 open Assets:Invest:STOCK "NONE"
- No Lot Matching: Beancount does not attempt to match reductions to augmentations.
- Allows Mixed Signs: This allows an account to hold both positive and negative balances of the same commodity simultaneously. This behavior is similar to how the Ledger CLI tool handles commodities.
Lot Specification
A "lot" is a specific block of a commodity acquired at a particular time and price. When you create or reduce a position, you can specify its lot attributes in detail.
Full Specification
When augmenting an inventory (buying), you can specify up to three attributes for the lot:
Assets:Invest:STOCK 10 STOCK {
100.00 USD, # Cost basis (per-unit cost)
2024-01-15, # Acquisition date
"lot-identifier" # A unique string label
}
While all three are optional, providing at least the cost basis is standard practice.
Matching Methods
When reducing an inventory (selling), you use the same syntax to specify which lot(s) to sell from.
-
Match by cost: This is the most common method.
Assets:Invest:STOCK -5 STOCK {100.00 USD}
-
Match by date: If costs are identical, you can disambiguate using the acquisition date.
Assets:Invest:STOCK -5 STOCK {2024-01-15}
-
Match by label: Labels provide a foolproof way to identify a lot.
Assets:Invest:STOCK -5 STOCK {"lot-identifier"}
-
Match any lot: An empty set of braces
{}
will match any available lot. This is often used withFIFO
orLIFO
booking, where the specific lot is selected automatically.Assets:Invest:STOCK -5 STOCK {}
Price Handling
It is crucial to understand the difference between cost basis ({}
) and price (@
). They serve different purposes and are not interchangeable.
Price vs Cost
{cost}
: Defines the acquisition cost of an asset. It is part of the inventory lot itself and is used for booking reductions and calculating capital gains.@ price
: An annotation that records a market price at the time of a transaction. It is used for currency conversions or to note the market value on a particular date.
Here are the three scenarios:
-
Price Annotation (Conversion): Use
@
to convert from one currency to another.Assets:Forex 1000 USD @ 0.85 EUR
-
Cost Basis (Acquisition): Use
{}
when buying an asset to establish its cost.Assets:Invest 10 STOCK {100.00 USD}
-
Both (Sale with Price Record): When selling an asset, use
{}
to identify the lot being sold and@
to record the sale price. This allows for automated capital gains calculation.Assets:Invest -10 STOCK {100.00 USD} @ 105.00 USD
This entry sells 10
STOCK
from the lot that cost 105.00 each.
Price Usage Rules
- Price annotations (
@
) do not affect which lot is booked. Lot matching is handled exclusively by the cost basis ({}
) and the account's booking method. - The
@
symbol is used only for:
- Currency conversions.
- Recording the market value of an asset at the time of a transaction.
- Providing the sale price for capital gains calculations.
Configuration
You can configure booking methods globally or on a per-account basis.
Global Booking Method
You can set a default booking method for your entire Beancount file using the option
directive.
option "booking_method" "STRICT"
The available options are "STRICT"
, "FIFO"
, "LIFO"
, and "NONE"
.
Per-Account Override
It's often useful to have different methods for different accounts. For example, you might want FIFO
for a retirement account but STRICT
for a taxable brokerage account to ensure you are selling specific tax lots. You can set the booking method when you open the account.
2024-01-01 open Assets:Retirement:401K "FIFO"
2024-01-01 open Assets:Taxable:Stock "STRICT"
Best Practices
-
Inventory Organization: To keep your ledger clean and simple, it is highly recommended to use separate accounts for each unique commodity you hold.
# GOOD: Separate accounts by commodity
Assets:Invest:VTSAX ; Only VTSAX positions here
Assets:Invest:VFIAX ; Only VFIAX positions hereAvoid mixing different stocks or funds in the same account, as it complicates inventory management.
-
Lot Management:
- Use meaningful labels for lots, especially for specific transactions like tax-loss harvesting or employee stock grants.
Assets:Invest:STOCK 10 STOCK {100.00 USD, "tax-loss-harvest-2024"}
- Document your trades with comments. This makes your ledger easier to read and understand later.
Assets:Invest:STOCK -10 STOCK {100.00 USD} @ 110.00 USD ; Gain: 10%
- Debugging: If you encounter errors or unexpected behavior, Beancount provides tools to inspect the state of your inventory.
-
Examine Inventory State: The
bean-doctor
tool can show you the exact state of all inventories at any point in your file.Replace
<LINENO>
with the line number just after a transaction to see its effect. -
Verify Lot Matching: The
bean-check
tool validates your entire file. It will catch any booking errors, such as ambiguous lot matches inSTRICT
mode.