EuNEx Developers Guide

Euronext Optiq-Modeled Exchange Simulator
Version 0.5.0
C++20 Actor Architecture • Price-Time Priority Matching • FIX 4.4 • Kafka Bus

1. Overview

EuNEx (Euronext Exchange Simulator) is a C++20 actor-based matching engine that mirrors the Euronext Optiq production architecture. It implements the full order lifecycle: entry, validation, matching, market data dissemination, trade clearing, and FIX protocol connectivity.

FIX Clients TCP :9001 OEG Gateway ME Core Book (per sym) Price-Time Priority MDG Market Data Clearing House KafkaBus AI Traders x10 members ExecReport
Actor IsolationNo shared mutable state in the hot path
Lock-Free PipesEvent::Pipe for cross-actor comms
Fixed-Point Pricing8 decimals, PRICE_SCALE = 108
Price-Time PriorityMulti-level sweep matching
Recovery GatingMaster/Mirror for HA

2. Architecture

High-Level System Diagram

EXTERNAL CLIENTS Browser / FIX 4.4 / Direct API / REST HTTP :7860 TCP :9001 PRESENTATION LAYER (Python / nginx) nginx :7860 Dashboard :8090 Order Book, Charts, SSE OHLCV, SQLite Clearing House :8091 Leaderboard, P&L Holdings, Portfolios getSnapshot() getLeaderboard() C++ ACTOR ENGINE CORE 0 — Gateway FIXAcceptorActor TCP accept loop FIX 4.4 parse/build D, F, G → Events OEGActor Session validation Symbol routing Exec report fanout CORE 1 — Matching Engine (per symbol) AAPL Book MSFT Book GOOGL Book EURO50 Book KafkaBus* → Kafka topics • No locks in matching path Events CORE 2 — Market Data MDGActor BBO snapshots • Trade history (200) • Thread-safe reads Trade count • Volume • Last price per symbol CORE 3 — Post-Trade & AI ClearingHouseActor 10 members, capital, P&L Session → Member map AITraderActor 10 AI members Momentum / MeanRev / Rand TradeEvent + BookUpdate TradeEvent ExecReport → OEG KAFKA BUS (optional, Optiq KFK) eunex.orders eunex.trades eunex.market-data eunex.recovery.fragments NewOrderEvent → OEG → ME

3. Optiq Naming Alignment

EuNEx components mirror Euronext Optiq production terminology:

Optiq ProductionEuNEx ClassFile
OEActor (OE Gateway)OEGActorsrc/actors/OEGActor.hpp
LogicalCoreActor + BookMECoreActorsrc/actors/MECoreActor.hpp
Book (order book engine)Booksrc/common/Book.hpp
MDLimitLogicalCoreHandlerMDGActorsrc/actors/MDGActor.hpp
OEG FIX GatewayFIXAcceptorActorsrc/actors/FIXAcceptorActor.hpp
Clearing House (PTB path)ClearingHouseActorsrc/actors/ClearingHouseActor.hpp
Member Trading BotsAITraderActorsrc/actors/AITraderActor.hpp
Kafka Bus (KFK)KafkaBussrc/persistence/KafkaBus.hpp
RecoveryProxyRecoveryProxysrc/recovery/RecoveryProxy.hpp
IacaAggregatorActorIacaAggregatorsrc/iaca/IacaAggregator.hpp

OEG Order Entry Gateway — ME Matching Engine — MDG Market Data Gateway — KFK Kafka Bus

4. Actor Topology

Core Affinity Layout

CPU Core 0 OEGActor Symbol → Book routing FIXAcceptorActor TCP :9001, FIX 4.4 CPU Core 1 MECoreActor (AAPL) MECoreActor (MSFT) MECoreActor (GOOGL) MECoreActor (EURO50) KafkaBus* • No locks CPU Core 2 MDGActor BBO per symbol Trade history ← Dashboard reads CPU Core 3 ClearingHouseActor 10 members, P&L AITraderActor Momentum / MeanRev / Rand Event::Pipe Connections FIXAcceptor → OEG → MECoreActor MECore → OEG (ExecReport fanout) MECore → MDGActor (Trade + BookUpdate) MECore → ClearingHouse (Trade) AITrader → OEG (NewOrderEvent) MECore → KafkaBus (if enabled) Dashboard → MDGActor (thread-safe read) CH UI → ClearingHouseActor (thread-safe)

