The Quarterly Rebalancing Ritual: Automating Portfolio Drift Detection with Beancount + Python

A few years ago, I was spending 2-3 hours every quarter manually rebalancing my portfolio across multiple accounts (401k, Roth IRA, taxable brokerage, rental property REIT allocation). I’d open each provider’s website, export balances to a spreadsheet, calculate asset class percentages, and try to figure out which trades would get me back to my 60/40 target without triggering unnecessary capital gains.

It was tedious, error-prone, and honestly not something I looked forward to. Every quarter I’d tell myself “there has to be a better way.”

Then I discovered Beancount. And then I discovered Python. And then I thought: “Wait… I can automate this, can’t I?”

The Journey to Automated Rebalancing

I want to share what I’ve learned building a portfolio rebalancing automation system with Beancount, because I see a lot of folks—especially those pursuing FIRE—struggling with the same quarterly ritual I used to dread.

Why Commercial Tools Weren’t Enough

Tools like Empower (formerly Personal Capital) and Copilot offer automatic rebalancing analysis with beautiful dashboards. But they have fundamental problems for Beancount users:

  1. No integration with Beancount - They want you to link accounts through Plaid, but can’t read your meticulously tracked Beancount transaction history
  2. Black-box calculations - You see outputs but can’t audit the raw data or understand exactly how they calculated drift
  3. Limited tax optimization - They don’t know which accounts are tax-advantaged vs taxable, so recommendations aren’t tax-aware
  4. No historical decisions - You can’t version control your rebalancing strategy or see how your target allocation evolved over time

For someone who values Beancount’s precision and auditability, trusting a proprietary tool felt like a step backward.

The Beancount Foundation

I found two excellent starting points on GitHub:

  • beancount_portfolio_allocation - Reports current portfolio allocations, useful for risk analysis and rebalancing
  • beancount_asset_allocation - Analyzes asset allocation by account using metadata

These tools calculate current allocations from Beancount files, which is great! But they don’t generate rebalancing recommendations, don’t consider tax implications, and don’t optimize across multiple account types.

So I built my own script that extends these ideas.

What I Built: A Tax-Aware Rebalancing Script

My script does four things:

1. Calculates Current Allocations by Asset Class

Using Beancount’s query language, it aggregates positions across all accounts and groups by asset class metadata:

Assets:Investments:Vanguard:VTSAX
  asset_class: "US_Stocks"

Assets:Investments:Fidelity:VBTLX
  asset_class: "US_Bonds"

The query sums up all US_Stocks, US_Bonds, International_Stocks, etc., and calculates percentages.

2. Compares Against Target Allocations

I keep my target portfolio in a YAML config file:

target_allocation:
  US_Stocks: 48%
  International_Stocks: 12%
  US_Bonds: 35%
  International_Bonds: 5%

This lives in version control, so I can track how my risk tolerance changes over time (I was 80/20 stocks/bonds in my 30s, now 60/40 at 42).

3. Generates Tax-Aware Rebalancing Recommendations

This is where it gets interesting. The script:

  • Prioritizes tax-advantaged accounts (401k, IRA) for rebalancing—no capital gains to worry about
  • Identifies tax-loss harvesting opportunities in taxable accounts—flag losses that could offset gains
  • Considers transaction costs - Only recommends rebalancing if drift exceeds 5% (configurable threshold)
  • Outputs specific trades like “Sell $2,500 of VTSAX in Fidelity 401k, buy $2,500 of VBTLX”

4. Tracks Decisions Over Time

Every rebalancing run is logged with a timestamp. I can see: “On 2024-03-15, portfolio was 67% stocks / 33% bonds, rebalanced back to 60/40 target.”

This historical record helps me understand if I’m rebalancing too often (wasting transaction costs) or not often enough (letting drift get out of hand).

Lessons Learned (So You Don’t Make My Mistakes)

Start simple. My first version just calculated drift—didn’t generate recommendations. I ran it for 3 months alongside my manual spreadsheet to verify accuracy. Don’t try to automate everything on day one.

Metadata is your friend. Tagging each investment with asset_class, tax_treatment (taxable vs tax-deferred), and expense_ratio makes queries incredibly powerful.

