Axis Exchange is a C++ exchange simulator built around a limit order book, matching engine, event persistence, replay, snapshots, REST APIs, and real-time trade streaming.
The project started as a CLI-based matching engine and has since been refactored into a reusable core library with a separate API layer. It is also shaped by a Guyanese market context: the server initializes a default set of books using symbols associated with publicly listed companies in Guyana. The goal is to keep the exchange logic independent from transport concerns while still exposing the engine through a practical service interface.
- Price-time priority matching
- Limit and market orders
- Partial fills and multi-level matching
- Order cancellation and modification
- Multiple symbol-specific order books
- Exchange-wide and symbol-level trade history
- Event persistence using JSONL records
- Recovery through snapshot restoration and event replay
- REST API built with Crow
- WebSocket trade broadcasts
- Thread-safe exchange access with
std::shared_mutex - GoogleTest-based C++ test suite
- Node.js REST, WebSocket, and persistence integration tests
- Google Benchmark performance suite
- GitHub Actions CI for build and test validation
Axis is split into a reusable exchange core and a separate server layer.
Client
|
| REST / WebSocket
v
Crow API Server
|
v
ExchangeEngine
|
+-- SymbolRegistry
+-- OrderBook
+-- TradeHistory
+-- RecordLog
+-- Snapshot / Replay
The exchange core does not depend on HTTP routes or console output. Core operations return structured results that can be used by the API layer, tests, benchmarks, or another application.
Each symbol is backed by its own OrderBook.
Orders are matched using:
- Best price
- Earliest sequence number at the same price
The engine supports:
- Resting limit orders
- Market orders
- Full fills
- Partial fills
- Matching across multiple price levels
- FIFO execution at each price level
- Modify and cancel operations
- Order lookup by ID
- Book and order snapshots
A direct order-location index is maintained so active orders can be located without scanning the full book.
ExchangeEngine manages the exchange at a higher level.
Responsibilities include:
- Registering and removing books
- Routing orders by symbol
- Coordinating modifications and cancellations
- Maintaining symbol and exchange trade history
- Persisting state-changing operations
- Restoring state after restart
- Managing exchange-level synchronization
The engine uses std::shared_mutex so read operations can share access while state-changing operations remain exclusive.
When the API server starts, it initializes a default group of order books for Guyanese-listed symbols, including names such as BDH, DIH, DDL, DTC, GSI, NCB, and RBL.
These books provide an immediate local-market structure for testing and keep the simulator grounded in a Guyanese exchange context.
Axis writes durable exchange records to a JSONL event log.
Persisted operations include:
- Book creation
- Book removal
- Order submission
- Order modification
- Order cancellation
Each order submission stores the generated metadata required for deterministic recovery, including:
- Order ID
- Sequence number
- Original timestamp
Replay does not generate replacement metadata. Stored values are passed back into the engine so recovered orders retain their original identity and ordering.
Replaying every event from the beginning becomes slower as the log grows, so Axis also writes versioned exchange snapshots.
A snapshot contains:
- Registered order books
- Resting buy and sell orders
- Remaining quantities after partial fills
- Order IDs
- Sequence numbers
- Original timestamps
- Next order ID
- Next sequence number
Recovery follows this flow:
Latest snapshot
|
v
Restore exchange state
|
v
Replay records written after the snapshot
Snapshots and events are stored in the same JSONL record log.
The Crow-based API exposes the exchange over HTTP.
| Method | Route | Description |
|---|---|---|
GET |
/ |
API welcome response |
GET |
/health |
Health check |
GET |
/symbols |
List registered symbols |
POST |
/books |
Create an order book |
GET |
/books/:symbol |
Get an order book snapshot |
DELETE |
/books/:symbol |
Remove an empty order book |
POST |
/orders |
Submit an order |
GET |
/symbols/:symbol/orders/:orderId |
Get an active order |
PUT |
/symbols/:symbol/orders/:orderId |
Modify an active order |
DELETE |
/symbols/:symbol/orders/:orderId |
Cancel an active order |
GET |
/trades |
Get exchange-wide trade history |
GET |
/trades/:symbol |
Get trade history for a symbol |
{
"symbol": "TST",
"side": "BUY",
"type": "LIMIT",
"quantity": 10,
"priceTicks": 10000
}priceTicks is stored as an integer to avoid floating-point price errors.
Real-time trade events are available through:
/ws/trades
Clients can subscribe to a symbol:
{
"type": "subscribe",
"symbol": "TST"
}Trade broadcasts use the following structure:
{
"type": "trade.executed",
"symbol": "TST",
"buyOrderId": 1,
"sellOrderId": 2,
"priceTicks": 10000,
"quantity": 10,
"aggressorSide": "SELL"
}The WebSocket layer supports:
- Connection-ready events
- Symbol subscriptions
- Symbol unsubscriptions
- Symbol-specific trade delivery
- Stream removal notifications
- Validation and error responses
Axis combines low-level correctness testing with end-to-end integration coverage.
GoogleTest is used for:
- Order book construction
- Input validation
- Limit and market order behavior
- Price priority
- FIFO priority
- Partial fills
- Multi-level matching
- Execution report correctness
- Book registration and removal
- Order routing
- Cancellation and modification
- Trade history
- Persistence replay
- Snapshot restoration
- ID continuation after recovery
Run the C++ tests with:
cmake -S . -B build -DBUILD_TESTING=ON
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failureFor single-config generators such as Ninja, omit -C Release.
The Node integration suite verifies:
- REST route behavior
- Book lifecycle
- Validation errors
- Order submission and modification
- Matching and trade history
- WebSocket subscriptions
- Symbol-specific trade delivery
- Unsubscription behavior
- Snapshot restoration
- Post-snapshot replay
- Order ID continuation
- Removal persistence after restart
From tests/integration:
npm install
npm run test:integrationPersistence flow:
npm run test:persistence:prepare
# restart Axis
npm run test:persistence:verify
npm run test:persistence:cleanup
# restart Axis
npm run test:persistence:verify-removedAxis uses Google Benchmark for repeatable engine-level performance measurements.
Current benchmark coverage includes:
- Resting limit-order submission
- Matching against one resting order
- Partial fills
- Matching across multiple price levels
- Matching many orders at one price
- Submission into a deep book
- Cancellation
- Modification
- Modify-to-cross behavior
- Book snapshot generation
- Order snapshot lookup
A recent Release build on a 4-thread 2.5 GHz system produced results in roughly these ranges:
| Operation | Approximate CPU time |
|---|---|
| Resting limit submission | ~0.6 µs |
| Single-order match | ~0.8 µs |
| Partial fill | ~0.8 µs |
| Cancel order | ~0.7 µs |
| Reduce quantity | ~0.6 µs |
| Modify price | ~1.0 µs |
| Modify to cross | ~1.0 µs |
| Active order snapshot lookup | ~9–11 ns |
Order snapshot lookup remained nearly flat from 1 to 10,000 active orders because the engine uses a direct order-location index.
Build benchmarks separately:
cmake -S . -B build-bench \
-DBUILD_TESTING=OFF \
-DBUILD_BENCHMARKS=ONFor Visual Studio:
cmake --build build-bench --config Release --parallelRun:
./build-bench/benchmarks/Release/axis_benchmarksOn Windows:
.\build-bench\benchmarks\Release\axis_benchmarks.exe- C++20-compatible compiler
- CMake
- Boost
- Crow
- nlohmann/json
- GoogleTest for tests
- Google Benchmark for benchmarks
- Node.js 18+ for integration tests
cmake -S . -B build -DBUILD_TESTING=ON
cmake --build build --config Release --parallelRun the server:
./build/axis_apiOn Windows with Visual Studio:
.\build\Release\axis_api.exeThe API listens on:
http://localhost:18080
axis-exchange/
├── .github/
│ └── workflows/
│ └── axis-ci.yml
├── .vscode/
├── api/
│ └── src/
│ └── server.cpp
├── benchmarks/
│ ├── CMakeLists.txt
│ └── OrderBookBenchmarks.cpp
├── build/ # Generated application build output
├── build-bench/ # Generated benchmark build output
├── core/
│ ├── include/
│ └── src/
├── data/
│ └── events.jsonl
├── tests/
│ ├── integration/
│ ├── server/
│ └── unit/
├── third_party/
│ ├── boost/
│ ├── crow/
│ └── nlohmann/
├── ws/
├── .clang-format
├── .gitignore
├── CMakeLists.txt
├── LICENSE
└── README.md
The layout may evolve as new benchmark and recovery scenarios are added.
GitHub Actions validates the project on pushes and pull requests to main.
The workflow:
- Installs C++ dependencies
- Downloads Crow and nlohmann/json
- Configures CMake
- Builds Axis
- Runs C++ tests
- Installs Node.js dependencies
- Starts the API server
- Runs REST and WebSocket integration tests
- Restarts the server for persistence verification
- Confirms snapshot recovery and event replay
- Confirms removals remain removed after another restart
Prices are represented as integer ticks rather than floating-point values. This avoids rounding issues and makes comparisons deterministic.
Core snapshots retain raw enum, timestamp, and price data. Formatting is handled at the API boundary.
This keeps order lookup lightweight and avoids forcing JSON-specific presentation work into the matching engine.
Live operations generate metadata and write records.
Replay operations use persisted metadata and do not write duplicate events.
This prevents recovered orders from receiving new IDs, timestamps, or sequence numbers.
State-changing exchange operations return structured persistence failures when a record cannot be written. Book creation is rolled back if its event cannot be stored.
Axis currently supports a complete exchange flow:
Book registration
→ order submission
→ matching
→ execution reports
→ trade history
→ WebSocket broadcast
→ event persistence
→ snapshot creation
→ restart recovery
The next areas of development are expected to focus on deeper performance analysis, persistence guarantees, concurrency stress testing, and broader market behavior.
Axis Exchange is licensed under the PolyForm Noncommercial License 1.0.0.
The project may be used, studied, modified, and distributed for permitted noncommercial purposes under the terms of that license.
Commercial use, including use in paid products, commercial services, internal business systems, or other revenue-generating activities, requires prior written permission and a separate commercial license from the copyright holder.
Copyright © 2026 Ezra Minty. All rights reserved except where expressly granted by the license.