5. Data Flow

Order Lifecycle (New Limit Order)

Client (FIX 4.4) 35=D FIXAcceptorActor Parse FIX tags Map symbol → idx OEGActor Route by symbolIdx Validate session NewOrderEvent MECoreActor 1. KafkaBus.publishOrder() → eunex.orders 2. RecoveryProxy.cause() — persist fragment 3. Book.newOrder() — price-time matching 4. For each fill: a. TradeEvent → MDGActor b. TradeEvent → ClearingHouseActor c. KafkaBus.publishTrade() → eunex.trades 5. ExecReportEvent → OEGActor 6. BookUpdateEvent → MDGActor * if KAFKA_BROKERS set ExecReport OEGActor fanout → FIX, AI ClearingHouseActor session → member capital, holdings FIX 35=8 Client receives fill Trade + BookUpdate MDGActor snapshots • history Dashboard :8090 Clearing UI :8091

6. Core Components

6.1 SimplxShim — Actor Framework

The actor engine emulates the Tredzone Simplx framework API:

Simplx ConceptEuNEx Implementation
ActorBase class with event handlers, mailbox
Event::PipeLock-free cross-actor channel
ActorId{id: uint64, coreId: uint8}
EngineMulti-threaded scheduler with core affinity
CallbackTimer-like periodic invocation
AsyncServiceService locator pattern

6.2 Types

Price_t       = int64_t     // Fixed-point, PRICE_SCALE = 100'000'000
Quantity_t    = uint64_t
OrderId_t     = uint64_t    // Exchange-assigned order ID
ClOrdId_t     = uint64_t    // Client order ID
SymbolIndex_t = uint32_t    // Instrument identifier
SessionId_t   = uint16_t    // Client session identifier
MemberId_t    = uint16_t    // Clearing member identifier

6.3 Price Conversion

Price_t px = toFixedPrice(150.25);  // → 15'025'000'000
double  d  = toDouble(px);          // → 150.25

7. Event System

Events flow between actors via Event::Pipe. Each event struct inherits tredzone::Actor::Event:

EventDirectionPurpose
NewOrderEventOEG → MENew order submission
CancelOrderEventOEG → MECancel resting order
ModifyOrderEventOEG → MEModify price/qty (cancel-replace)
ExecReportEventME → OEGAck, fill, partial, reject
TradeEventME → MDG/CHTrade execution
BookUpdateEventME → MDG/AIBBO + depth snapshot
RecoveryFragmentEventME → PersistRecovery persistence

Usage Pattern

// In MECoreActor constructor:
oePipe_ = Event::Pipe(*this, oeGatewayId);
mdPipe_ = Event::Pipe(*this, marketDataId);

// Pushing an event (lock-free):
oePipe_.push<ExecReportEvent>(report, sessionId);
mdPipe_.push<TradeEvent>(trade);

8. Order Book & Matching

BIDS (descending) Price Orders (FIFO) 155.00 100 50 154.50 200 154.00 75 100 ASKS (ascending) Price Orders (FIFO) 157.00 200 158.00 100 150 159.00 300 SPREAD 2.00 std::map<Price_t, vector<Order>, std::greater<>> std::map<Price_t, vector<Order>, std::less<>>

Order Types & TIF

Order TypeBehavior
LimitRests on book if no match; price-time priority
MarketSweeps all levels; never rests (IOC behavior)
StopMarketParks until trigger price hit, converts to Market
StopLimitParks until trigger price hit, converts to Limit
Time-in-ForceBehavior
DayRests until end of session
IOCFill what's available, cancel remainder
FOKFill all or reject entirely

9. Recovery & IACA

RecoveryProxy

Master/Mirror gating for high availability. cause() always executes (both sides). effect() only on Master. recoveryEffect() only on Mirror during failover replay.

IACA Fragment Chains

Fragments form a tree. Completion check: sum(nextCount) == total_fragments - 1. On completion, the handler fires and generates IA SBE messages.

10. Kafka Bus

The Kafka Bus (src/persistence/KafkaBus.hpp) mirrors the Optiq KFK that connects ME to downstream consumers: MDG, PTB, Clearing, IDS, SATURN.

Topics

TopicContentKey
eunex.ordersRaw Order structssymbolIdx
eunex.tradesTrade structssymbolIdx
eunex.market-dataBBO snapshotssymbolIdx
eunex.recovery.fragmentsRecovery fragmentsoriginId:originKey
eunex.controlControl messages(reserved)

Compile-Time Toggle

cmake .. -DEUNEX_USE_KAFKA=ON    # requires librdkafka-dev
cmake .. -DEUNEX_USE_KAFKA=OFF   # no-op stub (default)

Runtime

export EUNEX_KAFKA_BROKERS=kafka:9092
./eunex_me

Docker Deployment

docker/docker-compose.yml runs Kafka in KRaft mode (no ZooKeeper) using apache/kafka:3.9.0, creates all topics via kafka-init, then starts the engine.

cd docker
docker compose up --build

11. FIX Protocol Gateway

TCP server on port 9001 with per-client receive threads. Parses FIX 4.4 messages and routes to the actor engine via Event::Pipe.

Tag 35MessageDirection
ALogonInbound
5LogoutInbound
0HeartbeatInbound
DNewOrderSingleInbound
FOrderCancelRequestInbound
GOrderCancelReplaceRequestInbound
8ExecutionReportOutbound

Symbol Mapping

Symbol StringSymbolIndex_t
AAPL1
MSFT2
GOOGL3
EURO504

12. Clearing House

Receives TradeEvent from MECoreActor. Maps sessions to 10 clearing members. Tracks capital, holdings per symbol, P&L, and trade counts.

Session RangeMembersSource
100-109MBR01-MBR10FIX gateway clients
200-209MBR01-MBR10AI traders

On each trade: Buy side reduces capital, updates weighted average cost. Sell side adds proceeds, realizes P&L. getLeaderboard() returns members sorted by capital (thread-safe, mutex-protected).

13. AI Trading Members

10 automated trading members with 3 rotating strategies:

StrategyLogic
MomentumIf last N prices trending up → BUY. Down → SELL. Follow the trend.
Mean ReversionIf price > moving average → SELL. Below → BUY. Fade the move.
RandomRandom side (50/50), random qty (10-100), price around midpoint.

Data sources: BookUpdateEvent (BBO), TradeEvent (price history), ExecReportEvent (fill confirmations).

Output: NewOrderEvent → OEGActor via Event::Pipe, ~3s intervals via Actor::Callback.

14. Project Structure

EuNEx/
├── CMakeLists.txt                    Build configuration
├── README.md                         Project overview
├── src/
│   ├── main.cpp                      Entry point, actor wiring
│   ├── common/
│   │   ├── Types.hpp                 Core types (Price_t, Order, Trade)
│   │   ├── Book.hpp / Book.cpp       Price-time priority matching
│   ├── engine/
│   │   └── SimplxShim.hpp            Actor framework (Simplx API)
│   ├── actors/
│   │   ├── Events.hpp                Inter-actor event definitions
│   │   ├── OEGActor.hpp/cpp          Order Entry Gateway
│   │   ├── MECoreActor.hpp/cpp       Matching Engine core (per symbol)
│   │   ├── MDGActor.hpp/cpp          Market Data Gateway
│   │   ├── FIXAcceptorActor.hpp/cpp  FIX 4.4 TCP protocol gateway
│   │   ├── ClearingHouseActor.hpp/cpp Trade clearing & positions
│   │   └── AITraderActor.hpp/cpp     Automated trading members
│   ├── net/SocketCompat.hpp          Cross-platform sockets
│   ├── persistence/
│   │   ├── KafkaBus.hpp              Multi-topic Kafka publisher
│   │   ├── PersistenceStore.hpp      Store interface
│   │   └── KafkaStore.hpp            Kafka adapter (optional)
│   ├── recovery/RecoveryProxy.hpp    Master/Mirror recovery
│   └── iaca/
│       ├── Fragment.hpp              IACA fragment definitions
│       └── IacaAggregator.hpp/cpp    Fragment chain assembly
├── tests/                            7 test suites
├── examples/                         ping_pong, simple_match
├── dashboard/                        Flask web UI (:8090)
├── clearing_house/                   Flask clearing UI (:8091)
├── fix_gateway/                      Python FIX gateway (alt)
└── docker/
    ├── Dockerfile                    Multi-stage build
    ├── docker-compose.yml            Kafka + engine + nginx
    └── nginx.conf                    Reverse proxy config

