For querying upcoming maturities, I use a Python script that parses metadata:
from beancount import loader
from datetime import date, timedelta
entries, errors, options = loader.load_file('main.beancount')
upcoming = []
for entry in entries:
if hasattr(entry, 'meta') and 'maturity-date' in entry.meta:
mat_date = entry.meta['maturity-date']
if mat_date <= date.today() + timedelta(days=90):
upcoming.append((mat_date, entry))
for mat_date, entry in sorted(upcoming):
print(f"{mat_date}: {entry.narration}")
Recording Maturity
Here’s my full maturity workflow:
; Bond matures
2027-01-15 * "Treasury 1Y matures"
Assets:Investments:Bonds:Treasury1Y -10,000 TREAS1Y {1.00 USD}
Assets:Bank:Checking 10,000.00 USD ; Principal
Income:Interest:Treasury -200.00 USD ; Final coupon
; Reinvest at the long end
2027-01-15 * "Buy 5-year Treasury (reinvestment)"
Assets:Investments:Bonds:Treasury5Y 10,000 TREAS5Y {1.00 USD}
Assets:Bank:Checking -10,000.00 USD
maturity-date: 2032-01-15
coupon-rate: 4.50%
reinvested-from: "TREAS1Y-2026"
The reinvested-from metadata creates an audit trail.
I want to add a perspective on why bond ladders rather than bond funds.
Bond Ladder vs Bond Fund
Bond fund (like BND):
No maturity date
NAV fluctuates with interest rates
You might sell at a loss
Individual bonds (ladder):
Known maturity date
Hold to maturity = get your principal back
Interest rate changes don’t affect you if you hold
When Ladders Make Sense
Retirement income: Known cash flows on specific dates
Liability matching: Save for a car in 3 years, buy a 3-year bond
Interest rate uncertainty: Lock in rates without NAV risk
My Dashboard Query
I run this to see my ladder at a glance:
SELECT
currency,
meta("maturity-date") AS maturity,
meta("coupon-rate") AS rate,
sum(cost(position)) AS principal
WHERE account ~ 'Assets:Investments:Bonds'
GROUP BY currency, maturity, rate
ORDER BY maturity
This shows me exactly when each rung matures and at what rate.