orderbook
Embeddable limit order book and matching engine with integer-exact pricing, a single-writer core, and write-ahead-log crash recovery
About orderbook
This large README is shown as a plain-text preview. Read the full README at the source
# orderbook
<p align="center">
<a href="https://intrepidkarthi.github.io/orderbook/console.html"><img src=".github/readme/demo.gif" alt="orderbook live console: a bid you place rests in the ladder, your market order prints on the tape, and surveillance names the spoofer" width="820"></a>
</p>
<p align="center"><b><a href="https://intrepidkarthi.github.io/orderbook/">▶ Live demo</a></b> — the real engine, compiled to WebAssembly, running in your browser · <b><a href="https://intrepidkarthi.github.io/orderbook/console.html">▶ Live console</a></b> — a running market with signals and surveillance, every panel titled by the call that produces it.</p>
[](https://pkg.go.dev/github.com/intrepidkarthi/orderbook)
[](https://github.com/intrepidkarthi/orderbook/actions/workflows/ci.yml)

[](LICENSE)
[](https://intrepidkarthi.github.io/orderbook/)
An embeddable limit order book and matching engine in Go. Integer-exact `int64`
pricing with no floating point on the money path, a match path that allocates
**0 B/op**, a single-writer core with no lock contention on the match path,
deterministic crash recovery gated in CI against a 2,000-command tape, and
`cmd/obgw` — a reference TCP gateway speaking a frozen binary protocol on both
edges, order entry and public market data. `go get` it into an exchange, a
simulator, or a backtester: the core owns the book, the matching algorithm,
order lifecycle, sequencing and market-data snapshots, while `pkg/wal` (durable
persistence), `pkg/surveillance` (market-abuse detection), `pkg/gateway`
(pre-trade admission) and `pkg/auction` (uniform-price call auction) cover the
layers around it. It has never run a live market, and
[what that costs you](#experimental-use-at-your-own-risk) is written down rather
than implied.
| What | Supported |
|---|---|
| Order types | limit, market; stop and stop-limit; trailing stop; iceberg; one-cancels-other; pegged to bid, ask or mid (priced at entry, not re-pegged) |
| Time in force | GTC, IOC, FOK, DAY, GTD |
| Flags | post-only |
| Amend | cancel; reduce, keeping queue priority; replace, losing it; cancel all for a user |
| Matching | price-time (FIFO), or pro-rata at each level (`Config.ProRata`) |
| Self-trade prevention | cancel newest, cancel oldest, cancel both, decrement, or allow |
| Sessions | halt, cancel-only, and a uniform-price call auction (`pkg/auction`) |
| Not provided | all-or-none, minimum fill per order, hidden (non-displayed) orders, re-pegging |
| Reached from | Go; C and Python ([C-API](docs/C-API.md), [Python](docs/PYTHON.md)); TCP ([obgw](docs/PROTOCOL.md)); FIX 4.4 order entry ([`pkg/fix`](docs/FIX.md)) |
---
## Installation
```sh
go get github.com/intrepidkarthi/orderbook/pkg/matching
```
Requires Go 1.23 or later.
**A running venue.** `docker compose up --build` starts the gateway, a market maker
quoting both sides, a little order flow, and the operator dashboard on
<http://127.0.0.1:8090> ([docs/EXCHANGE-IN-A-BOX.md](docs/EXCHANGE-IN-A-BOX.md)). CI
builds and smoke-tests it on every change to its parts.
**From C or Python.** `cmd/libobook` builds the engine as a C shared library behind a
versioned ABI ([docs/C-API.md](docs/C-API.md)). The Python bindings use the standard
library only, and ship with the library inside: `pip install obook` on Linux (x86_64,
aarch64) and macOS 12+ ([docs/PYTHON.md](docs/PYTHON.md)).
Before evaluating a change or trusting a claim, run the local gate:
```sh
go vet ./...
go test ./...
go test -race ./...
make bench
```
The benchmarks measure the core, not an end-to-end venue. Regressions are checked by a base-against-head comparison (`make bench-check`, and [a CI gate](docs/BENCH-GATE.md) that fails a push on an allocation increase in any core benchmark and on a timing regression in the four benchmarks its calibration cleared). See [docs/BENCHMARKS.md](docs/BENCHMARKS.md) for the measurement boundary and [docs/PRODUCTION-READINESS.md](docs/PRODUCTION-READINESS.md) before using the gateway with anything valuable.
---
## Quickstart
The engine works in integer ticks and lots. Pass them directly, or use an
`Instrument` to convert from decimals at the boundary. The snippets below are
API excerpts; [examples/basic](examples/basic/main.go) is the complete program
with imports and error handling.
Each call, then the book it leaves ([examples/quickstart](examples/quickstart/main.go);
the output below is that program's, checked by its test):
```go
eng := matching.NewEngine(matching.DefaultConfig("BTC-USD"))
eng.Process(limit("alice", types.SideSell, 101, 5)) // limit = types.NewOrder(..., OrderTypeLimit, ..., TIFGoodTillCancel)
eng.Process(limit("bob", types.SideSell, 101, 2)) // same price, behind alice in time
eng.Process(limit("carol", types.SideBuy, 99, 4))
eng.Process(limit("dave", types.SideBuy, 101, 6)) // crosses: alice first, then bob
eng.Process(limit("erin", types.SideBuy, 101, 1).AsPostOnly())
```
```text
(empty book)
eng.Process(limit("alice", types.SideSell, 101, 5))
-> NEW
ask 101 5 alice:5
------------
eng.Process(limit("bob", types.SideSell, 101, 2))
-> NEW
ask 101 7 alice:5 bob:2
------------
eng.Process(limit("carol", types.SideBuy, 99, 4))
-> NEW
ask 101 7 alice:5 bob:2
------------
bid 99 4 carol:4
eng.Process(limit("dave", types.SideBuy, 101, 6))
-> FILLED, traded 5 @ 101, traded 1 @ 101
ask 101 1 bob:1
------------
bid 99 4 carol:4
eng.Process(limit("erin", types.SideBuy, 101, 1).AsPostOnly())
-> REJECTED (post-only order would cross the spread)
ask 101 1 bob:1
------------
bid 99 4 carol:4
```
Decimals at the boundary, concurrent submission, and the zero-allocation path:
```go
// Decimals in, int64 ticks/lots out.
inst := types.NewInstrument("BTC-USD", decimal.RequireFromString("0.01"), decimal.RequireFromString("0.001"))
order, _ := inst.NewOrder("alice", types.SideBuy, types.OrderTypeLimit,
decimal.RequireFromString("30000.50"), decimal.RequireFromString("0.25"), types.TIFGoodTillCancel)
// Many producers, one matching goroutine.
r := matching.NewRunner(matching.RunnerConfig{Engine: matching.DefaultConfig("BTC-USD")})
defer r.Close()
r.SubmitAsync(order) // enqueue without blocking; result arrives on the returned channel
// Zero-allocation hot path: reuse the trade buffer across calls.
buf := make([]types.Trade, 0, 8)
buf, status, _ := eng.Match(order, buf[:0])
```
Or run the reference gateway and talk to it over a socket. Use a protected
accounts file; credentials passed with `-accounts` are visible in the host's
process list:
```sh
go run ./cmd/obgw -addr 127.0.0.1:9000 -symbol BTC-USD \
-accounts-file /path/to/accounts -tls-cert server.crt -tls-key server.key
```
`go run ./cmd/obgw -h` gives the account-file format and every other flag;
[docs/PROTOCOL.md](docs/PROTOCOL.md) says what the transport does and does not
protect. Without `-tls-cert` and `-tls-key` the venue speaks plaintext, which is
a loopback-development mode and nothing else. `cmd/obgw`'s tests are a
working client — login, enter, cancel, reduce, query, resume — and are the most
useful reference for writing another one. The protocol helpers live in
`server_test.go`. The gateway is a reference edge, not a claim that the
repository is a complete production venue.
Runnable, testable examples render on
[pkg.go.dev](https://pkg.go.dev/github.com/intrepidkarthi/orderbook/pkg/matching#pkg-examples).
---
## Experimental. Use at your own risk.
> **⚠ This is an experiment, not a product.** It has never run a live market. It has
> had no independent review. Its Go API and its wire protocol have each broken more
> than once in a single week, and the compatibility promise that now governs them
> ([docs/COMPATIBILITY.md](docs/COMPATIBILITY.md)) is days old. Known problems are
> documented rather than fixed — a venue left running with `-wal-retain` unset still
> becomes progressively harder to restart, because the command log then keeps every
> byte it has ever written and recovery reads all of it.
>
> **If you run this and something goes wrong — lost orders, a wrong book, money —
> that is yours, not mine.** The MIT licence's "without warranty of any kind" is the
> legal form of that sentence; this is the plain one. I am not responsible for what
> happens in your production, and nobody has run it in theirs.
>
> What this repository does offer is unusual specificity about its own limits. Every
> claim names the test or the measurement behind it, several sections exist only to
> correct earlier claims that were wrong, and
> [docs/PRODUCTION-READINESS.md](docs/PRODUCTION-READINESS.md) is a list of what
> would stop you. Read those before trusting any of this, not after.
**Scope.** *Embeddable* concedes the venue is not here — and no adjective in
this README claims what only a deployment can prove. Production readiness is a
property of a deployment, not of code:
[docs/PRODUCTION-READINESS.md](docs/PRODUCTION-READINESS.md) is the honest
account of what is solid, what is partial, and what is yours. Nobody runs this
in production today, and this paragraph will say so until someone does.
What ships: the matching core, durable recovery, an event stream proven to
reconstruct the book, an operator kill switch, a frozen binary protocol
([docs/PROTOCOL.md](docs/PROTOCOL.md)), and `cmd/obgw` — a reference TCP gateway
serving **both edges**: order entry with authentication, per-account outbound streams
and gap-free resume across a disconnect, and a public market-data feed with
snapshot-plus-delta recovery. Bounded backpressure at every stage. Around it:
`EngineSnapshot.Digest` (the book fingerprint replication and recovery agree on),
`examples/replication` (the drilled primary-backup reference), `cmd/obdash` (an
operator dashboard that is an ordinary subscriber of the venue's own feed), and a
[live console](https://intrepidkarthi.github.io/orderbook/console.html) running
the engine, signals and surveillance in the browser.
What does not, and is yours: **continuous operation** (the log rotates and a
covered prefix can be deleted, but retention is opt-in and unset by default, so a
venue left running without `-wal-retain` still becomes slower and hungrier to
restart every day it stays up — the first thing to set before running this for a
week), **credential lifecycle** (the reference speaks TLS and
holds secret digests, never plaintext — but rotation, revocation and expiry are
yours, and it says so), **multi-symbol routing** (order ids and
sequences are per-engine, so several symbols means several engines and a router
above them), **clearing and settlement**, and **any HA topology** — the library
ships the seams for primary-backup (deterministic apply, an ordered command log,
replay mode, snapshot bootstrap), proves them with a reference example and CI
drills ([docs/REPLICATION.md](docs/REPLICATION.md)), and deliberately not the
consensus, because bundling one forces a wrong answer on everybody. See
[docs/EXCHANGE-ARCHITECTURE.md](docs/EXCHANGE-ARCHITECTURE.md) for why, including
the venues that lost quorum getting it wrong.
What is offered here is that the pieces you build on are correct, tested, and
honest about their edges.
[docs/PRODUCTION-READINESS.md](docs/PRODUCTION-READINESS.md) is the checklist:
what ships, what is deliberately absent, what you would have to build — and which
gaps no library work can close, because they are properties of your deployment.
---
## Highlights
- **Integer-exact pricing.** The engine works in `int64` ticks and lots; a
per-symbol `Instrument` converts decimals only at the API boundary. No
floating-point on the money path.
- **Low latency, zero allocation.** O(1) cancel, pooled book nodes and price
levels, and a caller-buffer match path that allocates **0 B/op**. A realistic
cancel-heavy flow runs at **p50 83 ns · p99 167 ns · p999 250 ns** per
operation.
- **Single-writer core.** One matching goroutine owns the book, so nothing
contends its mutex on the match path (the LMAX model). A `Runner` fronts it
with an MPSC command queue so many producers can submit concurrently. Queue
order is the engine's order; deployments that need a venue-wide ingress policy
must sequence commands before they reach the runner.
- **Deterministic and recoverable.** The same ordered command stream produces
byte-identical trades and book state — enabling command-log replay, durable
WAL crash recovery (`pkg/wal`: write-ahead log + snapshots), and reproducible
backtests. `Runner.Checkpoint` and `wal.Recover` join the snapshot to its log
position, and the property is gated in CI against a 2,000-command tape:
checkpoint anywhere, recover, and the book, all three sequence counters, the
duplicate guard and the conditional-order state match the uninterrupted run. What
a snapshot bounds is the *replay* and the *parse*, not the read: recovery reads and
checksum-verifies every retained byte however recent the snapshot. What bounds the
read is retention — the log rotates into segments and a prefix of them is deleted
once a verified snapshot covers it, so restart cost is O(retained log) and the
retained size is a byte budget you set. That budget is **not set by default**, and a
venue that leaves it unset gets slower to restart every day it stays up. Measured in
[docs/PRODUCTION-READINESS.md](docs/PRODUCTION-READINESS.md); the design and its
costs are in [docs/LOG-ROTATION.md](docs/LOG-ROTATION.md) and
[docs/BOUNDED-RECOVERY.md](docs/BOUNDED-RECOVERY.md).
- **An event stream that reconstructs the book.** `Accepted`/`Trade`/`Canceled`/
`Replaced` replay into an L3 book identical to the engine's, asserted on every
commit across 28 scenarios covering every order class — including iceberg
refill, all five self-trade-prevention modes, FOK reversal, FOK against a
resting iceberg, cascade-fired stops and a bust.
- **Full order surface.** Limit, market, stop / stop-limit, iceberg (hidden),
post-only, pegged, OCO / bracket, and trailing-stop orders; GTC / IOC / FOK / DAY /
GTD time-in-force, with the venue holding the deadline rather than the client
remembering to cancel; self-trade prevention; a price-band circuit breaker; FIFO or
pro-rata allocation.
- **A trading session.** Pre-open accepts orders without matching them, so the book
accumulates and may legitimately cross; opening resolves it at a single clearing
price by auction, in price-time priority, so a venue never opens onto a crossed book.
The engine holds no calendar — it knows what each phase permits, not when phases
change, because that is the venue's business.
- **Market integrity & safety.** Opt-in pre-trade risk controls — fat-finger and
dust caps, per-account order limits, minimum resting time, client-order-id
idempotency, mark-price step **and** depth bounds, a self-output guardrail, and
timed band-breach pauses — plus a market-abuse surveillance suite (spoofing,
order-to-trade ratio, marking-the-close, ramping, pinging, cross-book), an
enforcing per-account rate gate, an operator kill switch, and a
uniform-price call auction. Each maps to a real case in
[docs/THREAT-MODEL.md](docs/THREAT-MODEL.md). The taker speed bump is an
observation hook, not enforcement — it reports, it does not delay.
- **A network edge that works.** `cmd/obgw` is a reference TCP order-entry
gateway: authentication defaulting to deny, a frozen binary protocol
([docs/PROTOCOL.md](docs/PROTOCOL.md)), per-account outbound streams that
outlive a connection, and gap-free resume — reconnect with your cursor and
receive the fills that landed while you were disconnected. A client can never
name another account's order, because the wire has no field for it.
The full client lifecycle is on the wire: six order types (limit, market, stop /
stop-limit, OCO, iceberg, pegged, trailing), **reduce in place keeping queue
position**, **atomic cancel/replace**, **mass cancel**, cancel-on-disconnect, and
**query your open orders** when resume is not available. Orders that outlive a
restart stay nameable and keep reporting their fills.
- **An admin edge, and a soak harness that used it.** `cmd/obgw -admin` serves
Prometheus metrics, `/healthz` and `/readyz` on a third port — counters off the
engine's own event stream, so they count what the book saw rather than what the
gateway believed it sent, and nothing a scrape touches goes through the command
queue. `cmd/obsoak` drives the venue at a sustained rate and reports what grows.
The longest run is four hours over three books — 14.4M messages, goroutines and
descriptors flat across 240 samples, no orphans ([docs/SOAK.md](docs/SOAK.md) §1e).
The first hour of ever running it found a defect that the whole test suite, two
fuzzers and the race detector had not: under load the venue refused cancels for orders live in its
own book, and filled to its order ceiling while reporting itself healthy. Fixed,
regression-tested, and written up in [docs/SOAK.md](docs/SOAK.md).
- **A market-data edge.** `cmd/obgw -mdaddr` publishes the venue's public feed on its
own listener: a snapshot naming the sequence it is consistent with, then incremental
level changes, trade prints and venue-state changes in one dense, gap-free stream.
A subscriber holding a cursor gets a gap-fill instead; one too far behind, or from
another venue incarnation, is refused explicitly rather than quietly resynchronised.
The guarantee — snapshot plus everything after its sequence equals the engine's book
— is asserted both in-process and end to end over a socket.
- **Market data.** L1 / L2 / L3 (market-by-order) snapshots with sequence numbers,
plus `marketdata.L2Feed` — incremental aggregated depth derived from the event
stream, coalesced per command, with absolute quantities so a subscriber that misses
an update recovers on the next one. Its test asserts the derived levels equal the
engine's own snapshot after every command, which is how a long-standing depth bug
was found.
- **Checked by engines that share no code with it.** In flash1's independent
harness the adapter reproduces the consensus report hash on every scenario,
passes the 34-case conformance gate and the 192-point state audit, and is
byte-identical to Liquibook on 100 scenario-seeds ([docs/FLASH1.md](docs/FLASH1.md)).
On a shared tape, geseq/orderbook, OrderBook-rs and CppTrader each produce the
same trades and final book ([docs/CROSS-ENGINE.md](docs/CROSS-ENGINE.md)).
- **Tested and benchmarked.** Race, fuzz, soak, and replay-recovery suites;
microbenchmarks run in CI on every push.
---
## Concurrency model
| | `matching.Engine` | `matching.Runner` |
|---|---|---|
| Contract | single writer — drive from **one** goroutine | safe for **concurrent** producers |
| Mechanism | direct calls, uncontended book mutex | MPSC command queue → one matching goroutine |
| Submit | `Process` (result) · `Match` (zero-alloc, into a buffer) | `Process` (enqueue + wait) · `SubmitAsync` (non-blocking) |
| Reads | `BestBid` / `Snapshot` / … (book has its own RW-lock) | same, delegated to the engine |
| Use when | you own the sequencing loop; benchmarks | any multi-goroutine service |
The bare `Engine` has no internal mutex by design; calling its mutating methods
from multiple goroutines is a data race. Use a `Runner`, or serialize to one
goroutine. See [docs/INTEGRATION.md](docs/INTEGRATION.md).
---
## Performance
Frequently Asked Questions
What is orderbook?
orderbook is a Financial library for the Go programming language. Embeddable limit order book and matching engine with integer-exact pricing, a single-writer core, and write-ahead-log crash recovery
How do I install orderbook?
Install orderbook with the Go module system using `go get intrepidkarthi/orderbook`. Check the repository for the current installation instructions.
What category does orderbook belong to?
orderbook is listed under Financial, specifically Financial.