Tax-loss harvesting is complex. The wash-sale rule (can’t buy “substantially identical” security within 30 days before or after selling) is hard to automate safely. My script flags opportunities for manual review, but I approve before executing.

Drift tolerance matters. I use 5% drift as my rebalancing threshold. Lower thresholds mean more frequent rebalancing (higher transaction costs). Higher thresholds mean more risk drift. Find what works for your situation.

Version control your strategy. Storing target allocations in a config file (not hardcoded in scripts) means you can track how your investment philosophy evolves. It’s fascinating to look back and see how risk tolerance changes.

Questions for the Community

I’m curious how others are handling portfolio rebalancing with Beancount:

  1. Are you automating rebalancing calculations, or still doing it manually? If manual, what’s your process?

  2. What drift tolerance do you use? 3%? 5%? 10%? How did you choose that number?

  3. For FIRE folks: do you rebalance more with contributions or by selling? Using new money to rebalance avoids transaction costs.

  4. Has anyone automated tax-loss harvesting detection? I’d love to compare approaches to handling the wash-sale rule.

My script is open source on GitHub (I’ll share the link once I clean up the README). It’s not perfect, but it’s reduced my quarterly rebalancing from 2+ hours to about 15 minutes of reviewing recommendations.

For those tracking detailed portfolios in Beancount: what’s your biggest rebalancing pain point?

This is exactly what I’ve been looking for! I’m currently at the “manually exporting to spreadsheets every quarter” stage you described, and it’s driving me crazy.

My Current Pain Points:

  1. Multi-provider headache - I’ve got Vanguard 401k, Fidelity Roth IRA, Schwab taxable brokerage, and an HSA through Lively. Each provider has different export formats. Normalizing the data takes 30 minutes alone.

  2. Asset class mapping is manual - Every quarter I have to remember “VTSAX = US Stocks, VGTSX = International Stocks, VBTLX = Bonds…” I keep a lookup table in my spreadsheet but it’s fragile.

  3. No tax awareness - My spreadsheet just says “you need to move 5% from stocks to bonds” but doesn’t tell me which account to rebalance in. So I end up triggering capital gains in my taxable account when I should’ve rebalanced in my 401k.

  4. Drift detection is reactive - I only check once per quarter. Sometimes I discover I’ve been drifting for 2 months and missed an opportunity to rebalance during market corrections.

Questions About Your Implementation:

When you say “Use Beancount query language to calculate allocations”—are you using the built-in bean-query tool, or writing custom Python that reads the Beancount file directly? I’m comfortable with Python but haven’t explored Beancount’s query capabilities much.

For the YAML config file with target allocations—do you version control it separately from your main Beancount file? I’m thinking about how to track allocation changes over time (like you said, 80/20 in 30s, 60/40 at 42). Being able to git log my risk tolerance evolution sounds amazing.

My Drift Tolerance:

I’ve been using 5% drift as my threshold too, mostly because transaction costs in my 401k are zero (Vanguard index funds, no commissions), but my taxable account charges $0 trades only if I use Schwab ETFs. If I need to rebalance using Vanguard mutual funds, there’s a $50 transaction fee. So 5% drift needs to justify at least $50 in fees.

Rebalancing with Contributions:

This is brilliant and I haven’t been doing it systematically. I contribute $2,000/month to my Roth IRA and $1,500/month to my 401k. That’s $42,000/year in new money I could be using to rebalance without selling anything. I just need to calculate “which asset class is underweight” and direct contributions there.

Has your script automated this? Like: “You’re contributing $2,000 this month. Allocate $1,200 to VTSAX (US stocks are 3% underweight) and $800 to VGTSX (international stocks are 2% underweight).”

Tax-Loss Harvesting:

The wash-sale rule terrifies me because it applies across all accounts—including my spouse’s accounts. I’ve been avoiding tax-loss harvesting entirely because I’m worried about accidentally triggering it. Your approach of “flag opportunities for manual review” seems like the right balance.

I’d love to see your code when you share it! This could save me hours every quarter and actually make me confident I’m rebalancing optimally.

