EdgeDepth: a real-time market data and replay platform, built solo

The EdgeDepth terminal

What it is

EdgeDepth records the crypto perpetual futures market in full detail and lets you play any moment of it back exactly as it happened. Not candles reconstructed after the fact, but the order book, the trade tape, the liquidations and every derived value in the precise configuration they held at that instant, scrubable on a timeline at variable speed.

I have been building and operating it since 2025. I wrote all of it: the C++ client, the Go backend, the research engine, and the infrastructure it runs on. It holds exchange connections open around the clock and pages me when it stops.

The terminal is open source and self-hostable with Docker.

The client: C++20 to WebAssembly

The terminal is C++20 compiled to WebAssembly with Emscripten, rendered through Dear ImGui, ImPlot and WebGL2 on SDL3, drawing dense real-time visualisations at up to 180 FPS in a browser tab.

The interesting engineering is not the rendering, it is deciding how each kind of data crosses the thread boundary. There are two answers, and the split is by data semantics rather than by convenience.

Order-book updates bypass the queue entirely. Only the latest book is ever drawn, so history has no value. The worker writes into a separate mutex-protected model and the render thread publishes it once per frame. Intermediate states are dropped on purpose, because rendering them would cost frames to display information nobody sees.

Discrete events go through a dispatch queue, in order, under a budget. Trades, liquidations and event detections all matter individually, so they cannot be dropped. They drain on the render thread under a 3 ms per-frame budget, with the remainder carried into later frames. A burst of ingest can fall behind, but it can never consume a whole frame and stall the UI.

That reasoning, along with the ownership rules and the invariants contributors must not break, is written up in the repo’s architecture document.

The EdgeDepth order flow column

The backend: Go across three sites

The backend runs as distributed services on bare-metal Linux hosts in three locations joined by WireGuard.

A deliberately thin producer holds the exchange connections for 558 instruments and does nothing but publish into NATS JetStream. Analytics consumers on the other side reconstruct order books, process concurrent streams, detect events, and deliver Protobuf over WebSocket to the terminal. Because the durable log sits between them, either side can be redeployed without losing data, which matters when the thing you are recording does not pause for your deploys.

Storage is tiered: a NATS tail for the immediate window, PostgreSQL and TimescaleDB with streaming replication to a standby for recent history, and Parquet on object storage for the archive.

I run all of it. Backups behind a daily automated integrity gate, Prometheus and Grafana, nginx, firewalling, and my own on-call.

Some numbers

  • 38.7 billion rows of historical depth backfilled across 171,146 files, 337 GiB, in 71 hours with zero errors
  • 844 instruments in the research universe, 400+ days of tick-level history
  • 1,000+ market events detected automatically and archived
  • 180 FPS in a browser tab, from C++ through WebAssembly

The research engine, and designing bias out

On top of the recording sits a query engine with a closed 39-feature grammar. Two design decisions define it.

Queries are content-addressed and byte-reproducible. The same query returns the same bytes, and the key that identifies it is derived from the query itself. A result you quote today can be regenerated and checked later.

Outcome fields are structurally excluded from predicates. You cannot filter on the thing you are trying to measure. This is the single most common way backtests lie to the people running them, and the usual defence is discipline: remember not to do it. Here it is not possible to express. The grammar rejects it, so selecting on the result is impossible by construction rather than by good intentions.

Counts come back with denominators and forward outcomes computed over every occurrence, not over the page you happened to look at.

The engine is also exposed to AI agents over the Model Context Protocol, via edgedepth-research-mcp.

The liquidation heatmap