Repository navigation
v0.8.0 — Phase 8: dashboard telemetry pipeline
Telemetry pipeline — every currently-empty dashboard panel now gets real data. Pure plumbing: no algorithmic changes to strategy, risk, or execution.
What is in
C++ writer side
src/db/pg_reporter.hpp— extendedDbPositionSnapshotwith 13 new fields (best_bid,best_ask,spread_bps,bid_qty,ask_qty,volatility,gamma,k,T,lat_p50_us,lat_p95_us,lat_p99_us,open_orders). ExtendedDbTradewithis_maker(postOnly orders are always maker).is_trivially_copyablestatic_assert preserved.src/db/pg_reporter.cpp— appended idempotentALTER TABLE ... ADD COLUMN IF NOT EXISTSblock tokSchemaSql; extended both the batched-flush and per-event INSERTs. Schema migration verified against the live 3925-rowposition_snapshotstable without disruption.src/market_data/tick_processor.hpp— addedcurrent_volatility()accessor so the 1 Hz snap_thread can pull the latest stdev.src/market_data/orderbook.hpp— documented single-writer / multi-reader contract now that snap_thread reads the book concurrently with tick_processor's consumer thread. Same Phase 3 PositionTracker pattern: telemetry-tolerant, no seqlock.src/risk/risk_manager.hpp— exposedopen_order_count()accessor (atomic load).src/execution/order_manager.{hpp,cpp}— RDTSC at SUBMITTED, delta on ACKNOWLEDGED, rolling 1000-sample circular buffer,latency_percentiles(p50, p95, p99)via snapshot-then-sort under a per-instance mutex. Lock orderingmu_→latency_mu_is one-way; snap_thread only takeslatency_mu_, no deadlock.is_maker = trueon every fill (inject_fill).submit_cycleslives onOrderSlotso a future async-ACK path (Phase 9 FIX, user-data stream) can compute latency across method boundaries.src/main.cppsnap_thread — populates all 13 newDbPositionSnapshotfields per second: order-book values fromprocessor.book(), volatility fromprocessor.current_volatility(), A-S params fromcfg.strategy.{gamma,k,horizon}, latency fromorder_manager.latency_percentiles(...)(single call, three out-params), open-order count fromrisk_mgr.open_order_count().
Go backend side
dashboard_backend/server.go— extendedSnapshotstruct with 13 telemetry fields + 4 derived (fill_count_10s,fill_count_60s,maker_ratio,fills_per_10s) +max_inventory_display.latestSnapshot()SQL pulls all 20 columns viasql.NullFloat64/NullInt64so pre-Phase-8 rows scan as 0 silently. NewfillStats()helper does a singleCOUNT(*) FILTER (WHERE ...)query (Postgres-specific) over the trades table, scoped to the last 60 s.COALESCE(is_maker, TRUE)covers any legacy rows where the column predates the schema migration./api/v5/status— extended withws_connected(proxy: anyposition_snapshotsrow in the last 5 s),pg_connected(realdb.Ping()— agent already wired during scaffolding),last_snapshot_age_ms. NewlastSnapshotTs()cheap single-column query avoids the wasteful fulllatestSnapshot()invocation just forts_ns.
Config + env
config/config.toml— new[dashboard]section (max_open_orders,max_inventory_display). Operator documentation; Go backend reads viaSPREADARA_MAX_OPEN_ORDERS/SPREADARA_MAX_INVENTORY_DISPLAYenv vars with sensible defaults.
Deviations from spec
Tcolumn renamed toT_param—Tis reserved-feeling enough that some Postgres clients quote it inconsistently; the C++ struct field staysT, only the SQL column differs.DOUBLE PRECISIONinstead of spec'dNUMERIC(18,8)— matches existing schema types (inventory,mid_price, etc.) and avoids pqxx ↔ numeric conversion friction. Telemetry values are lossy-OK./api/v5/statusfalls back tolatestSnapshot()if the reader is not*sqlReader— preserves the test-stub interface contract without forcing the stub to implement a new method.
Tests
- C++: 8/8 pass (
test_orderbook,test_spsc,test_strategy,test_risk,test_execution,test_reporter,test_backtest,test_integration) under-O3 -march=native -Wall -Wextra -Werror, zero warnings. - Go: 6/6 pass (snapshot handler, 404, trades handler, WS version reject, WS origin reject, WS push interval).
- Schema migration: verified idempotent —
ALTER TABLE ... ADD COLUMN IF NOT EXISTSsucceeded against the populated production-style DB.test_reporter::SchemaCreationIdempotentcontinues to pass (live PG required); zero-initializedDbTrade.is_maker = falsefrom existing tests inserts cleanly into the newBOOLEAN DEFAULT TRUEcolumn. - Sample
/api/snapshotJSON after a fresh boot showedbest_bid=78212.6,gamma=0.1,k=1.5,T_param=1.0,open_orders=0populated alongside the existing fields.
Review-pass tightenings (all applied before tag)
submit_cyclesmoved from stack-local toOrderSlot::submit_cycles(future-proofs the async-ACK path).OrderBooksingle-writer / multi-reader contract documented./api/v5/statuslastSnapshotTs()replaces the wastefullatestSnapshot()call (single-column query, ~10× cheaper at scale).
Not in scope
- Bid/ask still 0 until the trading binary writes real top-of-book values per tick. The snap_thread now pulls from
OrderBookbut only after the first depth message — pre-Phase-8 dashboard rows remain at 0. - Latency percentiles are 0 until the first ACK is captured.
- Fill counts are 0 until real fills occur on the demo book.
- Per-stream WS health (instead of the coarse 5 s freshness proxy) is a future enhancement.