One More Question: How do you handle international stocks with currency conversions? My Beancount file tracks everything in USD, but I hold some Canadian ETFs (VCN.TO) that report in CAD. Does your script convert to USD for allocation calculations?

As a CPA, I love seeing this level of tax awareness in portfolio rebalancing! Too many clients rebalance reactively without considering the tax consequences, and it costs them thousands of dollars unnecessarily.

Tax-Advantaged vs Taxable Account Priorities

Your prioritization is exactly right:

  1. Tax-advantaged accounts first (401k, IRA) - Rebalance freely here. No capital gains, no wash-sale rule concerns, full flexibility.
  2. Taxable accounts second - Only if you can’t achieve target allocation in tax-advantaged accounts, or if there’s a tax-loss harvesting opportunity.
  3. HSAs last - These grow tax-free forever if used for medical expenses. Don’t touch unless absolutely necessary.

The Wash-Sale Rule Complexity

Fred’s concern about the wash-sale rule is valid—it’s more complex than most people realize:

  • 61-day window: 30 days before + day of sale + 30 days after = 61 total days you can’t buy “substantially identical” securities
  • Applies across ALL accounts: Your taxable account, your IRA, your Roth IRA, your spouse’s accounts, even 529 plans for your kids
  • “Substantially identical” is murky: The IRS has never clearly defined this. Selling VOO (Vanguard S&P 500 ETF) and buying SPY (SPDR S&P 500 ETF) is probably a wash-sale violation, even though they’re technically different securities tracking the same index.

Safe Tax-Loss Harvesting Strategy:

The conservative approach I recommend to clients:

  1. Sell the losing position (e.g., VTI - Vanguard Total Stock Market)
  2. Wait 31 days (to clear the wash-sale window)
  3. Buy a similar-but-not-identical fund (e.g., ITOT - iShares Total Stock Market, or SCHB - Schwab Total Stock Market)
  4. Optionally: after another 31 days, swap back to your original fund (if you prefer it)

Why This Matters:

If you trigger the wash-sale rule, the loss is disallowed and added to the cost basis of the replacement security. You don’t lose the loss permanently, but you defer it. This defeats the purpose of harvesting losses in the current tax year.

Cost Basis Tracking Requirements

One thing to emphasize: the IRS requires tracking cost basis for every lot (unless you use default FIFO - first in, first out). Beancount’s transaction history is PERFECT for this because:

  • Every purchase is timestamped with date and price
  • You can query: “Show me all VTSAX lots purchased more than 1 year ago that are currently underwater”
  • You can specify which lots to sell (avoiding short-term capital gains rates on lots held < 1 year)

Rebalancing with Contributions (Tax-Free Rebalancing)

This is my favorite strategy and I recommend it to every client:

Instead of selling existing positions to rebalance, use new contributions to buy underweight asset classes. This is “free” rebalancing—no transaction costs, no capital gains, no wash-sale concerns.

Example: Your portfolio is 65% stocks / 35% bonds (target: 60/40). You’re contributing $2,000 this month to your Roth IRA. Instead of buying your usual VTSAX, put $1,400 into VBTLX (bonds) and $600 into VTSAX (stocks). You’ve moved closer to 60/40 without selling anything.

Tax-Loss Harvesting ROI Example:

Concrete numbers help illustrate the benefit:

  • You have a $10,000 unrealized loss in VTSAX (bought at $120, now trading at $110)
  • You sell VTSAX, harvesting the $10,000 loss
  • You’re in the 24% federal tax bracket + 5% state tax bracket = 29% total
  • Tax savings: $10,000 × 29% = $2,900

That’s real money! But only if you don’t trigger the wash-sale rule.

Professional Use Case:

If you build this script and make it robust, I’d love to test it for my clients. Many small business owners have complex portfolios across multiple accounts, and a Beancount-based rebalancing tool that’s transparent and auditable would be incredibly valuable.

Commercial tools like Betterment and Wealthfront offer “tax-loss harvesting+” but charge 0.25-0.50% AUM annually. For a $500K portfolio, that’s $1,250-2,500/year. If we could replicate that functionality with open-source Beancount tools, it could save clients thousands.

