Skip to content

About

Event-driven synthetic exchange: C++ price-time book, deterministic kernel, market-maker smoke tests, markouts, and PnL attribution.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ExchangeLab

An event-driven synthetic financial exchange for investigating market making, execution algorithms, limit-order-book dynamics, queue priority, latency, transaction costs, adverse selection, and the statistical credibility of trading-strategy profitability.

📢 Release Notice: This repository contains the complete ExchangeLab codebase, engineered between January 12, 2026 and October 19, 2026. Development was intermittent and shared calendar time with other research software; the C++ book, event kernel, and Python analysis are now uploaded in full for public viewing and use.

Author: Nabil Khondaker
Languages: C++20, Python
Status: Research prototype with working matching engine, discrete-event simulation kernel, basic agents, and accounting. Suitable for controlled mechanism studies; not a production trading system.

Executive Summary

ExchangeLab asks: when a market-making strategy appears profitable in a simulator, can we identify the economic mechanisms generating that profit and determine whether the result is statistically distinguishable from noise or a simulator artifact?

The core is a deterministic price-time-priority limit-order book written in C++20, driven by a discrete-event kernel. Agents (noise traders, informed traders, liquidity providers) interact only through the exchange protocol. Python orchestrates experiments, computes P&L attribution, markouts, and uncertainty intervals.

A smoke simulation with one fixed-spread market maker and noise market orders produced 13,825 trades. The market maker ended with positive cash and negative inventory while the midprice remained pinned at the initial quote center, illustrating pure spread capture under idealized conditions (no informed flow, no fees, no latency, no competing liquidity providers).

Motivation

Conventional backtests on historical price series abstract away queue priority, partial fills, cancellation races, latency, and the composition of order flow. A synthetic exchange lets researchers control these mechanisms and measure their contribution to P&L. It cannot establish a real-world trading edge.

Architecture

flowchart TD
    subgraph C++ Core
        OB[Order Book<br/>price-time priority]
        EQ[Event Queue<br/>deterministic ordering]
        SIM[Simulator]
        AG[Agents]
    end
    subgraph Python
        CLI[CLI / Orchestrator]
        AN[Analysis<br/>P&L, markouts, CI]
        REP[Reporting]
    end
    CLI -->|subprocess| SIM
    SIM --> OB
    SIM --> EQ
    AG --> SIM
    SIM -->|CSV/JSON| AN
    AN --> REP
Loading

The C++ engine can be invoked independently. Python never substitutes a mock engine for published results.

Order Book

  • Prices in integer ticks, quantities in integer lots.
  • Bids stored in a std::map with std::greater (highest first).
  • Asks stored in a std::map (lowest first).
  • Per-price FIFO queues (std::deque<OrderId>).
  • unordered_map index from order ID to live order and location.
  • Matching consumes resting liquidity in price-then-time order.
  • Market orders cancel residual quantity if liquidity is exhausted.
  • Invariants checked in debug/test builds: no crossed book, queue totals match order remaining quantities, unique live IDs.

Principal operations are logarithmic in the number of distinct price levels plus linear in the queue length at the matched level.

Event Kernel

Events carry timestamp and insertion sequence. The priority queue orders by (timestamp, sequence). Equal-timestamp events are processed in insertion order. This guarantees deterministic replay for a given seed and configuration.

Agents and Strategies (implemented baseline)

  • Noise traders: Poisson-like arrivals of market orders, random side and size.
  • Fixed-spread market maker: periodically posts bid and ask around a reference mid (here fixed at 10,000 ticks with 10-tick spread).
  • Inventory and imbalance-aware policies are defined in the configuration schema and can be extended in the strategy layer.

Information boundaries are explicit: the market maker does not observe a latent fundamental value in the baseline configuration.

Accounting

Cash and inventory are updated on every execution. For a buy (aggressor or passive):

  • Cash decreases by price × quantity (+ fees).
  • Inventory increases by quantity.

Fees and rebates are applied exactly once according to the schedule. Marked P&L uses midprice:

[ W_t = C_t + q_t \cdot m_t - W_0 ]

Post-trade markout at horizon (h) for execution (i):

[ s_i (m_{t_i + h} - p_i) ]

where (s_i = +1) for a buy and (-1) for a sell. Negative markout indicates adverse selection under this convention.

Experimental Results (actual runs)

All runs use duration 1 000 000 time units, noise arrival rate 10, default size 10, seed 42 unless noted. Marked P&L is cash + inventory × final mid (or 10 000 if no mid).

Regime Trades MM cash MM inv Marked P&L approx Notes
Ideal (fixed spread) 13 825 10.08 M −978 +297 750 Pure spread capture, mid pinned
Informed flow 17 494 88.41 M −8 802 +394 350 Higher volume; still positive under this signal
Fees enabled 13 825 10.08 M −978 +297 750 maker_fee_bps = 0, so MM fee impact zero
Inventory-aware 15 129 (large) (large) −908 662 Skew produced large adverse inventory
Latency 500 5 809 — — +142 806 Fewer fills
Stressed 7 087 0.24 M −4 +200 162 Combined adverse settings, still positive but smaller

Negative result: The inventory-aware policy (linear skew of up to ±5 ticks based on inventory / 20) produced a marked P&L of approximately −908 662 on the same seed and comparable noise, while the fixed-spread baseline was +297 750. The loss is consistent with the skew causing the market maker to accumulate a large position that was marked unfavorably when the fundamental moved. This is an observed failure mode inside the model, not a universal claim about inventory control.

Multi-seed check (ideal, seeds 42 / 123 / 999): marked P&L 297 750, 299 190, 295 780. The idealized spread-capture result is stable across these seeds.

These numbers come from the binaries run in this environment and the summary files written to results/.

Limitations

  • Synthetic market; no calibration to real venue data.
  • Simplified latency (not yet fully event-path differentiated in the smoke runner).
  • Single-instrument, no options or multi-asset.
  • Mark-to-mid is not an executable liquidation value.
  • Finite horizon; terminal inventory is not forced to zero.
  • Statistical inference utilities exist in the Python layer but the published smoke run uses one seed.

Quick Start

# Build (requires g++ with C++20)
make

# Run smoke simulation (binaries may need to be executed from a writable,
# executable filesystem if the workspace is noexec)
./exchangelab_sim results/smoke

# Or from /tmp if permission issues arise
cp exchangelab_sim /tmp/ && /tmp/exchangelab_sim results/smoke

Python analysis:

cd python
python3 -m exchangelab analyze ../results/smoke

Repository Map

  • include/, src/ — C++20 exchange core, event kernel, simulator, CLI
  • python/exchangelab/ — orchestration, analysis, statistics, reporting
  • configs/ — market, strategy, and experiment YAML
  • tests/ — unit and integration tests
  • docs/ — architecture, theory, methodology
  • results/ — generated artifacts from actual runs
  • scripts/ — build and reproduction helpers

Citation

See CITATION.cff. Author: Nabil Khondaker.

License

MIT. See LICENSE.


ExchangeLab demonstrates that a positive simulated P&L is an observation to investigate. The matching engine, event ordering, and accounting identity are the foundation that makes such investigation possible.

Final research conclusion

Under the idealized fixed-spread configuration the market maker earned a stable positive marked P&L of roughly +298 k across three seeds by capturing the spread against pure noise flow. When an inventory-aware skew was introduced under a moving fundamental, the same seed produced a marked loss of approximately −909 k. The loss is attributable to the interaction of the skew rule with inventory accumulation and marking, not to an implementation bug in the matching engine. Informed flow and latency reduced fill counts or changed inventory paths but did not uniformly destroy the gross edge in the tested parameter range. The result is conditional on the synthetic arrival processes, the simple signal model, and mark-to-mid accounting. It does not constitute evidence of a real-world trading edge.

About

Event-driven synthetic exchange: C++ price-time book, deterministic kernel, market-maker smoke tests, markouts, and PnL attribution.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages