# Examples: end-to-end order book reference

This folder contains two reference implementations that together show an end-to-end “matching engine + durable event log + queryable read model + client” structure for an order book.

- `live.rs`: a local server that runs the engine, persists events to a WAL, maintains a projection (read model), and exposes HTTP + WebSocket APIs.
- `live_tui.rs`: a terminal UI client that consumes snapshots over WebSocket, sends commands back to the server, and can run embedded trading bots for load/behavior testing.

The concrete market simulated by `live.rs` is a **3-runner soccer market** (Home/Draw/Away), but the architectural pattern is the point: treat the order book as a **command → event → projection** pipeline.

## Quick start

In two terminals:

1) Start the server:

```bash
cargo run --example live
```

1) Start the TUI client:

```bash
cargo run --example live_tui
```

Optional server tuning (disruptor ring size must be a power of 2):

```bash
cargo run --example live -- --ring-size 32768
```

Use a blocking wait strategy for lower idle CPU usage:

```bash
cargo run --example live -- --wait-strategy blocking-wait
```

You can also query the server directly:

```bash
curl http://127.0.0.1:3000/snapshot
curl http://127.0.0.1:3000/risk
curl http://127.0.0.1:3000/completed_orders
curl http://127.0.0.1:3000/metrics
```

## `live.rs` (server): the end-to-end pipeline

At a high level `live.rs` splits the system into:

1) **Write path (commands)**: client submits commands (place/cancel/admin) to the engine.
2) **Event log (WAL)**: accepted commands produce events that are appended to durable storage.
3) **Read path (projection)**: a projection consumes the event stream and builds queryable state.
4) **API layer**: HTTP endpoints for ad-hoc reads, and WebSockets for streaming snapshots + control.

### Components and responsibilities

**Engine (authoritative state + matching)**

- The engine owns the authoritative order book state.
- Clients interact with it via `Command` (e.g. `PlaceOrder`, `CancelOrder`, `SetMarketState`, `SetMarketPhase`, `CloseMarket`, `SettleMarket`, `VoidMarket`).
- Command submission happens through an `EngineHandler` (`state.engine.submit(...)` / `submit_with_response(...)`).

**WAL (durable event stream)**

- The engine’s outputs are journaled to an LMDB-backed WAL (`WAL_PATH`).
- On restart, the engine can be rebuilt from prior state (either from the WAL alone, or from an engine root snapshot if present).

**Projection (read model, decoupled from the engine)**

`ProjectionHandler` is registered as an engine handler and receives the same `JournalEvent` stream that is persisted.

It maintains two complementary read-side structures:

- `EngineRoot` (`ProjectionHandler.root`): a compact, query-friendly view that can generate `MarketSnapshot` (state, phase, and a tagged `book` payload for exchange or binary depth).
- `LiveState` (`ProjectionHandler.live_state`): an explicitly materialized “ops” view for convenience queries:
  - per-order lifecycle (open vs terminal state),
  - a trade table (including voided trades),
  - derived sets like `open_orders` to make risk computations fast.

The projection keeps an in-memory `ProjectionSnapshot` (`projection_snapshot`) updated at end-of-batch, which is what the HTTP/WS endpoints serve.

### The data flow (command → event → projection → snapshot)

1) A client sends a JSON `ClientMsg` over `ws_ctl` (or you can call engine APIs directly inside the process).
2) The server translates `ClientMsg` into a `betex::book::protocol::command::Command`.
3) The engine validates and matches:
   - emits `BookEventEnvelope` events (order accepted/cancelled, trade matched/voided, market state transitions, batch-process markers, …),
   - appends them to the WAL,
   - optionally returns a per-command `Response` (used for acks).
4) `ProjectionHandler` consumes the `JournalEvent` stream:
   - asserts monotonic sequence (`event.seq`) and applies to `EngineRoot`,
   - applies a subset of events to `LiveState` for “live” queries,
   - periodically refreshes the shared `ProjectionSnapshot` for readers.
5) HTTP endpoints and WebSocket snapshot streams serve the latest projection snapshot.

### APIs exposed by `live.rs`

**HTTP**

- `GET /snapshot`: one JSON snapshot of the market using the `book { kind, data }` shape.
- `GET /completed_orders?market_id=&account_id=&limit=`: recent terminal orders observed by the projection. `account_id` is a string filter.
- `GET /risk?market_id=&account_id=`: simple risk/PnL summary derived from projection trades + open orders. `account_id` is a string filter.
- `GET /metrics`: engine/WAL/projection metrics (polled by the TUI).
- `GET /health`: health probe.
- `GET /debug/live_counts`: sizes of in-memory projection tables (orders, trades, …).
- `GET /v1/engines`: list engines known to the control plane.
- `POST /v1/engines` (JSON): create a new engine instance (defaults to `market_ids=[1]`).
- `POST /v1/engines/{engine_id}/stop?remove=1`: stop an engine (optionally remove it from the control plane).
- `DELETE /v1/engines/{engine_id}/books/{market_id}`: detach a market from the served projection (engine-level, not persisted).
- `GET /v1/engines/{engine_id}/snapshot?market_id=&depth=`: per-engine snapshot endpoint (and similarly for `/completed_orders`, `/risk`, `/debug/live_counts`).

**WebSocket**

- `GET /ws?depth=N&market_id=M`: **read-only** stream of JSON `Snapshot` messages (every ~200ms).
- `GET /ws_ctl?depth=N&market_id=M[&no_ack=1]`: snapshot stream + control plane:
  - accepts JSON `ClientMsg` commands from the client,
  - periodically emits `Status` messages (global sequence + market count),
  - optionally emits `Ack` / `OrderResponse` messages for commands.
- `GET /v1/engines/{engine_id}/ws?depth=N&market_id=M`: per-engine snapshot stream.
- `GET /v1/engines/{engine_id}/ws_ctl?depth=N&market_id=M[&no_ack=1]`: per-engine snapshot+control stream.

`no_ack=1` is intended for throughput testing: the server does “fire-and-forget” command submission and avoids per-command response channels and websocket replies.

### Notes on durability and recovery

- The WAL path is stable (`target/live.lmdb`) so restarts can recover prior state.
- Additional engines created via `POST /v1/engines` use per-engine WAL directories under `target/live_engines/{engine_id}.lmdb`.
- `ProjectionDiskSnapshot` is present to support projection snapshots on disk (`PROJECTION_PATH`), but `ProjectionDiskSnapshot::save_json_ref` is currently a stub (the JSON write is commented out). This means projection state is effectively rebuilt from the event stream / engine root on startup.

If you want disk-based projection snapshots for crash recovery, re-enable `save_json_ref` and ensure the write is atomic (write-temp + rename), as sketched in the commented code.

## `live_tui.rs` (client): UI + bots over `ws_ctl`

`live_tui.rs` connects to the server’s control websocket (defaults to `ws://127.0.0.1:3000/ws_ctl?depth=10`) and runs three loops:

1) **WebSocket loop (`ws_loop`)**
   - reconnects with exponential backoff,
   - reads `ServerMsg` (`Snapshot`, `Status`, `Ack`, `OrderResponse`) and applies them to a shared `UiState`,
   - sends serialized `ClientMsg` commands from two channels:
     - user commands (keypress-driven, low volume),
     - bot commands (high volume; can be dropped/drained when pausing bots).

2) **Metrics loop (`metrics_loop`)**
   - polls `GET /metrics` over a simple TCP HTTP client,
   - derives rates (events/sec, commands/sec) over a sliding window,
   - computes sticky p50/p99 stage duration estimates from histogram deltas.

3) **UI loop (ratatui + crossterm)**
   - renders the latest snapshot and connection status,
   - provides key bindings for market/engine controls and bot toggles,
   - displays bot counters (orders sent, trades observed).

### Embedded bots

Bots are simple strategy generators that react to the current `MarketSnapshot` and emit `PlaceOrder` commands:

- **Market maker bots**: quote both sides across several ladder levels.
- **Noise trader bots**: place random small orders around the market.
- **Informed trader bots**: occasionally “sweep” (cross) when the snapshot looks favorable.

Correlation IDs encode `account_id` in the upper 32 bits (`(account_id << 32) | seq`), which lets the client attribute `OrderResponse` trades back to the originating bot for UI counters.

### Useful CLI flags

Run `cargo run --example live_tui -- --help` for the full list. Common ones:

- `--url <WS_URL>`: point the TUI at a different server or parameters.
- `--no-ack`: appends `no_ack=1` to the URL to suppress per-order acks (throughput mode).
- `--high-throughput`: faster bots + larger client buffers.
- `--bots <N>` / `--mm-bots` / `--noise-bots` / `--informed-bots`: control bot counts.
