Specification · 004-portfolio-weights

Portfolio weight calculation

Every holding as a fraction of the book, recomputed the instant a position or price moves — well-defined with shorts, and always summing to one.

Status Accepted Spec ID 004-portfolio-weights Parent 001-portfolio-analytics Owner Brian Corbin · @briancorbinxyz Reviewers Brian Corbin · @briancorbinxyz Created Principlesv0.1.0 Smallest demoableTwo held instruments show weights that sum to 100%; a price tick on one reweights the book live. Read time

Portfolio weight is each holding's market value as a fraction of the whole book. This slice computes it live from the position engine's mark-to-market values, recomputing incrementally as positions and prices move rather than rescanning the book on every tick. It pins down the definition that naive weight code gets wrong — the denominator — supporting both a gross basis (Σ|MV|, weights sum to 100%) and a net basis (ΣMV), so weights stay well-defined when the book holds shorts. It also bounds staleness and offers concentration rollups.

I Independent
Depends on the umbrella and the position engine (003) for market values; tax lots are unrelated.
N Negotiable
Outcome (correct, normalized, fresh weights under a stated denominator), not a named numeric library.
V Valuable
Unlocks SC-001 — live concentration the desk and risk can act on.
E Estimable
A running denominator plus per-instrument shares; the shorts/denominator choice is enumerable.
S Small
One derived view over positions, four functional requirements; benchmark-relative weights deferred.
T Testable
The sum-to-one invariant and staleness bound are directly checkable; latency is measurable.

1 · Context

"How concentrated am I" drives risk limits, rebalancing, and the desk's intuition — but a portfolio weight is only as meaningful as its denominator, and that is precisely what breaks when a book holds shorts. A short position has negative market value; divide by a naive ΣMV and weights can exceed 100%, go negative in confusing ways, or blow up when the net value nears zero. This slice makes the denominator an explicit, tested choice and keeps the recompute cheap enough to run on every tick.

It owns weights, normalization, staleness, and concentration rollups. It reads market values from 003 and knows nothing about tax lots.

2 · User scenarios & testing

Each story MUST be independently testable.

US1 · Weights that sum to one P1

As a risk viewer, I want each holding's weight as a fraction of the book under a stated denominator, so concentration is unambiguous.

Acceptance: under the gross basis, per-instrument weights are |MV| / Σ|MV| and sum to 1.0 within tolerance; the chosen basis (gross or net) is explicit in the output.

US2 · Reweight on a tick P2

As a desk engineer, I want a price tick to update the affected weights and the denominator without a full O(N) rescan, so the book stays fresh at tick rates.

Acceptance: a tick updates the moved instrument's share and the running denominator incrementally; other weights follow from the new denominator without re-reading every position.

US3 · Shorts stay well-defined P3

As a risk manager on a long/short book, I want weights that behave sensibly with short positions, so a mixed book doesn't produce weights over 100% or a denominator near zero.

Acceptance: with shorts present, the gross basis yields weights in [0, 1] summing to one; the net basis is offered with its sign semantics documented and guarded against a near-zero denominator.

Edge cases

3 · Requirements

Conformance keywords (MUST, SHOULD, MAY) follow RFC 2119.

Functional

The slice MUST compute per-instrument portfolio weight as position market value over total portfolio market value, recomputed incrementally as positions and price ticks change.

The denominator MUST be explicit and selectable: a gross basis (Σ|MV|, under which weights sum to 100%) and a net basis (ΣMV, with documented sign semantics). Under the gross basis weights MUST sum to one within tolerance.

Weights MUST be staleness-bounded: a weight reflects prices no older than the freshness bound, and a weight past that bound MUST be marked stale rather than silently served.

The slice SHOULD expose concentration rollups — top-N holdings and a single Herfindahl-style concentration index — derived from the same weights.

Non-functional

Reweighting MUST meet SLO-reweight for both a single-instrument update and a full-book recompute.

in-process wall-clock to reweight the book on a denominator-moving tick, at 5,000 instruments (good ÷ valid ticks)

Weights MUST be numerically well-formed per SLO-normalization: under the gross basis they sum to one, and they reconcile to a full replay.

fraction of gross-basis recomputes whose weights sum to one within 1e-9 (conforming ÷ total recomputes)

Weight freshness MUST meet SLO-freshness so a served weight is never staler than the bound without being flagged.

wall-clock from a price tick to the dependent weights reflecting it (good ÷ valid ticks)

Out of scope (deferred)

  • Producing the market values weights divide — the position engine owns MTM.
  • Any tax or realized-gain view; weights are a market-value concept only.
  • Active weights relative to a benchmark index (over/underweight) — a later analytic.
  • Translating mixed-currency market values to a common base before weighting — depends on the deferred FX slice.

4 · Data model

The slice holds a running denominator and a share per instrument; market values are read from 003.

WeightBook
{ basis (gross|net), denominator, asOfSeq } plus, per instrument, { mtmValue, weight, stale (bool) }.
Concentration rollup
{ topN: [(instrument, weight)…], herfindahl: Σ weight² } — derived, not stored authoritatively.

Invariant: under the gross basis, Σ weight = 1 whenever the book is non-empty and the denominator is above the near-zero guard. Violations are correctness faults (NFR-002), not display rounding.

5 · Success criteria

For any book including at least one short, gross-basis weights lie in [0, 1] and sum to one within 1e-9, and a price tick reweights the affected holdings within SLO-reweight.

A weight whose price is older than the freshness bound is reported stale, and a live tick clears the stale flag within SLO-freshness.

6 · Assumptions

The position engine (003) exposes a consistent market-value snapshot at a ledger sequence; weights inherit that read-consistency rather than reconstructing it.

All market values are in a single base currency for this slice; multi-currency translation is deferred to TBD-multi-currency-base.

7 · Open questions

None outstanding — the gross-vs-net denominator choice (offer both, gross as default, net guarded near zero) and the staleness-flag contract were settled in the interview.

8 · Conformance index

Auto-built from every <spec-requirement> in this document.

9 · Change log

  1. Initial draft. Child of 001-portfolio-analytics: incremental weights (FR-001), explicit gross/net denominator with sum-to-one under gross (FR-002), staleness bound (FR-003), concentration rollups (FR-004). Envelope: reweight latency (NFR-001), normalization/reconcile (NFR-002), freshness (NFR-003). Consumes market values from 003-position-engine.