The rebalancer module inside almost every wealth-tech product looks the same on first inspection: target weights, current weights, drift threshold, list of trades that brings the portfolio back to target. The math is straightforward. The math also produces wrong answers if it's the only thing the engine considers.
A tax-aware rebalancer is a substantially different product. It has to know which lots to sell, which accounts to draw from, and which trades to avoid because they would trigger a wash-sale on a recent harvest. The synthetic data this needs is fundamentally different from what a naive rebalancer needs — and most products in market still operate at the naive level, producing rebalancing trades whose tax cost can exceed the rebalancing benefit.
This article is the working note on what a tax-aware rebalancer has to model and what its test corpus has to contain.
What "tax-aware" actually means
A naive rebalancer minimizes tracking error from the target allocation. A tax-aware rebalancer minimizes tracking error subject to a tax-cost constraint. The constraint can be expressed in a few ways:
min tracking_error(weights_after, target_weights)
s.t. tax_cost(trades) ≤ tax_budget
no_wash_sale_triggered(trades, recent_harvests)
lot_selection respects holding-period preferences
account_placement respects asset-location preferences- tax_cost
- = Realized capital gains × marginal rate, summed across trades. Includes federal + state + NIIT.
- tax_budget
- = User-specified annual budget for realized gains. Often $0 (don't realize anything) up to portfolio-specific value.
- wash_sale
- = 30-day window before and after the trade, across all linked accounts
- holding-period preferences
- = Prefer LTCG over STCG; prefer losses; respect QSBS 5-year clock
- asset-location preferences
- = Bonds in tax-deferred, equities in taxable, REITs in tax-deferred, etc.
What the data has to look like
The bare-minimum naive rebalancer needs:
- Current position weights
- Target position weights
- Drift threshold
That's it. Three numbers per position.
A tax-aware rebalancer needs:
Tax-aware data requirements
- Per-lot data — acquisition date, basis, holding period, special-status flags. 30–300 lots per typical position.
- Per-account data — taxable / IRA / Roth / 401(k) / HSA. Each account has different tax treatment of trades.
- Cross-account linkage — wash-sale rules apply taxpayer-wide, not account-locally.
- Recent-harvest history — 60+ day rolling window of realized losses to avoid wash-sale on rebalancing trades.
- Tax-rate context — household's marginal federal, state, NIIT applicability, AMT status, AGI projection for the year.
- Holding-period thresholds — long-term vs short-term boundary (1 year + 1 day). Lots within days of crossing should be deferred if possible.
- Special-status awareness — QSBS lots (5-year clock), Section 1042 lots, ESPP qualifying-disposition status.
The data shape is a meaningful step up. A naive rebalancer can run on $10/month of data; a tax-aware rebalancer needs $50–100/month of data minimum, plus integration with custody systems for live lot updates.
The decision sequence
A tax-aware rebalancer evaluates trades in a specific order:
- Step 1Identify required position deltasPer asset class, compute the trade size needed to bring weights to target. Cap at the drift threshold; don't trade for sub-threshold drift.
- Step 2Prefer non-realizing tradesUse new contributions or distributions to add to underweighted positions. Use cash from realized losses to add. Avoid sales when contributions can suffice.
- Step 3Identify free tradesSales in tax-deferred / Roth accounts have no immediate tax cost. If reweighting can be done within those accounts, prefer it.
- Step 4Identify loss-harvest tradesSales of underwater lots in taxable accounts realize losses that offset gains; sometimes lower the after-tax cost of the rebalancing.
- Step 5Score remaining required taxable tradesFor each remaining required sale, compute the tax cost across lot-selection alternatives. Choose the lot that minimizes tax.
- Step 6Wash-sale checkCross-reference each proposed sale against the 60-day window of activity in linked accounts. Reject any sale that would create a wash-sale.
- Step 7Validate outputTotal tax cost ≤ budget. No wash-sale triggers. Lot-selection respects preferences. Multi-account placement is consistent.
The decision tree is what separates "rebalancing" from "tax-aware rebalancing." Engines that skip steps 2–4 produce trades that are mathematically correct and tax-incompetent.
Common bugs we see
Across rebalancing engines we've audited:
The synthetic data the test corpus needs
A working test corpus for a tax-aware rebalancer:
| Spread dimension | What it covers | |
|---|---|---|
| Account structure | Households with taxable + IRA + Roth + 401(k) + HSA combinations. At least 200 unique combinations across the corpus. | |
| Lot composition | Per position: 30–300 lots with varied acquisition dates, costs, and special-status flags. | |
| Recent harvest history | Mix of households with no recent harvests, fresh harvests within 30 days, harvests at 31–60 days. The wash-sale window has to be exercised at boundaries. | |
| Tax-rate variability | Households at different marginal rates, with and without NIIT exposure, with and without AMT exposure. | |
| Special status | QSBS holdings at various points in the 5-year clock. Section 1042 ESOP rollover holdings. ESPP qualifying-disposition pending lots. | |
| Drift conditions | Households where each asset class is at, above, and below target, with various drift magnitudes. Edge cases at the drift threshold. |
A test corpus missing any spread is a corpus where the rebalancer has untested branches. The branches that don't get tested are the branches that ship bugs.
Key takeaways
- A tax-aware rebalancer is a fundamentally different product from a naive rebalancer. It optimizes tracking error subject to tax-cost, wash-sale, and asset-location constraints.
- The data step-up is meaningful: per-lot, per-account, cross-account-linked, with recent-harvest history and tax-rate context. Pure position-level data is insufficient.
- The decision sequence prefers non-realizing trades, then free trades in tax-deferred, then loss-harvest trades, before doing taxed trades — and only the lot-selection-optimal taxed trades that don't trigger wash-sales.
- Common bugs: wash-sale on rebalance, QSBS qualification vaporized by rebalancing, asset-location ignored in multi-account households.
- Test corpus needs spread on account structure, lot composition, recent-harvest history, tax-rate variability, special-status holdings, and drift conditions.