RayMelius Claude Opus 4.6 commited on
Commit
44cddac
Β·
1 Parent(s): a3b10ab

Rename modules to Optiq naming and add Developers Guide

Browse files

Align all component names with Euronext Optiq terminology:
- OEGatewayActor β†’ OEGActor (Order Entry Gateway)
- OrderBookActor β†’ MECoreActor (Matching Engine core)
- OrderBook β†’ Book
- MarketDataActor β†’ MDGActor (Market Data Gateway)
- FIXGatewayActor β†’ FIXAcceptorActor

Add comprehensive English-language Developers Guide with architecture
diagrams, data flow, component reference, and build instructions.
Update README.md and process-diagram.md with new naming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

CMakeLists.txt CHANGED
@@ -50,12 +50,12 @@ include_directories(${CMAKE_SOURCE_DIR}/src)
50
 
51
  # ── Core library ────────────────────────────────────────────────────
52
  set(EUNEX_CORE_SOURCES
53
- src/common/OrderBook.cpp
54
- src/actors/OrderBookActor.cpp
55
- src/actors/OEGatewayActor.cpp
56
- src/actors/MarketDataActor.cpp
57
  src/actors/ClearingHouseActor.cpp
58
- src/actors/FIXGatewayActor.cpp
59
  src/actors/AITraderActor.cpp
60
  src/recovery/RecoveryProxy.cpp
61
  src/iaca/IacaAggregator.cpp
 
50
 
51
  # ── Core library ────────────────────────────────────────────────────
52
  set(EUNEX_CORE_SOURCES
53
+ src/common/Book.cpp
54
+ src/actors/MECoreActor.cpp
55
+ src/actors/OEGActor.cpp
56
+ src/actors/MDGActor.cpp
57
  src/actors/ClearingHouseActor.cpp
58
+ src/actors/FIXAcceptorActor.cpp
59
  src/actors/AITraderActor.cpp
60
  src/recovery/RecoveryProxy.cpp
61
  src/iaca/IacaAggregator.cpp
README.md CHANGED
@@ -8,23 +8,32 @@ ported from the [StockEx](https://github.com/Bonum/StockEx) Python prototype.
8
  ```
9
  StockEx (Python/Kafka) EuNEx (C++/Simplx) Optiq (Production)
10
  ───────────────────── ────────────────── ──────────────────
11
- fix_oeg_server.py β†’ OEGatewayActor β†’ OEActor
12
  Kafka 'orders' topic β†’ Event::Pipe β†’ Simplx Event::Pipe
13
- matcher.py β†’ OrderBookActor β†’ LogicalCoreActor + Book
14
- match_order() β†’ OrderBook::newOrder() β†’ RecoveryCause β†’ IACA Cause β†’ forwardToBook
15
- handle_cancel() β†’ OrderBook::cancelOrder()β†’ CancelOrderData
16
- handle_amend() β†’ OrderBook::modifyOrder()β†’ ModifyOrderData
17
  Kafka 'trades' topic β†’ TradeEvent via Pipe β†’ IACA fragment chain
18
- dashboard.py (SSE) β†’ MarketDataActor β†’ MDLimitLogicalCoreHandler
19
  /orderbook/<sym> β†’ BookUpdateEvent β†’ PublishLimitUpdateRequest
20
  /trades β†’ TradeEvent β†’ IACA β†’ IA SBE message
21
- database.py (SQLite) β†’ SQLite persistence β†’ RecoveryProxy β†’ Kafka
22
- save_trade() β†’ dashboard/database.py β†’ PersistenceAgent β†’ Kafka produce
23
- fix_oeg_server.py β†’ fix_gateway/fix_server.py β†’ FIX 4.4 OEG Acceptor
24
  NewOrderSingle β†’ 35=D handling β†’ Optiq FIX gateway
25
  ExecutionReport β†’ 35=8 response β†’ Execution reports
26
- ch_ai_trader.py β†’ clearing_house/ β†’ Clearing House members
27
- AI strategies β†’ momentum/mean_revert β†’ Trading obligations
 
 
 
 
 
 
 
 
 
28
  ```
29
 
30
  ## Service Architecture
@@ -44,19 +53,18 @@ ch_ai_trader.py β†’ clearing_house/ β†’ Clearing House me
44
  β”‚ Trade Charts β”‚ β”‚ Leaderboard β”‚ β”‚ NewOrder/Cancel β”‚
45
  β”‚ OHLCV History β”‚ β”‚ Portfolios β”‚ β”‚ Amend/ExecRpt β”‚
46
  β”‚ SQLite DB β”‚ β”‚ Settlements β”‚ β”‚ β”‚
47
- β”‚ SSE Streaming β”‚ β”‚ SQLite DB β”‚ β”‚ β”‚
48
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””οΏ½οΏ½β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
49
- β”‚ β”‚ β”‚
50
- β”‚ HTTP REST β”‚ HTTP REST β”‚
51
- β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
52
- β”‚
53
- β–Ό
54
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
55
- β”‚ C++ Matching Engine β”‚
56
- β”‚ (eunex_me) β”‚
57
- β”‚ Multi-threaded actors β”‚
58
- β”‚ Price-time priority β”‚
59
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
60
  ```
61
 
62
  ## Quick Start (Linux)
@@ -95,31 +103,30 @@ docker compose up --build
95
  ## Build C++ Engine
96
 
97
  ```bash
98
- mkdir build && cd build
99
- cmake ..
100
- cmake --build . --config Release
101
 
102
- # Run matching engine demo
103
- ./eunex_me
104
 
105
- # Run all tests (26 tests)
106
- ctest
107
  ```
108
 
109
  ## With Kafka Persistence
110
 
111
  ```bash
112
- cmake -DEUNEX_USE_KAFKA=ON ..
113
- cmake --build . --config Release
114
  ```
115
 
116
  ## FIX Gateway
117
 
 
 
 
118
  ```bash
119
- # Start FIX server (requires dashboard running)
120
  python fix_gateway/fix_server.py
121
-
122
- # Test with built-in client
123
  python fix_gateway/fix_server.py test
124
  ```
125
 
@@ -128,49 +135,32 @@ OrderCancelReplaceRequest (35=G), ExecutionReport (35=8).
128
 
129
  ## Clearing House
130
 
131
- ```bash
132
- # Start clearing house (requires dashboard running)
133
- python clearing_house/app.py
134
- ```
135
 
136
- Features:
137
- - 10 AI trading members (MBR01-MBR10) with 3 strategies
138
- - Strategies: momentum, mean_reversion, random
139
- - Member portal with login, portfolio, order submission
140
- - Leaderboard with capital, holdings, P&L
141
- - Daily trading obligation (min 20 securities)
142
- - EOD settlement
143
-
144
- ## Web Dashboard
145
-
146
- Features:
147
- - Real-time order book with bid/ask depth bars
148
- - OHLCV price charts (1H, 8H, 1D, 1W) via Chart.js
149
- - Order entry with Limit/Market, TIF (Day/GTC/IOC/FOK)
150
- - Order amend and cancel
151
- - Trade blotter with history
152
- - Market snapshots (BBO, last price, volume)
153
- - Clearing House leaderboard integration
154
- - Session controls (Start/Suspend/Resume/End Day)
155
- - SSE real-time streaming
156
- - SQLite persistence for orders, trades, OHLCV
157
- - EQU (Cash/Equities) and EQD (Derivatives) segments
158
 
159
  ## Project Structure
160
 
161
  ```
162
  EuNEx/
163
  β”œβ”€β”€ src/ # C++ matching engine
164
- β”‚ β”œβ”€β”€ main.cpp # Entry point
165
  β”‚ β”œβ”€β”€ engine/SimplxShim.hpp # Multi-threaded actor engine
166
  β”‚ β”œβ”€β”€ common/
167
  β”‚ β”‚ β”œβ”€β”€ Types.hpp # Price, Order, Trade, enums
168
- β”‚ β”‚ β”œβ”€β”€ OrderBook.hpp/cpp # Price-time priority matching
169
  β”‚ β”œβ”€β”€ actors/
170
  β”‚ β”‚ β”œβ”€β”€ Events.hpp # Inter-actor event types
171
- β”‚ β”‚ β”œβ”€β”€ OrderBookActor.hpp/cpp # Matching engine actor
172
- β”‚ β”‚ β”œβ”€β”€ OEGatewayActor.hpp/cpp # Order entry gateway
173
- β”‚ β”‚ └── MarketDataActor.hpp/cpp # Market data publisher
 
 
 
 
174
  β”‚ β”œβ”€β”€ persistence/
175
  β”‚ β”‚ β”œβ”€β”€ PersistenceStore.hpp # Abstract store + InMemoryStore
176
  β”‚ β”‚ └── KafkaStore.hpp # Kafka persistence (optional)
@@ -183,34 +173,44 @@ EuNEx/
183
  β”‚ β”œβ”€β”€ database.py # SQLite (orders, trades, OHLCV)
184
  β”‚ └── templates/index.html # Trading UI with Chart.js
185
  β”œβ”€β”€ fix_gateway/
186
- β”‚ └── fix_server.py # FIX 4.4 TCP acceptor
187
  β”œβ”€β”€ clearing_house/
188
  β”‚ β”œβ”€β”€ app.py # Flask CH portal + API
189
- β”‚ β”œβ”€β”€ ch_database.py # SQLite (members, holdings, settlements)
190
  β”‚ β”œβ”€β”€ ch_ai_trader.py # AI trading strategies
191
- β”‚ └── templates/ # CH web UI (login, dashboard, portfolio)
192
- β”œβ”€β”€ shared/
193
- β”‚ └── config.py # Centralized configuration
194
  β”œβ”€β”€ docker/
195
  β”‚ β”œβ”€β”€ docker-compose.yml # Kafka + EuNEx (all services)
196
- οΏ½οΏ½οΏ½ β”œβ”€β”€ Dockerfile # Multi-stage Linux build
197
  β”‚ └── nginx.conf # Reverse proxy configuration
198
- β”œβ”€β”€ run.sh # Linux startup script
199
  β”œβ”€β”€ tests/
200
- β”‚ β”œβ”€β”€ test_orderbook.cpp # OrderBook unit tests (14)
201
- β”‚ β”œβ”€β”€ test_matching_engine.cpp # Actor integration tests (6)
202
- β”‚ └── test_threaded_engine.cpp # Multi-threaded engine tests (6)
 
 
 
203
  β”œβ”€β”€ examples/
204
  β”‚ β”œβ”€β”€ ping_pong.cpp # Actor basics tutorial
205
  β”‚ └── simple_match.cpp # Matching with Recovery + IACA
206
- └── docs/process-diagram.md # Architecture diagrams
 
 
207
  ```
208
 
 
 
 
 
 
209
  ## Next Steps
210
 
211
- 1. ~~Add real Simplx integration~~ βœ“ Multi-threaded actor engine with mailbox queues
212
  2. ~~Kafka persistence~~ βœ“ KafkaStore + Docker Compose (KRaft mode)
213
- 3. **SBE encoding** β€” replace event structs with SBE-encoded messages
214
- 4. ~~FIX gateway~~ βœ“ Pure Python FIX 4.4 TCP acceptor
215
- 5. **Master/Mirror failover** β€” implement full Recovery replay on Mirror node
216
- 6. ~~Clearing House actors~~ βœ“ AI trading members with 3 strategies + portal
 
 
 
8
  ```
9
  StockEx (Python/Kafka) EuNEx (C++/Simplx) Optiq (Production)
10
  ───────────────────── ────────────────── ──────────────────
11
+ fix_oeg_server.py β†’ OEGActor β†’ OEActor
12
  Kafka 'orders' topic β†’ Event::Pipe β†’ Simplx Event::Pipe
13
+ matcher.py β†’ MECoreActor β†’ LogicalCoreActor + Book
14
+ match_order() β†’ Book::newOrder() β†’ RecoveryCause β†’ IACA Cause β†’ forwardToBook
15
+ handle_cancel() β†’ Book::cancelOrder() β†’ CancelOrderData
16
+ handle_amend() β†’ Book::modifyOrder() β†’ ModifyOrderData
17
  Kafka 'trades' topic β†’ TradeEvent via Pipe β†’ IACA fragment chain
18
+ dashboard.py (SSE) β†’ MDGActor β†’ MDLimitLogicalCoreHandler
19
  /orderbook/<sym> β†’ BookUpdateEvent β†’ PublishLimitUpdateRequest
20
  /trades β†’ TradeEvent β†’ IACA β†’ IA SBE message
21
+ database.py (SQLite) β†’ RecoveryProxy (memory) β†’ RecoveryProxy β†’ Kafka
22
+ save_trade() β†’ FragmentStore::append() β†’ PersistenceAgent β†’ Kafka produce
23
+ fix_oeg_server.py β†’ FIXAcceptorActor β†’ FIX 4.4 OEG Acceptor
24
  NewOrderSingle β†’ 35=D handling β†’ Optiq FIX gateway
25
  ExecutionReport β†’ 35=8 response β†’ Execution reports
26
+ ch_ai_trader.py β†’ ClearingHouseActor β†’ Clearing House (PTB path)
27
+ AI strategies β†’ AITraderActor β†’ Trading obligations
28
+ ```
29
+
30
+ ## Actor Topology (v0.4)
31
+
32
+ ```
33
+ Core 0: OEGActor + FIXAcceptorActor ← Order entry & FIX protocol
34
+ Core 1: MECoreActor (per symbol) ← Matching engine (Book)
35
+ Core 2: MDGActor ← Market data snapshots
36
+ Core 3: ClearingHouseActor + AITrader ← Post-trade & AI members
37
  ```
38
 
39
  ## Service Architecture
 
53
  β”‚ Trade Charts β”‚ β”‚ Leaderboard β”‚ β”‚ NewOrder/Cancel β”‚
54
  β”‚ OHLCV History β”‚ β”‚ Portfolios β”‚ β”‚ Amend/ExecRpt β”‚
55
  β”‚ SQLite DB β”‚ β”‚ Settlements β”‚ β”‚ β”‚
56
+ β”‚ SSE Streaming β”‚ β”‚ β”‚ β”‚ β”‚
57
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
58
+ β”‚ β”‚ β”‚
59
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
60
+ β”‚
61
+ β–Ό
62
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
63
+ β”‚ C++ Matching Engine β”‚
64
+ β”‚ (eunex_me) β”‚
65
+ β”‚ Multi-threaded actors β”‚
66
+ β”‚ Price-time priority β”‚
67
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 
68
  ```
69
 
70
  ## Quick Start (Linux)
 
103
  ## Build C++ Engine
104
 
105
  ```bash
106
+ cmake -B build -DEUNEX_BUILD_TESTS=ON
107
+ cmake --build build --config Release
 
108
 
109
+ # Run matching engine
110
+ ./build/Release/eunex_me
111
 
112
+ # Run all tests (6 suites)
113
+ cd build && ctest -C Release
114
  ```
115
 
116
  ## With Kafka Persistence
117
 
118
  ```bash
119
+ cmake -B build -DEUNEX_USE_KAFKA=ON
120
+ cmake --build build --config Release
121
  ```
122
 
123
  ## FIX Gateway
124
 
125
+ The C++ engine includes a built-in FIX 4.4 acceptor on TCP port 9001.
126
+ A Python FIX gateway is also available:
127
+
128
  ```bash
 
129
  python fix_gateway/fix_server.py
 
 
130
  python fix_gateway/fix_server.py test
131
  ```
132
 
 
135
 
136
  ## Clearing House
137
 
138
+ 10 AI trading members (MBR01-MBR10) with 3 strategies:
139
+ - **Momentum**: follow price trends
140
+ - **Mean Reversion**: fade price moves
141
+ - **Random**: noise trading
142
 
143
+ Features: capital tracking, holdings per symbol, P&L, leaderboard.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  ## Project Structure
146
 
147
  ```
148
  EuNEx/
149
  β”œβ”€β”€ src/ # C++ matching engine
150
+ β”‚ β”œβ”€β”€ main.cpp # Entry point, actor wiring
151
  β”‚ β”œβ”€β”€ engine/SimplxShim.hpp # Multi-threaded actor engine
152
  β”‚ β”œβ”€β”€ common/
153
  β”‚ β”‚ β”œβ”€β”€ Types.hpp # Price, Order, Trade, enums
154
+ β”‚ β”‚ └── Book.hpp/cpp # Price-time priority matching
155
  β”‚ β”œβ”€β”€ actors/
156
  β”‚ β”‚ β”œβ”€β”€ Events.hpp # Inter-actor event types
157
+ β”‚ β”‚ β”œβ”€β”€ OEGActor.hpp/cpp # Order Entry Gateway
158
+ β”‚ β”‚ β”œβ”€β”€ MECoreActor.hpp/cpp # Matching Engine core (per symbol)
159
+ β”‚ β”‚ β”œβ”€β”€ MDGActor.hpp/cpp # Market Data Gateway
160
+ β”‚ β”‚ β”œβ”€β”€ FIXAcceptorActor.hpp/cpp # FIX 4.4 TCP acceptor
161
+ β”‚ β”‚ β”œβ”€β”€ ClearingHouseActor.hpp/cpp # Trade clearing & member positions
162
+ β”‚ β”‚ └── AITraderActor.hpp/cpp # Automated trading members
163
+ β”‚ β”œβ”€β”€ net/SocketCompat.hpp # Cross-platform socket abstraction
164
  β”‚ β”œβ”€β”€ persistence/
165
  β”‚ β”‚ β”œβ”€β”€ PersistenceStore.hpp # Abstract store + InMemoryStore
166
  β”‚ β”‚ └── KafkaStore.hpp # Kafka persistence (optional)
 
173
  β”‚ β”œβ”€β”€ database.py # SQLite (orders, trades, OHLCV)
174
  β”‚ └── templates/index.html # Trading UI with Chart.js
175
  β”œβ”€β”€ fix_gateway/
176
+ β”‚ └── fix_server.py # Python FIX 4.4 TCP acceptor
177
  β”œβ”€β”€ clearing_house/
178
  β”‚ β”œβ”€β”€ app.py # Flask CH portal + API
179
+ β”‚ β”œβ”€β”€ ch_database.py # SQLite (members, holdings)
180
  β”‚ β”œβ”€β”€ ch_ai_trader.py # AI trading strategies
181
+ β”‚ └── templates/ # CH web UI
182
+ β”œβ”€β”€ shared/config.py # Centralized configuration
 
183
  β”œβ”€β”€ docker/
184
  β”‚ β”œβ”€β”€ docker-compose.yml # Kafka + EuNEx (all services)
185
+ β”‚ β”œβ”€β”€ Dockerfile # Multi-stage Linux build
186
  β”‚ └── nginx.conf # Reverse proxy configuration
 
187
  β”œβ”€β”€ tests/
188
+ β”‚ β”œβ”€β”€ test_orderbook.cpp # Book unit tests (26 cases)
189
+ β”‚ β”œβ”€β”€ test_matching_engine.cpp # ME integration tests
190
+ β”‚ β”œβ”€β”€ test_threaded_engine.cpp # Multi-threaded engine tests
191
+ β”‚ β”œβ”€β”€ test_clearing_house.cpp # Clearing house tests (7 cases)
192
+ β”‚ β”œβ”€β”€ test_fix_gateway.cpp # FIX gateway tests (5 cases)
193
+ β”‚ └── test_ai_trader.cpp # AI trader tests (6 cases)
194
  β”œβ”€β”€ examples/
195
  β”‚ β”œβ”€β”€ ping_pong.cpp # Actor basics tutorial
196
  β”‚ └── simple_match.cpp # Matching with Recovery + IACA
197
+ └── docs/
198
+ β”œβ”€β”€ developers-guide.md # Detailed developers guide
199
+ └── process-diagram.md # Architecture diagrams
200
  ```
201
 
202
+ ## Documentation
203
+
204
+ - **[Developers Guide](docs/developers-guide.md)** β€” Detailed architecture, data flow, component reference
205
+ - **[Process Diagram](docs/process-diagram.md)** β€” Optiq architecture diagrams and roadmap
206
+
207
  ## Next Steps
208
 
209
+ 1. ~~Multi-threaded actor engine~~ βœ“ SimplxShim with mailbox queues
210
  2. ~~Kafka persistence~~ βœ“ KafkaStore + Docker Compose (KRaft mode)
211
+ 3. ~~FIX gateway~~ βœ“ C++ FIXAcceptorActor + Python fallback
212
+ 4. ~~Clearing House~~ βœ“ ClearingHouseActor + AITraderActor
213
+ 5. **SBE encoding** β€” replace event structs with SBE-encoded messages
214
+ 6. **Master/Mirror failover** β€” implement full Recovery replay on Mirror node
215
+ 7. **Trading phases** β€” pre-open, uncrossing, continuous, close, TAL
216
+ 8. **Additional order types** β€” Stop, Pegged, Mid-Point, Iceberg
docs/developers-guide.md ADDED
@@ -0,0 +1,889 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EuNEx Developers Guide
2
+
3
+ **Version 0.4.0** | Euronext Optiq-Modeled Exchange Simulator
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Overview](#1-overview)
10
+ 2. [Architecture](#2-architecture)
11
+ 3. [Optiq Naming Alignment](#3-optiq-naming-alignment)
12
+ 4. [Actor Topology](#4-actor-topology)
13
+ 5. [Data Flow](#5-data-flow)
14
+ 6. [Core Components](#6-core-components)
15
+ 7. [Event System](#7-event-system)
16
+ 8. [Order Book & Matching](#8-order-book--matching)
17
+ 9. [Recovery & IACA](#9-recovery--iaca)
18
+ 10. [FIX Protocol Gateway](#10-fix-protocol-gateway)
19
+ 11. [Clearing House](#11-clearing-house)
20
+ 12. [AI Trading Members](#12-ai-trading-members)
21
+ 13. [Project Structure](#13-project-structure)
22
+ 14. [Build & Test](#14-build--test)
23
+ 15. [Configuration](#15-configuration)
24
+ 16. [Extending EuNEx](#16-extending-eunex)
25
+
26
+ ---
27
+
28
+ ## 1. Overview
29
+
30
+ 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.
31
+
32
+ ```
33
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
34
+ β”‚ EuNEx v0.4.0 β”‚
35
+ β”‚ β”‚
36
+ β”‚ FIX 4.4 Clients ──► OEG ──► ME (per symbol) ──► MDG β”‚
37
+ β”‚ β”‚ β”‚ β”‚
38
+ β”‚ ◄── Exec Reports β—„β”€β”€β”€β”˜ β”‚
39
+ β”‚ β”‚ β”‚
40
+ β”‚ Clearing House β”‚
41
+ β”‚ β”‚ β”‚
42
+ β”‚ AI Traders (x10) β”‚
43
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
44
+ ```
45
+
46
+ **Key design principles:**
47
+ - Actor-per-core isolation (no shared mutable state in the hot path)
48
+ - Lock-free `Event::Pipe` for cross-actor communication
49
+ - Fixed-point pricing (8 decimal places, `PRICE_SCALE = 10^8`)
50
+ - Price-time priority matching with multi-level sweep
51
+ - Master/Mirror recovery gating for high availability
52
+
53
+ ---
54
+
55
+ ## 2. Architecture
56
+
57
+ ### High-Level System Diagram
58
+
59
+ ```
60
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
61
+ β”‚ EXTERNAL CLIENTS β”‚
62
+ β”‚ FIX 4.4 / Direct API / Python Bridge β”‚
63
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
64
+ β”‚ β”‚
65
+ TCP :9001 Direct API
66
+ β”‚ β”‚
67
+ ╔═════════════════════════β•ͺ═══════════════════════════════β•ͺ═══════════════════╗
68
+ β•‘ CORE 0 β€” Gateway β•‘
69
+ β•‘ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β” β•‘
70
+ β•‘ β”‚ FIXAcceptorActor β”‚ β”‚ OEGActor β”‚ β•‘
71
+ β•‘ β”‚ │───►│ β”‚ β•‘
72
+ β•‘ β”‚ β€’ TCP accept loop β”‚ β”‚ β€’ Session validation β”‚ β•‘
73
+ β•‘ β”‚ β€’ FIX 4.4 parse/build β”‚ β”‚ β€’ Symbol routing β”‚ β•‘
74
+ β•‘ β”‚ β€’ Logon/Logout/HB β”‚ β”‚ β€’ Exec report fanout β”‚ β•‘
75
+ β•‘ β”‚ β€’ D, F, G β†’ Events β”‚ β”‚ β€’ Cancel, Modify β”‚ β•‘
76
+ β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘
77
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════════════════════╝
78
+ β”‚ NewOrderEvent
79
+ β”‚ CancelOrderEvent
80
+ β”‚ ModifyOrderEvent
81
+ β”‚ (Event::Pipe)
82
+ β–Ό
83
+ ╔═════════════════════════════════════════════════════════════════════════════╗
84
+ β•‘ CORE 1 β€” Matching Engine (one MECoreActor per symbol) β•‘
85
+ β•‘ β•‘
86
+ β•‘ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β•‘
87
+ β•‘ β”‚ MECoreActor β”‚ β”‚ MECoreActor β”‚ β”‚ MECoreActor β”‚ β”‚ MECoreActor β”‚ β•‘
88
+ β•‘ β”‚ AAPL (sym=1) β”‚ β”‚ MSFT (sym=2) β”‚ β”‚ GOOGL (sym=3) β”‚ β”‚ EURO50 (sym=4)β”‚ β•‘
89
+ β•‘ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β•‘
90
+ β•‘ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β•‘
91
+ β•‘ β”‚ β”‚ Book β”‚ β”‚ β”‚ β”‚ Book β”‚ β”‚ β”‚ β”‚ Book β”‚ β”‚ β”‚ β”‚ Book β”‚ β”‚ β•‘
92
+ β•‘ β”‚ β”‚ (bids) β”‚ β”‚ β”‚ β”‚ (bids) β”‚ β”‚ β”‚ β”‚ (bids) β”‚ β”‚ β”‚ β”‚ (bids) β”‚ β”‚ β•‘
93
+ β•‘ β”‚ β”‚ (asks) β”‚ β”‚ β”‚ β”‚ (asks) β”‚ β”‚ β”‚ β”‚ (asks) β”‚ β”‚ β”‚ β”‚ (asks) β”‚ β”‚ β•‘
94
+ β•‘ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β•‘
95
+ β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘
96
+ β•‘ β”‚ β”‚ β”‚ β”‚ β•‘
97
+ β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘
98
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ════════════════β•ͺ════════════════β•ͺ═══════════════════════╝
99
+ β”‚ β”‚ β”‚
100
+ ExecReportβ”‚ TradeEventβ”‚ BookUpdateβ”‚
101
+ (β†’ OEG) β”‚ (β†’ MDG) β”‚ (β†’ MDG) β”‚
102
+ β”‚ β”‚ β”‚
103
+ ╔═══════════════════β•ͺ════════════════β•ͺ════════════════β•ͺ═══════════════════════╗
104
+ β•‘ CORE 2 β€” Market Data β–Ό β–Ό β•‘
105
+ β•‘ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β•‘
106
+ β•‘ β”‚ MDGActor β”‚ β•‘
107
+ β•‘ β”‚ β”‚ β•‘
108
+ β•‘ β”‚ β€’ BBO snapshots per symbol (bestBid, bestAsk, depths) β”‚ β•‘
109
+ β•‘ β”‚ β€’ Recent trade history (last 200 trades) β”‚ β•‘
110
+ β•‘ β”‚ β€’ Trade count, volume, last price per symbol β”‚ β•‘
111
+ β•‘ β”‚ β€’ Thread-safe reads for Python dashboard bridge β”‚ β•‘
112
+ β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘
113
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
114
+ β”‚
115
+ TradeEvent
116
+ (β†’ Clearing)
117
+ β”‚
118
+ ╔═════════════════════════════════════════════β•ͺ═══════════════════════════════╗
119
+ β•‘ CORE 3 β€” Post-Trade & AI β–Ό β•‘
120
+ β•‘ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β•‘
121
+ β•‘ β”‚ ClearingHouseActor β”‚ β”‚ AITraderActor β”‚ β•‘
122
+ β•‘ β”‚ β”‚ β”‚ β”‚ β•‘
123
+ β•‘ β”‚ β€’ Session β†’ Member map β”‚ β”‚ β€’ 10 members (MBR01-10) β”‚ β•‘
124
+ β•‘ β”‚ β€’ Capital tracking β”‚ β”‚ β€’ 3 strategies: β”‚ β•‘
125
+ β•‘ β”‚ β€’ Holdings per symbol β”‚ β”‚ momentum, mean_revert, β”‚ β•‘
126
+ β•‘ β”‚ β€’ P&L calculation β”‚ β”‚ random β”‚ β•‘
127
+ β•‘ β”‚ β€’ Leaderboard (sorted) β”‚ β”‚ β€’ Periodic order bursts β”‚ β•‘
128
+ β•‘ β”‚ β€’ Thread-safe for bridge β”‚ β”‚ β€’ BBO + price history β”‚ β•‘
129
+ β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘
130
+ β•‘ β”‚ β•‘
131
+ β•‘ NewOrderEvent β•‘
132
+ β•‘ (β†’ OEG β†’ ME) β•‘
133
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 3. Optiq Naming Alignment
139
+
140
+ EuNEx components are named to match Euronext Optiq production terminology:
141
+
142
+ ```
143
+ Optiq Production EuNEx Class File
144
+ ═══════════════════════ ════════════════════ ═══════════════════════════
145
+ OEActor (OE Gateway) ───► OEGActor src/actors/OEGActor.hpp
146
+ LogicalCoreActor + Book ───► MECoreActor src/actors/MECoreActor.hpp
147
+ Book (order book engine) ───► Book src/common/Book.hpp
148
+ MDLimitLogicalCoreHandler ───► MDGActor src/actors/MDGActor.hpp
149
+ OEG FIX Gateway ───► FIXAcceptorActor src/actors/FIXAcceptorActor.hpp
150
+ Clearing House (PTB path) ───► ClearingHouseActor src/actors/ClearingHouseActor.hpp
151
+ Member Trading Bots ───► AITraderActor src/actors/AITraderActor.hpp
152
+ RecoveryProxy ───► RecoveryProxy src/recovery/RecoveryProxy.hpp
153
+ IacaAggregatorActor ───► IacaAggregator src/iaca/IacaAggregator.hpp
154
+ ```
155
+
156
+ **Why these names?**
157
+ - **OEG** = Order Entry Gateway (the Optiq front-end for all order flow)
158
+ - **ME** = Matching Engine (the core `Book` + `LogicalCoreActor` in Optiq)
159
+ - **MDG** = Market Data Gateway (publishes BBO, trades, depth to consumers)
160
+ - **FIXAcceptor** = Optiq's FIX protocol acceptor component within OEG
161
+
162
+ ---
163
+
164
+ ## 4. Actor Topology
165
+
166
+ ### Core Affinity Layout
167
+
168
+ ```
169
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
170
+ β”‚ β”‚
171
+ β”‚ β”Œβ”€ CPU Core 0 ─────────────────┐ β”Œβ”€ CPU Core 1 ────────────────────┐ β”‚
172
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
173
+ β”‚ β”‚ OEGActor β”‚ β”‚ MECoreActor (AAPL, sym=1) β”‚ β”‚
174
+ β”‚ β”‚ β€’ Symbol β†’ Book routing β”‚ β”‚ MECoreActor (MSFT, sym=2) β”‚ β”‚
175
+ β”‚ β”‚ β€’ Exec report fanout β”‚ β”‚ MECoreActor (GOOGL, sym=3) β”‚ β”‚
176
+ β”‚ β”‚ β”‚ β”‚ MECoreActor (EURO50, sym=4) β”‚ β”‚
177
+ β”‚ β”‚ FIXAcceptorActor β”‚ β”‚ β”‚ β”‚
178
+ β”‚ β”‚ β€’ TCP :9001 β”‚ β”‚ Each owns a Book instance β”‚ β”‚
179
+ β”‚ β”‚ β€’ FIX 4.4 protocol β”‚ β”‚ Single-threaded per actor β”‚ β”‚
180
+ β”‚ β”‚ β”‚ β”‚ No locks in matching path β”‚ β”‚
181
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
182
+ β”‚ β”‚
183
+ β”‚ β”Œβ”€ CPU Core 2 ─────────────────┐ β”Œβ”€ CPU Core 3 ────────────────────┐ β”‚
184
+ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
185
+ β”‚ β”‚ MDGActor β”‚ β”‚ ClearingHouseActor β”‚ β”‚
186
+ β”‚ β”‚ β€’ BBO per symbol β”‚ β”‚ β€’ 10 members, capital, P&L β”‚ β”‚
187
+ β”‚ β”‚ β€’ Trade history β”‚ β”‚ β€’ Session β†’ Member mapping β”‚ β”‚
188
+ β”‚ β”‚ β€’ Snapshot queries β”‚ β”‚ β”‚ β”‚
189
+ β”‚ β”‚ β”‚ β”‚ AITraderActor β”‚ β”‚
190
+ β”‚ β”‚ β”‚ β”‚ β€’ 10 AI members β”‚ β”‚
191
+ β”‚ β”‚ β”‚ β”‚ β€’ Momentum / MeanRev / Rand β”‚ β”‚
192
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
193
+ β”‚ β”‚
194
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
195
+ ```
196
+
197
+ ### Event::Pipe Connections
198
+
199
+ ```
200
+ FIXAcceptorActor ──NewOrderEvent──────► OEGActor ──NewOrderEvent──► MECoreActor
201
+ ──CancelOrderEvent──►
202
+ ──ModifyOrderEvent──►
203
+
204
+ MECoreActor ──ExecReportEvent──► OEGActor ──ExecReportEvent──► FIXAcceptorActor
205
+ ──ExecReportEvent──► AITraderActor
206
+
207
+ MECoreActor ──TradeEvent──────► MDGActor
208
+ ──BookUpdateEvent─► MDGActor
209
+ ──TradeEvent──────► ClearingHouseActor
210
+
211
+ AITraderActor ──NewOrderEvent──► OEGActor (via pipe)
212
+ ```
213
+
214
+ ---
215
+
216
+ ## 5. Data Flow
217
+
218
+ ### Order Lifecycle (New Limit Order)
219
+
220
+ ```
221
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” FIX 35=D β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” NewOrderEvent β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
222
+ β”‚ Client β”‚ ──────────────► β”‚ FIXAcceptorActor β”‚ ────────────────► β”‚ OEGActor β”‚
223
+ β”‚ (FIX) β”‚ β”‚ parse FIX tags β”‚ β”‚ route by sym β”‚
224
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ map symbol string β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
225
+ β”‚ to SymbolIndex_t β”‚ β”‚
226
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
227
+ NewOrderEvent
228
+ (Event::Pipe)
229
+ β”‚
230
+ β–Ό
231
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
232
+ β”‚ MECoreActor β”‚
233
+ β”‚ β”‚
234
+ β”‚ 1. RecoveryProxy.cause() β€” persist fragment β”‚
235
+ β”‚ 2. Book.newOrder() β€” price-time matching β”‚
236
+ β”‚ 3. For each fill: β”‚
237
+ β”‚ a. Trade generated (buyer + seller session) β”‚
238
+ β”‚ b. TradeEvent β†’ MDGActor β”‚
239
+ β”‚ c. TradeEvent β†’ ClearingHouseActor β”‚
240
+ β”‚ 4. ExecReportEvent β†’ OEGActor (ack/fill/reject) β”‚
241
+ β”‚ 5. BookUpdateEvent β†’ MDGActor (new BBO) β”‚
242
+ β”‚ 6. IACA fragments β†’ IacaAggregator β”‚
243
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
244
+ β”‚ β”‚
245
+ ExecReportEvent TradeEvent
246
+ β”‚ β”‚
247
+ β–Ό β–Ό
248
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
249
+ β”‚ OEGActor β”‚ β”‚ ClearingHouseActor β”‚
250
+ │ fanout to: │ │ ‒ map session→member│
251
+ β”‚ β€’ FIXAcceptor β”‚ β”‚ β€’ update capital β”‚
252
+ β”‚ β€’ AITrader β”‚ β”‚ β€’ update holdings β”‚
253
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
254
+ β”‚
255
+ FIX 35=8 (ExecReport)
256
+ β”‚
257
+ β–Ό
258
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
259
+ β”‚ Client β”‚
260
+ β”‚ receives fill β”‚
261
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
262
+ ```
263
+
264
+ ### Market Data Flow
265
+
266
+ ```
267
+ MECoreActor ──TradeEvent──────────────► MDGActor
268
+ ──BookUpdateEvent──────────► β”‚
269
+ β”‚
270
+ β–Ό
271
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
272
+ β”‚ MarketDataSnapshot β”‚
273
+ β”‚ per SymbolIndex_t: β”‚
274
+ β”‚ β”‚
275
+ β”‚ β€’ bestBid, bestAsk β”‚
276
+ β”‚ β€’ lastTradePrice β”‚
277
+ β”‚ β€’ lastTradeQty β”‚
278
+ β”‚ β€’ totalBidQty β”‚
279
+ β”‚ β€’ totalAskQty β”‚
280
+ β”‚ β€’ tradeCount β”‚
281
+ β”‚ β€’ updateTime (ns) β”‚
282
+ β”‚ β”‚
283
+ β”‚ recentTrades[] β”‚
284
+ β”‚ (last 200 trades) β”‚
285
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
286
+ β”‚
287
+ getSnapshot() / getRecentTrades()
288
+ (thread-safe, read by Python bridge)
289
+ ```
290
+
291
+ ---
292
+
293
+ ## 6. Core Components
294
+
295
+ ### 6.1 SimplxShim β€” Actor Framework (`src/engine/SimplxShim.hpp`)
296
+
297
+ The actor engine emulates the [Tredzone Simplx](https://github.com/Tredzone/simplx) framework API:
298
+
299
+ | Simplx Concept | EuNEx Implementation |
300
+ |----------------------|-----------------------------------------------|
301
+ | `Actor` | Base class with event handlers, mailbox |
302
+ | `Event::Pipe` | Lock-free cross-actor channel |
303
+ | `ActorId` | `{id: uint64, coreId: uint8}` |
304
+ | `Engine` | Multi-threaded scheduler with core affinity |
305
+ | `Callback` | Timer-like periodic invocation |
306
+ | `AsyncService` | Service locator pattern |
307
+
308
+ **Two operating modes:**
309
+ - **Synchronous** (tests): events delivered inline, single thread
310
+ - **Threaded** (`Engine::runFor`): per-core OS threads, mailbox queues
311
+
312
+ ```
313
+ Synchronous Mode (unit tests) Threaded Mode (Engine::runFor)
314
+ ═══════════════════════════ ═══════════════════════════════
315
+
316
+ ActorA.pipe.push<Event>() ActorA.pipe.push<Event>()
317
+ β”‚ β”‚
318
+ β–Ό β–Ό
319
+ ActorB.onEvent() ← inline Mailbox[coreB].enqueue(Ξ»)
320
+ β”‚
321
+ Core B thread:
322
+ mbox.waitAndDrain()
323
+ β”‚
324
+ β–Ό
325
+ ActorB.onEvent()
326
+ ```
327
+
328
+ ### 6.2 Types (`src/common/Types.hpp`)
329
+
330
+ ```cpp
331
+ Price_t = int64_t // Fixed-point, PRICE_SCALE = 100'000'000
332
+ Quantity_t = uint64_t
333
+ OrderId_t = uint64_t // Exchange-assigned order ID
334
+ ClOrdId_t = uint64_t // Client order ID
335
+ SymbolIndex_t = uint32_t // Instrument identifier
336
+ TradeId_t = uint64_t
337
+ SessionId_t = uint16_t // Client session identifier
338
+ MemberId_t = uint16_t // Clearing member identifier
339
+ Timestamp_ns = uint64_t // Nanosecond timestamp
340
+ ```
341
+
342
+ **Price conversion:**
343
+ ```cpp
344
+ Price_t px = toFixedPrice(150.25); // β†’ 15'025'000'000
345
+ double d = toDouble(px); // β†’ 150.25
346
+ ```
347
+
348
+ ### 6.3 Order & Trade Structs
349
+
350
+ ```
351
+ Order (88 bytes, packed) Trade (72 bytes, packed)
352
+ ═══════════════════════ ═══════════════════════
353
+ orderId : uint64 tradeId : uint64
354
+ clOrdId : uint64 symbolIdx : uint32
355
+ symbolIdx : uint32 price : int64
356
+ side : uint8 (Buy=1,Sell=2) quantity : uint64
357
+ ordType : uint8 (Market=1,Lim=2) buyOrderId : uint64
358
+ tif : uint8 (Day/GTC/IOC/FOK) sellOrderId : uint64
359
+ price : int64 buyClOrdId : uint64
360
+ quantity : uint64 sellClOrdId : uint64
361
+ remainingQty : uint64 matchTime : uint64
362
+ entryTime : uint64 buySessionId : uint16
363
+ sessionId : uint16 sellSessionId : uint16
364
+ status : uint8
365
+ ```
366
+
367
+ ---
368
+
369
+ ## 7. Event System
370
+
371
+ Events flow between actors via `Event::Pipe`. Each event struct inherits `tredzone::Actor::Event`:
372
+
373
+ ```
374
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
375
+ β”‚ EVENT CATALOG β”‚
376
+ β”‚ β”‚
377
+ β”‚ NewOrderEvent OEG β†’ ME New order submission β”‚
378
+ β”‚ CancelOrderEvent OEG β†’ ME Cancel resting order β”‚
379
+ β”‚ ModifyOrderEvent OEG β†’ ME Modify price/qty (cancel-replace) β”‚
380
+ β”‚ ExecReportEvent ME β†’ OEG Ack, fill, partial, reject β”‚
381
+ β”‚ TradeEvent ME β†’ MDG/CH Trade execution β”‚
382
+ β”‚ BookUpdateEvent ME β†’ MDG/AI BBO + depth snapshot β”‚
383
+ β”‚ RecoveryFragmentEvent ME β†’ Persist Recovery persistence β”‚
384
+ β”‚ β”‚
385
+ β”‚ Direction Key: β”‚
386
+ β”‚ OEG = OEGActor ME = MECoreActor MDG = MDGActor β”‚
387
+ β”‚ CH = ClearingHouse AI = AITraderActor β”‚
388
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
389
+ ```
390
+
391
+ ### Event::Pipe Usage Pattern
392
+
393
+ ```cpp
394
+ // In MECoreActor constructor:
395
+ oePipe_ = Event::Pipe(*this, oeGatewayId); // pipe to OEG
396
+ mdPipe_ = Event::Pipe(*this, marketDataId); // pipe to MDG
397
+
398
+ // Pushing an event (lock-free, zero-copy in same-core mode):
399
+ oePipe_.push<ExecReportEvent>(report, sessionId);
400
+ mdPipe_.push<TradeEvent>(trade);
401
+ ```
402
+
403
+ ---
404
+
405
+ ## 8. Order Book & Matching
406
+
407
+ ### Book (`src/common/Book.hpp`)
408
+
409
+ The `Book` class implements price-time priority matching:
410
+
411
+ ```
412
+ BIDS (sorted descending) ASKS (sorted ascending)
413
+ ════════════════════════ ════════════════════════
414
+
415
+ Price β”‚ Orders (FIFO) Price β”‚ Orders (FIFO)
416
+ ─────────┼────────────── ─────────┼──────────────
417
+ 155.00 β”‚ [100] [50] 157.00 β”‚ [200]
418
+ 154.50 β”‚ [200] 158.00 β”‚ [100] [150]
419
+ 154.00 β”‚ [75] [100] [25] 159.00 β”‚ [300]
420
+ 153.00 β”‚ [500] 160.00 β”‚ [50]
421
+
422
+ std::map<Price_t, std::map<Price_t,
423
+ vector<Order>, vector<Order>,
424
+ std::greater<>> std::less<>>
425
+ ```
426
+
427
+ **Matching algorithm:**
428
+
429
+ ```
430
+ Incoming BUY @ 158.00, qty=250
431
+ ──────────────────────────────
432
+
433
+ Step 1: Match vs best ask 157.00 (qty=200)
434
+ β†’ Trade: 200 @ 157.00
435
+ β†’ Ask level 157.00 exhausted
436
+
437
+ Step 2: Match vs next ask 158.00 (qty=100 + 150)
438
+ β†’ Trade: 50 @ 158.00 (from first order, partial)
439
+ β†’ Remaining incoming qty = 0 β†’ DONE
440
+
441
+ Step 3: Incoming fully filled
442
+ β†’ ExecReport: status=Filled, filledQty=250
443
+ ```
444
+
445
+ **Order types:**
446
+ - `Limit`: rests on book if no match; price-time priority
447
+ - `Market`: sweeps all levels; never rests (IOC behavior)
448
+
449
+ **Time-in-force:**
450
+ - `Day`: rests until end of session
451
+ - `IOC`: fill what's available, cancel remainder
452
+ - `FOK`: fill all or reject entirely
453
+
454
+ ### MECoreActor (`src/actors/MECoreActor.hpp`)
455
+
456
+ ```
457
+ NewOrderEvent ──► MECoreActor.onEvent()
458
+ β”‚
459
+ β”œβ”€β”€ RecoveryProxy.cause(persistenceId, order, Ξ»)
460
+ β”‚ └── Fragment persisted to store
461
+ β”‚
462
+ β”œβ”€β”€ book_.newOrder(order, onTrade, onExec)
463
+ β”‚ β”œβ”€β”€ matchBuy() / matchSell()
464
+ β”‚ β”‚ └── for each fill: onTrade(trade)
465
+ β”‚ └── onExec(report)
466
+ β”‚
467
+ β”œβ”€β”€ oePipe_.push<ExecReportEvent>(report, session)
468
+ β”œβ”€β”€ mdPipe_.push<TradeEvent>(trade) [per fill]
469
+ β”œβ”€β”€ mdPipe_.push<BookUpdateEvent>(snapshot) [if book changed]
470
+ └── chPipe_.push<TradeEvent>(trade) [per fill, if CH wired]
471
+ ```
472
+
473
+ ---
474
+
475
+ ## 9. Recovery & IACA
476
+
477
+ ### RecoveryProxy (`src/recovery/RecoveryProxy.hpp`)
478
+
479
+ ```
480
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
481
+ β”‚ RecoveryProxy (per MECoreActor) β”‚
482
+ β”‚ β”‚
483
+ β”‚ cause(persistenceId, payload, callback) β”‚
484
+ β”‚ β†’ persist Fragment to FragmentStore β”‚
485
+ β”‚ β†’ callback returns nextCount (# children expected) β”‚
486
+ β”‚ β†’ always executes (Master AND Mirror) β”‚
487
+ β”‚ β”‚
488
+ β”‚ effect(fn, args...) β”‚
489
+ β”‚ β†’ executes fn only on MASTER β”‚
490
+ β”‚ β†’ side effects: send ExecReports, publish trades β”‚
491
+ β”‚ β”‚
492
+ β”‚ recoveryEffect(fn, args...) β”‚
493
+ β”‚ β†’ executes fn only on MIRROR (during failover replay) β”‚
494
+ β”‚ β”‚
495
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
496
+ β”‚ β”‚ MASTER β”‚ β”‚ MIRROR β”‚ β”‚
497
+ β”‚ β”‚ cause() βœ“ β”‚ ◄──────► β”‚ cause() βœ“ β”‚ β”‚
498
+ β”‚ β”‚ effect() βœ“ β”‚ Fragment β”‚ effect() βœ— β”‚ β”‚
499
+ β”‚ β”‚ recEff() βœ— β”‚ Store β”‚ recEff() βœ“ β”‚ β”‚
500
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ (shared) β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
501
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
502
+ ```
503
+
504
+ ### IACA Fragment Chains (`src/iaca/IacaAggregator.hpp`)
505
+
506
+ ```
507
+ Chain Completion Algorithm:
508
+ ══════════════════════════
509
+
510
+ Fragment 1 (ROOT: NEW_ORDER_BUY) nextCount = 2
511
+ β”œβ”€β”€ Fragment 2 (ACK_DATA) nextCount = 0
512
+ └── Fragment 3 (TRADE_DATA) nextCount = 0
513
+
514
+ Completion check:
515
+ sum(nextCount) = 2 + 0 + 0 = 2
516
+ total fragments = 3
517
+ complete when: sum == total - 1 β†’ 2 == 3 - 1 βœ“
518
+
519
+ On completion:
520
+ β†’ NewOrderHandler fires
521
+ β†’ Generates IA SBE message (future: IDS/PTB/SATURN)
522
+ ```
523
+
524
+ ---
525
+
526
+ ## 10. FIX Protocol Gateway
527
+
528
+ ### FIXAcceptorActor (`src/actors/FIXAcceptorActor.hpp`)
529
+
530
+ ```
531
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
532
+ β”‚ FIXAcceptorActor (TCP :9001) β”‚
533
+ β”‚ β”‚
534
+ β”‚ β”Œβ”€ Accept Thread ──────────────────────────────────────────────────┐ β”‚
535
+ β”‚ β”‚ while(running): β”‚ β”‚
536
+ β”‚ β”‚ sock = accept(listenSock) β”‚ β”‚
537
+ β”‚ β”‚ create FIXSession { sock, sessionId, senderCompId } β”‚ β”‚
538
+ β”‚ β”‚ spawn client recv thread β”‚ β”‚
539
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€οΏ½οΏ½β”€β”˜ β”‚
540
+ β”‚ β”‚
541
+ β”‚ β”Œβ”€ Client Recv Thread (per session) ───────────────────────────────┐ β”‚
542
+ β”‚ β”‚ while(loggedOn): β”‚ β”‚
543
+ β”‚ β”‚ data = recv(sock) β”‚ β”‚
544
+ β”‚ β”‚ msgs = parseFIXMessages(data) β”‚ β”‚
545
+ β”‚ β”‚ for msg in msgs: β”‚ β”‚
546
+ β”‚ β”‚ switch(msg[35]): β”‚ β”‚
547
+ β”‚ β”‚ "A" β†’ handleLogon() β”‚ β”‚
548
+ β”‚ β”‚ "D" β†’ handleNewOrderSingle() β†’ push NewOrderEvent β†’ OEG β”‚ β”‚
549
+ β”‚ β”‚ "F" β†’ handleCancelRequest() β†’ push CancelOrderEvent β”‚ β”‚
550
+ β”‚ β”‚ "G" β†’ handleCancelReplace() β†’ push ModifyOrderEvent β”‚ β”‚
551
+ β”‚ β”‚ "5" β†’ logout β”‚ β”‚
552
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
553
+ β”‚ β”‚
554
+ β”‚ β”Œβ”€ Actor Thread (onEvent) ─────────────────────────────────────────┐ β”‚
555
+ β”‚ β”‚ onEvent(ExecReportEvent): β”‚ β”‚
556
+ β”‚ β”‚ find session by sessionId β”‚ β”‚
557
+ β”‚ β”‚ build FIX ExecutionReport (35=8) β”‚ β”‚
558
+ β”‚ β”‚ send() over TCP to client β”‚ β”‚
559
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
560
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
561
+ ```
562
+
563
+ **Supported FIX messages:**
564
+
565
+ | Tag 35 | Message | Direction |
566
+ |--------|-----------------------------|-----------|
567
+ | A | Logon | Inbound |
568
+ | 5 | Logout | Inbound |
569
+ | 0 | Heartbeat | Inbound |
570
+ | D | NewOrderSingle | Inbound |
571
+ | F | OrderCancelRequest | Inbound |
572
+ | G | OrderCancelReplaceRequest | Inbound |
573
+ | 8 | ExecutionReport | Outbound |
574
+
575
+ **Symbol mapping:**
576
+
577
+ | Symbol String | SymbolIndex_t |
578
+ |---------------|---------------|
579
+ | AAPL | 1 |
580
+ | MSFT | 2 |
581
+ | GOOGL | 3 |
582
+ | EURO50 | 4 |
583
+
584
+ ---
585
+
586
+ ## 11. Clearing House
587
+
588
+ ### ClearingHouseActor (`src/actors/ClearingHouseActor.hpp`)
589
+
590
+ ```
591
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
592
+ β”‚ ClearingHouseActor β”‚
593
+ β”‚ β”‚
594
+ β”‚ Session-to-Member Mapping: β”‚
595
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
596
+ β”‚ β”‚ SessionId 100-109 ──► MemberId 1-10 (FIX gateway clients) β”‚ β”‚
597
+ β”‚ β”‚ SessionId 200-209 ──► MemberId 1-10 (AI traders) β”‚ β”‚
598
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
599
+ β”‚ β”‚
600
+ β”‚ On TradeEvent: β”‚
601
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
602
+ β”‚ β”‚ 1. Resolve buySessionId β†’ buyMember β”‚ β”‚
603
+ β”‚ β”‚ 2. Resolve sellSessionId β†’ sellMember β”‚ β”‚
604
+ β”‚ β”‚ 3. Buy side: β”‚ β”‚
605
+ β”‚ β”‚ capital -= price Γ— quantity β”‚ β”‚
606
+ β”‚ β”‚ holdings[symbol].quantity += quantity β”‚ β”‚
607
+ β”‚ β”‚ holdings[symbol].avgCost updated (weighted average) β”‚ β”‚
608
+ β”‚ β”‚ 4. Sell side: β”‚ β”‚
609
+ β”‚ β”‚ capital += price Γ— quantity β”‚ β”‚
610
+ β”‚ β”‚ holdings[symbol].quantity -= quantity β”‚ β”‚
611
+ β”‚ β”‚ 5. Both sides: tradeCount++ β”‚ β”‚
612
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
613
+ β”‚ β”‚
614
+ β”‚ Members (10): β”‚
615
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
616
+ β”‚ β”‚ Name β”‚ Capital β”‚ Holdings β”‚ Trades β”‚ β”‚
617
+ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
618
+ │ │ MBR01 │ 100000.0 │ {sym→qty,cost} │ 0 │ │
619
+ │ │ MBR02 │ 100000.0 │ {sym→qty,cost} │ 0 │ │
620
+ β”‚ β”‚ ... β”‚ ... β”‚ ... β”‚ ... β”‚ β”‚
621
+ │ │ MBR10 │ 100000.0 │ {sym→qty,cost} │ 0 │ │
622
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
623
+ β”‚ β”‚
624
+ β”‚ getLeaderboard(): β”‚
625
+ β”‚ sorted by capital descending β”‚
626
+ β”‚ returns: memberId, name, capital, pnl, tradeCount, holdingCount β”‚
627
+ β”‚ thread-safe (mutex) for Python bridge reads β”‚
628
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
629
+ ```
630
+
631
+ ---
632
+
633
+ ## 12. AI Trading Members
634
+
635
+ ### AITraderActor (`src/actors/AITraderActor.hpp`)
636
+
637
+ ```
638
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
639
+ β”‚ AITraderActor β€” 10 automated trading members β”‚
640
+ β”‚ β”‚
641
+ β”‚ Members: β”‚
642
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
643
+ β”‚ β”‚ Name β”‚ SessionId β”‚ Strategy β”‚ Description β”‚ β”‚
644
+ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚
645
+ β”‚ β”‚ MBR01 β”‚ 200 β”‚ Momentum β”‚ Follow trend β”‚ β”‚
646
+ β”‚ β”‚ MBR02 β”‚ 201 β”‚ MeanReversion β”‚ Fade moves β”‚ β”‚
647
+ β”‚ β”‚ MBR03 β”‚ 202 β”‚ Random β”‚ Noise trader β”‚ β”‚
648
+ β”‚ β”‚ MBR04 β”‚ 203 β”‚ Momentum β”‚ β”‚ β”‚
649
+ β”‚ β”‚ ... β”‚ ... β”‚ (rotates) β”‚ β”‚ β”‚
650
+ β”‚ β”‚ MBR10 β”‚ 209 β”‚ MeanReversion β”‚ β”‚ β”‚
651
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
652
+ β”‚ β”‚
653
+ β”‚ Strategy Details: β”‚
654
+ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
655
+ β”‚ β”‚ β”‚ β”‚
656
+ β”‚ β”‚ MOMENTUM: β”‚ β”‚
657
+ β”‚ β”‚ if last N prices trending up β†’ BUY (follow the trend) β”‚ β”‚
658
+ β”‚ β”‚ if last N prices trending down β†’ SELL β”‚ β”‚
659
+ β”‚ β”‚ price = BBO Β± small offset β”‚ β”‚
660
+ β”‚ β”‚ β”‚ β”‚
661
+ β”‚ β”‚ MEAN REVERSION: β”‚ β”‚
662
+ β”‚ β”‚ if price > moving average β†’ SELL (expect revert) β”‚ β”‚
663
+ β”‚ β”‚ if price < moving average β†’ BUY β”‚ β”‚
664
+ β”‚ β”‚ price = BBO Β± small offset β”‚ β”‚
665
+ β”‚ β”‚ β”‚ β”‚
666
+ β”‚ β”‚ RANDOM: β”‚ β”‚
667
+ β”‚ β”‚ random side (50/50), random qty (10-100) β”‚ β”‚
668
+ β”‚ β”‚ price around midpoint with random spread β”‚ β”‚
669
+ β”‚ β”‚ β”‚ β”‚
670
+ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
671
+ β”‚ β”‚
672
+ β”‚ Data Sources: β”‚
673
+ β”‚ β€’ BookUpdateEvent β†’ BBO (bestBid, bestAsk) per symbol β”‚
674
+ β”‚ β€’ TradeEvent β†’ price history (last 50 prices) per symbol β”‚
675
+ β”‚ β€’ ExecReportEvent β†’ order fill confirmations β”‚
676
+ β”‚ β”‚
677
+ β”‚ Output: β”‚
678
+ β”‚ β€’ NewOrderEvent β†’ OEGActor (via Event::Pipe) β”‚
679
+ β”‚ β€’ ~30s intervals via Actor::Callback β”‚
680
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
681
+ ```
682
+
683
+ ---
684
+
685
+ ## 13. Project Structure
686
+
687
+ ```
688
+ EuNEx/
689
+ β”œβ”€β”€ CMakeLists.txt Build configuration
690
+ β”œβ”€β”€ README.md Project overview
691
+ β”‚
692
+ β”œβ”€β”€ src/
693
+ β”‚ β”œβ”€β”€ main.cpp Engine entry point, actor wiring
694
+ β”‚ β”‚
695
+ β”‚ β”œβ”€β”€ common/
696
+ β”‚ β”‚ β”œβ”€β”€ Types.hpp Core types (Price_t, Order, Trade, etc.)
697
+ β”‚ β”‚ β”œβ”€β”€ Book.hpp Order book interface
698
+ β”‚ β”‚ └── Book.cpp Price-time priority matching engine
699
+ β”‚ β”‚
700
+ β”‚ β”œβ”€β”€ engine/
701
+ β”‚ β”‚ └── SimplxShim.hpp Actor framework (Simplx API compatible)
702
+ β”‚ β”‚
703
+ β”‚ β”œβ”€β”€ actors/
704
+ β”‚ β”‚ β”œβ”€β”€ Events.hpp All inter-actor event definitions
705
+ β”‚ β”‚ β”œβ”€β”€ OEGActor.hpp/cpp Order Entry Gateway
706
+ β”‚ β”‚ β”œβ”€β”€ MECoreActor.hpp/cpp Matching Engine core (per symbol)
707
+ β”‚ β”‚ β”œβ”€β”€ MDGActor.hpp/cpp Market Data Gateway
708
+ β”‚ β”‚ β”œβ”€β”€ FIXAcceptorActor.hpp/cpp FIX 4.4 TCP protocol gateway
709
+ β”‚ β”‚ β”œβ”€β”€ ClearingHouseActor.hpp/cpp Trade clearing & member positions
710
+ β”‚ β”‚ └── AITraderActor.hpp/cpp Automated trading members
711
+ β”‚ β”‚
712
+ β”‚ β”œβ”€β”€ net/
713
+ β”‚ β”‚ └── SocketCompat.hpp Cross-platform socket abstraction
714
+ β”‚ β”‚
715
+ β”‚ β”œβ”€β”€ recovery/
716
+ β”‚ β”‚ β”œβ”€β”€ RecoveryProxy.hpp/cpp Master/Mirror recovery gating
717
+ β”‚ β”‚ └── (FragmentStore) In-memory persistence (β†’ Kafka future)
718
+ β”‚ β”‚
719
+ β”‚ β”œβ”€β”€ iaca/
720
+ β”‚ β”‚ β”œβ”€β”€ Fragment.hpp IACA fragment definitions
721
+ β”‚ β”‚ β”œβ”€β”€ IacaAggregator.hpp/cpp Fragment chain assembly & completion
722
+ β”‚ β”‚ └── (handlers) IA message generators
723
+ β”‚ β”‚
724
+ β”‚ └── persistence/
725
+ β”‚ β”œβ”€β”€ PersistenceStore.hpp Store interface
726
+ β”‚ └── KafkaStore.hpp Kafka adapter (optional, librdkafka)
727
+ β”‚
728
+ β”œβ”€β”€ tests/
729
+ β”‚ β”œβ”€β”€ test_orderbook.cpp Book unit tests (26 cases)
730
+ β”‚ β”œβ”€β”€ test_matching_engine.cpp ME integration tests
731
+ β”‚ β”œβ”€β”€ test_threaded_engine.cpp Multi-threaded actor tests
732
+ β”‚ β”œβ”€β”€ test_clearing_house.cpp Clearing house tests (7 cases)
733
+ β”‚ β”œβ”€β”€ test_fix_gateway.cpp FIX gateway tests (5 cases)
734
+ β”‚ └── test_ai_trader.cpp AI trader tests (6 cases)
735
+ β”‚
736
+ β”œβ”€β”€ examples/
737
+ β”‚ β”œβ”€β”€ ping_pong.cpp Basic actor communication demo
738
+ β”‚ └── simple_match.cpp Simple order matching demo
739
+ β”‚
740
+ └── docs/
741
+ β”œβ”€β”€ developers-guide.md This document
742
+ └── process-diagram.md Detailed Optiq architecture diagrams
743
+ ```
744
+
745
+ ---
746
+
747
+ ## 14. Build & Test
748
+
749
+ ### Prerequisites
750
+
751
+ - **C++20 compiler**: MSVC 19.30+, GCC 12+, Clang 15+
752
+ - **CMake**: 3.16+
753
+ - **Optional**: librdkafka (for Kafka persistence)
754
+
755
+ ### Build Commands
756
+
757
+ ```bash
758
+ # Configure (default: tests ON, Kafka OFF)
759
+ cmake -B build -DEUNEX_BUILD_TESTS=ON
760
+
761
+ # Build
762
+ cmake --build build --config Release
763
+
764
+ # Run tests
765
+ cd build && ctest -C Release --output-on-failure
766
+
767
+ # Run engine
768
+ ./build/Release/eunex_me
769
+ ```
770
+
771
+ ### CMake Options
772
+
773
+ | Option | Default | Description |
774
+ |---------------------|---------|------------------------------------|
775
+ | `EUNEX_USE_SIMPLX` | OFF | Use real Simplx framework |
776
+ | `EUNEX_USE_KAFKA` | OFF | Enable Kafka persistence |
777
+ | `EUNEX_BUILD_TESTS` | ON | Build test binaries |
778
+ | `EUNEX_BUILD_EXAMPLES` | ON | Build example binaries |
779
+
780
+ ### Test Suites
781
+
782
+ ```
783
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
784
+ β”‚ Test Suite β”‚ Cases β”‚ What it verifies β”‚
785
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
786
+ β”‚ OrderBookTest β”‚ 26 β”‚ Book matching, all TIFs, β”‚
787
+ β”‚ β”‚ β”‚ multi-level sweeps, cancels β”‚
788
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
789
+ β”‚ MatchingEngineTest β”‚ - β”‚ MECoreActor event handling β”‚
790
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
791
+ β”‚ ThreadedEngineTest β”‚ - β”‚ Multi-core Engine, mailbox β”‚
792
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
793
+ β”‚ ClearingHouseTest β”‚ 7 β”‚ Capital, holdings, P&L, β”‚
794
+ β”‚ β”‚ β”‚ session mapping, leaderboardβ”‚
795
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
796
+ β”‚ FIXGatewayTest β”‚ 5 β”‚ Symbol mapping, actor β”‚
797
+ β”‚ β”‚ β”‚ lifecycle, OEG routing β”‚
798
+ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
799
+ β”‚ AITraderTest β”‚ 6 β”‚ Strategy execution, multi- β”‚
800
+ β”‚ β”‚ β”‚ symbol, clearing integrationβ”‚
801
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
802
+ ```
803
+
804
+ ---
805
+
806
+ ## 15. Configuration
807
+
808
+ ### Runtime Configuration (main.cpp)
809
+
810
+ | Parameter | Value | Description |
811
+ |---------------------|-----------|--------------------------------------|
812
+ | FIX port | 9001 | FIXAcceptorActor TCP listen port |
813
+ | Symbols | 4 | AAPL(1), MSFT(2), GOOGL(3), EURO50(4) |
814
+ | AI members | 10 | MBR01-MBR10 |
815
+ | Initial capital | 100,000.0 | Per clearing member |
816
+ | AI trade interval | ~3s | Per round in main loop |
817
+ | Price scale | 10^8 | Fixed-point decimal places |
818
+
819
+ ### Seed Orders
820
+
821
+ The engine pre-populates order books with spread-defining orders:
822
+
823
+ ```
824
+ AAPL: Sell 155.00/100, 154.00/200 | Buy 153.00/150, 152.00/100
825
+ MSFT: Sell 325.00/100, 324.00/150 | Buy 323.00/200, 322.00/100
826
+ GOOGL: Sell 142.00/100, 141.00/200 | Buy 140.00/150, 139.00/100
827
+ EURO50: Sell 5050.00/50, 5040.00/80 | Buy 5030.00/60, 5020.00/40
828
+ ```
829
+
830
+ ---
831
+
832
+ ## 16. Extending EuNEx
833
+
834
+ ### Adding a New Symbol
835
+
836
+ 1. Define a new `SymbolIndex_t` constant in `main.cpp`
837
+ 2. Create a `MECoreActor` for it, passing OEG, MDG, and CH actor IDs
838
+ 3. Register it with `oeGateway->mapSymbol(newSym, bookActor->getActorId())`
839
+ 4. Add it to `AITraderActor`'s symbol list
840
+ 5. Add seed orders if desired
841
+
842
+ ### Adding a New Actor
843
+
844
+ 1. Create `src/actors/MyActor.hpp` inheriting `tredzone::Actor`
845
+ 2. Register event handlers in constructor: `registerEventHandler<EventT>(*this)`
846
+ 3. Create `Event::Pipe` members for each destination actor
847
+ 4. Add `.cpp` to `EUNEX_CORE_SOURCES` in `CMakeLists.txt`
848
+ 5. Wire into topology in `main.cpp`
849
+
850
+ ### Adding a New Event Type
851
+
852
+ 1. Define the struct in `src/actors/Events.hpp` inheriting `tredzone::Actor::Event`
853
+ 2. Add `onEvent(const MyEvent&)` to receiving actors
854
+ 3. Register handler: `registerEventHandler<MyEvent>(*this)`
855
+ 4. Push via pipe: `pipe.push<MyEvent>(args...)`
856
+
857
+ ### Adding a New Trading Strategy
858
+
859
+ 1. Add enum value to `Strategy` in `AITraderActor.hpp`
860
+ 2. Implement `strategyMyStrategy()` method
861
+ 3. Add case to `submitOrder()` switch
862
+ 4. Assign to desired members in `initMembers()`
863
+
864
+ ### Future Roadmap
865
+
866
+ ```
867
+ Current (v0.4) Planned
868
+ ══════════════ ═══════════════════════════════════
869
+
870
+ βœ“ Limit + Market orders β–‘ Stop, Pegged, Mid-Point, Iceberg
871
+ βœ“ IOC, FOK, Day β–‘ GTD, GTC, VFU, VFCU
872
+ βœ“ Continuous matching β–‘ Trading phases (pre-open, close)
873
+ βœ“ FIX 4.4 gateway β–‘ FIX 5.0 SP2 + SBE binary
874
+ βœ“ In-memory persistence β–‘ Kafka persistence
875
+ βœ“ SimplxShim (emulation) β–‘ Real Simplx multi-core
876
+ βœ“ 4 symbols β–‘ Multi-segment, partitions
877
+ βœ“ Single partition β–‘ Cross-partition routing
878
+ βœ“ Basic clearing β–‘ EuroCCP/LCH integration
879
+ βœ“ IACA fragments β–‘ IACA FINISH + COPY + IDS
880
+ βœ“ Python bridge (JSON) β–‘ SBE multicast MDG
881
+ β–‘ SATURN ARM (MiFID II RTS 22)
882
+ β–‘ PTB (Post-Trade Box)
883
+ β–‘ Uncrossing algorithm
884
+ β–‘ Price collars & reservations
885
+ ```
886
+
887
+ ---
888
+
889
+ *Generated for EuNEx v0.4.0 β€” Euronext Optiq Architecture Simulator*
docs/process-diagram.md CHANGED
@@ -10,10 +10,10 @@
10
  β”‚
11
  β–Ό
12
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
13
- β”‚ OEGatewayActor (Core 0) ← Optiq: OEG/OEActorβ”‚
14
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
15
  β”‚ β”‚ β€’ Accepts NewOrder / Cancel / Modify requests β”‚ β”‚
16
- β”‚ β”‚ β€’ Validates session, routes by SymbolIndex β†’ correct OrderBookActor β”‚ β”‚
17
  β”‚ β”‚ β€’ Sends ExecutionReports back to client (ack, fill, reject) β”‚ β”‚
18
  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
19
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
@@ -21,7 +21,7 @@
21
  β”‚ (Simplx Event::Pipe β€” lock-free cross-core delivery)
22
  β–Ό
23
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
24
- β”‚ OrderBookActor (Core 1) β€” one per symbol ← Optiq: LogicalCoreActor + Bookβ”‚
25
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
26
  β”‚ β”‚ RECOVERY CAUSE β”‚ β”‚
27
  β”‚ β”‚ RecoveryProxy.cause(persistenceId, order, callback) β”‚ β”‚
@@ -31,7 +31,7 @@
31
  β”‚ β–Ό β”‚
32
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
33
  β”‚ β”‚ MATCHING ENGINE β”‚ β”‚
34
- β”‚ β”‚ OrderBook.newOrder() / cancelOrder() / modifyOrder() β”‚ β”‚
35
  β”‚ β”‚ β€’ Price-time priority matching (std::map<Price, std::deque<Order>>) β”‚ β”‚
36
  β”‚ β”‚ β€’ FOK: reject if insufficient liquidity β”‚ β”‚
37
  β”‚ β”‚ β€’ IOC: fill what's available, cancel remainder β”‚ β”‚
@@ -43,7 +43,7 @@
43
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
44
  β”‚ β”‚ IACA CAUSE β”‚ β”‚ RECOVERY EFFECT (Master only) β”‚ β”‚
45
  β”‚ β”‚ IacaFragment: β”‚ β”‚ RecoveryProxy.effect(callback) β”‚ β”‚
46
- β”‚ β”‚ β€’ BOOK root frag β”‚ β”‚ β€’ Sends ExecReport β†’ OEGateway β”‚ β”‚
47
  β”‚ β”‚ β€’ ACK child frag β”‚ β”‚ β€’ Sends TradeEvent β†’ MarketData β”‚ β”‚
48
  β”‚ β”‚ β€’ TRADE child fragβ”‚ β”‚ β€’ Sends BookUpdate β†’ MarketData β”‚ β”‚
49
  β”‚ β”‚ (nextCount check) β”‚ β”‚ β€’ Mirror skips all effects β”‚ β”‚
@@ -68,7 +68,7 @@
68
  β”‚ IA message β”‚ β”‚
69
  β–Ό β”‚ β”‚
70
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
71
- β”‚ Future: IDS / PTB / β”‚ β”‚ MarketDataActor (Core 2) β”‚
72
  β”‚ SATURN / Clearing β”‚ β”‚ ← Optiq: MDLimitLogicalCoreHandler β”‚
73
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
74
  β”‚ β”‚ IDS: Intraday Data β”‚ β”‚ β”‚ β”‚ β€’ Maintains BBO snapshot per sym β”‚ β”‚
@@ -140,17 +140,17 @@
140
  StockEx (Python/Kafka) EuNEx (C++/Simplx shim) Optiq (Production)
141
  ═══════════════════════ ═══════════════════════ ══════════════════
142
 
143
- fix_oeg_server.py ───▢ OEGatewayActor ───▢ OEActor (FIX/SBE)
144
  Kafka 'orders' topic ───▢ Event::Pipe ───▢ Simplx cross-core
145
  session mgmt ───▢ submitNewOrder() ───▢ FIX 5.0 SP2 acceptor
146
 
147
- matcher.py ───▢ OrderBookActor ───▢ LogicalCoreActor
148
- match_order() ───▢ OrderBook::newOrder() ───▢ RecoveryCauseβ†’IACAβ†’Book
149
- handle_cancel() ───▢ OrderBook::cancelOrder() ───▢ CancelOrderData
150
- handle_amend() ───▢ OrderBook::modifyOrder() ───▢ ModifyOrderData
151
  Kafka 'trades' topic ───▢ TradeEvent via Pipe ───▢ IACA fragment chain
152
 
153
- dashboard.py (SSE) ───▢ MarketDataActor ───▢ MDLimitLogicalCoreHandler
154
  /orderbook/<sym> ───▢ BookUpdateEvent ───▢ PublishLimitUpdateRequest
155
  /trades ───▢ TradeEvent list ───▢ IAβ†’SBE multicast
156
 
@@ -170,16 +170,16 @@
170
  ## Data Flow Summary
171
 
172
  ```
173
- 1. Client ──FIX/SBE──▢ OEGatewayActor ──Event::Pipe──▢ OrderBookActor
174
  β–² β”‚
175
  β”‚ β”œβ”€ RecoveryProxy.cause() β†’ FragmentStore
176
- β”‚ β”œβ”€ OrderBook.match()
177
  β”‚ β”œβ”€ IACA fragments β†’ IacaAggregator
178
  β”‚ β”‚
179
  ExecReport β—€β”€β”˜ β”œβ”€ effect() [Master only]:
180
- β”‚ β”œβ”€ ExecReport β†’ OEGateway β†’ Client
181
- β”‚ β”œβ”€ TradeEvent β†’ MarketDataActor
182
- β”‚ └─ BookUpdate β†’ MarketDataActor
183
  β”‚
184
  └─ IacaAggregator
185
  └─ complete chain β†’ IA message
@@ -203,7 +203,7 @@ and `[FUTURE]` markers where implementation is needed.
203
  β–Ό β–Ό β–Ό β–Ό
204
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
205
  β”‚ OEG.n.BOTH β”‚ β”‚ TCS.IN/OUT β”‚ β”‚ SATURN Web/API β”‚ β”‚ EMS / DAS β”‚
206
- β”‚ (OEGateway β”‚ β”‚ [FUTURE] β”‚ β”‚ [FUTURE] β”‚ β”‚ [FUTURE] β”‚
207
  β”‚ Actor) βœ“ β”‚ β”‚ Trade β”‚ β”‚ External trade β”‚ β”‚ Execution β”‚
208
  β”‚ β”‚ β”‚ Capture β”‚ β”‚ declarations β”‚ β”‚ Mgmt System β”‚
209
  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Service β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
@@ -213,11 +213,11 @@ and `[FUTURE]` markers where implementation is needed.
213
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
214
  β”‚ ME / Trading Chain β”‚
215
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
216
- β”‚ β”‚ Book / LogCore#n (OrderBookActor) βœ“ β”‚ β”‚
217
  β”‚ β”‚ β”‚ β”‚
218
  β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
219
  β”‚ β”‚ β”‚ Recovery Cause β”‚ β”‚ Matching Engine β”‚ β”‚ IACA Inside β”‚ β”‚ β”‚
220
- β”‚ β”‚ β”‚ (RecoveryProxy β”‚ β”‚ (OrderBook) βœ“ β”‚ β”‚ (IacaAggregator β”‚ β”‚ β”‚
221
  β”‚ β”‚ β”‚ β†’ Kafka) βœ“ β”‚ β”‚ β”‚ β”‚ partial) βœ“ β”‚ β”‚ β”‚
222
  β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
223
  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
@@ -438,7 +438,7 @@ From the Optiq Architecture presentation.
438
  β”‚ β€’ Inherits trading group attributes β”‚
439
  β”‚ β€’ Can override: APF (Authorized Price Fluctuation) β”‚
440
  β”‚ β”‚
441
- β”‚ EuNEx currently: OrderBookActor per symbol, single "partition" β”‚
442
  β”‚ [FUTURE] PartitionManager distributes symbols across nodes β”‚
443
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
444
  ```
@@ -585,8 +585,8 @@ From Trading Manual Section 5.
585
 
586
  ```
587
  Phase 1 (Current) βœ“ DONE
588
- β”œβ”€β”€ OrderBook with price-time priority matching
589
- β”œβ”€β”€ OEGatewayActor β†’ OrderBookActor β†’ MarketDataActor pipeline
590
  β”œβ”€β”€ Recovery Cause/Effect with Master/Mirror gating
591
  β”œβ”€β”€ IACA fragment chains with completion detection
592
  └── Simplx shim for single-threaded testing
 
10
  β”‚
11
  β–Ό
12
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
13
+ β”‚ OEGActor (Core 0) ← Optiq: OEG/OEActorβ”‚
14
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
15
  β”‚ β”‚ β€’ Accepts NewOrder / Cancel / Modify requests β”‚ β”‚
16
+ β”‚ β”‚ β€’ Validates session, routes by SymbolIndex β†’ correct MECoreActor β”‚ β”‚
17
  β”‚ β”‚ β€’ Sends ExecutionReports back to client (ack, fill, reject) β”‚ β”‚
18
  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
19
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 
21
  β”‚ (Simplx Event::Pipe β€” lock-free cross-core delivery)
22
  β–Ό
23
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
24
+ β”‚ MECoreActor (Core 1) β€” one per symbol ← Optiq: LogicalCoreActor + Bookβ”‚
25
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
26
  β”‚ β”‚ RECOVERY CAUSE β”‚ β”‚
27
  β”‚ β”‚ RecoveryProxy.cause(persistenceId, order, callback) β”‚ β”‚
 
31
  β”‚ β–Ό β”‚
32
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
33
  β”‚ β”‚ MATCHING ENGINE β”‚ β”‚
34
+ β”‚ β”‚ Book.newOrder() / cancelOrder() / modifyOrder() β”‚ β”‚
35
  β”‚ β”‚ β€’ Price-time priority matching (std::map<Price, std::deque<Order>>) β”‚ β”‚
36
  β”‚ β”‚ β€’ FOK: reject if insufficient liquidity β”‚ β”‚
37
  β”‚ β”‚ β€’ IOC: fill what's available, cancel remainder β”‚ β”‚
 
43
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
44
  β”‚ β”‚ IACA CAUSE β”‚ β”‚ RECOVERY EFFECT (Master only) β”‚ β”‚
45
  β”‚ β”‚ IacaFragment: β”‚ β”‚ RecoveryProxy.effect(callback) β”‚ β”‚
46
+ β”‚ β”‚ β€’ BOOK root frag β”‚ β”‚ β€’ Sends ExecReport β†’ OEG β”‚ β”‚
47
  β”‚ β”‚ β€’ ACK child frag β”‚ β”‚ β€’ Sends TradeEvent β†’ MarketData β”‚ β”‚
48
  β”‚ β”‚ β€’ TRADE child fragβ”‚ β”‚ β€’ Sends BookUpdate β†’ MarketData β”‚ β”‚
49
  β”‚ β”‚ (nextCount check) β”‚ β”‚ β€’ Mirror skips all effects β”‚ β”‚
 
68
  β”‚ IA message β”‚ β”‚
69
  β–Ό β”‚ β”‚
70
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
71
+ β”‚ Future: IDS / PTB / β”‚ β”‚ MDGActor (Core 2) β”‚
72
  β”‚ SATURN / Clearing β”‚ β”‚ ← Optiq: MDLimitLogicalCoreHandler β”‚
73
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
74
  β”‚ β”‚ IDS: Intraday Data β”‚ β”‚ β”‚ β”‚ β€’ Maintains BBO snapshot per sym β”‚ β”‚
 
140
  StockEx (Python/Kafka) EuNEx (C++/Simplx shim) Optiq (Production)
141
  ═══════════════════════ ═══════════════════════ ══════════════════
142
 
143
+ fix_oeg_server.py ───▢ OEGActor ───▢ OEActor (FIX/SBE)
144
  Kafka 'orders' topic ───▢ Event::Pipe ───▢ Simplx cross-core
145
  session mgmt ───▢ submitNewOrder() ───▢ FIX 5.0 SP2 acceptor
146
 
147
+ matcher.py ───▢ MECoreActor ───▢ LogicalCoreActor
148
+ match_order() ───▢ Book::newOrder() ───▢ RecoveryCauseβ†’IACAβ†’Book
149
+ handle_cancel() ───▢ Book::cancelOrder() ───▢ CancelOrderData
150
+ handle_amend() ───▢ Book::modifyOrder() ───▢ ModifyOrderData
151
  Kafka 'trades' topic ───▢ TradeEvent via Pipe ───▢ IACA fragment chain
152
 
153
+ dashboard.py (SSE) ───▢ MDGActor ───▢ MDLimitLogicalCoreHandler
154
  /orderbook/<sym> ───▢ BookUpdateEvent ───▢ PublishLimitUpdateRequest
155
  /trades ───▢ TradeEvent list ───▢ IAβ†’SBE multicast
156
 
 
170
  ## Data Flow Summary
171
 
172
  ```
173
+ 1. Client ──FIX/SBE──▢ OEGActor ──Event::Pipe──▢ MECoreActor
174
  β–² β”‚
175
  β”‚ β”œβ”€ RecoveryProxy.cause() β†’ FragmentStore
176
+ β”‚ β”œβ”€ Book.match()
177
  β”‚ β”œβ”€ IACA fragments β†’ IacaAggregator
178
  β”‚ β”‚
179
  ExecReport β—€β”€β”˜ β”œβ”€ effect() [Master only]:
180
+ β”‚ β”œβ”€ ExecReport β†’ OEG β†’ Client
181
+ β”‚ β”œβ”€ TradeEvent β†’ MDGActor
182
+ β”‚ └─ BookUpdate β†’ MDGActor
183
  β”‚
184
  └─ IacaAggregator
185
  └─ complete chain β†’ IA message
 
203
  β–Ό β–Ό β–Ό β–Ό
204
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
205
  β”‚ OEG.n.BOTH β”‚ β”‚ TCS.IN/OUT β”‚ β”‚ SATURN Web/API β”‚ β”‚ EMS / DAS β”‚
206
+ β”‚ (OEG β”‚ β”‚ [FUTURE] β”‚ β”‚ [FUTURE] β”‚ β”‚ [FUTURE] β”‚
207
  β”‚ Actor) βœ“ β”‚ β”‚ Trade β”‚ β”‚ External trade β”‚ β”‚ Execution β”‚
208
  β”‚ β”‚ β”‚ Capture β”‚ β”‚ declarations β”‚ β”‚ Mgmt System β”‚
209
  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Service β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
 
213
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
214
  β”‚ ME / Trading Chain β”‚
215
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
216
+ β”‚ β”‚ Book / LogCore#n (MECoreActor) βœ“ β”‚ β”‚
217
  β”‚ β”‚ β”‚ β”‚
218
  β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
219
  β”‚ β”‚ β”‚ Recovery Cause β”‚ β”‚ Matching Engine β”‚ β”‚ IACA Inside β”‚ β”‚ β”‚
220
+ β”‚ β”‚ β”‚ (RecoveryProxy β”‚ β”‚ (Book) βœ“ β”‚ β”‚ (IacaAggregator β”‚ β”‚ β”‚
221
  β”‚ β”‚ β”‚ β†’ Kafka) βœ“ β”‚ β”‚ β”‚ β”‚ partial) βœ“ β”‚ β”‚ β”‚
222
  β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
223
  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
 
438
  β”‚ β€’ Inherits trading group attributes β”‚
439
  β”‚ β€’ Can override: APF (Authorized Price Fluctuation) β”‚
440
  β”‚ β”‚
441
+ β”‚ EuNEx currently: MECoreActor per symbol, single "partition" β”‚
442
  β”‚ [FUTURE] PartitionManager distributes symbols across nodes β”‚
443
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
444
  ```
 
585
 
586
  ```
587
  Phase 1 (Current) βœ“ DONE
588
+ β”œβ”€β”€ Book with price-time priority matching
589
+ β”œβ”€β”€ OEGActor β†’ MECoreActor β†’ MDGActor pipeline
590
  β”œβ”€β”€ Recovery Cause/Effect with Master/Mirror gating
591
  β”œβ”€β”€ IACA fragment chains with completion detection
592
  └── Simplx shim for single-threaded testing
examples/simple_match.cpp CHANGED
@@ -12,7 +12,7 @@
12
  // ════════════════════════════════════════════════════════════════════
13
 
14
  #include "engine/SimplxShim.hpp"
15
- #include "common/OrderBook.hpp"
16
  #include "recovery/RecoveryProxy.hpp"
17
  #include "iaca/Fragment.hpp"
18
  #include "iaca/IacaAggregator.hpp"
@@ -40,7 +40,7 @@ int main() {
40
  }
41
  ));
42
 
43
- OrderBook book(1);
44
 
45
  // ── Process a sell order (Optiq-style) ─────────────────────────
46
  std::cout << "── Processing SELL 100 @ 50.00 ──\n";
@@ -177,7 +177,7 @@ int main() {
177
  // ── Simulate Mirror replay ─────────────────────────────────────
178
  std::cout << "\n── Mirror Replay Simulation ──────────────\n";
179
  RecoveryProxy mirrorProxy(ORIGIN_BOOK, 1, store, /*isMaster=*/false);
180
- OrderBook mirrorBook(1);
181
 
182
  // Effects should NOT fire on mirror
183
  mirrorProxy.effect([]() {
 
12
  // ════════════════════════════════════════════════════════════════════
13
 
14
  #include "engine/SimplxShim.hpp"
15
+ #include "common/Book.hpp"
16
  #include "recovery/RecoveryProxy.hpp"
17
  #include "iaca/Fragment.hpp"
18
  #include "iaca/IacaAggregator.hpp"
 
40
  }
41
  ));
42
 
43
+ Book book(1);
44
 
45
  // ── Process a sell order (Optiq-style) ─────────────────────────
46
  std::cout << "── Processing SELL 100 @ 50.00 ──\n";
 
177
  // ── Simulate Mirror replay ─────────────────────────────────────
178
  std::cout << "\n── Mirror Replay Simulation ──────────────\n";
179
  RecoveryProxy mirrorProxy(ORIGIN_BOOK, 1, store, /*isMaster=*/false);
180
+ Book mirrorBook(1);
181
 
182
  // Effects should NOT fire on mirror
183
  mirrorProxy.effect([]() {
src/actors/AITraderActor.hpp CHANGED
@@ -9,7 +9,7 @@
9
  //
10
  // Uses Actor::Callback for periodic trading decisions (~30s intervals).
11
  // Receives BookUpdateEvent for BBO data and TradeEvent for price history.
12
- // Pushes NewOrderEvent to OEGateway.
13
  // ════════════════════════════════════════════════════════════════════
14
 
15
  #include "engine/SimplxShim.hpp"
 
9
  //
10
  // Uses Actor::Callback for periodic trading decisions (~30s intervals).
11
  // Receives BookUpdateEvent for BBO data and TradeEvent for price history.
12
+ // Pushes NewOrderEvent to OEGActor.
13
  // ════════════════════════════════════════════════════════════════════
14
 
15
  #include "engine/SimplxShim.hpp"
src/actors/ClearingHouseActor.hpp CHANGED
@@ -4,7 +4,7 @@
4
  //
5
  // Optiq equivalent: Clearing House downstream from Kafka Bus / PTB
6
  //
7
- // Receives TradeEvent from OrderBookActor(s), attributes trades to
8
  // members via SessionIdβ†’MemberId mapping, maintains capital and
9
  // holdings per member. Exposes thread-safe getLeaderboard() for
10
  // the Python bridge to read.
 
4
  //
5
  // Optiq equivalent: Clearing House downstream from Kafka Bus / PTB
6
  //
7
+ // Receives TradeEvent from MECoreActor(s), attributes trades to
8
  // members via SessionIdβ†’MemberId mapping, maintains capital and
9
  // holdings per member. Exposes thread-safe getLeaderboard() for
10
  // the Python bridge to read.
src/actors/{FIXGatewayActor.cpp β†’ FIXAcceptorActor.cpp} RENAMED
@@ -1,4 +1,4 @@
1
- #include "actors/FIXGatewayActor.hpp"
2
  #include <iostream>
3
  #include <sstream>
4
  #include <cstring>
@@ -9,7 +9,7 @@
9
 
10
  namespace eunex {
11
 
12
- FIXGatewayActor::FIXGatewayActor(const tredzone::ActorId& oeGatewayId, uint16_t port)
13
  : oePipe_(*this, oeGatewayId)
14
  , port_(port)
15
  {
@@ -22,7 +22,7 @@ FIXGatewayActor::FIXGatewayActor(const tredzone::ActorId& oeGatewayId, uint16_t
22
 
23
  listenSock_ = socket(AF_INET, SOCK_STREAM, 0);
24
  if (listenSock_ == INVALID_SOCK) {
25
- std::cerr << "FIXGateway: socket() failed\n";
26
  return;
27
  }
28
 
@@ -34,30 +34,30 @@ FIXGatewayActor::FIXGatewayActor(const tredzone::ActorId& oeGatewayId, uint16_t
34
  addr.sin_port = htons(port_);
35
 
36
  if (bind(listenSock_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0) {
37
- std::cerr << "FIXGateway: bind() failed on port " << port_ << "\n";
38
  closeSocket(listenSock_);
39
  listenSock_ = INVALID_SOCK;
40
  return;
41
  }
42
 
43
  if (listen(listenSock_, 5) != 0) {
44
- std::cerr << "FIXGateway: listen() failed\n";
45
  closeSocket(listenSock_);
46
  listenSock_ = INVALID_SOCK;
47
  return;
48
  }
49
 
50
  running_ = true;
51
- acceptThread_ = std::thread(&FIXGatewayActor::acceptLoop, this);
52
 
53
- std::cout << "FIXGateway: listening on port " << port_ << "\n";
54
  }
55
 
56
- FIXGatewayActor::~FIXGatewayActor() {
57
  stop();
58
  }
59
 
60
- void FIXGatewayActor::stop() {
61
  if (!running_.exchange(false)) return;
62
 
63
  if (listenSock_ != INVALID_SOCK) {
@@ -83,14 +83,14 @@ void FIXGatewayActor::stop() {
83
  sessions_.clear();
84
  }
85
 
86
- int FIXGatewayActor::clientCount() const {
87
  std::lock_guard<std::mutex> lock(sessionMutex_);
88
  return static_cast<int>(sessions_.size());
89
  }
90
 
91
  // ── Accept loop (background thread) ──────────────────────────────
92
 
93
- void FIXGatewayActor::acceptLoop() {
94
  while (running_) {
95
  sockaddr_in clientAddr{};
96
  socklen_t len = sizeof(clientAddr);
@@ -98,7 +98,7 @@ void FIXGatewayActor::acceptLoop() {
98
  reinterpret_cast<sockaddr*>(&clientAddr), &len);
99
 
100
  if (clientSock == INVALID_SOCK) {
101
- if (running_) std::cerr << "FIXGateway: accept() failed\n";
102
  continue;
103
  }
104
 
@@ -112,16 +112,16 @@ void FIXGatewayActor::acceptLoop() {
112
  sess->loggedOn = false;
113
 
114
  FIXSession* raw = sess.get();
115
- sess->recvThread = std::thread(&FIXGatewayActor::clientRecvLoop, this, sessId);
116
 
117
  sessions_[sessId] = std::move(sess);
118
- std::cout << "FIXGateway: client connected (session " << sessId << ")\n";
119
  }
120
  }
121
 
122
  // ── Client recv loop (per-client thread) ─────────────────────────
123
 
124
- void FIXGatewayActor::clientRecvLoop(SessionId_t sessionId) {
125
  socket_t sock;
126
  {
127
  std::lock_guard<std::mutex> lock(sessionMutex_);
@@ -177,7 +177,7 @@ void FIXGatewayActor::clientRecvLoop(SessionId_t sessionId) {
177
 
178
  // ── FIX message parsing ──────────────────────────────────────────
179
 
180
- std::vector<FIXGatewayActor::TagMap> FIXGatewayActor::parseFIXMessages(const std::string& data) {
181
  std::vector<TagMap> result;
182
  std::string remaining = data;
183
 
@@ -213,17 +213,17 @@ std::vector<FIXGatewayActor::TagMap> FIXGatewayActor::parseFIXMessages(const std
213
  return result;
214
  }
215
 
216
- std::string FIXGatewayActor::getTag(const TagMap& msg, int tag, const std::string& def) {
217
  auto it = msg.find(tag);
218
  return (it != msg.end()) ? it->second : def;
219
  }
220
 
221
  // ── FIX message handlers ─────────────────────────────────────────
222
 
223
- void FIXGatewayActor::handleLogon(FIXSession& sess, const TagMap& msg) {
224
  sess.senderCompId = getTag(msg, 49, "UNKNOWN");
225
  sess.loggedOn = true;
226
- std::cout << "FIXGateway: Logon from " << sess.senderCompId
227
  << " (session " << sess.sessionId << ")\n";
228
 
229
  sendFIX(sess, "A", {
@@ -232,7 +232,7 @@ void FIXGatewayActor::handleLogon(FIXSession& sess, const TagMap& msg) {
232
  });
233
  }
234
 
235
- void FIXGatewayActor::handleNewOrderSingle(FIXSession& sess, const TagMap& msg) {
236
  std::string clOrdIdStr = getTag(msg, 11);
237
  std::string symbol = getTag(msg, 55);
238
  std::string sideStr = getTag(msg, 54);
@@ -256,7 +256,7 @@ void FIXGatewayActor::handleNewOrderSingle(FIXSession& sess, const TagMap& msg)
256
  oePipe_.push<NewOrderEvent>(clOrdId, symIdx, side, ordType, tif, price, qty, sess.sessionId);
257
  }
258
 
259
- void FIXGatewayActor::handleCancelRequest(FIXSession& sess, const TagMap& msg) {
260
  std::string origClOrdIdStr = getTag(msg, 41);
261
  std::string orderIdStr = getTag(msg, 37, "0");
262
  std::string symbol = getTag(msg, 55);
@@ -268,7 +268,7 @@ void FIXGatewayActor::handleCancelRequest(FIXSession& sess, const TagMap& msg) {
268
  oePipe_.push<CancelOrderEvent>(orderId, origClOrdId, symIdx, sess.sessionId);
269
  }
270
 
271
- void FIXGatewayActor::handleCancelReplaceRequest(FIXSession& sess, const TagMap& msg) {
272
  std::string orderIdStr = getTag(msg, 37, "0");
273
  std::string origClOrdIdStr = getTag(msg, 41);
274
  std::string symbol = getTag(msg, 55);
@@ -286,7 +286,7 @@ void FIXGatewayActor::handleCancelReplaceRequest(FIXSession& sess, const TagMap&
286
 
287
  // ── Send FIX message ─────────────────────────────────────────────
288
 
289
- void FIXGatewayActor::sendFIX(FIXSession& sess, const std::string& msgType,
290
  const std::vector<std::pair<int, std::string>>& fields) {
291
  std::ostringstream body;
292
  body << "35=" << msgType << '\x01';
@@ -333,11 +333,11 @@ void FIXGatewayActor::sendFIX(FIXSession& sess, const std::string& msgType,
333
 
334
  // ── Exec report handling ─────────────────────────────────────────
335
 
336
- void FIXGatewayActor::onEvent(const ExecReportEvent& event) {
337
  sendExecReport(event.sessionId, event);
338
  }
339
 
340
- void FIXGatewayActor::sendExecReport(SessionId_t sessionId, const ExecReportEvent& rpt) {
341
  std::lock_guard<std::mutex> lock(sessionMutex_);
342
  auto it = sessions_.find(sessionId);
343
  if (it == sessions_.end()) return;
@@ -386,7 +386,7 @@ void FIXGatewayActor::sendExecReport(SessionId_t sessionId, const ExecReportEven
386
 
387
  // ── Symbol mapping ───────────────────────────────────────────────
388
 
389
- SymbolIndex_t FIXGatewayActor::symbolFromString(const std::string& sym) {
390
  if (sym == "AAPL") return 1;
391
  if (sym == "MSFT") return 2;
392
  if (sym == "GOOGL") return 3;
@@ -394,7 +394,7 @@ SymbolIndex_t FIXGatewayActor::symbolFromString(const std::string& sym) {
394
  return static_cast<SymbolIndex_t>(std::strtoul(sym.c_str(), nullptr, 10));
395
  }
396
 
397
- std::string FIXGatewayActor::symbolToString(SymbolIndex_t idx) {
398
  switch (idx) {
399
  case 1: return "AAPL";
400
  case 2: return "MSFT";
 
1
+ #include "actors/FIXAcceptorActor.hpp"
2
  #include <iostream>
3
  #include <sstream>
4
  #include <cstring>
 
9
 
10
  namespace eunex {
11
 
12
+ FIXAcceptorActor::FIXAcceptorActor(const tredzone::ActorId& oeGatewayId, uint16_t port)
13
  : oePipe_(*this, oeGatewayId)
14
  , port_(port)
15
  {
 
22
 
23
  listenSock_ = socket(AF_INET, SOCK_STREAM, 0);
24
  if (listenSock_ == INVALID_SOCK) {
25
+ std::cerr << "FIXAcceptor: socket() failed\n";
26
  return;
27
  }
28
 
 
34
  addr.sin_port = htons(port_);
35
 
36
  if (bind(listenSock_, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) != 0) {
37
+ std::cerr << "FIXAcceptor: bind() failed on port " << port_ << "\n";
38
  closeSocket(listenSock_);
39
  listenSock_ = INVALID_SOCK;
40
  return;
41
  }
42
 
43
  if (listen(listenSock_, 5) != 0) {
44
+ std::cerr << "FIXAcceptor: listen() failed\n";
45
  closeSocket(listenSock_);
46
  listenSock_ = INVALID_SOCK;
47
  return;
48
  }
49
 
50
  running_ = true;
51
+ acceptThread_ = std::thread(&FIXAcceptorActor::acceptLoop, this);
52
 
53
+ std::cout << "FIXAcceptor: listening on port " << port_ << "\n";
54
  }
55
 
56
+ FIXAcceptorActor::~FIXAcceptorActor() {
57
  stop();
58
  }
59
 
60
+ void FIXAcceptorActor::stop() {
61
  if (!running_.exchange(false)) return;
62
 
63
  if (listenSock_ != INVALID_SOCK) {
 
83
  sessions_.clear();
84
  }
85
 
86
+ int FIXAcceptorActor::clientCount() const {
87
  std::lock_guard<std::mutex> lock(sessionMutex_);
88
  return static_cast<int>(sessions_.size());
89
  }
90
 
91
  // ── Accept loop (background thread) ──────────────────────────────
92
 
93
+ void FIXAcceptorActor::acceptLoop() {
94
  while (running_) {
95
  sockaddr_in clientAddr{};
96
  socklen_t len = sizeof(clientAddr);
 
98
  reinterpret_cast<sockaddr*>(&clientAddr), &len);
99
 
100
  if (clientSock == INVALID_SOCK) {
101
+ if (running_) std::cerr << "FIXAcceptor: accept() failed\n";
102
  continue;
103
  }
104
 
 
112
  sess->loggedOn = false;
113
 
114
  FIXSession* raw = sess.get();
115
+ sess->recvThread = std::thread(&FIXAcceptorActor::clientRecvLoop, this, sessId);
116
 
117
  sessions_[sessId] = std::move(sess);
118
+ std::cout << "FIXAcceptor: client connected (session " << sessId << ")\n";
119
  }
120
  }
121
 
122
  // ── Client recv loop (per-client thread) ─────────────────────────
123
 
124
+ void FIXAcceptorActor::clientRecvLoop(SessionId_t sessionId) {
125
  socket_t sock;
126
  {
127
  std::lock_guard<std::mutex> lock(sessionMutex_);
 
177
 
178
  // ── FIX message parsing ──────────────────────────────────────────
179
 
180
+ std::vector<FIXAcceptorActor::TagMap> FIXAcceptorActor::parseFIXMessages(const std::string& data) {
181
  std::vector<TagMap> result;
182
  std::string remaining = data;
183
 
 
213
  return result;
214
  }
215
 
216
+ std::string FIXAcceptorActor::getTag(const TagMap& msg, int tag, const std::string& def) {
217
  auto it = msg.find(tag);
218
  return (it != msg.end()) ? it->second : def;
219
  }
220
 
221
  // ── FIX message handlers ─────────────────────────────────────────
222
 
223
+ void FIXAcceptorActor::handleLogon(FIXSession& sess, const TagMap& msg) {
224
  sess.senderCompId = getTag(msg, 49, "UNKNOWN");
225
  sess.loggedOn = true;
226
+ std::cout << "FIXAcceptor: Logon from " << sess.senderCompId
227
  << " (session " << sess.sessionId << ")\n";
228
 
229
  sendFIX(sess, "A", {
 
232
  });
233
  }
234
 
235
+ void FIXAcceptorActor::handleNewOrderSingle(FIXSession& sess, const TagMap& msg) {
236
  std::string clOrdIdStr = getTag(msg, 11);
237
  std::string symbol = getTag(msg, 55);
238
  std::string sideStr = getTag(msg, 54);
 
256
  oePipe_.push<NewOrderEvent>(clOrdId, symIdx, side, ordType, tif, price, qty, sess.sessionId);
257
  }
258
 
259
+ void FIXAcceptorActor::handleCancelRequest(FIXSession& sess, const TagMap& msg) {
260
  std::string origClOrdIdStr = getTag(msg, 41);
261
  std::string orderIdStr = getTag(msg, 37, "0");
262
  std::string symbol = getTag(msg, 55);
 
268
  oePipe_.push<CancelOrderEvent>(orderId, origClOrdId, symIdx, sess.sessionId);
269
  }
270
 
271
+ void FIXAcceptorActor::handleCancelReplaceRequest(FIXSession& sess, const TagMap& msg) {
272
  std::string orderIdStr = getTag(msg, 37, "0");
273
  std::string origClOrdIdStr = getTag(msg, 41);
274
  std::string symbol = getTag(msg, 55);
 
286
 
287
  // ── Send FIX message ─────────────────────────────────────────────
288
 
289
+ void FIXAcceptorActor::sendFIX(FIXSession& sess, const std::string& msgType,
290
  const std::vector<std::pair<int, std::string>>& fields) {
291
  std::ostringstream body;
292
  body << "35=" << msgType << '\x01';
 
333
 
334
  // ── Exec report handling ─────────────────────────────────────────
335
 
336
+ void FIXAcceptorActor::onEvent(const ExecReportEvent& event) {
337
  sendExecReport(event.sessionId, event);
338
  }
339
 
340
+ void FIXAcceptorActor::sendExecReport(SessionId_t sessionId, const ExecReportEvent& rpt) {
341
  std::lock_guard<std::mutex> lock(sessionMutex_);
342
  auto it = sessions_.find(sessionId);
343
  if (it == sessions_.end()) return;
 
386
 
387
  // ── Symbol mapping ───────────────────────────────────────────────
388
 
389
+ SymbolIndex_t FIXAcceptorActor::symbolFromString(const std::string& sym) {
390
  if (sym == "AAPL") return 1;
391
  if (sym == "MSFT") return 2;
392
  if (sym == "GOOGL") return 3;
 
394
  return static_cast<SymbolIndex_t>(std::strtoul(sym.c_str(), nullptr, 10));
395
  }
396
 
397
+ std::string FIXAcceptorActor::symbolToString(SymbolIndex_t idx) {
398
  switch (idx) {
399
  case 1: return "AAPL";
400
  case 2: return "MSFT";
src/actors/{FIXGatewayActor.hpp β†’ FIXAcceptorActor.hpp} RENAMED
@@ -1,13 +1,13 @@
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
- // FIXGatewayActor β€” C++ FIX 4.4 TCP acceptor
4
  //
5
  // Optiq equivalent: OEG FIX Gateway (FIX 4.4 acceptor for OE frontal)
6
  //
7
  // Runs a TCP accept loop on a background thread. Each connected client
8
  // gets a recv thread that parses FIX messages and enqueues them to the
9
  // actor's mailbox. The actor thread pushes NewOrderEvent / CancelEvent
10
- // to OEGateway via Event::Pipe.
11
  //
12
  // Supported messages:
13
  // Logon (35=A), Logout (35=5), Heartbeat (35=0),
@@ -40,12 +40,12 @@ struct FIXSession {
40
  std::thread recvThread;
41
  };
42
 
43
- class FIXGatewayActor : public tredzone::Actor {
44
  public:
45
  struct Service : tredzone::AsyncService {};
46
 
47
- FIXGatewayActor(const tredzone::ActorId& oeGatewayId, uint16_t port = 9001);
48
- ~FIXGatewayActor();
49
 
50
  void onEvent(const ExecReportEvent& event);
51
 
 
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
+ // FIXAcceptorActor β€” C++ FIX 4.4 TCP acceptor
4
  //
5
  // Optiq equivalent: OEG FIX Gateway (FIX 4.4 acceptor for OE frontal)
6
  //
7
  // Runs a TCP accept loop on a background thread. Each connected client
8
  // gets a recv thread that parses FIX messages and enqueues them to the
9
  // actor's mailbox. The actor thread pushes NewOrderEvent / CancelEvent
10
+ // to OEGActor via Event::Pipe.
11
  //
12
  // Supported messages:
13
  // Logon (35=A), Logout (35=5), Heartbeat (35=0),
 
40
  std::thread recvThread;
41
  };
42
 
43
+ class FIXAcceptorActor : public tredzone::Actor {
44
  public:
45
  struct Service : tredzone::AsyncService {};
46
 
47
+ FIXAcceptorActor(const tredzone::ActorId& oeGatewayId, uint16_t port = 9001);
48
+ ~FIXAcceptorActor();
49
 
50
  void onEvent(const ExecReportEvent& event);
51
 
src/actors/{MarketDataActor.cpp β†’ MDGActor.cpp} RENAMED
@@ -1,13 +1,13 @@
1
- #include "actors/MarketDataActor.hpp"
2
 
3
  namespace eunex {
4
 
5
- MarketDataActor::MarketDataActor() {
6
  registerEventHandler<TradeEvent>(*this);
7
  registerEventHandler<BookUpdateEvent>(*this);
8
  }
9
 
10
- void MarketDataActor::onEvent(const TradeEvent& event) {
11
  const auto& t = event.trade;
12
  auto& snap = snapshots_[t.symbolIdx];
13
  snap.symbolIdx = t.symbolIdx;
@@ -22,7 +22,7 @@ void MarketDataActor::onEvent(const TradeEvent& event) {
22
  }
23
  }
24
 
25
- void MarketDataActor::onEvent(const BookUpdateEvent& event) {
26
  auto& snap = snapshots_[event.symbolIdx];
27
  snap.symbolIdx = event.symbolIdx;
28
  snap.updateTime = nowNs();
@@ -42,7 +42,7 @@ void MarketDataActor::onEvent(const BookUpdateEvent& event) {
42
  }
43
  }
44
 
45
- const MarketDataSnapshot* MarketDataActor::getSnapshot(SymbolIndex_t sym) const {
46
  auto it = snapshots_.find(sym);
47
  return it != snapshots_.end() ? &it->second : nullptr;
48
  }
 
1
+ #include "actors/MDGActor.hpp"
2
 
3
  namespace eunex {
4
 
5
+ MDGActor::MDGActor() {
6
  registerEventHandler<TradeEvent>(*this);
7
  registerEventHandler<BookUpdateEvent>(*this);
8
  }
9
 
10
+ void MDGActor::onEvent(const TradeEvent& event) {
11
  const auto& t = event.trade;
12
  auto& snap = snapshots_[t.symbolIdx];
13
  snap.symbolIdx = t.symbolIdx;
 
22
  }
23
  }
24
 
25
+ void MDGActor::onEvent(const BookUpdateEvent& event) {
26
  auto& snap = snapshots_[event.symbolIdx];
27
  snap.symbolIdx = event.symbolIdx;
28
  snap.updateTime = nowNs();
 
42
  }
43
  }
44
 
45
+ const MarketDataSnapshot* MDGActor::getSnapshot(SymbolIndex_t sym) const {
46
  auto it = snapshots_.find(sym);
47
  return it != snapshots_.end() ? &it->second : nullptr;
48
  }
src/actors/{MarketDataActor.hpp β†’ MDGActor.hpp} RENAMED
@@ -1,6 +1,6 @@
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
- // MarketDataActor β€” Market Data Publisher
4
  //
5
  // StockEx equivalent: dashboard.py SSE streaming + mdf_simulator.py
6
  // - Receives trades/snapshots, streams to UI
@@ -30,11 +30,11 @@ struct MarketDataSnapshot {
30
  Timestamp_ns updateTime;
31
  };
32
 
33
- class MarketDataActor : public tredzone::Actor {
34
  public:
35
  struct Service : tredzone::AsyncService {};
36
 
37
- MarketDataActor();
38
 
39
  void onEvent(const TradeEvent& event);
40
  void onEvent(const BookUpdateEvent& event);
 
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
+ // MDGActor β€” Market Data Publisher
4
  //
5
  // StockEx equivalent: dashboard.py SSE streaming + mdf_simulator.py
6
  // - Receives trades/snapshots, streams to UI
 
30
  Timestamp_ns updateTime;
31
  };
32
 
33
+ class MDGActor : public tredzone::Actor {
34
  public:
35
  struct Service : tredzone::AsyncService {};
36
 
37
+ MDGActor();
38
 
39
  void onEvent(const TradeEvent& event);
40
  void onEvent(const BookUpdateEvent& event);
src/actors/{OrderBookActor.cpp β†’ MECoreActor.cpp} RENAMED
@@ -1,9 +1,9 @@
1
- #include "actors/OrderBookActor.hpp"
2
  #include <iostream>
3
 
4
  namespace eunex {
5
 
6
- OrderBookActor::OrderBookActor(SymbolIndex_t symbolIdx,
7
  const tredzone::ActorId& oeGatewayId,
8
  const tredzone::ActorId& marketDataId,
9
  const tredzone::ActorId& clearingHouseId)
@@ -23,7 +23,7 @@ OrderBookActor::OrderBookActor(SymbolIndex_t symbolIdx,
23
  // StockEx: match_order(order, producer)
24
  // Optiq: RecoveryCause.onInput β†’ CauseOperator β†’ forwardToBook()
25
 
26
- void OrderBookActor::onEvent(const NewOrderEvent& event) {
27
  Order order{};
28
  order.clOrdId = event.clOrdId;
29
  order.symbolIdx = event.symbolIdx;
@@ -57,7 +57,7 @@ void OrderBookActor::onEvent(const NewOrderEvent& event) {
57
  // ── Cancel ─────────────────────────────────────────────────────────
58
  // StockEx: handle_cancel(msg, producer)
59
 
60
- void OrderBookActor::onEvent(const CancelOrderEvent& event) {
61
  ExecutionReport rpt{};
62
  if (book_.cancelOrder(event.orderId, rpt)) {
63
  oePipe_.push<ExecReportEvent>(rpt, event.sessionId);
@@ -72,7 +72,7 @@ void OrderBookActor::onEvent(const CancelOrderEvent& event) {
72
  // ── Modify (Cancel-Replace) ────────────────────────────────────────
73
  // StockEx: handle_amend(msg, producer)
74
 
75
- void OrderBookActor::onEvent(const ModifyOrderEvent& event) {
76
  ExecutionReport rpt{};
77
  if (book_.modifyOrder(event.orderId, event.newPrice, event.newQuantity, rpt)) {
78
  oePipe_.push<ExecReportEvent>(rpt, event.sessionId);
@@ -88,7 +88,7 @@ void OrderBookActor::onEvent(const ModifyOrderEvent& event) {
88
  // StockEx: the Dashboard reads orderbook via REST GET /orderbook/<symbol>
89
  // Optiq: publishLimitEffect β†’ push to MDLimitLogicalCoreHandler
90
 
91
- void OrderBookActor::publishBookUpdate() {
92
  BookUpdateEvent update;
93
  update.symbolIdx = book_.symbolIndex();
94
 
 
1
+ #include "actors/MECoreActor.hpp"
2
  #include <iostream>
3
 
4
  namespace eunex {
5
 
6
+ MECoreActor::MECoreActor(SymbolIndex_t symbolIdx,
7
  const tredzone::ActorId& oeGatewayId,
8
  const tredzone::ActorId& marketDataId,
9
  const tredzone::ActorId& clearingHouseId)
 
23
  // StockEx: match_order(order, producer)
24
  // Optiq: RecoveryCause.onInput β†’ CauseOperator β†’ forwardToBook()
25
 
26
+ void MECoreActor::onEvent(const NewOrderEvent& event) {
27
  Order order{};
28
  order.clOrdId = event.clOrdId;
29
  order.symbolIdx = event.symbolIdx;
 
57
  // ── Cancel ─────────────────────────────────────────────────────────
58
  // StockEx: handle_cancel(msg, producer)
59
 
60
+ void MECoreActor::onEvent(const CancelOrderEvent& event) {
61
  ExecutionReport rpt{};
62
  if (book_.cancelOrder(event.orderId, rpt)) {
63
  oePipe_.push<ExecReportEvent>(rpt, event.sessionId);
 
72
  // ── Modify (Cancel-Replace) ────────────────────────────────────────
73
  // StockEx: handle_amend(msg, producer)
74
 
75
+ void MECoreActor::onEvent(const ModifyOrderEvent& event) {
76
  ExecutionReport rpt{};
77
  if (book_.modifyOrder(event.orderId, event.newPrice, event.newQuantity, rpt)) {
78
  oePipe_.push<ExecReportEvent>(rpt, event.sessionId);
 
88
  // StockEx: the Dashboard reads orderbook via REST GET /orderbook/<symbol>
89
  // Optiq: publishLimitEffect β†’ push to MDLimitLogicalCoreHandler
90
 
91
+ void MECoreActor::publishBookUpdate() {
92
  BookUpdateEvent update;
93
  update.symbolIdx = book_.symbolIndex();
94
 
src/actors/{OrderBookActor.hpp β†’ MECoreActor.hpp} RENAMED
@@ -1,6 +1,6 @@
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
- // OrderBookActor β€” The matching engine core
4
  //
5
  // StockEx equivalent: matcher.py (match_order, handle_cancel, handle_amend)
6
  // Optiq equivalent: LogicalCoreActor + RecoveryHelperCore + Book
@@ -17,17 +17,17 @@
17
  // ════════════════════════════════════════════════════════════════════
18
 
19
  #include "engine/SimplxShim.hpp"
20
- #include "common/OrderBook.hpp"
21
  #include "actors/Events.hpp"
22
  #include <optional>
23
 
24
  namespace eunex {
25
 
26
- class OrderBookActor : public tredzone::Actor {
27
  public:
28
  struct Service : tredzone::AsyncService {};
29
 
30
- OrderBookActor(SymbolIndex_t symbolIdx,
31
  const tredzone::ActorId& oeGatewayId,
32
  const tredzone::ActorId& marketDataId,
33
  const tredzone::ActorId& clearingHouseId = tredzone::ActorId{});
@@ -37,7 +37,7 @@ public:
37
  void onEvent(const ModifyOrderEvent& event);
38
 
39
  private:
40
- OrderBook book_;
41
  tredzone::Actor::Event::Pipe oePipe_;
42
  tredzone::Actor::Event::Pipe mdPipe_;
43
  std::optional<tredzone::Actor::Event::Pipe> chPipe_;
 
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
+ // MECoreActor β€” The matching engine core
4
  //
5
  // StockEx equivalent: matcher.py (match_order, handle_cancel, handle_amend)
6
  // Optiq equivalent: LogicalCoreActor + RecoveryHelperCore + Book
 
17
  // ════════════════════════════════════════════════════════════════════
18
 
19
  #include "engine/SimplxShim.hpp"
20
+ #include "common/Book.hpp"
21
  #include "actors/Events.hpp"
22
  #include <optional>
23
 
24
  namespace eunex {
25
 
26
+ class MECoreActor : public tredzone::Actor {
27
  public:
28
  struct Service : tredzone::AsyncService {};
29
 
30
+ MECoreActor(SymbolIndex_t symbolIdx,
31
  const tredzone::ActorId& oeGatewayId,
32
  const tredzone::ActorId& marketDataId,
33
  const tredzone::ActorId& clearingHouseId = tredzone::ActorId{});
 
37
  void onEvent(const ModifyOrderEvent& event);
38
 
39
  private:
40
+ Book book_;
41
  tredzone::Actor::Event::Pipe oePipe_;
42
  tredzone::Actor::Event::Pipe mdPipe_;
43
  std::optional<tredzone::Actor::Event::Pipe> chPipe_;
src/actors/{OEGatewayActor.cpp β†’ OEGActor.cpp} RENAMED
@@ -1,25 +1,25 @@
1
- #include "actors/OEGatewayActor.hpp"
2
 
3
  namespace eunex {
4
 
5
- OEGatewayActor::OEGatewayActor() {
6
  registerEventHandler<NewOrderEvent>(*this);
7
  registerEventHandler<CancelOrderEvent>(*this);
8
  registerEventHandler<ModifyOrderEvent>(*this);
9
  registerEventHandler<ExecReportEvent>(*this);
10
  }
11
 
12
- void OEGatewayActor::mapSymbol(SymbolIndex_t symbolIdx,
13
  const tredzone::ActorId& bookActorId) {
14
  symbolMap_[symbolIdx] = bookActorId;
15
  }
16
 
17
- void OEGatewayActor::submitNewOrder(ClOrdId_t clOrdId, SymbolIndex_t symbolIdx,
18
  Side side, OrderType ordType, TimeInForce tif,
19
  Price_t price, Quantity_t qty, SessionId_t session) {
20
  auto it = symbolMap_.find(symbolIdx);
21
  if (it == symbolMap_.end()) {
22
- std::cerr << "OEGateway: unknown symbol " << symbolIdx << "\n";
23
  return;
24
  }
25
 
@@ -27,7 +27,7 @@ void OEGatewayActor::submitNewOrder(ClOrdId_t clOrdId, SymbolIndex_t symbolIdx,
27
  pipe.push<NewOrderEvent>(clOrdId, symbolIdx, side, ordType, tif, price, qty, session);
28
  }
29
 
30
- void OEGatewayActor::submitCancel(OrderId_t orderId, ClOrdId_t origClOrdId,
31
  SymbolIndex_t symbolIdx, SessionId_t session) {
32
  auto it = symbolMap_.find(symbolIdx);
33
  if (it == symbolMap_.end()) return;
@@ -36,7 +36,7 @@ void OEGatewayActor::submitCancel(OrderId_t orderId, ClOrdId_t origClOrdId,
36
  pipe.push<CancelOrderEvent>(orderId, origClOrdId, symbolIdx, session);
37
  }
38
 
39
- void OEGatewayActor::submitModify(OrderId_t orderId, ClOrdId_t origClOrdId,
40
  SymbolIndex_t symbolIdx, Price_t newPrice,
41
  Quantity_t newQty, SessionId_t session) {
42
  auto it = symbolMap_.find(symbolIdx);
@@ -46,25 +46,25 @@ void OEGatewayActor::submitModify(OrderId_t orderId, ClOrdId_t origClOrdId,
46
  pipe.push<ModifyOrderEvent>(orderId, origClOrdId, symbolIdx, newPrice, newQty, session);
47
  }
48
 
49
- void OEGatewayActor::onEvent(const NewOrderEvent& event) {
50
  submitNewOrder(event.clOrdId, event.symbolIdx, event.side, event.ordType,
51
  event.tif, event.price, event.quantity, event.sessionId);
52
  }
53
 
54
- void OEGatewayActor::onEvent(const CancelOrderEvent& event) {
55
  submitCancel(event.orderId, event.origClOrdId, event.symbolIdx, event.sessionId);
56
  }
57
 
58
- void OEGatewayActor::onEvent(const ModifyOrderEvent& event) {
59
  submitModify(event.orderId, event.origClOrdId, event.symbolIdx,
60
  event.newPrice, event.newQuantity, event.sessionId);
61
  }
62
 
63
- void OEGatewayActor::addExecReportSubscriber(const tredzone::ActorId& subscriberId) {
64
  execReportSubscribers_.push_back(subscriberId);
65
  }
66
 
67
- void OEGatewayActor::onEvent(const ExecReportEvent& event) {
68
  reports_.push_back(event);
69
 
70
  for (auto& subId : execReportSubscribers_) {
 
1
+ #include "actors/OEGActor.hpp"
2
 
3
  namespace eunex {
4
 
5
+ OEGActor::OEGActor() {
6
  registerEventHandler<NewOrderEvent>(*this);
7
  registerEventHandler<CancelOrderEvent>(*this);
8
  registerEventHandler<ModifyOrderEvent>(*this);
9
  registerEventHandler<ExecReportEvent>(*this);
10
  }
11
 
12
+ void OEGActor::mapSymbol(SymbolIndex_t symbolIdx,
13
  const tredzone::ActorId& bookActorId) {
14
  symbolMap_[symbolIdx] = bookActorId;
15
  }
16
 
17
+ void OEGActor::submitNewOrder(ClOrdId_t clOrdId, SymbolIndex_t symbolIdx,
18
  Side side, OrderType ordType, TimeInForce tif,
19
  Price_t price, Quantity_t qty, SessionId_t session) {
20
  auto it = symbolMap_.find(symbolIdx);
21
  if (it == symbolMap_.end()) {
22
+ std::cerr << "OEG: unknown symbol " << symbolIdx << "\n";
23
  return;
24
  }
25
 
 
27
  pipe.push<NewOrderEvent>(clOrdId, symbolIdx, side, ordType, tif, price, qty, session);
28
  }
29
 
30
+ void OEGActor::submitCancel(OrderId_t orderId, ClOrdId_t origClOrdId,
31
  SymbolIndex_t symbolIdx, SessionId_t session) {
32
  auto it = symbolMap_.find(symbolIdx);
33
  if (it == symbolMap_.end()) return;
 
36
  pipe.push<CancelOrderEvent>(orderId, origClOrdId, symbolIdx, session);
37
  }
38
 
39
+ void OEGActor::submitModify(OrderId_t orderId, ClOrdId_t origClOrdId,
40
  SymbolIndex_t symbolIdx, Price_t newPrice,
41
  Quantity_t newQty, SessionId_t session) {
42
  auto it = symbolMap_.find(symbolIdx);
 
46
  pipe.push<ModifyOrderEvent>(orderId, origClOrdId, symbolIdx, newPrice, newQty, session);
47
  }
48
 
49
+ void OEGActor::onEvent(const NewOrderEvent& event) {
50
  submitNewOrder(event.clOrdId, event.symbolIdx, event.side, event.ordType,
51
  event.tif, event.price, event.quantity, event.sessionId);
52
  }
53
 
54
+ void OEGActor::onEvent(const CancelOrderEvent& event) {
55
  submitCancel(event.orderId, event.origClOrdId, event.symbolIdx, event.sessionId);
56
  }
57
 
58
+ void OEGActor::onEvent(const ModifyOrderEvent& event) {
59
  submitModify(event.orderId, event.origClOrdId, event.symbolIdx,
60
  event.newPrice, event.newQuantity, event.sessionId);
61
  }
62
 
63
+ void OEGActor::addExecReportSubscriber(const tredzone::ActorId& subscriberId) {
64
  execReportSubscribers_.push_back(subscriberId);
65
  }
66
 
67
+ void OEGActor::onEvent(const ExecReportEvent& event) {
68
  reports_.push_back(event);
69
 
70
  for (auto& subId : execReportSubscribers_) {
src/actors/{OEGatewayActor.hpp β†’ OEGActor.hpp} RENAMED
@@ -1,10 +1,10 @@
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
- // OEGatewayActor β€” Order Entry Gateway
4
  //
5
  // StockEx equivalent: fix_oeg_server.py + consumer.py
6
  // - Receives FIX messages, normalizes them, pushes to Kafka
7
- // - Here: receives external input and routes to OrderBookActor
8
  //
9
  // Optiq equivalent: OEActor
10
  // - Receives messages from OE frontal (SBE over TCP)
@@ -20,11 +20,11 @@
20
 
21
  namespace eunex {
22
 
23
- class OEGatewayActor : public tredzone::Actor {
24
  public:
25
  struct Service : tredzone::AsyncService {};
26
 
27
- OEGatewayActor();
28
 
29
  // Register an OrderBook actor for a symbol
30
  void mapSymbol(SymbolIndex_t symbolIdx, const tredzone::ActorId& bookActorId);
 
1
  #pragma once
2
  // ════════════════════════════════════════════════════════════════════
3
+ // OEGActor β€” Order Entry Gateway
4
  //
5
  // StockEx equivalent: fix_oeg_server.py + consumer.py
6
  // - Receives FIX messages, normalizes them, pushes to Kafka
7
+ // - Here: receives external input and routes to MECoreActor
8
  //
9
  // Optiq equivalent: OEActor
10
  // - Receives messages from OE frontal (SBE over TCP)
 
20
 
21
  namespace eunex {
22
 
23
+ class OEGActor : public tredzone::Actor {
24
  public:
25
  struct Service : tredzone::AsyncService {};
26
 
27
+ OEGActor();
28
 
29
  // Register an OrderBook actor for a symbol
30
  void mapSymbol(SymbolIndex_t symbolIdx, const tredzone::ActorId& bookActorId);
src/common/{OrderBook.cpp β†’ Book.cpp} RENAMED
@@ -1,14 +1,14 @@
1
- #include "common/OrderBook.hpp"
2
  #include <algorithm>
3
 
4
  namespace eunex {
5
 
6
- OrderBook::OrderBook(SymbolIndex_t symbolIdx)
7
  : symbolIdx_(symbolIdx) {}
8
 
9
  // ── New Order Entry ────────────────────────────────────────────────
10
 
11
- void OrderBook::newOrder(Order& order, const TradeCallback& onTrade,
12
  const ExecCallback& onExec) {
13
  order.orderId = allocateOrderId();
14
  order.remainingQty = order.quantity;
@@ -76,7 +76,7 @@ void OrderBook::newOrder(Order& order, const TradeCallback& onTrade,
76
 
77
  // ── Matching: Buy against Asks ─────────────────────────────────────
78
 
79
- void OrderBook::matchBuy(Order& incoming, const TradeCallback& onTrade,
80
  const ExecCallback& onExec) {
81
  auto it = asks_.begin();
82
  while (incoming.remainingQty > 0 && it != asks_.end()) {
@@ -133,7 +133,7 @@ void OrderBook::matchBuy(Order& incoming, const TradeCallback& onTrade,
133
 
134
  // ── Matching: Sell against Bids ────────────────────────────────────
135
 
136
- void OrderBook::matchSell(Order& incoming, const TradeCallback& onTrade,
137
  const ExecCallback& onExec) {
138
  auto it = bids_.begin();
139
  while (incoming.remainingQty > 0 && it != bids_.end()) {
@@ -189,7 +189,7 @@ void OrderBook::matchSell(Order& incoming, const TradeCallback& onTrade,
189
 
190
  // ── Cancel ─────────────────────────────────────────────────────────
191
 
192
- bool OrderBook::cancelOrder(OrderId_t orderId, ExecutionReport& report) {
193
  auto it = orderIndex_.find(orderId);
194
  if (it == orderIndex_.end())
195
  return false;
@@ -232,7 +232,7 @@ bool OrderBook::cancelOrder(OrderId_t orderId, ExecutionReport& report) {
232
 
233
  // ── Modify (Cancel-Replace) ────────────────────────────────────────
234
 
235
- bool OrderBook::modifyOrder(OrderId_t orderId, Price_t newPrice, Quantity_t newQty,
236
  ExecutionReport& report) {
237
  ExecutionReport cancelRpt{};
238
  if (!cancelOrder(orderId, cancelRpt))
@@ -259,7 +259,7 @@ bool OrderBook::modifyOrder(OrderId_t orderId, Price_t newPrice, Quantity_t newQ
259
 
260
  // ── Insert resting order ───────────────────────────────────────────
261
 
262
- void OrderBook::insertResting(Order& order) {
263
  orderIndex_[order.orderId] = {order.side, order.price};
264
  if (order.side == Side::Buy) {
265
  bids_[order.price].push_back(order);
@@ -268,7 +268,7 @@ void OrderBook::insertResting(Order& order) {
268
  }
269
  }
270
 
271
- void OrderBook::removeOrder(OrderId_t orderId, Side side, Price_t price) {
272
  orderIndex_.erase(orderId);
273
  if (side == Side::Buy) {
274
  auto it = bids_.find(price);
@@ -292,7 +292,7 @@ void OrderBook::removeOrder(OrderId_t orderId, Side side, Price_t price) {
292
  // ── Query helpers ──────────────────────────────────────────────────
293
 
294
  template<typename MapT>
295
- std::vector<OrderBook::Level> OrderBook::getLevels(const MapT& map, int depth) const {
296
  std::vector<Level> result;
297
  result.reserve(depth);
298
  int count = 0;
@@ -306,21 +306,21 @@ std::vector<OrderBook::Level> OrderBook::getLevels(const MapT& map, int depth) c
306
  return result;
307
  }
308
 
309
- std::vector<OrderBook::Level> OrderBook::getBids(int depth) const {
310
  return getLevels(bids_, depth);
311
  }
312
 
313
- std::vector<OrderBook::Level> OrderBook::getAsks(int depth) const {
314
  return getLevels(asks_, depth);
315
  }
316
 
317
- size_t OrderBook::bidCount() const {
318
  size_t count = 0;
319
  for (auto& [_, orders] : bids_) count += orders.size();
320
  return count;
321
  }
322
 
323
- size_t OrderBook::askCount() const {
324
  size_t count = 0;
325
  for (auto& [_, orders] : asks_) count += orders.size();
326
  return count;
 
1
+ #include "common/Book.hpp"
2
  #include <algorithm>
3
 
4
  namespace eunex {
5
 
6
+ Book::Book(SymbolIndex_t symbolIdx)
7
  : symbolIdx_(symbolIdx) {}
8
 
9
  // ── New Order Entry ────────────────────────────────────────────────
10
 
11
+ void Book::newOrder(Order& order, const TradeCallback& onTrade,
12
  const ExecCallback& onExec) {
13
  order.orderId = allocateOrderId();
14
  order.remainingQty = order.quantity;
 
76
 
77
  // ── Matching: Buy against Asks ─────────────────────────────────────
78
 
79
+ void Book::matchBuy(Order& incoming, const TradeCallback& onTrade,
80
  const ExecCallback& onExec) {
81
  auto it = asks_.begin();
82
  while (incoming.remainingQty > 0 && it != asks_.end()) {
 
133
 
134
  // ── Matching: Sell against Bids ────────────────────────────────────
135
 
136
+ void Book::matchSell(Order& incoming, const TradeCallback& onTrade,
137
  const ExecCallback& onExec) {
138
  auto it = bids_.begin();
139
  while (incoming.remainingQty > 0 && it != bids_.end()) {
 
189
 
190
  // ── Cancel ─────────────────────────────────────────────────────────
191
 
192
+ bool Book::cancelOrder(OrderId_t orderId, ExecutionReport& report) {
193
  auto it = orderIndex_.find(orderId);
194
  if (it == orderIndex_.end())
195
  return false;
 
232
 
233
  // ── Modify (Cancel-Replace) ────────────────────────────────────────
234
 
235
+ bool Book::modifyOrder(OrderId_t orderId, Price_t newPrice, Quantity_t newQty,
236
  ExecutionReport& report) {
237
  ExecutionReport cancelRpt{};
238
  if (!cancelOrder(orderId, cancelRpt))
 
259
 
260
  // ── Insert resting order ───────────────────────────────────────────
261
 
262
+ void Book::insertResting(Order& order) {
263
  orderIndex_[order.orderId] = {order.side, order.price};
264
  if (order.side == Side::Buy) {
265
  bids_[order.price].push_back(order);
 
268
  }
269
  }
270
 
271
+ void Book::removeOrder(OrderId_t orderId, Side side, Price_t price) {
272
  orderIndex_.erase(orderId);
273
  if (side == Side::Buy) {
274
  auto it = bids_.find(price);
 
292
  // ── Query helpers ──────────────────────────────────────────────────
293
 
294
  template<typename MapT>
295
+ std::vector<Book::Level> Book::getLevels(const MapT& map, int depth) const {
296
  std::vector<Level> result;
297
  result.reserve(depth);
298
  int count = 0;
 
306
  return result;
307
  }
308
 
309
+ std::vector<Book::Level> Book::getBids(int depth) const {
310
  return getLevels(bids_, depth);
311
  }
312
 
313
+ std::vector<Book::Level> Book::getAsks(int depth) const {
314
  return getLevels(asks_, depth);
315
  }
316
 
317
+ size_t Book::bidCount() const {
318
  size_t count = 0;
319
  for (auto& [_, orders] : bids_) count += orders.size();
320
  return count;
321
  }
322
 
323
+ size_t Book::askCount() const {
324
  size_t count = 0;
325
  for (auto& [_, orders] : asks_) count += orders.size();
326
  return count;
src/common/{OrderBook.hpp β†’ Book.hpp} RENAMED
@@ -16,12 +16,12 @@ using ExecCallback = std::function<void(const ExecutionReport&)>;
16
  // Mirrors StockEx matcher.py logic but uses sorted std::map for O(log N)
17
  // insert/match instead of Python list.sort() on every match.
18
  //
19
- // In Optiq, each OrderBookActor owns exactly one OrderBook instance.
20
  // The actor guarantees single-threaded access β€” no locks needed.
21
  //
22
- class OrderBook {
23
  public:
24
- explicit OrderBook(SymbolIndex_t symbolIdx);
25
 
26
  // Insert a new order and attempt matching. Returns executions via callbacks.
27
  void newOrder(Order& order, const TradeCallback& onTrade, const ExecCallback& onExec);
 
16
  // Mirrors StockEx matcher.py logic but uses sorted std::map for O(log N)
17
  // insert/match instead of Python list.sort() on every match.
18
  //
19
+ // In Optiq, each BookActor owns exactly one Book instance.
20
  // The actor guarantees single-threaded access β€” no locks needed.
21
  //
22
+ class Book {
23
  public:
24
+ explicit Book(SymbolIndex_t symbolIdx);
25
 
26
  // Insert a new order and attempt matching. Returns executions via callbacks.
27
  void newOrder(Order& order, const TradeCallback& onTrade, const ExecCallback& onExec);
src/main.cpp CHANGED
@@ -3,9 +3,9 @@
3
  //
4
  // Multi-threaded actor topology (mirrors Optiq architecture):
5
  //
6
- // Core 0: OEGatewayActor + FIXGatewayActor
7
- // Core 1: OrderBookActor per symbol (matching engine)
8
- // Core 2: MarketDataActor
9
  // Core 3: ClearingHouseActor + AITraderActor
10
  //
11
  // Optiq equivalent topology:
@@ -15,11 +15,11 @@
15
  // ════════════════════════════════════════════════════════════════════
16
 
17
  #include "engine/SimplxShim.hpp"
18
- #include "actors/OrderBookActor.hpp"
19
- #include "actors/OEGatewayActor.hpp"
20
- #include "actors/MarketDataActor.hpp"
21
  #include "actors/ClearingHouseActor.hpp"
22
- #include "actors/FIXGatewayActor.hpp"
23
  #include "actors/AITraderActor.hpp"
24
  #include <iostream>
25
  #include <thread>
@@ -52,10 +52,10 @@ int main() {
52
  std::vector<SymbolIndex_t> allSymbols = {SYM_AAPL, SYM_MSFT, SYM_GOOGL, SYM_EURO50};
53
 
54
  // ── Core 0: OE Gateway ────────────────────────────────────────
55
- auto oeGateway = std::make_unique<OEGatewayActor>();
56
 
57
  // ── Core 2: Market Data ───────────────────────────────────────
58
- auto mdActor = std::make_unique<MarketDataActor>();
59
 
60
  // ── Core 3: Clearing House ────────────────────────────────────
61
  auto chActor = std::make_unique<ClearingHouseActor>();
@@ -72,13 +72,13 @@ int main() {
72
  }
73
 
74
  // ── Core 1: Order Books (per symbol) ──────────────────────────
75
- auto bookAAPL = std::make_unique<OrderBookActor>(
76
  SYM_AAPL, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
77
- auto bookMSFT = std::make_unique<OrderBookActor>(
78
  SYM_MSFT, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
79
- auto bookGOOGL = std::make_unique<OrderBookActor>(
80
  SYM_GOOGL, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
81
- auto bookEURO50 = std::make_unique<OrderBookActor>(
82
  SYM_EURO50, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
83
 
84
  oeGateway->mapSymbol(SYM_AAPL, bookAAPL->getActorId());
@@ -87,7 +87,7 @@ int main() {
87
  oeGateway->mapSymbol(SYM_EURO50, bookEURO50->getActorId());
88
 
89
  // ── Core 0: FIX Gateway ──────────────────────────────────────
90
- auto fixGateway = std::make_unique<FIXGatewayActor>(oeGateway->getActorId(), 9001);
91
 
92
  // ── Core 3: AI Trader ─────────────────────────────────────────
93
  auto aiTrader = std::make_unique<AITraderActor>(oeGateway->getActorId(), allSymbols);
@@ -98,14 +98,14 @@ int main() {
98
 
99
  // ── Print topology ────────────────────────────────────────────
100
  std::cout << "Actor topology:\n";
101
- std::cout << " Core 0: OEGateway (id=" << oeGateway->getActorId().id
102
- << "), FIXGateway (id=" << fixGateway->getActorId().id << ")\n";
103
  std::cout << " Core 1: Book AAPL (id=" << bookAAPL->getActorId().id
104
  << "), MSFT (id=" << bookMSFT->getActorId().id
105
  << "), GOOGL (id=" << bookGOOGL->getActorId().id
106
  << "), EURO50 (id=" << bookEURO50->getActorId().id << ")\n";
107
- std::cout << " Core 2: MarketData (id=" << mdActor->getActorId().id << ")\n";
108
- std::cout << " Core 3: ClearingHouse (id=" << chActor->getActorId().id
109
  << "), AITrader (id=" << aiTrader->getActorId().id << ")\n\n";
110
 
111
  std::cout << "Services:\n";
 
3
  //
4
  // Multi-threaded actor topology (mirrors Optiq architecture):
5
  //
6
+ // Core 0: OEGActor + FIXAcceptorActor
7
+ // Core 1: MECoreActor per symbol (matching engine)
8
+ // Core 2: MDGActor
9
  // Core 3: ClearingHouseActor + AITraderActor
10
  //
11
  // Optiq equivalent topology:
 
15
  // ════════════════════════════════════════════════════════════════════
16
 
17
  #include "engine/SimplxShim.hpp"
18
+ #include "actors/MECoreActor.hpp"
19
+ #include "actors/OEGActor.hpp"
20
+ #include "actors/MDGActor.hpp"
21
  #include "actors/ClearingHouseActor.hpp"
22
+ #include "actors/FIXAcceptorActor.hpp"
23
  #include "actors/AITraderActor.hpp"
24
  #include <iostream>
25
  #include <thread>
 
52
  std::vector<SymbolIndex_t> allSymbols = {SYM_AAPL, SYM_MSFT, SYM_GOOGL, SYM_EURO50};
53
 
54
  // ── Core 0: OE Gateway ────────────────────────────────────────
55
+ auto oeGateway = std::make_unique<OEGActor>();
56
 
57
  // ── Core 2: Market Data ───────────────────────────────────────
58
+ auto mdActor = std::make_unique<MDGActor>();
59
 
60
  // ── Core 3: Clearing House ────────────────────────────────────
61
  auto chActor = std::make_unique<ClearingHouseActor>();
 
72
  }
73
 
74
  // ── Core 1: Order Books (per symbol) ──────────────────────────
75
+ auto bookAAPL = std::make_unique<MECoreActor>(
76
  SYM_AAPL, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
77
+ auto bookMSFT = std::make_unique<MECoreActor>(
78
  SYM_MSFT, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
79
+ auto bookGOOGL = std::make_unique<MECoreActor>(
80
  SYM_GOOGL, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
81
+ auto bookEURO50 = std::make_unique<MECoreActor>(
82
  SYM_EURO50, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
83
 
84
  oeGateway->mapSymbol(SYM_AAPL, bookAAPL->getActorId());
 
87
  oeGateway->mapSymbol(SYM_EURO50, bookEURO50->getActorId());
88
 
89
  // ── Core 0: FIX Gateway ──────────────────────────────────────
90
+ auto fixGateway = std::make_unique<FIXAcceptorActor>(oeGateway->getActorId(), 9001);
91
 
92
  // ── Core 3: AI Trader ─────────────────────────────────────────
93
  auto aiTrader = std::make_unique<AITraderActor>(oeGateway->getActorId(), allSymbols);
 
98
 
99
  // ── Print topology ────────────────────────────────────────────
100
  std::cout << "Actor topology:\n";
101
+ std::cout << " Core 0: OEG (id=" << oeGateway->getActorId().id
102
+ << "), FIXAcceptor (id=" << fixGateway->getActorId().id << ")\n";
103
  std::cout << " Core 1: Book AAPL (id=" << bookAAPL->getActorId().id
104
  << "), MSFT (id=" << bookMSFT->getActorId().id
105
  << "), GOOGL (id=" << bookGOOGL->getActorId().id
106
  << "), EURO50 (id=" << bookEURO50->getActorId().id << ")\n";
107
+ std::cout << " Core 2: MDG (id=" << mdActor->getActorId().id << ")\n";
108
+ std::cout << " Core 3: CH (id=" << chActor->getActorId().id
109
  << "), AITrader (id=" << aiTrader->getActorId().id << ")\n\n";
110
 
111
  std::cout << "Services:\n";
src/net/SocketCompat.hpp CHANGED
@@ -1,5 +1,5 @@
1
  #pragma once
2
- // Cross-platform socket abstraction for FIXGatewayActor
3
 
4
  #ifdef _WIN32
5
  #ifndef WIN32_LEAN_AND_MEAN
 
1
  #pragma once
2
+ // Cross-platform socket abstraction for FIXAcceptorActor
3
 
4
  #ifdef _WIN32
5
  #ifndef WIN32_LEAN_AND_MEAN
tests/test_ai_trader.cpp CHANGED
@@ -3,9 +3,9 @@
3
  // ════════════════════════════════════════════════════════════════════
4
 
5
  #include "actors/AITraderActor.hpp"
6
- #include "actors/OEGatewayActor.hpp"
7
- #include "actors/OrderBookActor.hpp"
8
- #include "actors/MarketDataActor.hpp"
9
  #include "actors/ClearingHouseActor.hpp"
10
  #include <iostream>
11
  #include <cassert>
@@ -30,18 +30,18 @@ static int testsFailed = 0;
30
  // ── Tests ─────────────────────────────────────────────────────────
31
 
32
  void test_ai_trader_creates() {
33
- auto oe = std::make_unique<OEGatewayActor>();
34
  std::vector<SymbolIndex_t> syms = {1, 2};
35
  auto ai = std::make_unique<AITraderActor>(oe->getActorId(), syms);
36
  ASSERT_TRUE(ai != nullptr);
37
  }
38
 
39
  void test_ai_submits_orders() {
40
- auto oe = std::make_unique<OEGatewayActor>();
41
- auto md = std::make_unique<MarketDataActor>();
42
 
43
- auto book1 = std::make_unique<OrderBookActor>(1, oe->getActorId(), md->getActorId());
44
- auto book2 = std::make_unique<OrderBookActor>(2, oe->getActorId(), md->getActorId());
45
  oe->mapSymbol(1, book1->getActorId());
46
  oe->mapSymbol(2, book2->getActorId());
47
 
@@ -54,9 +54,9 @@ void test_ai_submits_orders() {
54
  }
55
 
56
  void test_ai_responds_to_book_update() {
57
- auto oe = std::make_unique<OEGatewayActor>();
58
- auto md = std::make_unique<MarketDataActor>();
59
- auto book = std::make_unique<OrderBookActor>(1, oe->getActorId(), md->getActorId());
60
  oe->mapSymbol(1, book->getActorId());
61
 
62
  std::vector<SymbolIndex_t> syms = {1};
@@ -76,7 +76,7 @@ void test_ai_responds_to_book_update() {
76
  }
77
 
78
  void test_ai_responds_to_trade() {
79
- auto oe = std::make_unique<OEGatewayActor>();
80
  std::vector<SymbolIndex_t> syms = {1};
81
  auto ai = std::make_unique<AITraderActor>(oe->getActorId(), syms);
82
 
@@ -96,8 +96,8 @@ void test_ai_responds_to_trade() {
96
  }
97
 
98
  void test_ai_with_clearing_house() {
99
- auto oe = std::make_unique<OEGatewayActor>();
100
- auto md = std::make_unique<MarketDataActor>();
101
  auto ch = std::make_unique<ClearingHouseActor>();
102
 
103
  for (int i = 0; i < 10; ++i) {
@@ -105,7 +105,7 @@ void test_ai_with_clearing_house() {
105
  static_cast<MemberId_t>(i + 1));
106
  }
107
 
108
- auto book = std::make_unique<OrderBookActor>(
109
  1, oe->getActorId(), md->getActorId(), ch->getActorId());
110
  oe->mapSymbol(1, book->getActorId());
111
 
@@ -121,13 +121,13 @@ void test_ai_with_clearing_house() {
121
  }
122
 
123
  void test_multiple_symbols() {
124
- auto oe = std::make_unique<OEGatewayActor>();
125
- auto md = std::make_unique<MarketDataActor>();
126
 
127
  std::vector<SymbolIndex_t> syms = {1, 2, 3, 4};
128
- std::vector<std::unique_ptr<OrderBookActor>> books;
129
  for (auto s : syms) {
130
- auto book = std::make_unique<OrderBookActor>(s, oe->getActorId(), md->getActorId());
131
  oe->mapSymbol(s, book->getActorId());
132
  books.push_back(std::move(book));
133
  }
 
3
  // ════════════════════════════════════════════════════════════════════
4
 
5
  #include "actors/AITraderActor.hpp"
6
+ #include "actors/OEGActor.hpp"
7
+ #include "actors/MECoreActor.hpp"
8
+ #include "actors/MDGActor.hpp"
9
  #include "actors/ClearingHouseActor.hpp"
10
  #include <iostream>
11
  #include <cassert>
 
30
  // ── Tests ─────────────────────────────────────────────────────────
31
 
32
  void test_ai_trader_creates() {
33
+ auto oe = std::make_unique<OEGActor>();
34
  std::vector<SymbolIndex_t> syms = {1, 2};
35
  auto ai = std::make_unique<AITraderActor>(oe->getActorId(), syms);
36
  ASSERT_TRUE(ai != nullptr);
37
  }
38
 
39
  void test_ai_submits_orders() {
40
+ auto oe = std::make_unique<OEGActor>();
41
+ auto md = std::make_unique<MDGActor>();
42
 
43
+ auto book1 = std::make_unique<MECoreActor>(1, oe->getActorId(), md->getActorId());
44
+ auto book2 = std::make_unique<MECoreActor>(2, oe->getActorId(), md->getActorId());
45
  oe->mapSymbol(1, book1->getActorId());
46
  oe->mapSymbol(2, book2->getActorId());
47
 
 
54
  }
55
 
56
  void test_ai_responds_to_book_update() {
57
+ auto oe = std::make_unique<OEGActor>();
58
+ auto md = std::make_unique<MDGActor>();
59
+ auto book = std::make_unique<MECoreActor>(1, oe->getActorId(), md->getActorId());
60
  oe->mapSymbol(1, book->getActorId());
61
 
62
  std::vector<SymbolIndex_t> syms = {1};
 
76
  }
77
 
78
  void test_ai_responds_to_trade() {
79
+ auto oe = std::make_unique<OEGActor>();
80
  std::vector<SymbolIndex_t> syms = {1};
81
  auto ai = std::make_unique<AITraderActor>(oe->getActorId(), syms);
82
 
 
96
  }
97
 
98
  void test_ai_with_clearing_house() {
99
+ auto oe = std::make_unique<OEGActor>();
100
+ auto md = std::make_unique<MDGActor>();
101
  auto ch = std::make_unique<ClearingHouseActor>();
102
 
103
  for (int i = 0; i < 10; ++i) {
 
105
  static_cast<MemberId_t>(i + 1));
106
  }
107
 
108
+ auto book = std::make_unique<MECoreActor>(
109
  1, oe->getActorId(), md->getActorId(), ch->getActorId());
110
  oe->mapSymbol(1, book->getActorId());
111
 
 
121
  }
122
 
123
  void test_multiple_symbols() {
124
+ auto oe = std::make_unique<OEGActor>();
125
+ auto md = std::make_unique<MDGActor>();
126
 
127
  std::vector<SymbolIndex_t> syms = {1, 2, 3, 4};
128
+ std::vector<std::unique_ptr<MECoreActor>> books;
129
  for (auto s : syms) {
130
+ auto book = std::make_unique<MECoreActor>(s, oe->getActorId(), md->getActorId());
131
  oe->mapSymbol(s, book->getActorId());
132
  books.push_back(std::move(book));
133
  }
tests/test_clearing_house.cpp CHANGED
@@ -3,9 +3,9 @@
3
  // ════════════════════════════════════════════════════════════════════
4
 
5
  #include "actors/ClearingHouseActor.hpp"
6
- #include "actors/OrderBookActor.hpp"
7
- #include "actors/OEGatewayActor.hpp"
8
- #include "actors/MarketDataActor.hpp"
9
  #include <iostream>
10
  #include <cassert>
11
 
@@ -142,14 +142,14 @@ void test_leaderboard_sorted() {
142
  }
143
 
144
  void test_trade_with_clearing_pipe() {
145
- auto oe = std::make_unique<OEGatewayActor>();
146
- auto md = std::make_unique<MarketDataActor>();
147
  auto ch = std::make_unique<ClearingHouseActor>();
148
 
149
  ch->mapSession(1, 1);
150
  ch->mapSession(2, 2);
151
 
152
- auto book = std::make_unique<OrderBookActor>(
153
  1, oe->getActorId(), md->getActorId(), ch->getActorId());
154
  oe->mapSymbol(1, book->getActorId());
155
 
 
3
  // ════════════════════════════════════════════════════════════════════
4
 
5
  #include "actors/ClearingHouseActor.hpp"
6
+ #include "actors/MECoreActor.hpp"
7
+ #include "actors/OEGActor.hpp"
8
+ #include "actors/MDGActor.hpp"
9
  #include <iostream>
10
  #include <cassert>
11
 
 
142
  }
143
 
144
  void test_trade_with_clearing_pipe() {
145
+ auto oe = std::make_unique<OEGActor>();
146
+ auto md = std::make_unique<MDGActor>();
147
  auto ch = std::make_unique<ClearingHouseActor>();
148
 
149
  ch->mapSession(1, 1);
150
  ch->mapSession(2, 2);
151
 
152
+ auto book = std::make_unique<MECoreActor>(
153
  1, oe->getActorId(), md->getActorId(), ch->getActorId());
154
  oe->mapSymbol(1, book->getActorId());
155
 
tests/test_fix_gateway.cpp CHANGED
@@ -1,11 +1,11 @@
1
  // ════════════════════════════════════════════════════════════════════
2
- // FIXGatewayActor tests β€” protocol parsing and symbol mapping
3
  // ════════════════════════════════════════════════════════════════════
4
 
5
- #include "actors/FIXGatewayActor.hpp"
6
- #include "actors/OEGatewayActor.hpp"
7
- #include "actors/OrderBookActor.hpp"
8
- #include "actors/MarketDataActor.hpp"
9
  #include <iostream>
10
  #include <cassert>
11
  #include <string>
@@ -30,23 +30,23 @@ static int testsFailed = 0;
30
  // ── Tests ─────────────────────────────────────────────────────────
31
 
32
  void test_symbol_from_string() {
33
- ASSERT_EQ(FIXGatewayActor::symbolFromString("AAPL"), 1u);
34
- ASSERT_EQ(FIXGatewayActor::symbolFromString("MSFT"), 2u);
35
- ASSERT_EQ(FIXGatewayActor::symbolFromString("GOOGL"), 3u);
36
- ASSERT_EQ(FIXGatewayActor::symbolFromString("EURO50"), 4u);
37
  }
38
 
39
  void test_symbol_to_string() {
40
- ASSERT_TRUE(FIXGatewayActor::symbolToString(1) == "AAPL");
41
- ASSERT_TRUE(FIXGatewayActor::symbolToString(2) == "MSFT");
42
- ASSERT_TRUE(FIXGatewayActor::symbolToString(3) == "GOOGL");
43
- ASSERT_TRUE(FIXGatewayActor::symbolToString(4) == "EURO50");
44
- ASSERT_TRUE(FIXGatewayActor::symbolToString(99) == "99");
45
  }
46
 
47
  void test_fix_gateway_creates() {
48
- auto oe = std::make_unique<OEGatewayActor>();
49
- auto fix = std::make_unique<FIXGatewayActor>(oe->getActorId(), 19010);
50
  ASSERT_TRUE(fix->isRunning());
51
  ASSERT_EQ(fix->clientCount(), 0);
52
  fix->stop();
@@ -54,9 +54,9 @@ void test_fix_gateway_creates() {
54
  }
55
 
56
  void test_oe_gateway_routes_new_order_event() {
57
- auto oe = std::make_unique<OEGatewayActor>();
58
- auto md = std::make_unique<MarketDataActor>();
59
- auto book = std::make_unique<OrderBookActor>(1, oe->getActorId(), md->getActorId());
60
  oe->mapSymbol(1, book->getActorId());
61
 
62
  NewOrderEvent evt(5001, 1, Side::Buy, OrderType::Limit, TimeInForce::Day,
@@ -68,12 +68,12 @@ void test_oe_gateway_routes_new_order_event() {
68
  }
69
 
70
  void test_exec_report_forwarding() {
71
- auto oe = std::make_unique<OEGatewayActor>();
72
- auto md = std::make_unique<MarketDataActor>();
73
- auto book = std::make_unique<OrderBookActor>(1, oe->getActorId(), md->getActorId());
74
  oe->mapSymbol(1, book->getActorId());
75
 
76
- auto fix = std::make_unique<FIXGatewayActor>(oe->getActorId(), 19011);
77
  oe->addExecReportSubscriber(fix->getActorId());
78
 
79
  oe->submitNewOrder(1, 1, Side::Buy, OrderType::Limit,
 
1
  // ════════════════════════════════════════════════════════════════════
2
+ // FIXAcceptorActor tests β€” protocol parsing and symbol mapping
3
  // ════════════════════════════════════════════════════════════════════
4
 
5
+ #include "actors/FIXAcceptorActor.hpp"
6
+ #include "actors/OEGActor.hpp"
7
+ #include "actors/MECoreActor.hpp"
8
+ #include "actors/MDGActor.hpp"
9
  #include <iostream>
10
  #include <cassert>
11
  #include <string>
 
30
  // ── Tests ─────────────────────────────────────────────────────────
31
 
32
  void test_symbol_from_string() {
33
+ ASSERT_EQ(FIXAcceptorActor::symbolFromString("AAPL"), 1u);
34
+ ASSERT_EQ(FIXAcceptorActor::symbolFromString("MSFT"), 2u);
35
+ ASSERT_EQ(FIXAcceptorActor::symbolFromString("GOOGL"), 3u);
36
+ ASSERT_EQ(FIXAcceptorActor::symbolFromString("EURO50"), 4u);
37
  }
38
 
39
  void test_symbol_to_string() {
40
+ ASSERT_TRUE(FIXAcceptorActor::symbolToString(1) == "AAPL");
41
+ ASSERT_TRUE(FIXAcceptorActor::symbolToString(2) == "MSFT");
42
+ ASSERT_TRUE(FIXAcceptorActor::symbolToString(3) == "GOOGL");
43
+ ASSERT_TRUE(FIXAcceptorActor::symbolToString(4) == "EURO50");
44
+ ASSERT_TRUE(FIXAcceptorActor::symbolToString(99) == "99");
45
  }
46
 
47
  void test_fix_gateway_creates() {
48
+ auto oe = std::make_unique<OEGActor>();
49
+ auto fix = std::make_unique<FIXAcceptorActor>(oe->getActorId(), 19010);
50
  ASSERT_TRUE(fix->isRunning());
51
  ASSERT_EQ(fix->clientCount(), 0);
52
  fix->stop();
 
54
  }
55
 
56
  void test_oe_gateway_routes_new_order_event() {
57
+ auto oe = std::make_unique<OEGActor>();
58
+ auto md = std::make_unique<MDGActor>();
59
+ auto book = std::make_unique<MECoreActor>(1, oe->getActorId(), md->getActorId());
60
  oe->mapSymbol(1, book->getActorId());
61
 
62
  NewOrderEvent evt(5001, 1, Side::Buy, OrderType::Limit, TimeInForce::Day,
 
68
  }
69
 
70
  void test_exec_report_forwarding() {
71
+ auto oe = std::make_unique<OEGActor>();
72
+ auto md = std::make_unique<MDGActor>();
73
+ auto book = std::make_unique<MECoreActor>(1, oe->getActorId(), md->getActorId());
74
  oe->mapSymbol(1, book->getActorId());
75
 
76
+ auto fix = std::make_unique<FIXAcceptorActor>(oe->getActorId(), 19011);
77
  oe->addExecReportSubscriber(fix->getActorId());
78
 
79
  oe->submitNewOrder(1, 1, Side::Buy, OrderType::Limit,
tests/test_matching_engine.cpp CHANGED
@@ -6,9 +6,9 @@
6
  // execution reports, and market data snapshots.
7
  // ════════════════════════════════════════════════════════════════════
8
 
9
- #include "actors/OrderBookActor.hpp"
10
- #include "actors/OEGatewayActor.hpp"
11
- #include "actors/MarketDataActor.hpp"
12
  #include <iostream>
13
  #include <cassert>
14
 
@@ -32,16 +32,16 @@ static int testsFailed = 0;
32
  // ── Test fixtures ──────────────────────────────────────────────────
33
 
34
  struct Fixture {
35
- std::unique_ptr<OEGatewayActor> oeGateway;
36
- std::unique_ptr<MarketDataActor> mdActor;
37
- std::unique_ptr<OrderBookActor> book;
38
  static constexpr SymbolIndex_t SYM = 1;
39
  static constexpr SessionId_t SESS = 1;
40
 
41
  Fixture() {
42
- oeGateway = std::make_unique<OEGatewayActor>();
43
- mdActor = std::make_unique<MarketDataActor>();
44
- book = std::make_unique<OrderBookActor>(
45
  SYM, oeGateway->getActorId(), mdActor->getActorId());
46
  oeGateway->mapSymbol(SYM, book->getActorId());
47
  }
 
6
  // execution reports, and market data snapshots.
7
  // ════════════════════════════════════════════════════════════════════
8
 
9
+ #include "actors/MECoreActor.hpp"
10
+ #include "actors/OEGActor.hpp"
11
+ #include "actors/MDGActor.hpp"
12
  #include <iostream>
13
  #include <cassert>
14
 
 
32
  // ── Test fixtures ──────────────────────────────────────────────────
33
 
34
  struct Fixture {
35
+ std::unique_ptr<OEGActor> oeGateway;
36
+ std::unique_ptr<MDGActor> mdActor;
37
+ std::unique_ptr<MECoreActor> book;
38
  static constexpr SymbolIndex_t SYM = 1;
39
  static constexpr SessionId_t SESS = 1;
40
 
41
  Fixture() {
42
+ oeGateway = std::make_unique<OEGActor>();
43
+ mdActor = std::make_unique<MDGActor>();
44
+ book = std::make_unique<MECoreActor>(
45
  SYM, oeGateway->getActorId(), mdActor->getActorId());
46
  oeGateway->mapSymbol(SYM, book->getActorId());
47
  }
tests/test_orderbook.cpp CHANGED
@@ -5,7 +5,7 @@
5
  // price-time priority matching with fixed-point prices.
6
  // ════════════════════════════════════════════════════════════════════
7
 
8
- #include "common/OrderBook.hpp"
9
  #include <iostream>
10
  #include <cassert>
11
  #include <vector>
@@ -57,7 +57,7 @@ static auto onExec = [](const ExecutionReport& r) { reports.push_back(r); };
57
  // ── Tests ──────────────────────────────────────────────────────────
58
 
59
  void test_limit_buy_rests_on_empty_book() {
60
- OrderBook book(1);
61
  resetCallbacks();
62
 
63
  auto order = makeOrder(Side::Buy, OrderType::Limit, TimeInForce::Day, 100.0, 50);
@@ -70,7 +70,7 @@ void test_limit_buy_rests_on_empty_book() {
70
  }
71
 
72
  void test_limit_sell_rests_on_empty_book() {
73
- OrderBook book(1);
74
  resetCallbacks();
75
 
76
  auto order = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 100.0, 50);
@@ -82,7 +82,7 @@ void test_limit_sell_rests_on_empty_book() {
82
  }
83
 
84
  void test_exact_match() {
85
- OrderBook book(1);
86
  resetCallbacks();
87
 
88
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
@@ -99,7 +99,7 @@ void test_exact_match() {
99
  }
100
 
101
  void test_partial_fill() {
102
- OrderBook book(1);
103
  resetCallbacks();
104
 
105
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
@@ -118,7 +118,7 @@ void test_partial_fill() {
118
  }
119
 
120
  void test_price_priority() {
121
- OrderBook book(1);
122
  resetCallbacks();
123
 
124
  // Two sells at different prices
@@ -138,7 +138,7 @@ void test_price_priority() {
138
  }
139
 
140
  void test_time_priority() {
141
- OrderBook book(1);
142
  resetCallbacks();
143
 
144
  // Two sells at same price β€” first should match first (FIFO)
@@ -156,7 +156,7 @@ void test_time_priority() {
156
  }
157
 
158
  void test_market_order_matches_any_price() {
159
- OrderBook book(1);
160
  resetCallbacks();
161
 
162
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 999.99, 50);
@@ -171,7 +171,7 @@ void test_market_order_matches_any_price() {
171
  }
172
 
173
  void test_market_order_does_not_rest() {
174
- OrderBook book(1);
175
  resetCallbacks();
176
 
177
  auto buy = makeOrder(Side::Buy, OrderType::Market, TimeInForce::IOC, 0, 50);
@@ -183,7 +183,7 @@ void test_market_order_does_not_rest() {
183
  }
184
 
185
  void test_ioc_partial_fill_cancel_rest() {
186
- OrderBook book(1);
187
  resetCallbacks();
188
 
189
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 30);
@@ -199,7 +199,7 @@ void test_ioc_partial_fill_cancel_rest() {
199
  }
200
 
201
  void test_fok_rejected_when_insufficient() {
202
- OrderBook book(1);
203
  resetCallbacks();
204
 
205
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 30);
@@ -215,7 +215,7 @@ void test_fok_rejected_when_insufficient() {
215
  }
216
 
217
  void test_fok_fills_when_sufficient() {
218
- OrderBook book(1);
219
  resetCallbacks();
220
 
221
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
@@ -231,7 +231,7 @@ void test_fok_fills_when_sufficient() {
231
  }
232
 
233
  void test_cancel_order() {
234
- OrderBook book(1);
235
  resetCallbacks();
236
 
237
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
@@ -245,14 +245,14 @@ void test_cancel_order() {
245
  }
246
 
247
  void test_cancel_nonexistent() {
248
- OrderBook book(1);
249
  ExecutionReport rpt{};
250
  bool ok = book.cancelOrder(9999, rpt);
251
  ASSERT_TRUE(!ok);
252
  }
253
 
254
  void test_multi_level_sweep() {
255
- OrderBook book(1);
256
  resetCallbacks();
257
 
258
  // Three sell levels
 
5
  // price-time priority matching with fixed-point prices.
6
  // ════════════════════════════════════════════════════════════════════
7
 
8
+ #include "common/Book.hpp"
9
  #include <iostream>
10
  #include <cassert>
11
  #include <vector>
 
57
  // ── Tests ──────────────────────────────────────────────────────────
58
 
59
  void test_limit_buy_rests_on_empty_book() {
60
+ Book book(1);
61
  resetCallbacks();
62
 
63
  auto order = makeOrder(Side::Buy, OrderType::Limit, TimeInForce::Day, 100.0, 50);
 
70
  }
71
 
72
  void test_limit_sell_rests_on_empty_book() {
73
+ Book book(1);
74
  resetCallbacks();
75
 
76
  auto order = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 100.0, 50);
 
82
  }
83
 
84
  void test_exact_match() {
85
+ Book book(1);
86
  resetCallbacks();
87
 
88
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
 
99
  }
100
 
101
  void test_partial_fill() {
102
+ Book book(1);
103
  resetCallbacks();
104
 
105
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
 
118
  }
119
 
120
  void test_price_priority() {
121
+ Book book(1);
122
  resetCallbacks();
123
 
124
  // Two sells at different prices
 
138
  }
139
 
140
  void test_time_priority() {
141
+ Book book(1);
142
  resetCallbacks();
143
 
144
  // Two sells at same price β€” first should match first (FIFO)
 
156
  }
157
 
158
  void test_market_order_matches_any_price() {
159
+ Book book(1);
160
  resetCallbacks();
161
 
162
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 999.99, 50);
 
171
  }
172
 
173
  void test_market_order_does_not_rest() {
174
+ Book book(1);
175
  resetCallbacks();
176
 
177
  auto buy = makeOrder(Side::Buy, OrderType::Market, TimeInForce::IOC, 0, 50);
 
183
  }
184
 
185
  void test_ioc_partial_fill_cancel_rest() {
186
+ Book book(1);
187
  resetCallbacks();
188
 
189
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 30);
 
199
  }
200
 
201
  void test_fok_rejected_when_insufficient() {
202
+ Book book(1);
203
  resetCallbacks();
204
 
205
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 30);
 
215
  }
216
 
217
  void test_fok_fills_when_sufficient() {
218
+ Book book(1);
219
  resetCallbacks();
220
 
221
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
 
231
  }
232
 
233
  void test_cancel_order() {
234
+ Book book(1);
235
  resetCallbacks();
236
 
237
  auto sell = makeOrder(Side::Sell, OrderType::Limit, TimeInForce::Day, 50.0, 100);
 
245
  }
246
 
247
  void test_cancel_nonexistent() {
248
+ Book book(1);
249
  ExecutionReport rpt{};
250
  bool ok = book.cancelOrder(9999, rpt);
251
  ASSERT_TRUE(!ok);
252
  }
253
 
254
  void test_multi_level_sweep() {
255
+ Book book(1);
256
  resetCallbacks();
257
 
258
  // Three sell levels
tests/test_threaded_engine.cpp CHANGED
@@ -1,7 +1,7 @@
1
  #include "engine/SimplxShim.hpp"
2
- #include "actors/OrderBookActor.hpp"
3
- #include "actors/OEGatewayActor.hpp"
4
- #include "actors/MarketDataActor.hpp"
5
  #include <iostream>
6
  #include <cassert>
7
  #include <atomic>
@@ -29,8 +29,8 @@ static int testsFailed = 0;
29
 
30
  void test_engine_multi_core_creation() {
31
  Engine::StartSequence seq;
32
- seq.addActor<OEGatewayActor>(0);
33
- seq.addActor<MarketDataActor>(2);
34
  Engine engine(seq);
35
 
36
  ASSERT_EQ(engine.coreCount(), 2UL);
@@ -39,12 +39,12 @@ void test_engine_multi_core_creation() {
39
  // ── Test: cross-core event delivery via mailbox ────────────────────
40
 
41
  void test_cross_core_event_delivery() {
42
- auto oeGateway = std::make_unique<OEGatewayActor>();
43
- auto mdActor = std::make_unique<MarketDataActor>();
44
 
45
  // Manually assign to different cores
46
  constexpr SymbolIndex_t SYM = 1;
47
- auto book = std::make_unique<OrderBookActor>(
48
  SYM, oeGateway->getActorId(), mdActor->getActorId());
49
 
50
  oeGateway->mapSymbol(SYM, book->getActorId());
@@ -60,15 +60,15 @@ void test_cross_core_event_delivery() {
60
  // ── Test: threaded engine runs matching across cores ───────────────
61
 
62
  struct ThreadedFixture {
63
- OEGatewayActor* oeGateway = nullptr;
64
- MarketDataActor* mdActor = nullptr;
65
  std::unique_ptr<Engine> engine;
66
 
67
  ThreadedFixture() {
68
  Engine::StartSequence seq;
69
- seq.addActor<OEGatewayActor>(0);
70
- seq.addActor<MarketDataActor>(2);
71
- seq.addActor<OrderBookActor>(1,
72
  SymbolIndex_t(1), ActorId{1, 0}, ActorId{2, 2});
73
 
74
  engine = std::make_unique<Engine>(seq);
@@ -77,11 +77,11 @@ struct ThreadedFixture {
77
 
78
  void test_threaded_matching() {
79
  // Use synchronous mode for deterministic testing
80
- auto oeGateway = std::make_unique<OEGatewayActor>();
81
- auto mdActor = std::make_unique<MarketDataActor>();
82
 
83
  constexpr SymbolIndex_t SYM = 1;
84
- auto book = std::make_unique<OrderBookActor>(
85
  SYM, oeGateway->getActorId(), mdActor->getActorId());
86
 
87
  oeGateway->mapSymbol(SYM, book->getActorId());
@@ -124,9 +124,9 @@ void test_mailbox_concurrent_enqueue() {
124
 
125
  void test_engine_core_assignment() {
126
  Engine::StartSequence seq;
127
- seq.addActor<OEGatewayActor>(0);
128
- seq.addActor<MarketDataActor>(1);
129
- seq.addActor<OEGatewayActor>(2);
130
  Engine engine(seq);
131
 
132
  ASSERT_EQ(engine.coreCount(), 3UL);
@@ -136,11 +136,11 @@ void test_engine_core_assignment() {
136
 
137
  void test_sync_backward_compat() {
138
  // Actors created outside Engine should work exactly as before
139
- auto oeGateway = std::make_unique<OEGatewayActor>();
140
- auto mdActor = std::make_unique<MarketDataActor>();
141
 
142
  constexpr SymbolIndex_t SYM = 1;
143
- auto book = std::make_unique<OrderBookActor>(
144
  SYM, oeGateway->getActorId(), mdActor->getActorId());
145
 
146
  oeGateway->mapSymbol(SYM, book->getActorId());
 
1
  #include "engine/SimplxShim.hpp"
2
+ #include "actors/MECoreActor.hpp"
3
+ #include "actors/OEGActor.hpp"
4
+ #include "actors/MDGActor.hpp"
5
  #include <iostream>
6
  #include <cassert>
7
  #include <atomic>
 
29
 
30
  void test_engine_multi_core_creation() {
31
  Engine::StartSequence seq;
32
+ seq.addActor<OEGActor>(0);
33
+ seq.addActor<MDGActor>(2);
34
  Engine engine(seq);
35
 
36
  ASSERT_EQ(engine.coreCount(), 2UL);
 
39
  // ── Test: cross-core event delivery via mailbox ────────────────────
40
 
41
  void test_cross_core_event_delivery() {
42
+ auto oeGateway = std::make_unique<OEGActor>();
43
+ auto mdActor = std::make_unique<MDGActor>();
44
 
45
  // Manually assign to different cores
46
  constexpr SymbolIndex_t SYM = 1;
47
+ auto book = std::make_unique<MECoreActor>(
48
  SYM, oeGateway->getActorId(), mdActor->getActorId());
49
 
50
  oeGateway->mapSymbol(SYM, book->getActorId());
 
60
  // ── Test: threaded engine runs matching across cores ───────────────
61
 
62
  struct ThreadedFixture {
63
+ OEGActor* oeGateway = nullptr;
64
+ MDGActor* mdActor = nullptr;
65
  std::unique_ptr<Engine> engine;
66
 
67
  ThreadedFixture() {
68
  Engine::StartSequence seq;
69
+ seq.addActor<OEGActor>(0);
70
+ seq.addActor<MDGActor>(2);
71
+ seq.addActor<MECoreActor>(1,
72
  SymbolIndex_t(1), ActorId{1, 0}, ActorId{2, 2});
73
 
74
  engine = std::make_unique<Engine>(seq);
 
77
 
78
  void test_threaded_matching() {
79
  // Use synchronous mode for deterministic testing
80
+ auto oeGateway = std::make_unique<OEGActor>();
81
+ auto mdActor = std::make_unique<MDGActor>();
82
 
83
  constexpr SymbolIndex_t SYM = 1;
84
+ auto book = std::make_unique<MECoreActor>(
85
  SYM, oeGateway->getActorId(), mdActor->getActorId());
86
 
87
  oeGateway->mapSymbol(SYM, book->getActorId());
 
124
 
125
  void test_engine_core_assignment() {
126
  Engine::StartSequence seq;
127
+ seq.addActor<OEGActor>(0);
128
+ seq.addActor<MDGActor>(1);
129
+ seq.addActor<OEGActor>(2);
130
  Engine engine(seq);
131
 
132
  ASSERT_EQ(engine.coreCount(), 3UL);
 
136
 
137
  void test_sync_backward_compat() {
138
  // Actors created outside Engine should work exactly as before
139
+ auto oeGateway = std::make_unique<OEGActor>();
140
+ auto mdActor = std::make_unique<MDGActor>();
141
 
142
  constexpr SymbolIndex_t SYM = 1;
143
+ auto book = std::make_unique<MECoreActor>(
144
  SYM, oeGateway->getActorId(), mdActor->getActorId());
145
 
146
  oeGateway->mapSymbol(SYM, book->getActorId());