Implementation design · 001-portfolio-analytics

Real-time portfolio analytics

How we will build it: one append-only ledger, analytics as pure folds, exact integer money, on Java 21 + Quarkus.

Status Draft Spec 001-portfolio-analytics Owner Brian Corbin · @briancorbinxyz Branch 001-portfolio-analytics Created Read time

Build the platform as an event-sourced core: a single append-only TradeLedger, and each analytic a stateful in-memory projection that folds TradeEvent and PriceTick. All money is exact integer fixed-point (scale 1e-4), so the fast path and a from-zero replay are the same integer function and reconcile bit-for-bit — the whole reliability case rests on that. The stack is Java 21 + Quarkus 3.37.3. The single biggest risk is that the spike's in-memory hot tail is not the durable, cold-tiering store the unlimited-history NFR ultimately needs — deferred deliberately and logged.

1 · Principles check

This umbrella carries the platform-wide principle conformance for all five specs; each child design checks only the principles most load-bearing for its slice and points here. Verified against v0.1.0 (enterprise, 20 principles).

PrincipleComplianceNotes
P-1 · Clarity over clevernessOKOne idea — fold a ledger. No framework magic on the hot path.
P-2 · Small, reversible changesOKSliced into five specs; each analytic is an independent projection addable/removable without touching the ledger.
P-3 · Explicit over implicitOKMoney scale, denominator basis, and relief policy are all explicit, never inferred.
P-4 · One set of conventions, enforcedOKSpotless/format + a single money representation; jacoco in the build.
P-5 · Record the whyOKDecisions D-001..D-004 below carry context + consequences.
P-6 · Tests accompany behaviorOKTDD per user story; reconcile-to-replay is the anchoring test.
P-7 · Done means verifiedOKEach slice closes its SC via a passing test; the platform is verified by the end-to-end reconcile run.
P-8 · Ship dark by defaultOKAnalytics REST behind portfolio.analytics.enabled, default false.
P-9 · Decisions are groundedOK§3 evidence table; the stack decision rests on a toolchain probe run this turn.
P-10 · The enforcement floor holdsOKBuild fails on format, test, or coverage regression; no waivers.
P-11 · Contract-first interfacesOKOpenAPI generated via smallrye-openapi at /q/openapi; each analytic an independently versioned contract (spec FR-004).
P-12 · Library-first boundariesOKDomain (model/ledger/position/weights/tax) is pure Java with no Quarkus imports; the framework touches only api/ and the wiring bean.
P-13 · Secure by defaultOKRead-only analytics, no secrets, strict CSP inherited by artifacts; no user-supplied code path.
P-14 · Least privilegeOKIngest is a single logical writer; consumers get read views, never the mutable projection.
P-15 · Structured observabilityOKMicrometer timers on every fold step; Prometheus at /q/metrics — the SLO instrument.
P-16 · Service level objectivesOKEvery NFR across the five specs carries a <spec-slo>; the timers measure exactly those.
P-17 · Semantic versioningOKModule 1.0.0-SNAPSHOT; the analytic contracts version independently (FR-004).
P-18 · Supply-chain hygieneOKPinned Quarkus BOM; dependency-check is a wired-but-optional build step for the spike.
P-19 · Supply-chain provenancen/aSBOM + build attestation deferred for the spike — not exercised; would land before any real release.
P-20 · Accessibility conformancen/aBackend analytics platform with no end-user UI; the spec-family artifacts themselves are accessible HTML.

2 · Technical context

