Rebalance one account toward target weights, tax-aware

One command on the Portfolio lots and harvesting server, callable over MCP as review_rebalance. Every result carries the method it used, the rule pack it was pinned to, what is still missing and whether coverage was complete, for a licensed professional to review.

What it needs, and why

Every input is checked before anything is calculated. subject_ref, document_ref, as_of, prices, idempotency_key and target_weights are required; the rest refine the answer.

InputWhat it asks forWhy it mattersIf you omit it
subject_refAccount or household id you are entitled to readThe account or household the analysis is scoped to. Naming one is a request to read it, never a grant.required
document_refPath to the normalized holdings export to analyseAccount metadata, lots and basis come from the export, and nothing resolves until one has been read.required
as_ofAnalysis date, YYYY-MM-DDHolding periods and the 30-day wash-sale window are both measured from the analysis date.required
pricesPrice per share by security id, as decimal stringsA gain cannot be computed without a price per security, and no price is ever fetched or guessed here.required
idempotency_keyCaller-chosen keyRepeating the key returns the first run instead of running again, so a retry cannot spend a gain budget twice.required
target_weightsTarget weight by security id, as decimal strings summing to 1Targets are supplied by the advisor. Inferring them from the current holdings would make the portfolio its own benchmark.required
toleranceDrift below this fraction is left alone, so small deviations do not generate tradesDrift below the tolerance is left alone, so small deviations do not generate trades.optional
gain_budgetCap on realized gains from the sellsA cap on realized gains is a decision rather than a default, and supplying one requires saying how it is measured.optional
netting_basisWhether the gain budget is measured net of losses or on gains alone one of net_gains, gross_gainsGross and net readings of the same budget give different answers, so the server will not pick one for you.optional
periodTax period the budget applies to, e.g. "2026"A budget belongs to a tax period, and gains already realized in that period consume the allowance.optional

Requests it answers

  • “How do I rebalance without realizing a big gain?”
  • “Will rebalancing wash a loss I just harvested?”

An agent with these skills loaded picks the command from phrasing like that, asks for whatever input is missing, and reports the figures with the method that produced them.

Run it

TAXAGENT_TENANT_ID=... TAXAGENT_PRINCIPAL_ID=... TAXAGENT_ACCOUNTS=... scripts/mcp_server.sh --server portfolio

The same command is exposed by taxagent-portfolio, so Claude, Claude Code, Codex, Cursor and any other MCP client can call it directly. Analysis only. No tool in this server places orders, modifies custodian records or files returns. Every result is scoped to the accounts this principal is entitled to read; an account named in an argument is a request, not a grant. Unknown basis blocks a lot rather than defaulting to zero.

Scripted cases that exercise it

2 of the 71 console cases call this command on a real server over JSON-RPC and assert the outcome. Every one runs on every build, and all of them are listed on the case inventory.

CaseWhat it asksWhat it showsOutcome
portfolio-rebalanceRebalance to target weights, tax-awareCloses drift within a gain budget, never selling a security below its target. With chunky lots it under-trades rather than overshooting, and reports the residual.completed_with_limitations
portfolio-rebalance-needs-targetsA rebalance with no target weightsRefused rather than inferred. Deriving a target from the current holdings would make the portfolio its own benchmark.protocol_or_needs_input

Limits it inherits from taxagent-portfolio

  • Greedy selection; no optimality claim.
  • No multi-period or risk-aware objective.
  • Optimal means optimal over the lots, prices and constraints supplied, for the stated objective: reach the cash target, realize as little gain as possible, use as few trades as possible. It is not a statement about what the account should do.
  • Risk, tracking error, a client's view on a position and the reason a lot was bought are outside the model entirely.
  • Beyond 40 budget-consuming lots the search is not exhausted. The status degrades to feasible and a gap is reported; optimality is never claimed from a truncated search.
  • Account scope only. Running it once per account is not a household optimum.
  • Drift is the sum of absolute differences between current and target weight. The engine never sells more of a security than its overweight, so with chunky lots it under-trades rather than overshooting, and residual drift is expected and reported.
  • Buys are allocated across underweight securities in proportion to their shortfall. A purchase has no tax consequence, so there is nothing to optimise about which lot it becomes.
  • A buy is withheld when the security was sold at a loss inside the wash-sale window, which leaves that drift open until the window closes.
  • Requires advisor-supplied target weights. Inferring a target from the current holdings would make the portfolio its own benchmark.
  • Account scope only, and no view on transaction costs, market impact, tracking error or which account should hold which exposure.

The server, its other 8 commands and what it deliberately leaves alone are on the Portfolio lots and harvesting page.