Skip to main content

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

inventories

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

  1. 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
  2. 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:

  1. 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 USD

    Here, we buy 50 units of STOCK at a per-unit cost of $25.00 USD. This creates a lot in the Assets:Invest:STOCK account.

  2. 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 USD

    In 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 with FIFO or LIFO 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:

  1. Price Annotation (Conversion): Use @ to convert from one currency to another.

    Assets:Forex     1000 USD @ 0.85 EUR
  2. Cost Basis (Acquisition): Use {} when buying an asset to establish its cost.

    Assets:Invest    10 STOCK {100.00 USD}
  3. 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 100.00each,atasalepriceof100.00 each, at a sale price of 105.00 each.

Price Usage Rules

  1. Price annotations (@) do not affect which lot is booked. Lot matching is handled exclusively by the cost basis ({}) and the account's booking method.
  2. 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

  1. 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 here

    Avoid mixing different stocks or funds in the same account, as it complicates inventory management.

  2. 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%
  1. 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 in STRICT mode.