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.
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
- Empty book. With no positions the denominator is zero; weights are defined as empty, not a divide-by-zero.
- Net basis near zero. A balanced long/short book drives ΣMV toward zero; the net basis flags the instability rather than emitting exploding weights.
- Stale price. A weight whose underlying price is older than the freshness bound is marked stale, not silently trusted.
3 · Requirements
Conformance keywords (
Functional
The slice
The denominator Σ|MV|, under which weights sum to 100%) and a net basis (ΣMV, with documented sign
semantics). Under the gross basis weights
Weights
The slice
Non-functional
Reweighting
Weights
Weight freshness
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
- 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.