Automating Tax-Loss Harvesting: Tracking Wash Sales Across Accounts with Beancount

As we approach the end of 2026, I’m staring at a portfolio that’s down about 12% for the year. Not fun—but I keep hearing that this is actually an opportunity for tax-loss harvesting (TLH). The idea sounds simple: sell losers, use those losses to offset my capital gains, save on taxes. Easy, right?

Except it’s not.

The more I dig into this, the more I realize the wash sale rule turns tax-loss harvesting into a 61-day minefield. Sell a stock at a loss and accidentally buy it back within 30 days (or bought it 30 days before the sale)? Boom—loss disallowed. And here’s the kicker: the rule applies across all my accounts, including my IRA, and even my spouse’s accounts.

The Manual Tracking Nightmare

I have accounts at three different brokerages: Vanguard for retirement, Fidelity for taxable, and Robinhood for… well, the less we say about my meme stock phase, the better. Each broker only tracks wash sales within their own accounts for the same CUSIP. That means I’m responsible for tracking potential wash sales across all of them.

Spreadsheets? Tried it. Within a week I was drowning in tabs, formulas, and purchase date tracking. I missed a cross-account wash sale and only caught it when preparing my taxes. Not good.

Why Beancount Might Be the Answer

This is where I think Beancount’s strength really shines: aggregate view of all investments in one unified ledger. I can query across every brokerage account, track cost basis with STRICT booking, and—theoretically—identify wash sale risks before they happen.

I’ve heard about the fava_investor tlh module that can help automate this, and Red Street’s tax loss harvester plugin looks promising. But I’m still figuring out the practical workflow.

Questions for the Community

Here’s where I need your wisdom:

  1. Has anyone successfully automated wash sale tracking with Beancount? What’s your workflow?

  2. How do you structure your accounts to monitor the 61-day window? Do you use metadata to tag purchase dates? Custom queries?

  3. What’s the best practice for STRICT booking with cost basis? I understand the concept but not the implementation details for tax-loss harvesting specifically.

  4. How do you handle “substantially identical” securities? Like, if I sell VOO (Vanguard S&P 500 ETF), is buying SPY (SPDR S&P 500 ETF) within 30 days a wash sale? They track the same index but are different securities.

  5. Any BQL query examples for identifying TLH opportunities? I’d love to see working code I can adapt.

I know this is a complex topic, but if we can crack the code on Beancount-based TLH automation, it could be a game-changer for anyone managing their own investments in plain text.

Looking forward to your insights—especially if you’ve already solved this problem!


Sources for wash sale info:

Great topic, Fred! As a former IRS auditor, I want to add some important regulatory context that every Beancount user needs to understand about wash sales.

The Broker Reporting Gap

Here’s what most people don’t realize: Your broker is only required to track and report wash sales within the same account and the same CUSIP number. That’s it.

If you sell AAPL at a loss in your Fidelity taxable account and buy it back in your Vanguard IRA within 30 days, Fidelity won’t flag it. Vanguard won’t flag it. But the IRS expects you to catch it and report it correctly.

And here’s the really painful part: When you trigger a wash sale between a taxable account and an IRA, the loss is permanently disallowed. It doesn’t get added to your IRA cost basis because IRAs don’t have cost basis tracking. That loss is just… gone. Forever.

2026 Compliance Changes

Starting this year, we’re seeing new IRS reporting requirements for digital assets (Form 1099-DA). This adds another layer of complexity for anyone tracking crypto alongside traditional securities. If you’re using Beancount to track both, make sure you’re distinguishing between asset classes in your account structure.

Practical Beancount Recommendations

From a compliance perspective, here’s what I recommend:

1. Use Metadata for Purchase Dates

Every time you acquire a position, add metadata with the purchase date:

2026-01-15 * "Buy VOO"
  Assets:Fidelity:Taxable:VOO   10 VOO {350.00 USD}
    purchase-date: "2026-01-15"
  Assets:Fidelity:Taxable:Cash

This lets you query for positions bought within the last 30 days before considering a sale.

2. Tag Substantially Identical Securities

The IRS defines “substantially identical” as securities that are virtually the same. There’s no bright-line rule, but here’s my guidance:

  • VOO vs SPY: Gray area. IRS hasn’t issued clear guidance, but they track the same index. I’d be cautious.
  • Individual stocks: If it’s the same company, it’s identical. Period.
  • Options on the same stock: Wash sale risk.

Consider creating a metadata tag like index-family: "SP500" to track which holdings might trigger wash sales with each other.

