Skip to content

v0.5.0 — Phase 5: reporter + dashboard (15/15)

Choose a tag to compare

@ChronoCoders ChronoCoders released this 16 May 03:25
· 46 commits to main since this release

Persistence and dashboard. The trading binary keeps writing in-process; a new pinned-thread reporter ships events to Postgres; a Go sidecar exposes REST + WebSocket; a React + TypeScript frontend renders the panels.

Verification status: 15/15 items in the Phase 5 fix pass are DONE, audited against the source, and all tests run live (including the two Postgres-dependent ones that previously skipped).

What is in

Persistence (C++)

  • PgReporter (src/db/pg_reporter.{hpp,cpp}): consumer thread pinned to cfg.reporter.core. Pimpl over libpqxx so the header is pqxx-free. DSN only from SPREADARA_PG_DSN, never config, never logged. Empty DSN → dry mode, trading keeps running.
  • Connection pool of cfg.reporter.pg_pool_min connections (default 2), round-robin per flush, lazy reconnect on failure of a single connection.
  • Batched flush: whole-batch transaction → retry whole-batch on failure → per-event single-row fallback (poison-pill skip). One bad event no longer kills a whole batch — verified live by PoisonPillRetainsGoodEvents.
  • Schema (trades, position_snapshots, system_events, daily_pnl) created idempotently via CREATE TABLE IF NOT EXISTS on connection 0 at startup. Verified live by SchemaCreationIdempotent. ts_ns columns store raw nanoseconds-since-epoch as BIGINT; dashboard converts client-side.
  • Daily P&L coalesces in-memory; flushes the closed-out day's final totals on UTC date rollover (flush BEFORE updating current_day_ — a bug fix caught during review) and on shutdown drain. Test asserts flushed CONTENT, not just count.
  • Producer wire-up: OrderManager::inject_fill pushes DbTrade; CircuitBreaker::set_halt_synchronously pushes critical DbSystemEvent; RiskManager::pre_trade_check pushes warn DbSystemEvent on every rejection; main.cpp pushes DbPositionSnapshot + DbDailyPnl every second.

Shutdown safety

  • shutdown_cancel_all in OrderManager cancels live orders and (if |inv| > flatten_threshold) sends a MARKET/IOC flatten via RestClient::place_market_order. Invoked from the SIGINT handler BEFORE client.stop() / ioc.stop() so the REST client is still operational. Shares a cancel_and_flatten_locked helper with on_halt. flattened log flag reflects actual market-order ack.ok.

REST client refinements

  • place_market_order for the flatten path (LIMIT + GTX is wrong for halt).
  • Price → min_tick, qty → qty_step rounding before formatting (Binance PRICE_FILTER / LOT_SIZE).
  • AmendAck::cancelled_only surfaces the half-state when cancel succeeds but the replacement place fails; pushes a critical SystemEvent. Slot-side wiring intentionally deferred to Phase 6's FIX/REST hybrid — documented at the half-state site so future readers don't mistake it for an oversight.
  • Suppressed double-log on curl transport failures (stage=http http=0 skipped when curl already logged stage=curl).

Dashboard backend (Go)

  • dashboard_backend/: standard library net/http + github.com/lib/pq only (CGO_ENABLED=0). Endpoints GET /api/snapshot, /api/trades, /api/pnl/daily, /api/events, GET /ws. CORS allowlist for localhost/127.0.0.1 + configurable production origin.
  • Manual RFC 6455: handshake via Hijacker, Sec-WebSocket-Version=13 check (426 on mismatch), origin allowlist enforced at upgrade (CORS does not gate WS), ping → pong dispatched via channel so the writer goroutine stays the sole writer on the connection.

Frontend (React + TypeScript)

  • dashboard/: Vite + React + TypeScript scaffold, plain CSS, dark background, 8 panels (quotes, inventory, P&L, circuit breaker, fill rate, latency, trades, events). WebSocket client with exponential-backoff reconnect. No npm install performed; run it yourself when you want to develop.

Tests

Six C++ binaries (test_orderbook, test_spsc, test_strategy, test_risk, test_execution, test_reporter) pass under -O3 -march=native -Wall -Wextra -Werror. Go tests pass (6 cases including version-reject, origin-reject, push interval). With SPREADARA_PG_DSN exported and the chronocoder Postgres role in place, the two live tests (SchemaCreationIdempotent, PoisonPillRetainsGoodEvents) execute against a real database — not GTEST_SKIP.

One-time local setup for live Postgres tests

sudo -u postgres psql -c "CREATE ROLE chronocoder LOGIN; CREATE DATABASE chronocoder OWNER chronocoder;"
export SPREADARA_PG_DSN="host=/var/run/postgresql dbname=chronocoder user=chronocoder"

Known limitations

  • daily_pnl carries cumulative-since-startup totals, not daily deltas. SUM(total) FROM daily_pnl over-counts when used as cum_total in /api/snapshot. Dashboard math fix is a Phase 6 follow-up.
  • amend_order slot-side wiring is dead code — RestClient::amend_order isn't called from OrderManager in Phase 5. The AmendAck::cancelled_only plumbing, half-state log, and SystemEvent push are all in place for the Phase 6 FIX/REST hybrid; the missing slot transition is documented at the half-state site.
  • Phase 7 geo-block still applies to fapi.binance.com REST from the current egress. WS resync remains the working fallback.

Not in scope yet

No user-data stream wiring fills back into inject_fill, no FIX execution, no live deployment. Phase 6+.