Two Warnings:

  1. Specific identification of lots: If you’re selling specific lots (not default FIFO), you MUST notify your broker in writing before the settlement date. Document this in your Beancount transaction metadata.

  2. State tax considerations: Some states (California, New York) have different capital gains treatment than federal. Make sure your script accounts for state-level tax implications if relevant.

Looking forward to seeing your code! This could be a game-changer for the plain text accounting community.

Okay, this thread just blew my mind. I’ve been using Beancount for 2 months and I’m still struggling to import transactions consistently—meanwhile you all are building automated tax-aware rebalancing systems.

But as a software developer, I’m excited about the architecture here because it feels like portfolio CI/CD:

  • Continuous monitoring: Script runs quarterly (or on-demand) to detect drift
  • Automated testing: Compare against target allocation, flag when thresholds are exceeded
  • Code review phase: Human reviews recommendations before executing trades
  • Version control: Track how allocation strategy evolves over time (git log for investment philosophy!)

That’s the same pattern we use in DevOps: automate the detection and recommendation, but require human approval for deployment.

Beginner Questions (Sorry if These Are Obvious):

  1. Account structure for asset classes: How do you organize Beancount accounts to make asset class calculations easy? Is it something like:
Assets:Investments:Vanguard:VTSAX
  asset_class: "US_Stocks"

Assets:Investments:Fidelity:VBTLX
  asset_class: "US_Bonds"

Or do you use a different metadata scheme?

  1. Beancount query language capabilities: Can the query language calculate percentages across accounts automatically? Like: “SELECT asset_class, SUM(balance) / TOTAL(balance) AS percentage FROM accounts”?

I’m comfortable with SQL but haven’t explored Beancount’s query features yet. Is it similar?

  1. Plugin vs standalone script: Is this a Beancount plugin that hooks into Fava, or a standalone Python script that reads the .beancount file? I’m trying to understand the architecture.

The Version Control Angle (What Excites Me Most):

As a developer, the ability to git log my rebalancing decisions and see my risk tolerance evolution is incredible:

git log target_allocation.yaml

commit 2024-01-15: Reduced stocks to 60% (was 70%) - market volatility concerns
commit 2023-06-20: Increased international stocks to 15% (was 10%) - diversification
commit 2022-03-10: Aggressive 80/20 allocation at age 32

That’s a documented history of your investment philosophy! Commercial tools don’t give you that.

Automation Opportunities I’m Thinking About:

If this script can detect drift and recommend trades, could it also:

  • Send a notification when drift exceeds threshold? (email, Slack, SMS)
  • Generate a monthly report showing allocation trends over time?
  • Automatically export recommendations to a format brokers accept (CSV import for trade execution)?
  • Integrate with FIRE calculators to show “if you rebalance now, your retirement date shifts by X months”?

Maybe I’m getting ahead of myself (you did warn about over-engineering early!), but as a developer, I see so much potential.

My Current Spreadsheet Hell:

Right now I’m doing this manually in Google Sheets:

  1. Export each account’s holdings (4 different providers, 4 different CSV formats)
  2. Manually normalize column names (one provider calls it “Ticker”, another “Symbol”, another “Fund Name”)
  3. Look up asset classes in a separate tab (VTSAX = US Stocks, etc.)
  4. Calculate percentages with formulas
  5. Compare against target allocation (hardcoded in another tab)
  6. Figure out which account to rebalance in (no tax awareness, just guessing)

It takes 90 minutes every quarter and I KNOW I’m making mistakes. Your automated approach would save me hours and probably catch errors I’m missing.

Learning Request:

When you share the GitHub repo, would you consider including:

  • Example .beancount file structure (sanitized, of course)
  • Example target_allocation.yaml format
  • Sample query outputs so beginners can see what the script produces

I’d love to contribute once I understand the codebase better. Even if it’s just documentation or test cases, I want to help make this accessible to other Beancount beginners.

One Last Thought:

The DevOps world went through this same evolution: manual deployments → automated scripts → full CI/CD pipelines. Personal finance is going through the same transformation. Plain text accounting + version control + automation = the future of financial management.

Thanks for sharing your journey! This is inspiring me to level up my Beancount skills.