Specification · 001-portfolio-analytics
Real-time portfolio analytics
One append-only trade ledger; every number a deterministic fold over it. Positions, weights, and tax lots stay live at HFT tick rates and always reconcile to a full replay.
This is the umbrella for a real-time position, portfolio-weight, and tax-lot platform sized for high-frequency trading: unlimited trade history, sub-millisecond tick-to-analytic latency, and answers that must always agree with a from-zero replay. The architecture is one decision — an append-only trade ledger as the single source of truth, with every analytic a pure deterministic fold over it — and everything domain-specific is carved out to four child slices: the ledger itself, the position engine, portfolio weights, and FIFO/LIFO tax lots. This spec owns only the shared event model and the cross-cutting reliability envelope that binds them.
- I Independent
- Depends only on the principles. Its children depend on it; it depends on none of them — it defines the shared model and defers all math downstream.
- N Negotiable
- Outcome (one ledger, deterministic folds, a bounded latency envelope), not a named database, language, or in-memory structure.
- V Valuable
- Unlocks SC-001 — a single trade produces coherent, reconcilable position, weight, and lot analytics in real time.
- E Estimable
- The umbrella is a model + envelope; each child is independently estimable from its own slice.
- S Small
- Nine requirements, no algorithms — the domain weight lives in the four children, each under its own budget.
- T Testable
- Every requirement is observable; the reconcile-to-replay criterion (SC-001) is a single mechanical check.
1 · Context
A trading desk needs three answers continuously and correctly: what do I hold (position), how concentrated am I (portfolio weight), and what is my realized and unrealized gain (tax lots). At HFT rates these must be maintained incrementally — recomputing from scratch on every fill is not an option — yet an incremental number that has silently drifted from the truth is worse than no number at all.
The failure mode this platform exists to prevent is divergence between the fast path and the truth. We close it structurally: a single append-only ledger of trades and price ticks is the only source of truth, and every analytic is a pure fold (reducer) over that ledger. The fast, incremental path and the slow, from-zero replay are the same function applied two ways, so they can always be reconciled. Stakeholders: the trading desk (consumers of the live numbers), risk (concentration and exposure), and finance (realized-gain tax reporting).
2 · User scenarios & testing
Each story MUST be independently testable: implementing only US1 still yields a viable MVP.
US1 · One trade, three coherent analytics P1
As a desk engineer, I want a single filled trade to update my live position, portfolio weight, and open tax lot together, so the three views never contradict one another.
Acceptance: appending one TradeEvent updates the position engine, the weight book, and the tax-lot
book; a full replay of the ledger to the same sequence number reproduces all three to the cent.
US2 · Resume from a checkpoint P2
As an operator restarting a service, I want to restore analytics from a snapshot rather than replaying years of history, so recovery is bounded regardless of ledger size.
Acceptance: restoring from a snapshot at sequence N yields analytics bit-identical to a full replay
through N; appending event N+1 proceeds normally.
US3 · Add an analytic without touching the ledger P3
As a platform owner, I want a new analytic to be a new fold over the existing ledger, so adding one never changes the source of truth or the other analytics.
Acceptance: a fourth analytic is introduced consuming only the ledger (and price ticks); no ledger contract change and no edit to the position, weight, or lot slices is required.
Edge cases
- Correction after the fact. A booking error is fixed by appending a compensating event, never by editing history — the folds absorb it like any trade.
- Tick before any trade. A price tick for an instrument with no position is a no-op for positions and weights, not an error.
- Replay divergence. If the incremental analytic and a full replay ever disagree, that is a hard correctness fault (NFR-002), not a rounding footnote.
3 · Requirements
Conformance keywords (
Functional
An append-only trade ledger
Rationale
Making the fast path and the replay path the same function is what makes NFR-002 (reconcile-to-replay) checkable rather than aspirational.
The platform
The platform
Each analytic
Non-functional
End-to-end tick-to-analytic latency
Every incremental analytic
Given unlimited trade history, steady-state memory
Out of scope (deferred to child slices)
- The durable append-only log itself — sequencing, replay, snapshotting, and unlimited retention live in the ledger slice.
- Net position, average cost basis, and mark-to-market math live in the position engine.
- Mark-to-market portfolio weights, the gross/net denominator, and concentration rollups live in the weights slice.
- FIFO/LIFO lot relief and realized/unrealized gain live in the tax-lot slice.
- Ingestion and normalization of the raw price-tick feed (venue quirks, dedup, gap-fill) — assumed upstream here.
- Cross-account and cross-strategy netting of exposure — a later platform slice.
4 · Data model
This umbrella owns only the shared events every analytic folds over. Analytic-specific state (lots, weight books, position rows) belongs to the children.
TradeEvent- The atomic append:
{ seq, ts, account, instrument, side (buy|sell), qty, price, fees }. Immutable;seqis monotonic and gap-free. A correction is a new compensatingTradeEvent, never an edit. PriceTick{ ts, instrument, price }. Drives mark-to-market revaluation for positions and weights; never changes a position's quantity.Instrument- The traded symbol and its metadata (multiplier, currency). Reference data, assumed stable within a session.
Invariant: the state of every analytic at sequence N is a pure function of the ledger prefix [0, N]
and the price ticks up to N. Nothing an analytic exposes may depend on wall-clock time or arrival order beyond
the ledger sequence.
5 · Success criteria
An end-to-end run where one TradeEvent plus one PriceTick update position, weight, and tax-lot
analytics within SLO-latency, and a full replay to the same sequence reconciles all three
to the cent.
A new analytic is added as a fold over the existing ledger with zero changes to the ledger contract and zero edits to the position, weight, or tax-lot slices.
6 · Assumptions
A normalized price-tick feed is available upstream (deferred to TBD-market-data-feed); this platform consumes
ticks, it does not clean them.
Analytics run in-process, co-located with the ledger's hot tail; the sub-millisecond envelope (NFR-001) is an in-process target, not a network round-trip budget.
7 · Open questions
None outstanding — the single architectural decision (append-only ledger, analytics as pure folds) and the split into four child slices were settled up front; each child resolves its own domain questions.
8 · Conformance index
Auto-built from every <spec-requirement> in this document.
9 · Change log
- Initial umbrella draft. Establishes the append-only trade ledger as the single source of truth, analytics as deterministic folds (FR-001/FR-002), snapshot/restore (FR-003), and the cross-cutting reliability envelope (NFR-001 latency, NFR-002 reconcile-to-replay, NFR-003 bounded memory under unlimited history). Carves the domain into 002-trade-ledger, 003-position-engine, 004-portfolio-weights, and 005-tax-lots.