3. Don’t Forget Dividend Reinvestment

If you have automatic dividend reinvestment turned on and you sell shares at a loss, even a $5 dividend reinvestment within 30 days will trigger a wash sale. I’ve seen taxpayers lose thousands in deductions because they forgot about DRIP purchases.

Track your DRIP settings in Beancount comments so you don’t forget:

2026-01-01 open Assets:Vanguard:Taxable:VOO
  note: "DRIP enabled - watch for wash sales!"

Bottom Line

The wash sale rule isn’t just a technical inconvenience—it’s a compliance requirement where you bear the burden of proof. The IRS doesn’t care that your brokers didn’t coordinate. They expect you to get it right.

The good news? Beancount gives you the unified view you need to actually do this properly. The bad news? You still have to build the discipline and workflows to make it work.

Happy to share more specific query examples if folks are interested. Tax season is coming, and getting this right now will save a lot of headaches in April!

Oh man, this brings back memories of my early Beancount days. I learned about wash sales the hard way—let me share my story so you don’t make the same mistakes I did.

My $3,200 Mistake

Back in 2022, I was using a spreadsheet to track my investments across Vanguard (IRA) and Schwab (taxable). I sold 100 shares of Apple at a $3,200 loss in my Schwab account on December 15th, planning to use that loss on my taxes. Great, right?

Except I completely forgot that my Vanguard IRA had automatic monthly contributions set up, and on December 28th, it auto-purchased $500 worth of Apple shares. Wash sale triggered. And because it crossed the taxable/IRA boundary, that $3,200 loss was permanently gone—couldn’t add it to IRA basis, couldn’t claim it on taxes. Just… poof.

That’s when I got serious about Beancount. The unified ledger view would have caught this if I’d been using it properly.

How I Do It Now (The Working Solution)

After 4 years of refinement, here’s my workflow:

1. Account Structure That Screams “Don’t Mix These!”

I use clear naming to separate taxable from tax-advantaged:

Assets:Schwab:Taxable:Cash
Assets:Schwab:Taxable:AAPL
Assets:Schwab:Taxable:VOO
Assets:Vanguard:IRA:Cash
Assets:Vanguard:IRA:VTSAX
Assets:Vanguard:IRA:AAPL  ; ⚠️ Also own AAPL here!

That comment on the IRA AAPL position? That’s my reminder that I need to be extra careful with this ticker.

2. Simple BQL Query to Find All Positions By Ticker

Before I sell anything for tax-loss harvesting, I run this query:

SELECT account, sum(position) 
WHERE account ~ "Assets:" 
  AND currency = "AAPL"
GROUP BY account

This shows me EVERY place I own Apple across all accounts. If I see it in both taxable and IRA, red flag goes up immediately.

3. The 31-Day Wait Rule

Tina’s right about the metadata approach for serious tracking, but here’s my simpler version: I just wait 31 days before buying anything back.

Yes, I know you can buy it back on day 31. Yes, I know you lose some upside exposure during that window. But you know what’s worse? Accidentally triggering a wash sale and losing the deduction entirely.

I keep a simple text file: wash-sale-waitlist.txt

2026-11-15: Sold 100 AAPL @ 165.00 (loss: $1,200)
  -> Earliest rebuy: 2026-12-16
  -> Calendar reminder set

2026-11-18: Sold 50 VOO @ 380.00 (loss: $800)
  -> Earliest rebuy: 2026-12-19
  -> Calendar reminder set

Low-tech? Sure. But it works.

4. Annual Review: Check for “Substantially Identical” Pairs

I keep a list of holdings that might be considered substantially identical:

  • VOO / SPY / IVV (all S&P 500 trackers)
  • VTI / ITOT (total market trackers)
  • Any stock + call/put options on same stock

Tina’s index-family metadata idea is brilliant—I’m stealing that for 2027!

The fava_investor TLH Module

You asked about this, Fred. I tried it last year and it works pretty well for identifying opportunities (showing positions with unrealized losses), but it doesn’t do the wash sale compliance tracking for you automatically. You still need to manually verify you haven’t violated the 61-day window.

Here’s the GitHub: redstreet/fava_tax_loss_harvester

It’s helpful as a starting point, but think of it as a “loss opportunity scanner,” not a complete compliance solution.

My Advice: Start Simple

You mentioned you’re just figuring out the workflow. Here’s what I’d recommend:

  1. Don’t over-engineer it yet. Get your accounts into Beancount first with proper STRICT booking.
  2. Start with the manual query approach I showed above—it takes 30 seconds to check.
  3. Build the habit of checking before you sell, not after.
  4. Wait the full 31 days before repurchasing. The peace of mind is worth the opportunity cost.

