Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Axis Exchange

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.


Highlights

  • 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

Architecture

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.


Matching Engine

Each symbol is backed by its own OrderBook.

Orders are matched using:

  1. Best price
  2. 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.


Exchange Engine

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.

Guyanese Default Books

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.


Persistence and Recovery

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.

Snapshot Recovery

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.


REST API

The Crow-based API exposes the exchange over HTTP.

Core Routes

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

Example Order Submission

{
    "symbol": "TST",
    "side": "BUY",
    "type": "LIMIT",
    "quantity": 10,
    "priceTicks": 10000
}

priceTicks is stored as an integer to avoid floating-point price errors.


WebSocket Trade Stream

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

Testing

Axis combines low-level correctness testing with end-to-end integration coverage.

C++ Tests

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-failure

For single-config generators such as Ninja, omit -C Release.

Node.js Integration Tests

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:integration

Persistence 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-removed

Benchmarking

Axis 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=ON

For Visual Studio:

cmake --build build-bench --config Release --parallel

Run:

./build-bench/benchmarks/Release/axis_benchmarks

On Windows:

.\build-bench\benchmarks\Release\axis_benchmarks.exe

Build

Requirements

  • C++20-compatible compiler
  • CMake
  • Boost
  • Crow
  • nlohmann/json
  • GoogleTest for tests
  • Google Benchmark for benchmarks
  • Node.js 18+ for integration tests

Configure and Build

cmake -S . -B build -DBUILD_TESTING=ON
cmake --build build --config Release --parallel

Run the server:

./build/axis_api

On Windows with Visual Studio:

.\build\Release\axis_api.exe

The API listens on:

http://localhost:18080

Project Structure

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.


Continuous Integration

GitHub Actions validates the project on pushes and pull requests to main.

The workflow:

  1. Installs C++ dependencies
  2. Downloads Crow and nlohmann/json
  3. Configures CMake
  4. Builds Axis
  5. Runs C++ tests
  6. Installs Node.js dependencies
  7. Starts the API server
  8. Runs REST and WebSocket integration tests
  9. Restarts the server for persistence verification
  10. Confirms snapshot recovery and event replay
  11. Confirms removals remain removed after another restart

Design Decisions

Integer Prices

Prices are represented as integer ticks rather than floating-point values. This avoids rounding issues and makes comparisons deterministic.

Raw Core Snapshots

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.

Separate Live and Replay Execution

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.

Persistence-Aware Exchange Operations

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.


Current Status

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.


License

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.

About

C++ exchange simulator with a price-time priority matching engine, event persistence, snapshot recovery, REST APIs, WebSocket trade streams, tests, benchmarks, and CI.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages