| name | portfolio-drift-rebalancer |
| description | Aggregates investment holdings across taxable brokerage, 401k, and HSA into a single asset-allocation view, computes drift versus a target allocation, and produces a tax-efficient rebalance proposal that prefers tax-advantaged accounts for the trades and never executes. Flags drift over 5 percentage points and free-money issues like missed 401k employer match. Use when reviewing portfolio allocation, planning a rebalance, computing drift, or when user mentions asset allocation, drift, rebalancing proposal, or 401k match. |
Portfolio Drift Rebalancer
Table of Contents
Overview
The household typically holds investments across three account types: a taxable brokerage (e.g., Fidelity), a 401k, and an HSA's invested portion. A target allocation is defined at the household level (e.g., 55/20/20/5 US/intl/bonds/cash). This skill rolls up current allocation across all three accounts, computes the drift from target, and proposes specific buys and sells to restore target — preferring trades inside tax-advantaged accounts (401k, HSA) where realized gains have no tax consequence.
It never executes trades. It produces a proposal for the user.
Input contract
The caller provides:
accounts — array of investment accounts: {id, type, institution, holdings[]} where each holding has {symbol, shares, value_cents, cost_basis_cents, asset_class}.
target_allocation — { asset_class: target_pct } summing to 1.0.
drift_threshold_pct — default 5 percentage points (5.0). Below threshold, no rebalance proposed.
cash_to_deploy_cents — optional new cash being added (e.g., a paycheck deposit).
tax_rate_long_term — for cost-comparison estimates (default 15%).
Workflow
Drift / Rebalance Progress:
- [ ] Step 1: Aggregate holdings by asset_class across all accounts
- [ ] Step 2: Compute current allocation percentages
- [ ] Step 3: Compute drift vs target
- [ ] Step 4: Check drift threshold; if all classes within threshold, return no-op
- [ ] Step 5: If new cash, allocate it preferentially to under-target classes
- [ ] Step 6: For remaining drift, plan trades
- [ ] Step 7: Order trades by tax cost (advantaged-account first)
- [ ] Step 8: Verify proposal nets to zero cash impact (or matches new-cash deposit)
- [ ] Step 9: Emit proposal with per-trade rationale
Step 1 — Aggregate
For each asset_class in target, sum value_cents across all holdings of all accounts. Include cash positions in the brokerage (typically asset_class cash).
Step 2 — Allocation
current_pct[class] = total_value[class] / total_value_all. Keep two decimal places.
Step 3 — Drift
drift_pp[class] = current_pct[class] − target_pct[class] in percentage points.
Step 4 — Threshold
If max(|drift_pp|) < drift_threshold_pct, emit rebalance_needed: false and stop. Surface drift values for context but propose no trades.
Step 5 — Deploy new cash
If cash_to_deploy_cents > 0, allocate to under-target classes first. Each dollar of new cash to under-target classes reduces the trade volume needed in step 6.
Allocation order: largest negative drift_pp first.
Step 6 — Plan trades
After cash deployment, residual drift calls for trades. For each over-target class, compute excess_cents; for each under-target class, compute shortfall_cents. Match by greedy pairing (largest excess to largest shortfall), producing a list of (sell_class, buy_class, amount_cents) swaps.
Step 7 — Tax-efficient ordering
Within each swap, choose specific holdings:
Sell side priority (most preferred to least):
- Holdings inside
401k or hsa (any sale is tax-free).
- Holdings in taxable brokerage with unrealized loss (sale generates harvestable loss — coordinate with
tax-loss-harvest-scanner).
- Holdings in taxable brokerage with smallest unrealized gain (least tax cost).
- Holdings in taxable brokerage with long-term gains (taxed at 15%).
- Holdings in taxable brokerage with short-term gains (taxed at marginal rate).
Buy side priority:
- Holdings inside
401k or hsa of the desired asset class (no transaction-tax friction).
- Existing holdings in taxable brokerage of the desired asset class (avoid wash-sale by not buying back something just sold).
- New holding (broad-market index fund preferred).
For each proposed sell from a taxable account, compute and surface the estimated tax cost: (value − cost_basis) × tax_rate_long_term. The user can decide whether the rebalance benefit exceeds the tax cost.
Step 8 — Verify
The proposal must satisfy: Σ buys = Σ sells + cash_to_deploy_cents. If not, the planning has a bug — emit a proposal_invalid warning and stop.
Step 9 — Emit
Per-trade output includes: account, symbol, action (buy/sell), shares, dollar amount, asset_class change, estimated tax cost, rationale (under-target / over-target / new cash deploy).
Tax-efficient rebalancing logic
| Account | Sale tax cost | Notes |
|---|
| 401k | $0 | All sales tax-free |
| HSA (invested) | $0 | All sales tax-free |
| Brokerage (long-term gain) | 15% × gain | Hold > 1 year |
| Brokerage (short-term gain) | marginal income tax × gain | Hold < 1 year — usually avoid |
| Brokerage (loss) | savings (TLH benefit) | Coordinate with tax-loss-harvest-scanner |
Wash-sale awareness. If a sell is paired with a buy of substantially identical security within 30 days (in any account, including the spouse's), the loss is disallowed. When a rebalance proposes selling an ETF in taxable for a loss, the buy side must use a different but similar ETF (e.g., sell VTI, buy ITOT). Document the wash-sale guard on each trade.
Free-money flags — emit even when no rebalance is needed:
- 401k contribution rate < employer-match cap →
free_money_alert: missed_employer_match.
- HSA cash balance > investment threshold but not invested →
free_money_alert: idle_hsa_cash.
Output contract
{
"as_of": "2026-04-25",
"totals": {
"portfolio_value_cents": 49250000,
"by_account": {
"acc_inv_fid_001": 18750000,
"acc_401k_001": 22500000,
"acc_hsa_001": 8000000
}
},
"current_allocation": {
"us_equity": 0.625,
"intl_equity": 0.155,
"us_bond": 0.180,
"cash": 0.040
},
"target_allocation": {
"us_equity": 0.55,
"intl_equity"
Guardrails
- Never execute. This skill produces proposals only.
- Drift threshold is meaningful. Sub-5pp drift is noise; rebalancing it generates frictional costs (taxes, transaction overhead).
- Tax-cost transparency. Every taxable sale carries an explicit estimated tax cost. The user must see it.
- Wash-sale guard. Pair sell-for-loss with a different (not substantially identical) buy. Document.
- Whole shares only when a holding doesn't support fractional shares — round to nearest whole share and surface the residual cash.
- Don't auto-deploy new cash to over-target classes. Even if it minimizes trades, it widens drift; deploy to under-target instead.
- Account-level constraints respected. A 401k might only offer specific funds; never propose buying a fund the account does not support. The agent layer reads the eligible-fund list per account.