The Three-Fund Portfolio in Beancount: Simple FIRE Investing

If there’s one investment approach that dominates the FIRE community, it’s the three-fund portfolio: US stocks, international stocks, and bonds. Simple, diversified, low-cost.

Let me show you how I track mine in Beancount.

The Three-Fund Philosophy

The idea: instead of picking individual stocks or timing the market, own the entire market through low-cost index funds.

A typical allocation:

  • 60% US Total Stock Market (VTI, VTSAX, FSKAX)
  • 20% International Stocks (VXUS, VTIAX, FTIHX)
  • 20% US Bonds (BND, VBTLX, FXNAX)

Why this works:

  • Diversified across thousands of companies
  • Low expense ratios (0.03-0.05%)
  • Tax-efficient in taxable accounts
  • Requires minimal maintenance

My Beancount Setup

Here’s how I structure my investment accounts:

; Define the commodities
2020-01-01 commodity VTI
  name: "Vanguard Total Stock Market ETF"
  asset-class: "US Equity"
  
2020-01-01 commodity VXUS  
  name: "Vanguard Total International Stock ETF"
  asset-class: "International Equity"
  
2020-01-01 commodity BND
  name: "Vanguard Total Bond Market ETF"
  asset-class: "Fixed Income"

; Account structure
2020-01-01 open Assets:Investments:Brokerage:VTI
2020-01-01 open Assets:Investments:Brokerage:VXUS
2020-01-01 open Assets:Investments:Brokerage:BND
2020-01-01 open Assets:Investments:401k:VTI
2020-01-01 open Assets:Investments:401k:VXUS
2020-01-01 open Assets:Investments:IRA:BND

Tracking Allocation

The key query—am I at my target allocation?

SELECT 
  root(account, 3) as asset_class,
  sum(cost(position)) as cost_basis,
  sum(value(position)) as market_value
WHERE account ~ "Assets:Investments"
GROUP BY root(account, 3)

I also use commodity metadata to categorize:

SELECT 
  meta("asset-class") as class,
  sum(value(position)) as total
WHERE account ~ "Assets:Investments"
GROUP BY meta("asset-class")

My Current Allocation

Being transparent:

  • US Stocks: 62% (target: 60%)
  • International: 18% (target: 20%)
  • Bonds: 20% (target: 20%)

Slightly overweight US after 2025’s strong performance. I rebalance when any category drifts more than 5% from target.

Price Updates

For accurate market values, you need current prices. I use bean-price with Yahoo Finance:

bean-price --no-cache finances.beancount >> prices.beancount

My prices.beancount file looks like:

2026-02-12 price VTI 295.42 USD
2026-02-12 price VXUS 62.18 USD
2026-02-12 price BND 72.35 USD

Asset Location Strategy

Not all accounts are equal. I place assets strategically:

  • Taxable brokerage: Tax-efficient funds (VTI, VXUS) with low turnover
  • 401k: Anything (often international funds for foreign tax credit optimization)
  • IRA: Bonds and REITs (less tax-efficient)

This maximizes after-tax returns without changing my overall allocation.

Questions

  1. What’s your target allocation?
  2. How often do you rebalance?
  3. Any tips for automating price fetching in Beancount?

Love this breakdown! The three-fund portfolio is what finally got me to stop overthinking my investments.

For price automation, I run this cron job weekly:

# Every Sunday at 6pm
0 18 * * 0 cd ~/finances && bean-price main.beancount >> prices.beancount 2>&1

Works great for weekly rebalancing checks. If you need daily prices, just change the schedule.

One tip I learned the hard way: always review the prices before committing. Sometimes the API returns weird data (stock splits, data errors). I diff the new prices against last week’s before adding them to git:

tail -5 prices.beancount  # check what bean-price added
git diff prices.beancount # review changes

For asset location, I’ve taken a slightly different approach. Instead of separate accounts per fund, I use one account per brokerage but tag transactions:

2026-01-15 * "Buy VTI"
  Assets:Investments:Fidelity  10 VTI {295.00 USD}
    asset-class: "us-equity"
  Assets:Checking

Then I can query by account (for tax lots) or by tag (for allocation). Both approaches work—just personal preference.

My allocation: 70/15/15 (US/Intl/Bonds). More aggressive on US because I’m still 15+ years from retirement.

This is exactly what I needed! I’ve been paralyzed trying to pick individual stocks and this simplifies everything.

Quick clarifying question: how do you handle lots and cost basis in Beancount?

When I buy VTI multiple times at different prices, do I need to track each purchase separately? Like:

2026-01-01 * "Buy VTI"
  Assets:Investments:Brokerage  5 VTI {280.00 USD}
  Assets:Checking

2026-02-01 * "Buy VTI"  
  Assets:Investments:Brokerage  5 VTI {295.00 USD}
  Assets:Checking

And then when I sell, how do I specify which lot?

I’m coming from spreadsheets where I just tracked total cost and total shares. Beancount’s lot tracking is more precise but I want to make sure I’m doing it right from the start.

Also—is there a good guide for understanding the beancount cost basis syntax? The {...} stuff confuses me a bit.

@newbie_accountant Yes, you’ve got the buying part right! Each purchase creates a separate lot with its own cost basis.

For selling, you specify which lot using the cost in curly braces:

; Sell the shares bought at $280
2026-06-01 * "Sell VTI"
  Assets:Investments:Brokerage  -5 VTI {280.00 USD} @ 310.00 USD
  Assets:Checking  1550.00 USD
  Income:Investments:Gains  -50.00 USD

The {280.00 USD} says “sell the lot purchased at $280”. The @ 310.00 USD is the current sale price.

If you don’t care which lot, you can use FIFO (first in, first out):

; Sells oldest lot first
2026-06-01 * "Sell VTI"
  Assets:Investments:Brokerage  -5 VTI {} @ 310.00 USD
  ...

For tax loss harvesting, I specifically select lots that have losses:

; Find lots with unrealized losses
SELECT account, units(position), cost(position), value(position)
WHERE account ~ "Investments"
  AND cost(position) > value(position)

Then sell those specific lots at year-end to offset gains.

The Beancount documentation on inventory and cost basis is good: Beancount Language Syntax - Beancount Documentation

But honestly, the best way to learn is to enter a few transactions and run bean-check to see if they balance!