Once you’ve done a few TLH cycles manually and understand the workflow, then start automating with metadata and advanced queries.

The Beancount Superpower

The real advantage isn’t fancy automation—it’s the single source of truth. When all your investments are in one ledger with STRICT booking, you can query across everything instantly. That unified view is what finally made me confident I wasn’t missing wash sales across accounts.

Hope this helps! Let me know if you want to see my actual account structure or more query examples.

As a CPA who helps clients with tax-loss harvesting strategies, I want to add the professional accounting perspective to this excellent discussion.

The Documentation Standard That Saves Audits

I’ve had three clients get audited in the last 5 years specifically on disallowed losses. Two prevailed, one didn’t. The difference? Documentation quality.

The IRS doesn’t just want to see that you calculated the wash sale adjustment correctly. They want to see that you knew what you were doing and made informed decisions. Beancount’s transaction-level detail is exactly what auditors look for.

Real Client Story: The $8,000 Deduction That Survived

Client came to me in 2024 having done extensive TLH across four brokerage accounts. Total harvested losses: $22,000. IRS flagged it because they saw repurchases within the 61-day window at different brokers and assumed he didn’t know about cross-account wash sales.

We won the audit because he’d been using Beancount with detailed notes on every transaction:

2024-11-15 * "Sell NVDA for TLH - checked all accounts for positions"
  Assets:Schwab:Taxable:NVDA   -200 NVDA {520.00 USD} @ 465.00 USD
  Assets:Schwab:Taxable:Cash    93000.00 USD
  Income:CapitalGains:LongTerm  -11000.00 USD
  note: "Confirmed zero NVDA holdings in Fidelity IRA and Vanguard 401k before sale.
         Waiting 31 days minimum before considering repurchase.
         Loss: $11,000 to offset TSLA gains from Q2."

That level of documentation showed deliberate compliance. The audit closed in our favor, saving him $8,000 in deductions ($22k losses offsetting gains at his marginal rate).

The Cost Basis Tracking Requirement

Mike’s advice about STRICT booking is spot-on. Let me add the technical details:

Why STRICT Booking Matters for TLH

When you use FIFO (First In First Out) or average cost methods, you lose the ability to cherry-pick which lots to sell for optimal tax outcomes. With STRICT booking in Beancount:

  1. Every purchase creates a distinct lot with its own cost basis
  2. You can sell specific high-cost lots to maximize losses
  3. You maintain the audit trail for each lot’s holding period (short vs long-term)

Example of STRICT booking:

; Purchase 1 - January
2026-01-15 * "Buy VOO"
  Assets:Fidelity:Taxable:VOO   10 VOO {350.00 USD}
  Assets:Fidelity:Taxable:Cash  -3500.00 USD

; Purchase 2 - March  
2026-03-10 * "Buy VOO"
  Assets:Fidelity:Taxable:VOO   10 VOO {375.00 USD}
  Assets:Fidelity:Taxable:Cash  -3750.00 USD

; Tax-loss harvest: sell the March lot (higher cost basis)
2026-11-20 * "TLH - sell March lot for maximum loss"
  Assets:Fidelity:Taxable:VOO   -10 VOO {375.00 USD} @ 340.00 USD
  Assets:Fidelity:Taxable:Cash   3400.00 USD
  Income:CapitalGains:ShortTerm  -350.00 USD

This way, you keep the January lot (lower cost basis = higher future gains) and harvest the loss from the March lot.

Year-Round vs Year-End Strategy: The Advisory Opportunity

Tina mentioned this briefly, but it’s worth expanding: the best tax-loss harvesting happens year-round, not just in December.

Why Year-Round TLH Wins

  1. Market volatility happens all year: 2026’s Q1 tech selloff created TLH opportunities that disappeared by Q4
  2. Rate of return on losses: A loss harvested in March and offset against Q2 gains saves taxes 9 months earlier than December harvesting
  3. December crowding: Everyone TLH’s in December, creating temporary price inefficiencies

For Beancount users, this means building a quarterly review habit:

  • Q1 (April): Review after tax filing, identify positions for tracking
  • Q2 (July): First check for TLH opportunities
  • Q3 (October): Second check + planning for year-end gains
  • Q4 (December): Final harvest + wash sale compliance check

