Functional Weave
Code in Rust

invest.rebalance@1.0.1

README.md

3,320 bytes · view raw

# invest.rebalance

The trades that move a portfolio back to its target weights when it can only
trade whole units (shares, fund units) and does not want to pay dealing
charges on tiny trades. Prices are taken as given; the plan assumes every
trade fills at `unitPrice` and ignores dealing costs and taxes.

## How the plan is built

1. **Total** = cash + every holding's units x price. Targets sum to at most
   10000 basis points; whatever is left over is the target for cash.
2. **Target units** for each holding = total x target / (10000 x price),
   rounded to the nearest whole unit. An exact half rounds towards the units
   already held, so a tie never causes a trade.
3. **Minimum trade.** A trade whose value (units x price) is less than
   `minimumTrade` is dropped. A trade exactly equal to it is kept.
4. **Funding.** Rounding up can leave the buys costing more than the cash
   plus the sells, and a dropped sell can leave a buy unfunded. While the cash
   after trading would be negative, the buy whose holding would end up
   furthest above its target value loses one unit (ties go to the holding
   listed first), and a buy that shrinks below the minimum trade is dropped.
   Sells are never enlarged to pay for buys, so the plan never trades more
   than the targets ask for.

The result lists every holding in input order, including those with no trade,
so a caller can show a before-and-after table directly.

## Why not just divide the drift by the price

That is the naive plan, and it goes wrong three ways the vectors pin down:
rounding two buys up overspends the cash, a sell dropped for being small
leaves its matching buy unfunded, and truncating every target (to be safe)
sells more than needed. The steps above are deterministic, so the same
portfolio produces the same trades in every language.

## Edge cases

- A holding with a target of 0 is sold out (subject to the minimum trade).
- Weights after trading are rounded independently, half-up, and need not sum
  to 10000.
- Errors: no holdings, duplicate ids, negative or fractional units, a price
  of 0 or less, a target outside 0-10000 or targets over 10000 in total,
  negative cash or minimum trade, mixed currencies, and a portfolio worth
  nothing.

## Before you rely on this

**Not professional advice.** This capability calculates investment figures from published rules. It is a software component for developers, not financial advice. Rules change and every rate here has an effective date. Check that the dates cover your case. Verify results against the official sources listed above, and have a tax adviser review how you use it, before anyone relies on the output. Provided "as is" under its licence, without warranty.

**Unreviewed.** This capability's implementations agree in every language and pass its published test vectors, which were worked out from the official sources cited. But no qualified tax adviser has yet checked those vectors, or confirmed that the capability covers the cases it claims. Treat it as a draft. Do not use it for real people, money or decisions without your own expert review. Once a qualified reviewer signs off, this notice is replaced with their name, qualification and the date. Each new version needs fresh sign-off.

1.0.1 marks it unreviewed. The code and the tests are unchanged.