All projects
HardwareSoftwareIn Progress

DepthCharge

A physical limit-order-book display: an ESP32-S3 driving a 64×64 LED matrix behind smoked acrylic, rendering live depth first from my own matching engine (Anvil), then from Kraken and Binance — bids stacking green, asks red, trades flashing white at the touch, grey when the data can't be trusted. The portable C++20 book engine is proven on a desktop replay harness before it ever touches the microcontroller; custom carrier PCB and printed enclosure.

C++20ESP32-S3HUB75Order BooksWebSocketMarket DataKiCadPlatformIO

// Overview

DepthCharge is a desk-top market-data terminal built as an object: an ESP32-S3 driving a 64×64 HUB75 LED matrix behind smoked acrylic, rendering a live limit-order-book ladder. Asks stack red above the touch, bids green below, the spread is the dark gap between them, trade prints flash white where they land, and a last-price sparkline runs along the bottom. When the data can't be trusted the panel greys out and says why — a frozen ladder that still looks live is the one output the design refuses to produce.

It consumes three venues in ascending order of wire difficulty. First Anvil — my own C++20 matching engine, live at anvil.garethcooke.com — which publishes full top-N snapshots and per-fill trades; DepthCharge is the second independent client of its versioned wire contract, and building it needs zero changes to Anvil. Then Kraken, whose real L2 deltas self-verify against a CRC32 checksum over the top ten levels. Then Binance: a buffered diff stream bracketed against a REST snapshot with sequence-gap recovery, which is the graduation exercise. One book engine, one FeedEvent vocabulary, three adapters.

That engine is portable C++20 with no microcontroller headers in it at all. Transport lives outside the library; adapters take received frames as bytes and emit events, so the identical adapter logic runs under a captured replay file, a host WebSocket client, or esp_websocket_client on the target. The seam is where correctness lives: a desktop replay harness drives the engine from captured wire traces under ctest and renders the result as a console ladder, so behaviour is provable on the desk before it touches hardware. The trace worth watching is the reconnect one — 382 frames in, the feed goes quiet for 4.5 seconds, the ladder greys with a STALE — disconnect banner, and the resync snapshot brings it back. Nothing merges without a replay covering it.

Working against a real venue surfaced what a spec wouldn't. Anvil stamps every frame with a seq, but it's a single global engine counter shared across all tickers and frame types, so one socket's received subsequence is sparse and non-monotonic — M0 measured 42 backward steps in five minutes of healthy data. An adapter written to trust it would have declared a gap roughly nine times a minute. The adapter now synthesises its own monotonic sequence from receive order and never raises a sequence gap, which is safe precisely because Anvil's book frames are idempotent full replaces. Anvil also publishes no tick size or quantity step, so DepthCharge declares them per symbol and verifies every wire price is exactly representable at that scale — a mismatch is a reported error, never a silent rounding.

Where it is now: the engine shipped at M1 — Anvil adapter, phase-1 book and console ladder all green off captured traces — and the bench caught up at M2, with the DevKit, panel and PSU wired and the HUB75 DMA demo running. M3 is under way: the hand-off that carries a rendered snapshot from the feed task to the render task is now a real wait-free three-slot mailbox, built and ThreadSanitizer-clean, rather than the design intent it had been since the constitution was written. Ahead of it is live Anvil on the panel with the pull-the-Wi-Fi test, then the Kraken and Binance adapters, a custom KiCad carrier board around a WROOM-1-N16R8, and a printed enclosure with a smoked acrylic front and a rotary encoder for symbol, venue and price zoom.