The VOO vs SPY Question (Fred’s #4)

You asked whether selling VOO and buying SPY triggers a wash sale. Here’s the professional guidance:

IRS hasn’t issued specific rulings, but the “substantially identical” standard is based on:

  • Same underlying assets? (Yes - both hold S&P 500)
  • Same risk profile? (Yes - virtually identical)
  • Same economic exposure? (Yes - 99.9% correlation)

My conservative advice: Treat them as substantially identical. If you sell VOO at a loss, wait 31 days before buying SPY. Or, use a truly different asset class during the wait period (e.g., sell VOO, temporarily buy VTI for total market exposure).

The $800 you might save in taxes isn’t worth the IRS challenge risk if they rule against you.

Beancount Tax Report Templates

Several folks have asked about this. I maintain a set of year-end tax report queries for Beancount that calculate:

  • Total realized gains/losses by term (short vs long)
  • Wash sale adjustments (manual review required)
  • Tax-loss carryforwards
  • Estimated tax impact

If there’s interest, I can clean them up and share on GitHub. These templates have saved my clients hundreds of hours during tax season.

The Real Value: Advisory Services

Here’s something for the professional accountants in the room: TLH advisory is a high-margin service opportunity.

Clients will pay premium fees for:

  1. Quarterly TLH reviews and recommendations
  2. Cross-account wash sale monitoring
  3. Multi-year tax projection modeling
  4. Automated reporting and documentation

Beancount’s query capabilities let you deliver this at scale without manual spreadsheet hell. I’ve built my practice around it, and it’s been transformational for both service quality and profitability.


Great discussion, everyone. This is exactly the kind of practical, compliance-focused conversation that helps our community stay on the right side of the IRS while maximizing tax efficiency!

Wow, this is exactly the kind of deep dive I was hoping for. Thank you all—this community is incredible!

Key Takeaways That Changed My Approach

A few things really clicked for me:

1. IRA Wash Sales Are PERMANENT Losses (:scream:)

Tina and Mike both hammered this home, and it’s honestly terrifying. I have automatic contributions going into my Vanguard IRA that buy VTI on the 1st of every month. If I’m not careful, I could easily trigger a taxable-to-IRA wash sale and lose that deduction forever.

Action item: I’m turning off auto-invest for VTI in my IRA and switching to manual monthly purchases. That way I can check my taxable account for pending TLH sales before buying.

2. VOO vs SPY Is Too Risky

Alice’s point about the “substantially identical” standard makes sense. They track the same index with 99.9% correlation—I’d be gambling that the IRS wouldn’t challenge it. Not worth it.

Instead, I’m going to follow her advice: if I sell VOO at a loss, I’ll temporarily hold VTI (total market) or even international (VXUS) for the 31-day window. Different enough to avoid wash sale risk, keeps me invested.

3. Start Simple, Then Automate

Mike’s workflow is exactly what I needed: the simple BQL query to check all positions by ticker before selling. I was overthinking this with complex automation ideas when a 30-second query solves 90% of the problem.

SELECT account, sum(position) 
WHERE account ~ "Assets:" 
  AND currency = "AAPL"
GROUP BY account

I’m copying this into a text file as my pre-TLH checklist.

What I’m Implementing This Weekend

  1. Account restructure with clear Taxable vs IRA naming (stealing Mike’s structure)
  2. STRICT booking for all taxable positions going forward
  3. Disable auto-invest in IRA for any ticker I also hold in taxable
  4. Create wash-sale-waitlist.txt file for tracking 31-day windows
  5. Test fava_investor tlh module to see what it surfaces (but won’t rely on it for compliance)

The ETF Question Still Bugging Me

One follow-up: Alice mentioned using VTI as a substitute during the waiting period after selling VOO. But what if I already own both VOO and VTI in my taxable account and want to harvest losses on VOO?

Would selling VOO and then buying more VTI within 30 days trigger a wash sale because VTI includes the S&P 500 companies that VOO holds? Or is VTI different enough (total market vs large cap) to be safe?

I know you all said be conservative, but curious about the technical interpretation here.

Gratitude + Next Steps

Alice, if you do share those tax report templates on GitHub, please post the link! I’d love to contribute if I can help improve them.

Mike, I’d definitely be interested in seeing your full account structure if you’re willing to share (sanitized, of course).

Tina, your metadata approach for purchase dates and DRIP warnings is genius. Adding that to my workflow immediately.

This discussion has saved me from what could have been a very expensive mistake. I was literally about to sell some positions next week without checking my IRA holdings. Not anymore!

Going to start implementing this weekend and will report back with how it goes. Thanks again, everyone! :raising_hands: