Rename modules to Optiq naming and add Developers Guide
Browse filesAlign 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 +5 -5
- README.md +83 -83
- docs/developers-guide.md +889 -0
- docs/process-diagram.md +23 -23
- examples/simple_match.cpp +3 -3
- src/actors/AITraderActor.hpp +1 -1
- src/actors/ClearingHouseActor.hpp +1 -1
- src/actors/{FIXGatewayActor.cpp β FIXAcceptorActor.cpp} +27 -27
- src/actors/{FIXGatewayActor.hpp β FIXAcceptorActor.hpp} +5 -5
- src/actors/{MarketDataActor.cpp β MDGActor.cpp} +5 -5
- src/actors/{MarketDataActor.hpp β MDGActor.hpp} +3 -3
- src/actors/{OrderBookActor.cpp β MECoreActor.cpp} +6 -6
- src/actors/{OrderBookActor.hpp β MECoreActor.hpp} +5 -5
- src/actors/{OEGatewayActor.cpp β OEGActor.cpp} +12 -12
- src/actors/{OEGatewayActor.hpp β OEGActor.hpp} +4 -4
- src/common/{OrderBook.cpp β Book.cpp} +14 -14
- src/common/{OrderBook.hpp β Book.hpp} +3 -3
- src/main.cpp +18 -18
- src/net/SocketCompat.hpp +1 -1
- tests/test_ai_trader.cpp +19 -19
- tests/test_clearing_house.cpp +6 -6
- tests/test_fix_gateway.cpp +23 -23
- tests/test_matching_engine.cpp +9 -9
- tests/test_orderbook.cpp +15 -15
- tests/test_threaded_engine.cpp +22 -22
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/
|
| 54 |
-
src/actors/
|
| 55 |
-
src/actors/
|
| 56 |
-
src/actors/
|
| 57 |
src/actors/ClearingHouseActor.cpp
|
| 58 |
-
src/actors/
|
| 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 β
|
| 12 |
Kafka 'orders' topic β Event::Pipe β Simplx Event::Pipe
|
| 13 |
-
matcher.py β
|
| 14 |
-
match_order() β
|
| 15 |
-
handle_cancel() β
|
| 16 |
-
handle_amend() β
|
| 17 |
Kafka 'trades' topic β TradeEvent via Pipe β IACA fragment chain
|
| 18 |
-
dashboard.py (SSE) β
|
| 19 |
/orderbook/<sym> β BookUpdateEvent β PublishLimitUpdateRequest
|
| 20 |
/trades β TradeEvent β IACA β IA SBE message
|
| 21 |
-
database.py (SQLite) β
|
| 22 |
-
save_trade() β
|
| 23 |
-
fix_oeg_server.py β
|
| 24 |
NewOrderSingle β 35=D handling β Optiq FIX gateway
|
| 25 |
ExecutionReport β 35=8 response β Execution reports
|
| 26 |
-
ch_ai_trader.py β
|
| 27 |
-
AI strategies β
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 β β
|
| 48 |
-
ββββββββββ¬βββββββββ β
|
| 49 |
-
β
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
β
|
| 56 |
-
β
|
| 57 |
-
β
|
| 58 |
-
|
| 59 |
-
βββββββββββββββββββββββββββββββ
|
| 60 |
```
|
| 61 |
|
| 62 |
## Quick Start (Linux)
|
|
@@ -95,31 +103,30 @@ docker compose up --build
|
|
| 95 |
## Build C++ Engine
|
| 96 |
|
| 97 |
```bash
|
| 98 |
-
|
| 99 |
-
cmake
|
| 100 |
-
cmake --build . --config Release
|
| 101 |
|
| 102 |
-
# Run matching engine
|
| 103 |
-
./eunex_me
|
| 104 |
|
| 105 |
-
# Run all tests (
|
| 106 |
-
ctest
|
| 107 |
```
|
| 108 |
|
| 109 |
## With Kafka Persistence
|
| 110 |
|
| 111 |
```bash
|
| 112 |
-
cmake -DEUNEX_USE_KAFKA=ON
|
| 113 |
-
cmake --build
|
| 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 |
-
|
| 132 |
-
|
| 133 |
-
|
| 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 |
-
β β
|
| 169 |
β βββ actors/
|
| 170 |
β β βββ Events.hpp # Inter-actor event types
|
| 171 |
-
β β βββ
|
| 172 |
-
β β βββ
|
| 173 |
-
β β
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 190 |
β βββ ch_ai_trader.py # AI trading strategies
|
| 191 |
-
β βββ templates/ # CH web UI
|
| 192 |
-
βββ shared/
|
| 193 |
-
β βββ config.py # Centralized configuration
|
| 194 |
βββ docker/
|
| 195 |
β βββ docker-compose.yml # Kafka + EuNEx (all services)
|
| 196 |
-
|
| 197 |
β βββ nginx.conf # Reverse proxy configuration
|
| 198 |
-
βββ run.sh # Linux startup script
|
| 199 |
βββ tests/
|
| 200 |
-
β βββ test_orderbook.cpp #
|
| 201 |
-
β βββ test_matching_engine.cpp #
|
| 202 |
-
β
|
|
|
|
|
|
|
|
|
|
| 203 |
βββ examples/
|
| 204 |
β βββ ping_pong.cpp # Actor basics tutorial
|
| 205 |
β βββ simple_match.cpp # Matching with Recovery + IACA
|
| 206 |
-
βββ docs/
|
|
|
|
|
|
|
| 207 |
```
|
| 208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
## Next Steps
|
| 210 |
|
| 211 |
-
1. ~~
|
| 212 |
2. ~~Kafka persistence~~ β KafkaStore + Docker Compose (KRaft mode)
|
| 213 |
-
3.
|
| 214 |
-
4. ~~
|
| 215 |
-
5. **
|
| 216 |
-
6.
|
|
|
|
|
|
|
|
|
| 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 |
-
β
|
| 14 |
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
| 15 |
β β β’ Accepts NewOrder / Cancel / Modify requests β β
|
| 16 |
-
β β β’ Validates session, routes by SymbolIndex β correct
|
| 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 |
-
β
|
| 25 |
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
| 26 |
β β RECOVERY CAUSE β β
|
| 27 |
β β RecoveryProxy.cause(persistenceId, order, callback) β β
|
|
@@ -31,7 +31,7 @@
|
|
| 31 |
β βΌ β
|
| 32 |
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
|
| 33 |
β β MATCHING ENGINE β β
|
| 34 |
-
β β
|
| 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 β
|
| 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 / β β
|
| 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 ββββΆ
|
| 144 |
Kafka 'orders' topic ββββΆ Event::Pipe ββββΆ Simplx cross-core
|
| 145 |
session mgmt ββββΆ submitNewOrder() ββββΆ FIX 5.0 SP2 acceptor
|
| 146 |
|
| 147 |
-
matcher.py ββββΆ
|
| 148 |
-
match_order() ββββΆ
|
| 149 |
-
handle_cancel() ββββΆ
|
| 150 |
-
handle_amend() ββββΆ
|
| 151 |
Kafka 'trades' topic ββββΆ TradeEvent via Pipe ββββΆ IACA fragment chain
|
| 152 |
|
| 153 |
-
dashboard.py (SSE) ββββΆ
|
| 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βββΆ
|
| 174 |
β² β
|
| 175 |
β ββ RecoveryProxy.cause() β FragmentStore
|
| 176 |
-
β ββ
|
| 177 |
β ββ IACA fragments β IacaAggregator
|
| 178 |
β β
|
| 179 |
ExecReport βββ ββ effect() [Master only]:
|
| 180 |
-
β ββ ExecReport β
|
| 181 |
-
β ββ TradeEvent β
|
| 182 |
-
β ββ BookUpdate β
|
| 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 |
-
β (
|
| 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 (
|
| 217 |
β β β β
|
| 218 |
β β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β β
|
| 219 |
β β β Recovery Cause β β Matching Engine β β IACA Inside β β β
|
| 220 |
-
β β β (RecoveryProxy β β (
|
| 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:
|
| 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 |
-
βββ
|
| 589 |
-
βββ
|
| 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/
|
| 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 |
-
|
| 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 |
-
|
| 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
|
| 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
|
| 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/
|
| 2 |
#include <iostream>
|
| 3 |
#include <sstream>
|
| 4 |
#include <cstring>
|
|
@@ -9,7 +9,7 @@
|
|
| 9 |
|
| 10 |
namespace eunex {
|
| 11 |
|
| 12 |
-
|
| 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 << "
|
| 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 << "
|
| 38 |
closeSocket(listenSock_);
|
| 39 |
listenSock_ = INVALID_SOCK;
|
| 40 |
return;
|
| 41 |
}
|
| 42 |
|
| 43 |
if (listen(listenSock_, 5) != 0) {
|
| 44 |
-
std::cerr << "
|
| 45 |
closeSocket(listenSock_);
|
| 46 |
listenSock_ = INVALID_SOCK;
|
| 47 |
return;
|
| 48 |
}
|
| 49 |
|
| 50 |
running_ = true;
|
| 51 |
-
acceptThread_ = std::thread(&
|
| 52 |
|
| 53 |
-
std::cout << "
|
| 54 |
}
|
| 55 |
|
| 56 |
-
|
| 57 |
stop();
|
| 58 |
}
|
| 59 |
|
| 60 |
-
void
|
| 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
|
| 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
|
| 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 << "
|
| 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(&
|
| 116 |
|
| 117 |
sessions_[sessId] = std::move(sess);
|
| 118 |
-
std::cout << "
|
| 119 |
}
|
| 120 |
}
|
| 121 |
|
| 122 |
// ββ Client recv loop (per-client thread) βββββββββββββββββββββββββ
|
| 123 |
|
| 124 |
-
void
|
| 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<
|
| 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
|
| 217 |
auto it = msg.find(tag);
|
| 218 |
return (it != msg.end()) ? it->second : def;
|
| 219 |
}
|
| 220 |
|
| 221 |
// ββ FIX message handlers βββββββββββββββββββββββββββββββββββββββββ
|
| 222 |
|
| 223 |
-
void
|
| 224 |
sess.senderCompId = getTag(msg, 49, "UNKNOWN");
|
| 225 |
sess.loggedOn = true;
|
| 226 |
-
std::cout << "
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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
|
| 337 |
sendExecReport(event.sessionId, event);
|
| 338 |
}
|
| 339 |
|
| 340 |
-
void
|
| 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
|
| 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
|
| 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 |
-
//
|
| 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
|
| 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
|
| 44 |
public:
|
| 45 |
struct Service : tredzone::AsyncService {};
|
| 46 |
|
| 47 |
-
|
| 48 |
-
~
|
| 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/
|
| 2 |
|
| 3 |
namespace eunex {
|
| 4 |
|
| 5 |
-
|
| 6 |
registerEventHandler<TradeEvent>(*this);
|
| 7 |
registerEventHandler<BookUpdateEvent>(*this);
|
| 8 |
}
|
| 9 |
|
| 10 |
-
void
|
| 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
|
| 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*
|
| 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 |
-
//
|
| 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
|
| 34 |
public:
|
| 35 |
struct Service : tredzone::AsyncService {};
|
| 36 |
|
| 37 |
-
|
| 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/
|
| 2 |
#include <iostream>
|
| 3 |
|
| 4 |
namespace eunex {
|
| 5 |
|
| 6 |
-
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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 |
-
//
|
| 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/
|
| 21 |
#include "actors/Events.hpp"
|
| 22 |
#include <optional>
|
| 23 |
|
| 24 |
namespace eunex {
|
| 25 |
|
| 26 |
-
class
|
| 27 |
public:
|
| 28 |
struct Service : tredzone::AsyncService {};
|
| 29 |
|
| 30 |
-
|
| 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 |
-
|
| 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/
|
| 2 |
|
| 3 |
namespace eunex {
|
| 4 |
|
| 5 |
-
|
| 6 |
registerEventHandler<NewOrderEvent>(*this);
|
| 7 |
registerEventHandler<CancelOrderEvent>(*this);
|
| 8 |
registerEventHandler<ModifyOrderEvent>(*this);
|
| 9 |
registerEventHandler<ExecReportEvent>(*this);
|
| 10 |
}
|
| 11 |
|
| 12 |
-
void
|
| 13 |
const tredzone::ActorId& bookActorId) {
|
| 14 |
symbolMap_[symbolIdx] = bookActorId;
|
| 15 |
}
|
| 16 |
|
| 17 |
-
void
|
| 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 << "
|
| 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
|
| 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
|
| 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
|
| 50 |
submitNewOrder(event.clOrdId, event.symbolIdx, event.side, event.ordType,
|
| 51 |
event.tif, event.price, event.quantity, event.sessionId);
|
| 52 |
}
|
| 53 |
|
| 54 |
-
void
|
| 55 |
submitCancel(event.orderId, event.origClOrdId, event.symbolIdx, event.sessionId);
|
| 56 |
}
|
| 57 |
|
| 58 |
-
void
|
| 59 |
submitModify(event.orderId, event.origClOrdId, event.symbolIdx,
|
| 60 |
event.newPrice, event.newQuantity, event.sessionId);
|
| 61 |
}
|
| 62 |
|
| 63 |
-
void
|
| 64 |
execReportSubscribers_.push_back(subscriberId);
|
| 65 |
}
|
| 66 |
|
| 67 |
-
void
|
| 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 |
-
//
|
| 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
|
| 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
|
| 24 |
public:
|
| 25 |
struct Service : tredzone::AsyncService {};
|
| 26 |
|
| 27 |
-
|
| 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/
|
| 2 |
#include <algorithm>
|
| 3 |
|
| 4 |
namespace eunex {
|
| 5 |
|
| 6 |
-
|
| 7 |
: symbolIdx_(symbolIdx) {}
|
| 8 |
|
| 9 |
// ββ New Order Entry ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 10 |
|
| 11 |
-
void
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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
|
| 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<
|
| 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<
|
| 310 |
return getLevels(bids_, depth);
|
| 311 |
}
|
| 312 |
|
| 313 |
-
std::vector<
|
| 314 |
return getLevels(asks_, depth);
|
| 315 |
}
|
| 316 |
|
| 317 |
-
size_t
|
| 318 |
size_t count = 0;
|
| 319 |
for (auto& [_, orders] : bids_) count += orders.size();
|
| 320 |
return count;
|
| 321 |
}
|
| 322 |
|
| 323 |
-
size_t
|
| 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
|
| 20 |
// The actor guarantees single-threaded access β no locks needed.
|
| 21 |
//
|
| 22 |
-
class
|
| 23 |
public:
|
| 24 |
-
explicit
|
| 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:
|
| 7 |
-
// Core 1:
|
| 8 |
-
// Core 2:
|
| 9 |
// Core 3: ClearingHouseActor + AITraderActor
|
| 10 |
//
|
| 11 |
// Optiq equivalent topology:
|
|
@@ -15,11 +15,11 @@
|
|
| 15 |
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 16 |
|
| 17 |
#include "engine/SimplxShim.hpp"
|
| 18 |
-
#include "actors/
|
| 19 |
-
#include "actors/
|
| 20 |
-
#include "actors/
|
| 21 |
#include "actors/ClearingHouseActor.hpp"
|
| 22 |
-
#include "actors/
|
| 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<
|
| 56 |
|
| 57 |
// ββ Core 2: Market Data βββββββββββββββββββββββββββββββββββββββ
|
| 58 |
-
auto mdActor = std::make_unique<
|
| 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<
|
| 76 |
SYM_AAPL, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
|
| 77 |
-
auto bookMSFT = std::make_unique<
|
| 78 |
SYM_MSFT, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
|
| 79 |
-
auto bookGOOGL = std::make_unique<
|
| 80 |
SYM_GOOGL, oeGateway->getActorId(), mdActor->getActorId(), chActor->getActorId());
|
| 81 |
-
auto bookEURO50 = std::make_unique<
|
| 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<
|
| 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:
|
| 102 |
-
<< "),
|
| 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:
|
| 108 |
-
std::cout << " Core 3:
|
| 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
|
| 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/
|
| 7 |
-
#include "actors/
|
| 8 |
-
#include "actors/
|
| 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<
|
| 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<
|
| 41 |
-
auto md = std::make_unique<
|
| 42 |
|
| 43 |
-
auto book1 = std::make_unique<
|
| 44 |
-
auto book2 = std::make_unique<
|
| 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<
|
| 58 |
-
auto md = std::make_unique<
|
| 59 |
-
auto book = std::make_unique<
|
| 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<
|
| 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<
|
| 100 |
-
auto md = std::make_unique<
|
| 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<
|
| 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<
|
| 125 |
-
auto md = std::make_unique<
|
| 126 |
|
| 127 |
std::vector<SymbolIndex_t> syms = {1, 2, 3, 4};
|
| 128 |
-
std::vector<std::unique_ptr<
|
| 129 |
for (auto s : syms) {
|
| 130 |
-
auto book = std::make_unique<
|
| 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/
|
| 7 |
-
#include "actors/
|
| 8 |
-
#include "actors/
|
| 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<
|
| 146 |
-
auto md = std::make_unique<
|
| 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<
|
| 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 |
-
//
|
| 3 |
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 4 |
|
| 5 |
-
#include "actors/
|
| 6 |
-
#include "actors/
|
| 7 |
-
#include "actors/
|
| 8 |
-
#include "actors/
|
| 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(
|
| 34 |
-
ASSERT_EQ(
|
| 35 |
-
ASSERT_EQ(
|
| 36 |
-
ASSERT_EQ(
|
| 37 |
}
|
| 38 |
|
| 39 |
void test_symbol_to_string() {
|
| 40 |
-
ASSERT_TRUE(
|
| 41 |
-
ASSERT_TRUE(
|
| 42 |
-
ASSERT_TRUE(
|
| 43 |
-
ASSERT_TRUE(
|
| 44 |
-
ASSERT_TRUE(
|
| 45 |
}
|
| 46 |
|
| 47 |
void test_fix_gateway_creates() {
|
| 48 |
-
auto oe = std::make_unique<
|
| 49 |
-
auto fix = std::make_unique<
|
| 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<
|
| 58 |
-
auto md = std::make_unique<
|
| 59 |
-
auto book = std::make_unique<
|
| 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<
|
| 72 |
-
auto md = std::make_unique<
|
| 73 |
-
auto book = std::make_unique<
|
| 74 |
oe->mapSymbol(1, book->getActorId());
|
| 75 |
|
| 76 |
-
auto fix = std::make_unique<
|
| 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/
|
| 10 |
-
#include "actors/
|
| 11 |
-
#include "actors/
|
| 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<
|
| 36 |
-
std::unique_ptr<
|
| 37 |
-
std::unique_ptr<
|
| 38 |
static constexpr SymbolIndex_t SYM = 1;
|
| 39 |
static constexpr SessionId_t SESS = 1;
|
| 40 |
|
| 41 |
Fixture() {
|
| 42 |
-
oeGateway = std::make_unique<
|
| 43 |
-
mdActor = std::make_unique<
|
| 44 |
-
book = std::make_unique<
|
| 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/
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 249 |
ExecutionReport rpt{};
|
| 250 |
bool ok = book.cancelOrder(9999, rpt);
|
| 251 |
ASSERT_TRUE(!ok);
|
| 252 |
}
|
| 253 |
|
| 254 |
void test_multi_level_sweep() {
|
| 255 |
-
|
| 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/
|
| 3 |
-
#include "actors/
|
| 4 |
-
#include "actors/
|
| 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<
|
| 33 |
-
seq.addActor<
|
| 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<
|
| 43 |
-
auto mdActor = std::make_unique<
|
| 44 |
|
| 45 |
// Manually assign to different cores
|
| 46 |
constexpr SymbolIndex_t SYM = 1;
|
| 47 |
-
auto book = std::make_unique<
|
| 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 |
-
|
| 64 |
-
|
| 65 |
std::unique_ptr<Engine> engine;
|
| 66 |
|
| 67 |
ThreadedFixture() {
|
| 68 |
Engine::StartSequence seq;
|
| 69 |
-
seq.addActor<
|
| 70 |
-
seq.addActor<
|
| 71 |
-
seq.addActor<
|
| 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<
|
| 81 |
-
auto mdActor = std::make_unique<
|
| 82 |
|
| 83 |
constexpr SymbolIndex_t SYM = 1;
|
| 84 |
-
auto book = std::make_unique<
|
| 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<
|
| 128 |
-
seq.addActor<
|
| 129 |
-
seq.addActor<
|
| 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<
|
| 140 |
-
auto mdActor = std::make_unique<
|
| 141 |
|
| 142 |
constexpr SymbolIndex_t SYM = 1;
|
| 143 |
-
auto book = std::make_unique<
|
| 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());
|