Repository navigation
SPEC 017 Wallet Balancing
| Status | Draft |
| PRD source | F7 (FR-7.1–7.24) |
| Depends on | SPEC-002, SPEC-009, SPEC-010, SPEC-011 |
| Required by | — |
| Milestone | M7 |
A wallet accumulates concentration silently. Winners run, contributions land unevenly, and a position that started as a tenth of a wallet becomes a third without any single decision having been made. This spec lets a user state what proportions they intended, and shows the distance between that and what they actually hold.
The constraint that shaped it is regulatory, not technical. SPEC-015 listed target allocation and drift alerts under Out of Scope, and DL-015-03 rejected them because rebalancing advice would make the product an unlicensed advisor (PRD risk R7). PRD Q6 left the question open. This spec answers it — and the answer is not that the risk was overstated. It is that every number here originates with the user. The product stores targets the user typed, computes arithmetic against a price it already displays, and never originates a threshold, ranks an asset, or names a trade. Under that constraint the feature is a calculator, not an opinion.
One consequence is visible on every screen: fixed income is excluded from targets. A CDB before maturity usually cannot be sold, so drift against it is uncorrectable and would mark a wallet permanently out of balance for a reason the user cannot act on. Excluding it means targets cover a subset of the wallet, which would understate concentration if left unsaid — so the targeted share is stated next to the targets rather than left to be inferred.
| ID | Rule |
|---|---|
| BR-017-01 | A wallet may define target allocations. Targets are optional; a wallet without them behaves exactly as it does today. |
| BR-017-02 | A wallet is in one of two target modes: equal-weight or manual. The mode is stored, because it is a standing instruction rather than a one-off calculation. |
| BR-017-03 | Targets are a percentage of market value, not of cost. Consistent with SPEC-015 DL-015-02: a position that tripled represents three times the exposure it did at purchase. |
| BR-017-04 | Targets must total exactly 100%. A set that does not is rejected at write time, not accepted and flagged. |
| BR-017-05 | Equal-weight mode derives every target as 100 / n over the targeted assets. Targets are recomputed rather than stored per asset. |
| BR-017-06 | In equal-weight mode, an asset joining or leaving recomputes every target automatically. Equal-weight is the user's declared standing instruction, the same class of instruction as SPEC-010 BR-010-14 — not inferred intent. |
| BR-017-07 | In manual mode targets never change by themselves. An asset joining enters at a 0% target and the wallet is flagged as needing review until targets total 100% again. This is deliberately asymmetric with BR-017-06: a hand-set percentage is specific intent, which SPEC-010 BR-010-11 refuses to infer over. |
| BR-017-08 | Switching mode is explicit. Manual → equal-weight discards the manual targets, after confirmation naming what will be lost. |
| ID | Rule |
|---|---|
| BR-017-09 | Fixed income is excluded from targets. CDB, LCI, LCA and Tesouro Direto never carry a target and never count toward the 100%. |
| BR-017-10 | The balance view states the targeted share explicitly — "targets cover 60% of this wallet; 40% is fixed income and untargeted". Without this the view reports balance over a subset and understates exactly the concentration the feature exists to surface. |
| BR-017-11 | A wallet holding only fixed income cannot define targets, and says why rather than presenting an empty form. |
| BR-017-12 | Unassigned quantity (SPEC-010 BR-010-06) is not part of any wallet and never appears in a wallet's targets. |
| ID | Rule |
|---|---|
| BR-017-13 | An asset's current allocation is its market value as a share of the wallet's targeted market value — the denominator excludes fixed income, matching BR-017-09. |
| BR-017-14 | Drift is current − target, in percentage points, signed. |
| BR-017-15 | An asset is out of tolerance when ` |
| BR-017-16 | A wallet is out of balance when at least one targeted asset is out of tolerance. |
| BR-017-17 | Out-of-balance wallets surface in the existing "Needs attention" queue (SPEC-010 BR-010-12). No second queue is introduced — a second surface competes for the same attention. |
| ID | Rule |
|---|---|
| BR-017-18 | For each targeted asset the view shows drift in percentage points, the difference in R$, and the equivalent share count at the displayed quote — in both directions, over- and under-weight alike. |
| BR-017-19 | Share counts are arithmetic over a target the user set and a price already on screen. No wording may recommend an action (SPEC-015 BR-015-06). The figure is labelled as distance from the user's own target, never as a trade to make. |
| BR-017-20 | Share counts are shown at the asset's tradable precision and marked approximate, because the quote carries a delay (SPEC-008). |
| BR-017-21 | Where a quote is stale or unavailable, that asset's drift reads unavailable. It is never computed from a price known to be old. |
| ID | Rule |
|---|---|
| BR-017-22 | Targets and quantity allocations (SPEC-010) are independent. Changing a target never moves an allocation; allocating a quantity never changes a target. |
| BR-017-23 | Splits, grupamentos and bonificações do not change targets. Targets are percentages, and BR-010-18 adjusts allocations by the same ratio as the position, so shares are preserved mechanically. |
| BR-017-24 | An asset whose allocation returns entirely to Unassigned leaves the target set: equal-weight recomputes (BR-017-06), manual flags (BR-017-07). |
| BR-017-25 | Targets are current-state. Editing a target changes what the balance view shows and restates nothing historical — no drift-over-time chart exists in this spec, precisely so that nothing can be restated. |
- A wallet can be given equal-weight targets in one action; ten assets each read 10%.
- Adding an eleventh asset to an equal-weight wallet moves every target to 9.09% with no user action.
- Adding an asset to a manual wallet leaves existing targets untouched, enters the new asset at 0%, and flags the wallet as needing review.
- Manual targets that do not total 100% are rejected at write time with the shortfall or excess named.
- Switching manual → equal-weight warns that hand-set targets will be lost, and discards them only after confirmation.
- A wallet holding a CDB and four stocks shows targets over the four stocks only, and states the percentage of the wallet those targets cover.
- A wallet holding only fixed income offers no target form and explains why.
- Drift is signed and correct: an asset at 14% against a 10% target reads +4pp.
- An asset exceeding the configured tolerance is flagged; changing
wallets.drift_tolerance_ppchanges which assets flag, with no deploy. - An out-of-balance wallet appears in the existing "Needs attention" queue, not a new one.
- The gap for an overweight asset shows percentage points, R$ and an approximate share count; the same three figures appear for an underweight asset.
- No string anywhere in the feature recommends buying, selling, trimming or rebalancing — verified by human review and recorded in the PR.
- An asset with a stale quote shows drift as unavailable rather than computing it from the stale price.
- A 10:1 split changes no target, and the wallet's balance state is identical before and after.
- Editing a target changes only the current view; no historical figure anywhere in the product moves.
- Targets survive a full position rebuild from the ledger (DM-4).
- Placing or executing trades. The product connects to no broker and never will under SPEC-003 BR-003-08.
- Recommendations of any kind — SPEC-015 BR-015-06 stands unchanged. This spec adds arithmetic, not opinion.
- Fixed-income targets (BR-017-09). Revisit only if liquidity data per instrument becomes available.
- Portfolio-scope targets. Targets are wallet-scoped in v1; the portfolio's composition is served by SPEC-015.
- Drift history and drift-over-time charts — excluded deliberately, see DL-017-06.
- Automatic rebalancing, contribution-splitting, or directing an aporte toward target.
- Tax-aware rebalancing. Realized-gain consequences are out of scope while PRD Q3 is open.
| # | Decision | Alternatives considered | Rationale |
|---|---|---|---|
| DL-017-01 | PRD Q6 answered yes, superseding SPEC-015 DL-015-03's exclusion. | Keep target allocation out of scope | The advice risk was real, but omission is not the only mitigation. Requiring every threshold to originate with the user removes the product's judgement from the loop entirely, which is what DL-015-03 was protecting. The flag in SPEC-015 stays informational; this spec adds no opinion to it. |
| DL-017-02 | Targets are a share of market value. | Cost basis (amount invested) | Cost basis would make a tripled winner read as on-target while representing three times its original exposure — understating the concentration the feature exists to catch. It would also disagree with Composition, so the same wallet would look balanced on one screen and skewed on another. |
| DL-017-03 | Targets must total exactly 100%. | Allow under 100% as explicit headroom | Headroom creates two distinct states — "balanced" and "fully allocated" — both needing expression in the UI, and a second denominator for the drift arithmetic. The gain is a convenience for a user planning a future purchase, which a 0%-target row already covers. |
| DL-017-04 | Fixed income excluded from targets. | Participate fully; participate but flagged as illiquid | A CDB before maturity typically cannot be sold at all, so its drift is uncorrectable — the wallet would sit permanently out of balance over something the user cannot act on, training them to ignore the flag. The cost is that targets cover a subset, which BR-017-10 addresses by stating the subset rather than hiding it. |
| DL-017-05 | Equal-weight recomputes; manual flags. | Never recompute; always recompute proportionally | Equal-weight is itself a standing instruction — the user said "keep these equal", so honouring it on a change is obedience, not inference. A hand-set 15/10 split is specific intent, and silently rescaling it is exactly the inference SPEC-010 BR-010-11 was written to refuse. |
| DL-017-06 | No drift history; targets are current-state. | Effective-dated targets with a drift-over-time chart | History costs an event table and reconstruction logic, and the only consumer would be a chart this spec does not ship. Recorded so the omission reads as a decision: if a drift-over-time chart is ever wanted, effective-dating the targets is a prerequisite, not an afterthought. |
| DL-017-07 | Share counts shown in both directions. | Gap in percentage points only; contribution-only (never compute a disposal) | Both alternatives withhold arithmetic the product already has every input for, and neither reduces the regulatory exposure — a percentage-point gap implies the same action as a share count, just less legibly. Contribution-only would go silent in the one case that matters most, a wallet badly skewed by a winner. |
| DL-017-08 | Reuses the existing "Needs attention" queue. | A dedicated rebalancing alert surface | SPEC-010 BR-010-12 already established one queue for pending allocations and unclassified imports. A second surface splits the user's attention and makes "is there anything for me to do?" a two-place question. |