15. Build & Test

Prerequisites

Build Commands

# Configure
cmake -B build -DEUNEX_BUILD_TESTS=ON

# Build
cmake --build build --config Release

# Run tests (7 suites)
cd build && ctest -C Release

# Run engine
./build/Release/eunex_me

CMake Options

OptionDefaultDescription
EUNEX_USE_SIMPLXOFFUse real Simplx framework
EUNEX_USE_KAFKAOFFEnable Kafka persistence
EUNEX_BUILD_TESTSONBuild test binaries
EUNEX_BUILD_EXAMPLESONBuild example binaries

Test Suites

SuiteCasesVerifies
OrderBookTest26Book matching, all TIFs, multi-level sweeps, cancels
MatchingEngineTestMECoreActor event handling
ThreadedEngineTestMulti-core Engine, mailbox
ClearingHouseTest7Capital, holdings, P&L, session mapping
FIXGatewayTest5Symbol mapping, actor lifecycle, OEG routing
AITraderTest6Strategy execution, multi-symbol, clearing
StopOrdersTest12Stop triggers, phases, IOP, uncrossing

16. Configuration

ParameterValueDescription
FIX port9001FIXAcceptorActor TCP listen port
Symbols4AAPL(1), MSFT(2), GOOGL(3), EURO50(4)
AI members10MBR01–MBR10
Initial capital100,000.0Per clearing member
AI trade interval~3sPer round in main loop
Price scale108Fixed-point decimal places
Kafka brokersEUNEX_KAFKA_BROKERSEnv var, e.g. kafka:9092

Seed Orders

AAPL:   Sell 155.00/100, 154.00/200  |  Buy 153.00/150, 152.00/100
MSFT:   Sell 325.00/100, 324.00/150  |  Buy 323.00/200, 322.00/100
GOOGL:  Sell 142.00/100, 141.00/200  |  Buy 140.00/150, 139.00/100
EURO50: Sell 5050.00/50, 5040.00/80  |  Buy 5030.00/60, 5020.00/40

17. Extending EuNEx

Adding a New Symbol

  1. Define a new SymbolIndex_t constant in main.cpp
  2. Create a MECoreActor passing OEG, MDG, CH actor IDs
  3. Register: oeGateway->mapSymbol(newSym, bookActor->getActorId())
  4. Add to AITraderActor's symbol list
  5. Add seed orders if desired

Adding a New Actor

  1. Create src/actors/MyActor.hpp inheriting tredzone::Actor
  2. Register event handlers: registerEventHandler<EventT>(*this)
  3. Create Event::Pipe members for destination actors
  4. Add .cpp to EUNEX_CORE_SOURCES in CMakeLists.txt
  5. Wire into topology in main.cpp

Adding a New Event Type

  1. Define struct in Events.hpp inheriting tredzone::Actor::Event
  2. Add onEvent(const MyEvent&) to receiving actors
  3. Register: registerEventHandler<MyEvent>(*this)
  4. Push: pipe.push<MyEvent>(args...)

Adding a New Trading Strategy

  1. Add enum value to Strategy in AITraderActor.hpp
  2. Implement strategyMyStrategy() method
  3. Add case to submitOrder() switch
  4. Assign to members in initMembers()