# EuNEx Developers Guide **Version 0.9.5** | Euronext Optiq-Modeled Exchange Simulator --- ## Table of Contents 1. [Overview](#1-overview) 2. [Architecture](#2-architecture) 3. [Optiq Naming Alignment](#3-optiq-naming-alignment) 4. [Actor Topology](#4-actor-topology) 5. [Data Flow](#5-data-flow) 6. [Core Components](#6-core-components) 7. [Event System](#7-event-system) 8. [Order Book & Matching](#8-order-book--matching) 9. [Recovery & IACA](#9-recovery--iaca) 10. [Kafka Bus](#10-kafka-bus) 11. [FIX Protocol Gateway](#11-fix-protocol-gateway) 12. [Clearing House](#12-clearing-house) 13. [AI Trading Members](#13-ai-trading-members) 14. [Market Simulation](#14-market-simulation) 15. [AI Market Analyst](#15-ai-market-analyst) 16. [Developer Message Flow Visualizer](#16-developer-message-flow-visualizer) 17. [Engine Mode Switch](#17-engine-mode-switch) 18. [Project Structure](#18-project-structure) 19. [Build & Test](#19-build--test) 20. [Configuration](#20-configuration) 21. [Extending EuNEx](#21-extending-eunex) --- ## 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. ``` ┌─────────────────────────────────────────────────────────────────────┐ │ EuNEx v0.9.5 │ │ │ │ FIX 4.4 Clients ──► OEG ──► ME (per symbol) ──► MDG │ │ │ │ │ │ ◄── Exec Reports ◄───┘ │ │ │ │ │ Clearing House │ │ │ │ │ AI Traders (x10) │ └─────────────────────────────────────────────────────────────────────┘ ``` **Key design principles:** - Actor-per-core isolation (no shared mutable state in the hot path) - Lock-free `Event::Pipe` for cross-actor communication - Fixed-point pricing (8 decimal places, `PRICE_SCALE = 10^8`) - Price-time priority matching with multi-level sweep - Master/Mirror recovery gating for high availability --- ## 2. Architecture ### High-Level System Diagram ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ EXTERNAL CLIENTS │ │ Browser / FIX 4.4 / Direct API / REST │ └──────┬──────────────────┬───────────────────────────────┬───────────────────┘ │ │ │ HTTP :7860 TCP :9001 Direct API │ │ │ ╔══════╪══════════════════════════════════════════════════════════════════════╗ ║ PRESENTATION LAYER (Python / nginx) ║ ║ ║ ║ ┌──────────────────────┐ ┌──────────────────────┐ ║ ║ │ nginx (:7860) │ │ │ ║ ║ │ reverse proxy │ │ │ ║ ║ │ / → :8090 │ │ │ ║ ║ │ /ch/ → :8091 │ │ │ ║ ║ └───┬──────────┬───────┘ │ │ ║ ║ │ │ │ │ ║ ║ ▼ ▼ │ │ ║ ║ ┌──────────┐ ┌──────────┐ │ │ ║ ║ │Dashboard │ │Clearing │ │ │ ║ ║ │ (:8090) │ │House UI │ │ │ ║ ║ │ │ │ (:8091) │ │ │ ║ ║ │ Order │ │ │ │ │ ║ ║ │ Book │ │ Leader- │ │ │ ║ ║ │ Charts │ │ board │ │ │ ║ ║ │ OHLCV │ │ Holdings │ │ │ ║ ║ │ SSE │ │ P&L │ │ │ ║ ║ └────┬─────┘ └────┬─────┘ │ │ ║ ║ │ │ │ │ ║ ║ getSnapshot() getLeaderboard() │ ║ ║ getRecentTrades() (thread-safe reads) │ ║ ╚═══════╪════════════╪════════════════════════════════════════════════════════╝ │ │ │ │ └────────────┴────────┘ │ │ │ Thread-safe C++ API TCP :9001 │ │ ╔════════════════════╪════════════════════════════════════╪═══════════════════╗ ║ CORE 0 — Gateway ║ ║ ┌──────────────────────────┐ ┌──────────────────────────┐ ║ ║ │ FIXAcceptorActor │◄───┤ OEGActor ◄────┤ ║ ║ │ │───►│ │ ║ ║ │ • TCP accept loop │ │ • Session validation │ ║ ║ │ • FIX 4.4 parse/build │ │ • Symbol routing │ ║ ║ │ • Logon/Logout/HB │ │ • Exec report fanout │ ║ ║ │ • D, F, G → Events │ │ • Cancel, Modify │ ║ ║ └──────────────────────────┘ └──────────┬───────────────┘ ║ ╚═════════════════════════════════════════════╪═══════════════════════════════╝ │ NewOrderEvent │ CancelOrderEvent │ ModifyOrderEvent │ (Event::Pipe) ▼ ╔═════════════════════════════════════════════════════════════════════════════╗ ║ CORE 1 — Matching Engine (one MECoreActor per symbol) ║ ║ ║ ║ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ║ ║ │ MECoreActor │ │ MECoreActor │ │ MECoreActor │ │ MECoreActor │ ║ ║ │ AAPL (sym=1) │ │ MSFT (sym=2) │ │ GOOGL (sym=3) │ │ EURO50 (sym=4)│ ║ ║ │ │ │ │ │ │ │ │ ║ ║ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ │ ┌─────────┐ │ ║ ║ │ │ Book │ │ │ │ Book │ │ │ │ Book │ │ │ │ Book │ │ ║ ║ │ │ (bids) │ │ │ │ (bids) │ │ │ │ (bids) │ │ │ │ (bids) │ │ ║ ║ │ │ (asks) │ │ │ │ (asks) │ │ │ │ (asks) │ │ │ │ (asks) │ │ ║ ║ │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │ │ └─────────┘ │ ║ ║ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ ║ ║ │ │ │ │ ║ ║ └────────┬────────┴────────┬────────┴────────┬────────┘ ║ ╚═══════════════════╪════════════════╪════════════════╪═══════════════════════╝ │ │ │ ExecReport│ TradeEvent│ BookUpdate│ (→ OEG) │ (→ MDG) │ (→ MDG) │ │ │ │ ╔═══════════════════╪════════════════╪════════════════╪═══════════════════════╗ ║ KAFKA BUS (optional, Optiq KFK) │ │ ║ ║ ┌─────────────────────────────────────────────────────────────────────┐ ║ ║ │ KafkaBus (shared by all MECoreActors) │ ║ ║ │ │ ║ ║ │ • eunex.orders ← raw Order on entry │ ║ ║ │ • eunex.trades ← Trade on each fill │ ║ ║ │ • eunex.market-data ← BBO snapshot on book update │ ║ ║ │ • eunex.recovery.fragments ← recovery data │ ║ ║ │ │ ║ ║ │ Enabled: EUNEX_USE_KAFKA=ON + EUNEX_KAFKA_BROKERS env var │ ║ ║ │ Disabled: no-op stub compiles in (default) │ ║ ║ └─────────────────────────────────────────────────────────────────────┘ ║ ╚═════════════════════════════════════════════════════════════════════════════╝ │ │ │ ╔═══════════════════╪════════════════╪════════════════╪═══════════════════════╗ ║ CORE 2 — Market Data ▼ ▼ ║ ║ ┌─────────────────────────────────────────────────────────────────────┐ ║ ║ │ MDGActor │ ║ ║ │ │ ║ ║ │ • BBO snapshots per symbol (bestBid, bestAsk, depths) │ ║ ║ │ • Recent trade history (last 200 trades) │ ║ ║ │ • Trade count, volume, last price per symbol │ ║ ║ │ • Thread-safe reads for Python dashboard bridge │ ║ ║ └─────────────────────────────────────────────────────────────────────┘ ║ ╚═════════════════════════════════════════════════════════════════════════════╝ │ TradeEvent (→ Clearing) │ ╔═════════════════════════════════════════════╪═══════════════════════════════╗ ║ CORE 3 — Post-Trade & AI ▼ ║ ║ ┌─────────────────────────────┐ ┌─────────────────────────────┐ ║ ║ │ ClearingHouseActor │ │ AITraderActor │ ║ ║ │ │ │ │ ║ ║ │ • Session → Member map │ │ • 10 members (MBR01-10) │ ║ ║ │ • Capital tracking │ │ • 3 strategies: │ ║ ║ │ • Holdings per symbol │ │ momentum, mean_revert, │ ║ ║ │ • P&L calculation │ │ random │ ║ ║ │ • Leaderboard (sorted) │ │ • Periodic order bursts │ ║ ║ │ • Thread-safe for bridge │ │ • BBO + price history │ ║ ║ └─────────────────────────────┘ └──────────────┬──────────────┘ ║ ║ │ ║ ║ NewOrderEvent ║ ║ (→ OEG → ME) ║ ╚═════════════════════════════════════════════════════════════════════════════╝ ``` --- ## 3. Optiq Naming Alignment EuNEx components are named to match Euronext Optiq production terminology: ``` Optiq Production EuNEx Class File ═══════════════════════ ════════════════════ ═══════════════════════════ OEActor (OE Gateway) ───► OEGActor src/actors/OEGActor.hpp LogicalCoreActor + Book ───► MECoreActor src/actors/MECoreActor.hpp Book (order book engine) ───► Book src/common/Book.hpp MDLimitLogicalCoreHandler ───► MDGActor src/actors/MDGActor.hpp OEG FIX Gateway ───► FIXAcceptorActor src/actors/FIXAcceptorActor.hpp Clearing House (PTB path) ───► ClearingHouseActor src/actors/ClearingHouseActor.hpp Member Trading Bots ───► AITraderActor src/actors/AITraderActor.hpp RecoveryProxy ───► RecoveryProxy src/recovery/RecoveryProxy.hpp IacaAggregatorActor ───► IacaAggregator src/iaca/IacaAggregator.hpp ``` **Why these names?** - **OEG** = Order Entry Gateway (the Optiq front-end for all order flow) - **ME** = Matching Engine (the core `Book` + `LogicalCoreActor` in Optiq) - **MDG** = Market Data Gateway (publishes BBO, trades, depth to consumers) - **FIXAcceptor** = Optiq's FIX protocol acceptor component within OEG --- ## 4. Actor Topology ### Core Affinity Layout ``` ┌──────────────────────────────────────────────────────────────────────────────┐ │ PRESENTATION (Python) │ │ │ │ ┌─ nginx :7860 ────────────────────────────────────────────────────────┐ │ │ │ / → Dashboard :8090 /ch/ → Clearing House UI :8091 │ │ │ └──────────┬──────────────────────────────┬────────────────────────────┘ │ │ │ │ │ │ ┌──────────▼─────────────────┐ ┌────────▼───────────────────────┐ │ │ │ Dashboard (Flask :8090) │ │ Clearing House UI (Flask :8091)│ │ │ │ • Order Book, Charts │ │ • Leaderboard, P&L, Holdings │ │ │ │ • OHLCV, SSE streaming │ │ • Member portfolios │ │ │ │ • SQLite for history │ │ │ │ │ └──────────┬─────────────────┘ └────────┬───────────────────────┘ │ │ │ getSnapshot() │ getLeaderboard() │ │ │ getRecentTrades() │ (thread-safe reads) │ │ │ (thread-safe reads) │ │ └─────────────┼─────────────────────────────┼────────────────────────────────┘ │ │ ▼ ▼ ┌──────────────────────────────────────────────────────────────────────────────┐ │ C++ ACTOR ENGINE │ │ │ │ ┌─ CPU Core 0 ─────────────────┐ ┌─ CPU Core 1 ────────────────────┐ │ │ │ │ │ │ │ │ │ OEGActor │ │ MECoreActor (AAPL, sym=1) │ │ │ │ • Symbol → Book routing │ │ MECoreActor (MSFT, sym=2) │ │ │ │ • Exec report fanout │ │ MECoreActor (GOOGL, sym=3) │ │ │ │ │ │ MECoreActor (EURO50, sym=4) │ │ │ │ FIXAcceptorActor │ │ │ │ │ │ • TCP :9001 │ │ Each owns a Book instance │ │ │ │ • FIX 4.4 protocol │ │ KafkaBus* → Kafka topics │ │ │ │ │ │ No locks in matching path │ │ │ └───────────────────────────────┘ └─────────────────────────────────┘ │ │ │ │ ┌─ CPU Core 2 ─────────────────┐ ┌─ CPU Core 3 ────────────────────┐ │ │ │ │ │ │ │ │ │ MDGActor │ │ ClearingHouseActor │ │ │ │ • BBO per symbol │ │ • 10 members, capital, P&L │ │ │ │ • Trade history │ │ • Session → Member mapping │ │ │ │ • Snapshot queries │ │ │ │ │ │ ◄── Dashboard reads here │ │ AITraderActor │ │ │ │ │ │ • 10 AI members │ │ │ │ │ │ • Momentum / MeanRev / Rand │ │ │ └───────────────────────────────┘ └─────────────────────────────────┘ │ │ │ │ ┌─ KafkaBus (optional) ────────────────────────────────────────────────┐ │ │ │ eunex.orders │ eunex.trades │ eunex.market-data │ eunex.recovery │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────────────┘ ``` ### Event::Pipe Connections ``` FIXAcceptorActor ──NewOrderEvent──────► OEGActor ──NewOrderEvent──► MECoreActor ──CancelOrderEvent──► ──ModifyOrderEvent──► MECoreActor ──ExecReportEvent──► OEGActor ──ExecReportEvent──► FIXAcceptorActor ──ExecReportEvent──► AITraderActor MECoreActor ──TradeEvent──────► MDGActor ──BookUpdateEvent─► MDGActor ──TradeEvent──────► ClearingHouseActor ──publishOrder()──► KafkaBus → eunex.orders (if Kafka enabled) ──publishTrade()──► KafkaBus → eunex.trades (if Kafka enabled) ──publishMarketData()► KafkaBus → eunex.market-data (if Kafka enabled) AITraderActor ──NewOrderEvent──► OEGActor (via pipe) Dashboard (Python) ──getSnapshot()──────► MDGActor (thread-safe read) ──getRecentTrades()──► MDGActor (thread-safe read) Clearing House UI ──getLeaderboard()───► ClearingHouseActor (thread-safe read) ``` --- ## 5. Data Flow ### Order Lifecycle (New Limit Order) ``` ┌──────────┐ FIX 35=D ┌──────────────────┐ NewOrderEvent ┌──────────────┐ │ Client │ ──────────────► │ FIXAcceptorActor │ ────────────────► │ OEGActor │ │ (FIX) │ │ parse FIX tags │ │ route by sym │ └──────────┘ │ map symbol string │ └──────┬───────┘ │ to SymbolIndex_t │ │ └───────────────────┘ │ NewOrderEvent (Event::Pipe) │ ▼ ┌────────────────────────────────────────────────────┐ │ MECoreActor │ │ │ │ 1. KafkaBus.publishOrder() → eunex.orders * │ │ 2. RecoveryProxy.cause() — persist fragment │ │ 3. Book.newOrder() — price-time matching │ │ 4. For each fill: │ │ a. Trade generated (buyer + seller session) │ │ b. TradeEvent → MDGActor │ │ c. TradeEvent → ClearingHouseActor │ │ d. KafkaBus.publishTrade() → eunex.trades * │ │ 5. ExecReportEvent → OEGActor (ack/fill/reject) │ │ 6. BookUpdateEvent → MDGActor (new BBO) │ │ 7. KafkaBus.publishMarketData() → eunex.md * │ │ 8. IACA fragments → IacaAggregator │ │ │ │ * only when EUNEX_KAFKA_BROKERS is set │ └────────────────────────────────────────────────────┘ │ │ ExecReportEvent TradeEvent │ │ ▼ ▼ ┌───────────────────┐ ┌──────────────────────┐ │ OEGActor │ │ ClearingHouseActor │ │ fanout to: │ │ • map session→member│ │ • FIXAcceptor │ │ • update capital │ │ • AITrader │ │ • update holdings │ └────────┬──────────┘ └──────────────────────┘ │ FIX 35=8 (ExecReport) │ ▼ ┌───────────────────┐ │ Client │ │ receives fill │ └───────────────────┘ ``` ### Market Data Flow ``` MECoreActor ──TradeEvent──────────────► MDGActor ──BookUpdateEvent──────────► │ ──publishTrade()────────────► KafkaBus → eunex.trades * ──publishMarketData()───────► KafkaBus → eunex.market-data * │ ▼ ┌──────────────────────┐ │ MarketDataSnapshot │ │ per SymbolIndex_t: │ │ │ │ • bestBid, bestAsk │ │ • lastTradePrice │ │ • lastTradeQty │ │ • totalBidQty │ │ • totalAskQty │ │ • tradeCount │ │ • updateTime (ns) │ │ │ │ recentTrades[] │ │ (last 200 trades) │ └──────────────────────┘ │ getSnapshot() / getRecentTrades() (thread-safe, mutex-protected) │ ┌───────────────┴───────────────┐ │ │ ▼ ▼ ┌───────────────────┐ ┌───────────────────────┐ │ Dashboard :8090 │ │ Clearing House :8091 │ │ (Flask + SSE) │ │ (Flask) │ │ │ │ │ │ Order Book view │ │ getLeaderboard() │ │ Trade charts │ │ Member P&L, holdings│ │ OHLCV history │ │ │ └───────────────────┘ └───────────────────────┘ │ │ └───────────┬───────────────────┘ │ nginx :7860 (reverse proxy) │ ▼ Browser * KafkaBus publishes only when EUNEX_KAFKA_BROKERS is set at runtime ``` --- ## 6. Core Components ### 6.1 SimplxShim — Actor Framework (`src/engine/SimplxShim.hpp`) The actor engine emulates the [Tredzone Simplx](https://github.com/Tredzone/simplx) framework API: | Simplx Concept | EuNEx Implementation | |----------------------|-----------------------------------------------| | `Actor` | Base class with event handlers, mailbox | | `Event::Pipe` | Lock-free cross-actor channel | | `ActorId` | `{id: uint64, coreId: uint8}` | | `Engine` | Multi-threaded scheduler with core affinity | | `Callback` | Timer-like periodic invocation | | `AsyncService` | Service locator pattern | **Two operating modes:** - **Synchronous** (tests): events delivered inline, single thread - **Threaded** (`Engine::runFor`): per-core OS threads, mailbox queues ``` Synchronous Mode (unit tests) Threaded Mode (Engine::runFor) ═══════════════════════════ ═══════════════════════════════ ActorA.pipe.push() ActorA.pipe.push() │ │ ▼ ▼ ActorB.onEvent() ← inline Mailbox[coreB].enqueue(λ) │ Core B thread: mbox.waitAndDrain() │ ▼ ActorB.onEvent() ``` ### 6.2 Types (`src/common/Types.hpp`) ```cpp 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 TradeId_t = uint64_t SessionId_t = uint16_t // Client session identifier MemberId_t = uint16_t // Clearing member identifier Timestamp_ns = uint64_t // Nanosecond timestamp ``` **Price conversion:** ```cpp Price_t px = toFixedPrice(150.25); // → 15'025'000'000 double d = toDouble(px); // → 150.25 ``` ### 6.3 Order & Trade Structs ``` Order (88 bytes, packed) Trade (72 bytes, packed) ═══════════════════════ ═══════════════════════ orderId : uint64 tradeId : uint64 clOrdId : uint64 symbolIdx : uint32 symbolIdx : uint32 price : int64 side : uint8 (Buy=1,Sell=2) quantity : uint64 ordType : uint8 (Market=1,Lim=2) buyOrderId : uint64 tif : uint8 (Day/GTC/IOC/FOK) sellOrderId : uint64 price : int64 buyClOrdId : uint64 quantity : uint64 sellClOrdId : uint64 remainingQty : uint64 matchTime : uint64 entryTime : uint64 buySessionId : uint16 sessionId : uint16 sellSessionId : uint16 status : uint8 ``` --- ## 7. Event System Events flow between actors via `Event::Pipe`. Each event struct inherits `tredzone::Actor::Event`: ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ EVENT CATALOG │ │ │ │ NewOrderEvent OEG → ME New order submission │ │ CancelOrderEvent OEG → ME Cancel resting order │ │ ModifyOrderEvent OEG → ME Modify price/qty (cancel-replace) │ │ ExecReportEvent ME → OEG Ack, fill, partial, reject │ │ TradeEvent ME → MDG/CH Trade execution │ │ BookUpdateEvent ME → MDG/AI BBO + depth snapshot │ │ RecoveryFragmentEvent ME → Persist Recovery persistence │ │ │ │ Direction Key: │ │ OEG = OEGActor ME = MECoreActor MDG = MDGActor │ │ CH = ClearingHouse AI = AITraderActor │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ### Event::Pipe Usage Pattern ```cpp // In MECoreActor constructor: oePipe_ = Event::Pipe(*this, oeGatewayId); // pipe to OEG mdPipe_ = Event::Pipe(*this, marketDataId); // pipe to MDG // Pushing an event (lock-free, zero-copy in same-core mode): oePipe_.push(report, sessionId); mdPipe_.push(trade); ``` --- ## 8. Order Book & Matching ### Book (`src/common/Book.hpp`) The `Book` class implements price-time priority matching: ``` BIDS (sorted descending) ASKS (sorted ascending) ════════════════════════ ════════════════════════ Price │ Orders (FIFO) Price │ Orders (FIFO) ─────────┼────────────── ─────────┼────────────── 155.00 │ [100] [50] 157.00 │ [200] 154.50 │ [200] 158.00 │ [100] [150] 154.00 │ [75] [100] [25] 159.00 │ [300] 153.00 │ [500] 160.00 │ [50] std::map, vector, std::greater<>> std::less<>> ``` **Matching algorithm:** ``` Incoming BUY @ 158.00, qty=250 ────────────────────────────── Step 1: Match vs best ask 157.00 (qty=200) → Trade: 200 @ 157.00 → Ask level 157.00 exhausted Step 2: Match vs next ask 158.00 (qty=100 + 150) → Trade: 50 @ 158.00 (from first order, partial) → Remaining incoming qty = 0 → DONE Step 3: Incoming fully filled → ExecReport: status=Filled, filledQty=250 ``` **Order types:** - `Limit`: rests on book if no match; price-time priority - `Market`: sweeps all levels; never rests (IOC behavior) **Time-in-force:** - `Day`: rests until end of session - `IOC`: fill what's available, cancel remainder - `FOK`: fill all or reject entirely ### MECoreActor (`src/actors/MECoreActor.hpp`) ``` NewOrderEvent ──► MECoreActor.onEvent() │ ├── RecoveryProxy.cause(persistenceId, order, λ) │ └── Fragment persisted to store │ ├── book_.newOrder(order, onTrade, onExec) │ ├── matchBuy() / matchSell() │ │ └── for each fill: onTrade(trade) │ └── onExec(report) │ ├── oePipe_.push(report, session) ├── mdPipe_.push(trade) [per fill] ├── mdPipe_.push(snapshot) [if book changed] └── chPipe_.push(trade) [per fill, if CH wired] ``` --- ## 9. Recovery & IACA ### RecoveryProxy (`src/recovery/RecoveryProxy.hpp`) ``` ┌──────────────────────────────────────────────────────────────────────┐ │ RecoveryProxy (per MECoreActor) │ │ │ │ cause(persistenceId, payload, callback) │ │ → persist Fragment to FragmentStore │ │ → callback returns nextCount (# children expected) │ │ → always executes (Master AND Mirror) │ │ │ │ effect(fn, args...) │ │ → executes fn only on MASTER │ │ → side effects: send ExecReports, publish trades │ │ │ │ recoveryEffect(fn, args...) │ │ → executes fn only on MIRROR (during failover replay) │ │ │ │ ┌────────────────┐ ┌────────────────┐ │ │ │ MASTER │ │ MIRROR │ │ │ │ cause() ✓ │ ◄──────► │ cause() ✓ │ │ │ │ effect() ✓ │ Fragment │ effect() ✗ │ │ │ │ recEff() ✗ │ Store │ recEff() ✓ │ │ │ └────────────────┘ (shared) └────────────────┘ │ └──────────────────────────────────────────────────────────────────────┘ ``` ### IACA Fragment Chains (`src/iaca/IacaAggregator.hpp`) ``` Chain Completion Algorithm: ══════════════════════════ Fragment 1 (ROOT: NEW_ORDER_BUY) nextCount = 2 ├── Fragment 2 (ACK_DATA) nextCount = 0 └── Fragment 3 (TRADE_DATA) nextCount = 0 Completion check: sum(nextCount) = 2 + 0 + 0 = 2 total fragments = 3 complete when: sum == total - 1 → 2 == 3 - 1 ✓ On completion: → NewOrderHandler fires → Generates IA SBE message (future: IDS/PTB/SATURN) ``` --- ## 10. Kafka Bus ### Overview The Kafka Bus (`src/persistence/KafkaBus.hpp`) mirrors the Optiq KFK (Kafka Bus) that connects the Matching Engine to downstream consumers: MDG, PTB, Clearing, IDS, and SATURN. When `EUNEX_USE_KAFKA` is enabled at compile time and `EUNEX_KAFKA_BROKERS` is set at runtime, the bus publishes events to Kafka topics in real time. When disabled, a no-op stub compiles in its place so the engine runs standalone. ### Topics ``` Topic Content Key ───────────────────────────────────────────────────────────── eunex.orders Raw Order structs symbolIdx eunex.trades Trade structs symbolIdx eunex.market-data BBO snapshots symbolIdx eunex.recovery.fragments Recovery fragments originId:originKey eunex.control Control messages (reserved) ``` ### Architecture ``` MECoreActor (per symbol) │ ├─ onEvent(NewOrderEvent) │ ├─ kafka_->publishOrder(order) → eunex.orders │ ├─ onTrade callback: │ │ ├─ kafka_->publishTrade(trade) → eunex.trades │ │ └─ mdPipe_ / chPipe_ (actors) │ └─ publishBookUpdate() │ └─ kafka_->publishMarketData(...) → eunex.market-data │ └─ KafkaBus* kafka_ (nullptr when disabled) ``` ### Compile-Time Toggle ```cmake cmake .. -DEUNEX_USE_KAFKA=ON # requires librdkafka-dev cmake .. -DEUNEX_USE_KAFKA=OFF # no-op stub (default) ``` ### Runtime Configuration Set `EUNEX_KAFKA_BROKERS` environment variable: ```bash export EUNEX_KAFKA_BROKERS=kafka:9092 ./eunex_me ``` The engine prints Kafka connection status at startup and publishes cumulative stats (orders, trades, market-data messages) every 10 rounds. ### 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 with `EUNEX_KAFKA_BROKERS=kafka:9092`. ```bash cd docker docker compose up --build ``` --- ## 11. FIX Protocol Gateway ### FIXAcceptorActor (`src/actors/FIXAcceptorActor.hpp`) ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ FIXAcceptorActor (TCP :9001) │ │ │ │ ┌─ Accept Thread ──────────────────────────────────────────────────┐ │ │ │ while(running): │ │ │ │ sock = accept(listenSock) │ │ │ │ create FIXSession { sock, sessionId, senderCompId } │ │ │ │ spawn client recv thread │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ Client Recv Thread (per session) ───────────────────────────────┐ │ │ │ while(loggedOn): │ │ │ │ data = recv(sock) │ │ │ │ msgs = parseFIXMessages(data) │ │ │ │ for msg in msgs: │ │ │ │ switch(msg[35]): │ │ │ │ "A" → handleLogon() │ │ │ │ "D" → handleNewOrderSingle() → push NewOrderEvent → OEG │ │ │ │ "F" → handleCancelRequest() → push CancelOrderEvent │ │ │ │ "G" → handleCancelReplace() → push ModifyOrderEvent │ │ │ │ "5" → logout │ │ │ └───────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─ Actor Thread (onEvent) ─────────────────────────────────────────┐ │ │ │ onEvent(ExecReportEvent): │ │ │ │ find session by sessionId │ │ │ │ build FIX ExecutionReport (35=8) │ │ │ │ send() over TCP to client │ │ │ └───────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────┘ ``` **Supported FIX messages:** | Tag 35 | Message | Direction | |--------|-----------------------------|-----------| | A | Logon | Inbound | | 5 | Logout | Inbound | | 0 | Heartbeat | Inbound | | D | NewOrderSingle | Inbound | | F | OrderCancelRequest | Inbound | | G | OrderCancelReplaceRequest | Inbound | | 8 | ExecutionReport | Outbound | **Symbol mapping:** | Symbol String | SymbolIndex_t | |---------------|---------------| | AAPL | 1 | | MSFT | 2 | | GOOGL | 3 | | TSLA | 4 | | NVDA | 5 | | AMD | 6 | | ENX | 7 | --- ## 12. Clearing House ### ClearingHouseActor (`src/actors/ClearingHouseActor.hpp`) ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ ClearingHouseActor │ │ │ │ Session-to-Member Mapping: │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ SessionId 100-109 ──► MemberId 1-10 (FIX gateway clients) │ │ │ │ SessionId 200-209 ──► MemberId 1-10 (AI traders) │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ On TradeEvent: │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ 1. Resolve buySessionId → buyMember │ │ │ │ 2. Resolve sellSessionId → sellMember │ │ │ │ 3. Buy side: │ │ │ │ capital -= price × quantity │ │ │ │ holdings[symbol].quantity += quantity │ │ │ │ holdings[symbol].avgCost updated (weighted average) │ │ │ │ 4. Sell side: │ │ │ │ capital += price × quantity │ │ │ │ holdings[symbol].quantity -= quantity │ │ │ │ 5. Both sides: tradeCount++ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ Members (10): │ │ ┌────────┬──────────┬─────────────────┬────────┐ │ │ │ Name │ Capital │ Holdings │ Trades │ │ │ ├────────┼──────────┼─────────────────┼────────┤ │ │ │ MBR01 │ 100000.0 │ {sym→qty,cost} │ 0 │ │ │ │ MBR02 │ 100000.0 │ {sym→qty,cost} │ 0 │ │ │ │ ... │ ... │ ... │ ... │ │ │ │ MBR10 │ 100000.0 │ {sym→qty,cost} │ 0 │ │ │ └────────┴──────────┴─────────────────┴────────┘ │ │ │ │ getLeaderboard(): │ │ sorted by capital descending │ │ returns: memberId, name, capital, pnl, tradeCount, holdingCount │ │ thread-safe (mutex) for Python bridge reads │ └──────────────────────────────────────────────────────────────────────────┘ ``` --- ## 13. AI Trading Members ### AITraderActor (`src/actors/AITraderActor.hpp`) ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ AITraderActor — 10 automated trading members │ │ │ │ Members: │ │ ┌────────┬───────────┬────────────────┬──────────────┐ │ │ │ Name │ SessionId │ Strategy │ Description │ │ │ ├────────┼───────────┼────────────────┼──────────────┤ │ │ │ MBR01 │ 200 │ Momentum │ Follow trend │ │ │ │ MBR02 │ 201 │ MeanReversion │ Fade moves │ │ │ │ MBR03 │ 202 │ Random │ Noise trader │ │ │ │ MBR04 │ 203 │ Momentum │ │ │ │ │ ... │ ... │ (rotates) │ │ │ │ │ MBR10 │ 209 │ Random │ │ │ │ └────────┴───────────┴────────────────┴──────────────┘ │ │ │ │ Strategy Details: │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ MOMENTUM: │ │ │ │ if last N prices trending up → BUY (follow the trend) │ │ │ │ if last N prices trending down → SELL │ │ │ │ price = BBO ± small offset │ │ │ │ │ │ │ │ MEAN REVERSION: │ │ │ │ if price > moving average → SELL (expect revert) │ │ │ │ if price < moving average → BUY │ │ │ │ price = BBO ± small offset │ │ │ │ │ │ │ │ RANDOM: │ │ │ │ random side (50/50), random qty (10-100) │ │ │ │ price around midpoint with random spread │ │ │ │ │ │ │ │ LLM (Python CH only): │ │ │ │ sends portfolio + market data to LLM │ │ │ │ expects JSON response: symbol, side, quantity, price, reason │ │ │ │ uses same provider chain as AI Analyst │ │ │ │ falls back to random on LLM failure │ │ │ │ explanations shown in CH dashboard panel │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ Data Sources: │ │ • BookUpdateEvent → BBO (bestBid, bestAsk) per symbol │ │ • TradeEvent → price history (last 50 prices) per symbol │ │ • ExecReportEvent → order fill confirmations │ │ │ │ Output: │ │ • NewOrderEvent → OEGActor (via Event::Pipe) │ │ • ~30s intervals via Actor::Callback │ └──────────────────────────────────────────────────────────────────────────┘ ``` --- ## 14. Market Simulation Both the C++ engine and the Python dashboard include market simulation to continuously generate orders and trades. ### C++ Engine (AITraderActor) The `AITraderActor` generates orders every ~3 seconds per round. Each of the 10 AI members picks a random symbol and applies its strategy (Momentum, Mean Reversion, or Random). All strategies use per-symbol **reference prices** matching real market levels: | Symbol | Reference Price | |--------|----------------| | AAPL | $154.00 | | MSFT | $324.00 | | GOOGL | $141.00 | | TSLA | $375.00 | | NVDA | $201.00 | | AMD | $320.00 | | ENX | €146.00 | When no BBO data is available yet, the fallback `submitOrder()` generates prices within ±3 ticks of the reference price instead of random values. ### Python Dashboard (MarketSimulator) The dashboard runs a `MarketSimulator` thread that generates orders when the session status is `"active"`: ``` Configuration (shared/config.py): SIM_INTERVAL = 30s (env: EUNEX_SIM_INTERVAL) SIM_ORDERS_PER_ROUND = 4 (env: EUNEX_SIM_ORDERS) Per round (every SIM_INTERVAL seconds): For each of 7 symbols: 1. Submit SIM_ORDERS_PER_ROUND limit orders near current mid price (±0.5%) 2. Submit 1 crossing order that will match against the book → Generates resting orders + at least 1 trade per symbol per round ``` **Order flow:** - Session "Start Day" → seeds initial orders (2 bid + 2 ask per symbol) - Simulation thread wakes every 30s → submits ~35 orders → ~7 trades - All trades are persisted to SQLite (trades table + OHLCV aggregation) - Dashboard chart shows candlestick data from OHLCV buckets ### Seed Orders (Session Start) When the dashboard session starts, 4 orders are seeded per symbol to establish a market: ``` For each symbol at startPrice: Buy @ startPrice - 1.00, qty=100 Buy @ startPrice - 0.50, qty=150 Sell @ startPrice + 0.50, qty=200 Sell @ startPrice + 1.00, qty=100 ``` ### Daily Close Persistence When the session ends ("End Day"), closing prices are saved to the `daily_close` SQLite table: ``` daily_close table: symbol TEXT, trade_date TEXT, close_price REAL, bid REAL, ask REAL, volume INTEGER, trade_count INTEGER PRIMARY KEY (symbol, trade_date) ``` On the next "Start Day", the engine loads the most recent closing prices from `daily_close` and uses them as seed prices instead of the hardcoded `startPrice` from config. This ensures price continuity across trading sessions. **Flow:** 1. Session "End Day" → `_save_closing_prices()` writes current snapshot to `daily_close` 2. Next "Start Day" → `_seed_initial_orders()` calls `get_last_closing_prices()` from DB 3. If closing prices exist → seed around those prices 4. If no history → fall back to `startPrice` from `shared/config.py` The simulator also loads closing prices on startup via `_load_closing_refs()`. ### Ticker Tape The dashboard displays a scrolling ticker tape below the header showing all 7 symbols with: - Current price, percentage change (vs. previous price), volume - Green/red arrows for up/down movement - Auto-updates via SSE snapshot events ### OHLCV Candlestick Chart The price chart renders OHLCV data as candlestick bars using a custom Chart.js plugin: - Green candles (close >= open), red candles (close < open) - High/low wicks drawn as vertical lines - Volume bars on secondary axis with matching colors - Tooltip shows full OHLCV values per bar --- ## 15. AI Market Analyst The dashboard includes an AI analyst powered by local or cloud LLM providers. ### Provider Fallback Chain ``` Auto mode tries in order: 1. Ollama (local) → POST {OLLAMA_HOST}/api/chat 2. Groq (cloud) → POST api.groq.com/openai/v1/chat/completions 3. HuggingFace → POST router.huggingface.co/v1/chat/completions ``` ### Environment Variables | Variable | Default | Description | |---------------|-------------------------------|--------------------------| | OLLAMA_HOST | http://localhost:11434 | Ollama API endpoint | | OLLAMA_MODEL | llama3.2:3b | Default Ollama model | | GROQ_API_KEY | (empty) | Groq API key | | GROQ_MODEL | llama-3.1-8b-instant | Default Groq model | | HF_TOKEN | (empty) | HuggingFace token | | HF_MODEL | Qwen/Qwen2.5-7B-Instruct | Default HF model | ### Prompt Template The analyst builds a market context prompt from current trades and order book: ``` You are a concise financial market analyst for the EuNEx simulated exchange. Time: HH:MM:SS | Session: ACTIVE Recent trades: AAPL: 12 trade(s), range 153.50-154.50, vol 1200, last 154.10 MSFT: 8 trade(s), range 323.80-324.20, vol 800, last 324.00 ... Order book: AAPL: Bid 153.80 / Ask 154.20 (spread 0.40) ... In 3-4 sentences: activity level, notable moves, market sentiment. ``` ### API Endpoints | Endpoint | Method | Description | |-------------------|--------|--------------------------------| | /ai/generate | POST | Trigger async LLM generation | | /ai/insights | GET | Get cached insight history | | /ai/config | GET | Provider availability/status | | /ai/select | POST | Switch provider/model | Insights are broadcast via SSE `ai_insight` event and cached in memory (last 20). --- ## 16. Developer Message Flow Visualizer The dashboard includes a "Message Flow" tab that traces the full order lifecycle through all system components. This is a developer tool for understanding and debugging the Optiq-modeled pipeline. ### Pipeline Stages ``` ┌─────┐ ┌──────┐ ┌───────┐ ┌───────┐ ┌────┐ ┌────┐ │ OEG │ ─► │ Book │ ─► │ Match │ ─► │ Trade │ ─► │ DB │ ─► │ CH │ └─────┘ └──────┘ └───────┘ └───────┘ └────┘ └────┘ Order Insert Fill Record SQLite Clear Entry /Status Partial Trade Persist House ``` Each stage logs a timestamped message with detail text. Messages flow via SSE `msgflow` events for real-time display. ### Implementation The visualizer uses Python function patching to intercept the matching engine methods: - `engine.submit_order()` → logs OEG (entry) + Book (status) + Match (fill) - `engine.cancel_order()` → logs OEG (cancel) + Book (cancelled) - `broadcast_event("trade", ...)` → logs Trade + DB + CH steps ### API | Endpoint | Method | Description | |-------------------|--------|---------------------------------| | /dev/messages | GET | Get recent message log (500 max)| ### UI Features - **Pipeline counter**: shows cumulative message count per stage - **Live log**: new messages appear at top with highlight animation - **Clear**: resets all counters and log entries --- ## 17. Engine Mode Switch The dashboard supports two matching engine backends, switchable at runtime via a toggle in the header. ### Architecture ``` ┌──────────────────────────────────────────────────────────────────────────┐ │ Dashboard (Flask :8090) │ │ │ │ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │ │ │ Python MatchingEngine │ │ CppEngineBridge │ │ │ │ (built-in, default) │ │ (FIX 4.4 TCP client) │ │ │ │ │ │ │ │ │ │ • In-process order book │ │ • Connects to :9001 │ │ │ │ • Price-time priority │ │ • Sends NewOrderSingle(D) │ │ │ │ • No compilation needed │ │ • Sends CancelRequest (F) │ │ │ │ • SQLite persistence │ │ • Sends CancelReplace (G) │ │ │ │ • Good for demos │ │ • Receives ExecReport (8) │ │ │ └──────────────────────────────┘ │ • Logon/Logout/Heartbeat │ │ │ └──────────────┬───────────────┘ │ │ _active_engine() │ │ │ returns engine or cpp_bridge │ │ │ based on engine_mode variable │ │ └─────────────────────────────────────────────────────┼─────────────────────┘ │ FIX 4.4 TCP :9001 │ ▼ ┌───────────────────────────┐ │ eunex_me (C++ engine) │ │ FIXAcceptorActor :9001 │ │ OEG → MECore → MDG → CH │ └───────────────────────────┘ ``` ### Switching Modes **From the UI:** The header contains a toggle switch `[Python ○──── C++]` with a status dot: - **Green dot**: C++ engine is running and reachable on port 9001 - **Red dot**: C++ engine not detected - Health check runs every 15 seconds **From the API:** ```bash # Check current mode and C++ availability GET /engine/mode → {"mode": "python", "cpp_available": true, "cpp_host": "localhost", "cpp_port": 9001} # Switch to C++ engine POST /engine/mode {"mode": "cpp"} → {"mode": "cpp"} # Switch back to Python POST /engine/mode {"mode": "python"} → {"mode": "python"} ``` ### Order Routing When `engine_mode == "cpp"`: 1. Dashboard builds a FIX `NewOrderSingle` (35=D) message 2. Sends it over TCP to `eunex_me:9001` 3. C++ engine matches and returns `ExecutionReport` (35=8) 4. Bridge parses the response and updates dashboard state (orders, trades, snapshots) 5. SSE events broadcast to browser as usual When `engine_mode == "python"`: - Orders processed by the built-in `MatchingEngine` class (default behavior) **Simulation orders** (`source=sim`, `source=seed`) always use the Python engine regardless of mode. ### FIX Message Flow (C++ Mode) ``` Browser Dashboard C++ Engine ─────── ───────── ────────── │ POST /order/new │ │ │ ─────────────────► │ │ │ │ 35=D NewOrderSingle │ │ │ ────────────────────► │ │ │ Book::newOrder() │ │ match → Trade │ │ 35=8 ExecutionReport │ │ │ ◄──────────────────── │ │ │ update state, SSE │ │ SSE: order, trade │ │ │ ◄───────────────── │ │ ``` ### Connection Lifecycle ``` Toggle ON (→ C++) Toggle OFF (→ Python) ═══════════════════ ════════════════════════ 1. TCP connect(:9001) 1. Send FIX Logout (35=5) 2. Send FIX Logon (35=A) 2. Close TCP socket 3. Start recv thread 3. Set mode = "python" 4. Set mode = "cpp" 4. Orders route to MatchingEngine 5. Orders route to bridge ``` --- ## 18. Project Structure ``` EuNEx/ ├── CMakeLists.txt Build configuration ├── README.md Project overview │ ├── src/ │ ├── main.cpp Engine entry point, actor wiring │ │ │ ├── common/ │ │ ├── Types.hpp Core types (Price_t, Order, Trade, etc.) │ │ ├── Book.hpp Order book interface │ │ └── Book.cpp Price-time priority matching engine │ │ │ ├── engine/ │ │ └── SimplxShim.hpp Actor framework (Simplx API compatible) │ │ │ ├── actors/ │ │ ├── Events.hpp All 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 & member positions │ │ └── AITraderActor.hpp/cpp Automated trading members │ │ │ ├── net/ │ │ └── SocketCompat.hpp Cross-platform socket abstraction │ │ │ ├── recovery/ │ │ ├── RecoveryProxy.hpp/cpp Master/Mirror recovery gating │ │ └── (FragmentStore) In-memory persistence (→ Kafka future) │ │ │ ├── iaca/ │ │ ├── Fragment.hpp IACA fragment definitions │ │ ├── IacaAggregator.hpp/cpp Fragment chain assembly & completion │ │ └── (handlers) IA message generators │ │ │ └── persistence/ │ ├── PersistenceStore.hpp Store interface │ └── KafkaStore.hpp Kafka adapter (optional, librdkafka) │ ├── dashboard/ │ ├── app.py Flask dashboard + dual engine bridge │ ├── database.py SQLite (orders, trades, OHLCV) │ └── templates/index.html Trading UI with engine toggle │ ├── clearing_house/ │ ├── app.py Flask CH portal + API │ ├── ch_database.py SQLite (members, holdings) │ ├── ch_ai_trader.py AI trading (momentum/mean_revert/random/llm) │ └── templates/ CH web UI (leaderboard, portfolios) │ ├── fix_gateway/ │ └── fix_server.py Python FIX 4.4 TCP acceptor │ ├── shared/ │ └── config.py Centralized config (auto-loads .env) │ ├── .env LLM and service configuration │ ├── tests/ │ ├── test_orderbook.cpp Book unit tests (26 cases) │ ├── test_matching_engine.cpp ME integration tests │ ├── test_threaded_engine.cpp Multi-threaded actor tests │ ├── test_clearing_house.cpp Clearing house tests (7 cases) │ ├── test_fix_gateway.cpp FIX gateway tests (5 cases) │ └── test_ai_trader.cpp AI trader tests (6 cases) │ ├── examples/ │ ├── ping_pong.cpp Basic actor communication demo │ └── simple_match.cpp Simple order matching demo │ └── docs/ ├── developers-guide.md This document └── process-diagram.md Detailed Optiq architecture diagrams ``` --- ## 19. Build & Test ### Prerequisites - **C++20 compiler**: MSVC 19.30+, GCC 12+, Clang 15+ - **CMake**: 3.16+ - **Optional**: librdkafka (for Kafka persistence) ### Build Commands ```bash # Configure (default: tests ON, Kafka OFF) cmake -B build -DEUNEX_BUILD_TESTS=ON # Build cmake --build build --config Release # Run tests cd build && ctest -C Release --output-on-failure # Run engine ./build/Release/eunex_me ``` ### CMake Options | Option | Default | Description | |---------------------|---------|------------------------------------| | `EUNEX_USE_SIMPLX` | OFF | Use real Simplx framework | | `EUNEX_USE_KAFKA` | OFF | Enable Kafka persistence | | `EUNEX_BUILD_TESTS` | ON | Build test binaries | | `EUNEX_BUILD_EXAMPLES` | ON | Build example binaries | ### Test Suites ``` ┌───────────────────────────────────────────────────────────────┐ │ Test Suite │ Cases │ What it verifies │ ├──────────────────────────┼───────┼─────────────────────────────┤ │ OrderBookTest │ 26 │ Book matching, all TIFs, │ │ │ │ multi-level sweeps, cancels │ ├──────────────────────────┼───────┼─────────────────────────────┤ │ MatchingEngineTest │ - │ MECoreActor event handling │ ├──────────────────────────┼───────┼─────────────────────────────┤ │ ThreadedEngineTest │ - │ Multi-core Engine, mailbox │ ├──────────────────────────┼───────┼─────────────────────────────┤ │ ClearingHouseTest │ 7 │ Capital, holdings, P&L, │ │ │ │ session mapping, leaderboard│ ├──────────────────────────┼───────┼─────────────────────────────┤ │ FIXGatewayTest │ 5 │ Symbol mapping, actor │ │ │ │ lifecycle, OEG routing │ ├──────────────────────────┼───────┼─────────────────────────────┤ │ AITraderTest │ 6 │ Strategy execution, multi- │ │ │ │ symbol, clearing integration│ └───────────────────────────────────────────────────────────────┘ ``` --- ## 20. Configuration ### Runtime Configuration (main.cpp) | Parameter | Value | Description | |---------------------|-----------|--------------------------------------| | FIX port | 9001 | FIXAcceptorActor TCP listen port | | Symbols | 7 | AAPL, MSFT, GOOGL, TSLA, NVDA, AMD, ENX | | AI members | 10 | MBR01-MBR10 | | Initial capital | 100,000.0 | Per clearing member | | AI trade interval | ~3s | Per round in main loop | | Price scale | 10^8 | Fixed-point decimal places | ### Seed Orders The engine pre-populates order books with spread-defining orders: ``` AAPL: Sell 155.00/100, 154.50/200 | Buy 153.50/150, 153.00/100 MSFT: Sell 325.00/100, 324.50/150 | Buy 323.50/200, 323.00/100 GOOGL: Sell 142.00/100, 141.50/200 | Buy 140.50/150, 140.00/100 TSLA: Sell 376.00/80, 375.50/120 | Buy 374.50/100, 374.00/80 NVDA: Sell 202.00/100, 201.50/150 | Buy 200.50/120, 200.00/100 AMD: Sell 321.00/90, 320.50/130 | Buy 319.50/110, 319.00/80 ENX: Sell 147.00/60, 146.50/100 | Buy 145.50/80, 145.00/60 ``` --- ## 21. Extending EuNEx ### Adding a New Symbol 1. Define a new `SymbolIndex_t` constant in `main.cpp` 2. Create a `MECoreActor` for it, passing OEG, MDG, and CH actor IDs 3. Register it with `oeGateway->mapSymbol(newSym, bookActor->getActorId())` 4. Add it 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 in constructor: `registerEventHandler(*this)` 3. Create `Event::Pipe` members for each destination actor 4. Add `.cpp` to `EUNEX_CORE_SOURCES` in `CMakeLists.txt` 5. Wire into topology in `main.cpp` ### Adding a New Event Type 1. Define the struct in `src/actors/Events.hpp` inheriting `tredzone::Actor::Event` 2. Add `onEvent(const MyEvent&)` to receiving actors 3. Register handler: `registerEventHandler(*this)` 4. Push via pipe: `pipe.push(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 desired members in `initMembers()` ### Future Roadmap ``` Current (v0.9.5) Planned ════════════════ ═══════════════════════════════════ ✓ Limit + Market + Stop orders □ Pegged, Mid-Point, Iceberg ✓ IOC, FOK, Day □ GTD, GTC, VFU, VFCU ✓ Trading phases (PreOpen/CTS) □ TAL, VDO, Reserved phases ✓ FIX 4.4 gateway □ FIX 5.0 SP2 + SBE binary ✓ Kafka Bus persistence □ Kafka consumer replay ✓ SimplxShim (emulation) □ Real Simplx multi-core ✓ 7 symbols (real prices) □ Multi-segment, partitions ✓ Single partition □ Cross-partition routing ✓ Clearing house + AI traders □ EuroCCP/LCH integration ✓ IACA fragments □ IACA FINISH + COPY + IDS ✓ Python bridge (JSON) □ SBE multicast MDG ✓ Market simulation (C++ + Py) □ Multi-day backtesting ✓ Ticker tape + OHLCV charts □ SATURN ARM (MiFID II RTS 22) ✓ Daily close persistence □ PTB (Post-Trade Box) ✓ AI Analyst (Ollama/Groq/HF) ✓ LLM trading strategy (CH) ✓ Message Flow Visualizer ✓ Engine mode switch (Py ↔ C++) ✓ .env auto-loading config ``` --- *Generated for EuNEx v0.9.5 — Euronext Optiq Architecture Simulator*