Language(s)
Java 21 (Temurin 21.0.9 LTS).
Frameworks
Quarkus 3.37.3 (quarkus-rest, rest-jackson, arc, micrometer-registry-prometheus, smallrye-openapi); build via Maven.
Architecture
Event-sourced modular monolith: a ledger plus one projection per analytic. Domain is framework-free (P-12); Quarkus touches only api/ and the wiring bean.
Storage
In-memory append-only hot tail for the spike (single writer). Durable + cold-tiering store deferred to TBD-storage-engine.
Testing
JUnit 5 unit tests on the pure domain (no Quarkus boot, fast) plus one @QuarkusTest REST smoke via REST-assured. The anchoring test is reconcile-to-replay.
Coverage
Diff-aware via jacoco: patch target ≥ 90% on touched domain code, and a change never lowers coverage of the lines it touches. No single universal %.
Perf targets
The umbrella envelope: tick-to-analytic p99 < 1 ms in-process; the child SLOs ladder beneath it (append 5 µs → position 25 µs → tax relief 40 µs → full-book reweight 500 µs).
Constraints
No floating point on the money path (exact integer fixed-point, scale 1e-4). Analytics off by default (ship dark). Single logical writer.

3 · Grounding & evidence

The design-bearing facts and how each was confirmed.

Claim the design rests onSourceStatusFinding / note
Java 21 and a current Quarkus are available in this environment asdf where java temurin-21.0.9; quarkus-bom/maven-metadata.xml verified Temurin 21.0.9 present; 3.37.3 is the latest stable Final (3.38 is a CR).
Exact integer money makes reconcile-to-replay bit-exact spec.html#NFR-002 (reconcile to the cent) verified An integer fold has no rounding divergence between fast path and replay — they are the same function.
Pure folds let a new analytic be added without touching the ledger spec.html#FR-001, #SC-002 verified A projection depends only on the event stream; the ledger has no analytic-specific coupling.
An in-memory hot tail meets the spike's append latency The append micro-benchmark (deferred) assumed Plausible for an ArrayList/array append; not yet measured — accepted as risk R-1, and the durable store is deferred anyway.

4 · Approach

The ledger is the spine. Producers append immutable TradeEvents; a market-data feed supplies PriceTicks. The AnalyticsPlatform bean fans each event into three projections — the position engine, the weight book, and the tax-lot book — every one a pure fold whose state at sequence N is a function of the prefix [0, N]. Because the fold is pure and the arithmetic is exact integers, the same code that maintains the live view also replays history to reconcile it. REST resources expose read views behind a feature toggle; Micrometer times each fold so the latency SLOs are measured, not asserted.

Producers trades · price ticks TradeLedger append-only · seq Position engine net qty · basis · MTM Portfolio weights gross/net · concentration Tax-lot book FIFO/LIFO · realized P&L REST + metrics read views (dark)
The ledger fans each event into three pure-fold projections; REST serves read views (dark by default) and Micrometer times each fold.

5 · Alternatives considered

OptionFast boot + built-in metrics/OpenAPIExact-math fit (JVM)Dev speed hereTotal
Java 21 + Quarkus (chosen)55515
Java 21 + Spring Boot35412
Go or Rust service44210

Quarkus wins on batteries-included observability (Micrometer) and contract-first OpenAPI with a fast dev loop, while the JVM's long arithmetic gives us exact fixed-point money for free. Spring Boot is close but heavier to boot; Go/Rust would cost dev velocity for no correctness gain here.

6 · Decisions

D-001 · Event-sourced projections over a single append-only ledger

Status
Accepted
Context
The spec's whole reliability case (spec.html#FR-001, #NFR-002) is that a live analytic must always equal a from-zero replay.
Decision
Model each analytic as a stateful in-memory projection that folds the ledger; the fast path and replay call the same fold function.
Consequences
+ Reconcile-to-replay is a mechanical test, not a hope. + New analytics need no ledger change. − Projection state must be rebuildable/snapshot-able; no analytic may hold hidden non-fold state.

D-002 · Exact integer fixed-point money (scale 1e-4)

