Specification · 003-position-engine
Real-time position engine
Net position and cost basis per instrument, folded incrementally from the ledger and revalued on every price tick — and always equal to a full replay.
The position engine folds the ledger into live per-instrument state: a signed net quantity, a volume-weighted average cost basis of the open exposure, and a mark-to-market value revalued on each price tick. Every update is incremental and O(1) per event, yet the result must equal a from-zero replay. It handles the awkward cases that break naive averaging — a position crossing through zero, and corrections arriving as compensating events — without special-case mutation.
- I Independent
- Depends on the umbrella model and the ledger (002) as its input stream. Weights and tax lots consume it, not the reverse.
- N Negotiable
- Outcome (correct signed net, VWAP basis, MTM), not a named data structure or accumulation order.
- V Valuable
- Unlocks SC-001 — the desk's live "what do I hold, at what basis, worth what" view.
- E Estimable
- An incremental fold with a known revaluation step; the sign-flip and correction cases are enumerable.
- S Small
- One fold, five functional requirements; realized-gain and weighting are deferred to siblings.
- T Testable
- Net qty, basis, and MTM all reconcile to replay; latency is directly measurable.
1 · Context
Position is the most-read number on the desk and the input to almost everything else — weights, exposure, and (with lot detail) realized gain. Recomputing it from the whole ledger on every fill is impossible at HFT rates, so it must be an incremental fold; but an incrementally-maintained average cost basis is exactly where subtle drift creeps in, especially when a position flips from long to short. This slice keeps the fold correct and fast.
It owns net quantity, cost basis, and mark-to-market revaluation. Turning positions into portfolio weights is 004; splitting basis into tax lots and realizing gain is 005.
2 · User scenarios & testing
Each story MUST be independently testable.
US1 · Net quantity and basis from fills P1
As a desk engineer, I want each fill to update the instrument's signed net quantity and average cost basis incrementally, so my position is live without a batch recompute.
Acceptance: after any sequence of buys and sells, net quantity equals the signed sum of fills and the basis equals the VWAP of the currently-open exposure; both match a full replay.
US2 · Mark-to-market on a tick P2
As a risk viewer, I want a price tick to revalue held instruments, so mark-to-market value and unrealized P&L are current.
Acceptance: a tick for a held instrument updates its MTM value and unrealized P&L in O(1); a tick for an un-held instrument is a no-op.
US3 · Consistent cross-instrument snapshot P3
As a weights consumer (004), I want all positions as of a single ledger sequence, so I never read a torn mix of pre- and post-event state.
Acceptance: a snapshot at sequence N reflects every instrument's state at exactly N — no
instrument ahead of or behind another.
Edge cases
- Position crosses zero. A sell that flips long to short resets the cost basis to the crossing fill's price for the residual — the old long basis does not bleed into the new short.
- Flat position. A fully-closed instrument holds a net quantity of zero and carries no basis; it must not accumulate floating-point residue over millions of round-trips.
- Correction. A compensating event folds like any fill; there is no separate mutate-in-place path.
3 · Requirements
Conformance keywords (
Functional
The engine (account, instrument),
updated incrementally per fill, equal to the signed sum of fills folded from the ledger.
The engine
On each PriceTick the engine
The engine
Corrections
Non-functional
Per-trade position update
Per-tick mark-to-market revaluation
Net quantity and cost basis
Out of scope (deferred)
- Turning position market values into portfolio weights and concentration — the weights slice consumes positions.
- Splitting the aggregate basis into individual tax lots and realizing gain on sells under FIFO/LIFO.
- Splits, dividends, and symbol changes that adjust quantity or basis outside a trade — a later slice.
- Translating non-base-currency positions into a common reporting currency.
4 · Data model
The engine's own state is a single row per open position; everything else it reads from the ledger.
Position{ account, instrument, netQty (signed), basis (VWAP of open exposure), mtmValue, unrealizedPnl, asOfSeq }.- Revaluation input
- The umbrella's
PriceTick; drivesmtmValueandunrealizedPnlonly, nevernetQty.
Invariant: aggregate cost basis here is the position-level VWAP. The lot-level basis needed for realized-gain relief is a different decomposition and belongs to 005 — the two must agree in aggregate but are maintained separately.
5 · Success criteria
For any generated fill-and-tick stream, the engine's net quantity, VWAP basis, and mark-to-market value match a from-zero replay to the cent, including across at least one sign-flip and one correction.
A price tick revalues a held instrument and is a verified no-op for an un-held instrument, both within SLO-revalue.
6 · Assumptions
The ledger (002) delivers fills in gap-free sequence order; the engine relies on that ordering rather than re-sorting.
Instrument reference data (multiplier, currency) is stable within a session; corporate actions are deferred.
7 · Open questions
None outstanding — the sign-flip basis-reset rule and the read-consistency (snapshot-at-sequence) contract were settled in the interview; lot-level decomposition is explicitly out of scope, not open.
8 · Conformance index
Auto-built from every <spec-requirement> in this document.
9 · Change log
- Initial draft. Child of 001-portfolio-analytics: incremental net quantity (FR-001), VWAP cost basis with sign-flip reset (FR-002), per-tick mark-to-market (FR-003), consistent cross-instrument snapshot (FR-004), correction-as-fold (FR-005). Envelope: update latency (NFR-001), revalue latency (NFR-002), reconcile-to-replay (NFR-003).