Skip to content

AlexJReid/pitboss

Repository files navigation

pitboss

boss

pitboss is a tiny deterministic matching engine in C for exploring ordered, replayable systems.

pitboss uses a segmented append-only command journal as its durable input.

The journal is the source of truth: commands are input facts, events are engine decisions, and the book is derived state - rebuilt by replaying the journal, or by loading a checkpoint + retained journal tail.

Scope is narrow: one symbol, limit orders only, fixed capacities, and one mutation owner for sequencing, persistence, matching, and book updates.

See theory.md for limit-order-book background, docs.md for protocol and architecture details, and roadmap.md for current status and planned work.

Some mechanical C/docs/sanity tests and benchmarks were written with non-human assistance. The design/layout/trade offs are mostly human, for better or worse. I've taken some ideas from monoblok.

Why this exists

pitboss is not an exchange. The limit-order book is a compact workload for looking at how ordered systems sequence commands, persist decisions, emit events, replicate logs, and rebuild state.

Matching, persistence, replay, replication, and recovery stay small enough to reason about as one ordered system.

Some of the shape comes from a long-running interest in the ideas around event sourcing and the LMAX architecture.

This project is not trying to build a bad cover version of a 2010 approach in 2026, or an amalgam of every piece of software and every idea I find interesting. Clever people have given this shape a lot of thought; pitboss is meant to be simple and composable.

Why C?

I want pitboss to approach high performance even though it is only a lab project. C keeps what's happening front and centre.

In C, many of the Java workarounds are plainly nonsense: there is no reason to copy ceremony from a different runtime.

Safety is the obvious push back. Rust is great, and there would be good reason to use it here. For this project, however, I wanted to show the nuts and bolts of a system like this without layers of abstraction or boilerplate hiding the moving parts. It is simple C.

Text protocol example

Clients send newline-delimited commands such as NEW_LIMIT order_id side price qty.

Over TCP, an accepted and stored command returns +OK sequence event-count, followed by that many event lines. The sequence is the ordered command number; it is separate from both the order id and the journal byte position used by replication.

Assume the book already has resting ask order 19: sell 9 at price 101, and the next journal sequence is 19.

> NEW_LIMIT 41 S 200 10
< +OK 19 2
< ACCEPTED 19 41
< RESTING 19 41 S 200 10

> NEW_LIMIT 42 B 500 10
< +OK 20 3
< ACCEPTED 20 42
< TRADE 20 42 19 101 9
< TRADE 20 42 41 200 1

Order 41 is accepted at sequence 19 and rests because it does not cross. Order 42 is a buy with limit 500, so it can trade with asks priced 500 or less. It consumes the cheaper resting ask first: order 19 at maker price 101 for quantity 9, then order 41 at maker price 200 for quantity 1. The buy is fully filled, so there is no RESTING event for order 42.

Build

cmake -S . -B build
cmake --build build

Run

build/pitboss run input.txt

Use - for stdin:

printf 'NEW_LIMIT 1 B 100 10\nNEW_LIMIT 2 S 99 4\nCANCEL 1\n' | build/pitboss run -

The CLI appends binary command records to a segment journal directory, pitboss.journal.d by default, before applying them. Set PITBOSS_JOURNAL=none to disable journaling, or set it to a different directory. The configured path is a directory name; pitboss will create it when it does not exist. A path ending in .journal is still accepted if it names a directory, but examples use .journal.d to avoid implying a single file.

Journal writes use fixed 64-byte records, fixed-size segment files, and a 64 KiB write buffer. Set PITBOSS_JOURNAL_SEGMENT_SIZE=N to choose the segment data size, and PITBOSS_JOURNAL_RETENTION_SEGMENTS=N to retain only the newest N segments. The TCP gateway acks accepted commands only after their journal buffer flushes; the default group-commit timer is 1 ms. Set PITBOSS_JOURNAL_FLUSH_MS=N to change it, or PITBOSS_JOURNAL_FLUSH_MS=0 to flush on the next loop turn after accepted work reaches the sequencer. Set PITBOSS_JOURNAL_FSYNC=1 to fsync each flushed buffer. Gateway flush and fsync work runs off the libuv loop, but durable ack throughput is still bounded by the storage device's sync latency and group size.

With unlimited retention, the command journal is the canonical input. With bounded retention, recovery requires a checkpoint baseline plus the retained segment tail; do not enable bounded retention unless snapshots/checkpoints are kept as the baseline for pruned history.

Replay a sequenced journal and print the same events again:

build/pitboss replay pitboss.journal.d

The replay output format is described in docs.md.

Dump the derived book state after replaying a journal:

build/pitboss dump pitboss.journal.d

build/pitboss dump without a path reads PITBOSS_JOURNAL when it is set, otherwise pitboss.journal.d.

Create a binary checkpoint of the current derived book, then recover from that checkpoint plus any later journal records:

build/pitboss checkpoint pitboss.journal.d pitboss.snap
build/pitboss recover pitboss.journal.d pitboss.snap

When run or listen append to an existing segment journal, pitboss first replays retained segments silently so sequence numbers and the in-memory book continue from the recovered state. If old segments have been pruned, startup needs a checkpoint baseline before the retained tail.

TCP listener

The TCP listener is an I/O shell around the deterministic core. Socket framing, parse work, and stateless validation can run outside the sequencer; sequence assignment, journal append, matching, book mutation, monitor fanout, and replication fanout stay ordered on the libuv loop.

flowchart LR
  client["client commands"]
  gate["gate / validation"]
  sequencer["sequencer"]
  journal_append["journal append"]
  journal_flush["journal flush"]
  matcher["matcher"]
  events["events"]
  book["book state"]
  monitors["monitor clients"]
  replicas["warm followers"]

  client --> gate --> sequencer --> journal_append --> journal_flush --> matcher --> events --> book
  events --> monitors
  journal_flush --> replicas
Loading
build/pitboss listen 127.0.0.1 17077
build/pitboss listen 127.0.0.1 17077 17078
build/pitboss listen 127.0.0.1 17077 17078 17079
scripts/pitboss-client.py 127.0.0.1:17077
scripts/pitboss-client.py 127.0.0.1:17077 -c 'NEW_LIMIT 1 B 100 10'
scripts/start-replication-pair.sh

The TCP listener handles SIGINT and SIGTERM as graceful shutdown requests: it closes the listeners and active sessions, then runs the normal journal close path. Set PITBOSS_JOURNAL_FSYNC=1 when flushed journal buffers should be fsynced.

When the optional monitor port is present, monitor clients receive PITBOSS MONITOR 1, a current book snapshot, then a read-only live event stream.

When the optional replication port is present, warm followers can connect and tail the primary's sequenced journal bytes to maintain their own segment journal.

PITBOSS_JOURNAL=follower.journal.d build/pitboss follow 127.0.0.1 17079

The follower recovers its local journal, sends a byte/sequence cursor, then appends and applies primary record bytes after that point. If the cursor is older than the primary's retained segment floor, the follower needs a checkpoint baseline before it can tail the retained journal. Client ingress remains single-primary.

For operator-driven promotion of follower to leader, inspect the follower journal and compare it with the old primary monitor sequence. See scripts/start-replication-pair.sh and the temp utility scripts it generates.

Test

ctest --test-dir build
scripts/listener-smoke.sh
scripts/monitor-smoke.sh
scripts/replication-smoke.sh

Commands

Protocol, matching semantics, architecture notes, test commands, and current limitations live in docs.md.

About

pitboss is a tiny deterministic matching engine in C for exploring ordered, replayable systems.

Topics

Resources

License

Stars

Watchers

Forks

Contributors