Status
Accepted
Context
"Reconcile to the cent" (#NFR-002) is impossible to guarantee with binary floating point.
Decision
Represent every price and monetary amount as a long in units of 1e-4 currency (PRICE_SCALE = 10_000); quantities are whole longs. No double on the money path; portfolio weights use double only for the final display ratio.
Consequences
+ Bit-exact folds and audit-grade realized gain. + Tax-lot basis needs no division (a lot's basis-per-unit is the exact fill price). − A hard invariant to police (no stray double); − average-cost basis reduction uses deterministic integer division, which the position engine and tax-lot book reconcile in aggregate but decompose differently.

D-003 · Java 21 + Quarkus 3.37.3 on Maven

Status
Accepted
Context
Need built-in observability and contract-first OpenAPI with a fast loop; toolchain probe (§3) confirmed Java 21 + Quarkus 3.37.3 available.
Decision
Quarkus with rest, rest-jackson, arc, micrometer-registry-prometheus, smallrye-openapi; JUnit 5 + REST-assured; jacoco for coverage.
Consequences
+ Metrics, OpenAPI, and DI out of the box; + JVM exact-integer math. − Quarkus is pinned to a Java-21-compatible release (avoid the bleeding-edge Java 25 default on this machine).

D-004 · In-memory hot tail for the spike; durable store deferred

Status
Provisional
Context
Unlimited history + durability (#NFR-003, ledger #NFR-004) ultimately need a tiered durable store; the spike proves the folds, not the storage engine.
Decision
Back the ledger with an in-memory append-only structure; model snapshot/restore so the durable engine can slot in later. Durability + cold-tiering deferred to TBD-storage-engine.
Consequences
+ Fastest path to a working, testable platform. − The unlimited-history and no-loss SLOs are demonstrated in shape (snapshot bounds memory), not proven at scale — carried as risk R-1.

7 · Project structure

New paths, grouped by the D-001 boundary (framework-free domain vs. the thin Quarkus edge).

pom.xml                                  Quarkus BOM + extensions + jacoco
src/main/resources/application.properties  ship-dark toggle, metrics, openapi
src/main/java/io/spectastic/portfolio/
  model/     Side, TradeEvent, PriceTick, Money (scale)         # shared, spec 001
  ledger/    TradeLedger, Snapshot                              # spec 002
  position/  PositionEngine, Position                           # spec 003
  weights/   PortfolioWeights, WeightBasis                      # spec 004
  tax/       TaxLotBook, Lot, ReliefPolicy, RealizedGainRecord  # spec 005
  AnalyticsPlatform.java                 @ApplicationScoped wiring: ledger + projections
  api/       AnalyticsResource + DTOs    JAX-RS read surface (dark)
src/test/java/io/spectastic/portfolio/
  LedgerTest, PositionEngineTest, PortfolioWeightsTest,
  TaxLotBookTest, ReconcileToReplayTest, AnalyticsResourceIT

8 · Risks & mitigations

RiskLikelihoodImpactMitigation
R-1 · In-memory ledger is not the durable, cold-tiering store unlimited history needsHMSnapshot/restore modelled now so the durable engine slots in behind the same interface; scope-limited to the spike; deferred to TBD-storage-engine.
R-2 · A stray double creeps into the money path and breaks reconcile-to-the-centMHSingle money representation (D-002); reconcile-to-replay test would catch any drift; weights are the only sanctioned double, and only for a display ratio.
R-3 · Aggregate (position) and per-lot (tax) basis divergeMMAn explicit invariant test: Σ open-lot qty equals net position qty per instrument; the two are maintained separately by design.

9 · Complexity tracking

Where this design adds anything beyond the minimum the specs demand.

Maintaining two basis decompositions — aggregate average-cost in the position engine and exact per-lot in the tax-lot book — is deliberate duplication, not accidental: they answer different questions (current exposure vs. which shares were sold) and the specs assign them to different slices. They are kept honest by the aggregate-agreement invariant (R-3), not merged.

10 · Open questions

None outstanding — the architecture (event-sourced folds), the money representation (exact integer fixed-point), and the stack (Java 21 + Quarkus) were all settled and grounded this turn; storage durability is explicitly deferred to TBD-storage-engine, not left open.

11 · Change log

  1. Initial design. Event-sourced projections over a single append-only ledger (D-001), exact integer fixed-point money at scale 1e-4 (D-002), Java 21 + Quarkus 3.37.3 (D-003), and an in-memory hot tail with durable storage deferred (D-004). Carries the platform-wide 20-principle check for the child designs.