RayMelius Claude Opus 4.6 commited on
Commit
12ad701
Β·
1 Parent(s): 80289a0

Remove legacy docs, backup copies, and artifacts

Browse files

Deleted:
- IMPROVEMENT_PLAN.md, README_OPTIQ.md β€” obsolete planning docs
- StockEx_Developer_Guide.html, StockEx_Technical_Guide.md,
StockEx_User_Guide.html β€” superseded by README.md
- dashboard/templates/index - Copy (6).html β€” accidental backup
- dashboard/templates/index_Matcher.html β€” unused alternate template
- matcher/matcher - Copy.py β€” accidental backup

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

IMPROVEMENT_PLAN.md DELETED
@@ -1,271 +0,0 @@
1
- # KafkaTradingSystem Improvement Plan
2
-
3
- > **Created:** 2026-02-03
4
- > **Status:** βœ… Core implementation complete (Phases 1-5 done)
5
- > **Last Updated:** 2026-02-03
6
- > **Resume:** Share this file with Claude to continue from where we left off
7
-
8
- ---
9
-
10
- ## Overview
11
-
12
- This plan outlines improvements to transform the trading system from a demo/prototype into a more robust, production-like system. Improvements are organized by priority and complexity.
13
-
14
- ---
15
-
16
- ## Phase 1: Real-Time Dashboard (WebSocket/SSE)
17
-
18
- **Problem:** Dashboard polls `/data` every 2 seconds (`setInterval(refresh, 2000)`), causing unnecessary load and delayed updates.
19
-
20
- **Solution:** Implement Server-Sent Events (SSE) for real-time push updates.
21
-
22
- ### Tasks
23
-
24
- - [x] **1.1** Add SSE endpoint to `dashboard/dashboard.py` βœ…
25
- - Create `/stream` endpoint using Flask's `Response` with `text/event-stream`
26
- - Push events when new orders/trades/snapshots arrive from Kafka
27
- - File: `dashboard/dashboard.py`
28
-
29
- - [x] **1.2** Update dashboard frontend to use SSE βœ…
30
- - Replace `setInterval(refresh, 2000)` with `EventSource('/stream')`
31
- - Handle reconnection on disconnect
32
- - File: `dashboard/templates/index.html`
33
-
34
- - [x] **1.3** Add connection status indicator βœ…
35
- - Show connected/disconnected state in UI
36
- - File: `dashboard/templates/index.html`
37
-
38
- ### Why SSE over WebSocket?
39
- - Simpler implementation (HTTP-based, no special protocol)
40
- - One-way server-to-client is sufficient for this use case
41
- - Better browser support and automatic reconnection
42
-
43
- ---
44
-
45
- ## Phase 2: SQLite Persistence
46
-
47
- **Problem:** All data is in-memory; order books and trades lost on restart.
48
-
49
- **Solution:** Add SQLite database for persistence with in-memory caching.
50
-
51
- ### Tasks
52
-
53
- - [x] **2.1** Create database schema and initialization βœ…
54
- - Tables: `orders`, `trades`, `order_book_entries`
55
- - Add migration/init script
56
- - New file: `matcher/database.py`
57
-
58
- - [x] **2.2** Modify matcher to persist trades βœ…
59
- - Write trades to SQLite when matched
60
- - Load recent trades on startup
61
- - File: `matcher/matcher.py`
62
-
63
- - [x] **2.3** Persist order book state βœ…
64
- - Save resting orders to database
65
- - Restore order books on matcher restart
66
- - File: `matcher/matcher.py`
67
-
68
- - [x] **2.4** Add trade history endpoint βœ…
69
- - `GET /trades?symbol=X&limit=N&offset=M`
70
- - Support filtering and pagination
71
- - File: `matcher/matcher.py`
72
-
73
- - [x] **2.5** Add Docker volume for database persistence βœ…
74
- - Mount SQLite file to host
75
- - File: `docker-compose.yml`
76
-
77
- ### Schema Design
78
-
79
- ```sql
80
- CREATE TABLE trades (
81
- id INTEGER PRIMARY KEY AUTOINCREMENT,
82
- symbol TEXT NOT NULL,
83
- price REAL NOT NULL,
84
- quantity INTEGER NOT NULL,
85
- buy_order_id TEXT,
86
- sell_order_id TEXT,
87
- timestamp REAL NOT NULL,
88
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
89
- );
90
-
91
- CREATE TABLE order_book (
92
- id INTEGER PRIMARY KEY AUTOINCREMENT,
93
- cl_ord_id TEXT UNIQUE,
94
- symbol TEXT NOT NULL,
95
- side TEXT NOT NULL, -- 'BUY' or 'SELL'
96
- price REAL,
97
- quantity INTEGER NOT NULL,
98
- remaining_qty INTEGER NOT NULL,
99
- status TEXT DEFAULT 'OPEN', -- OPEN, FILLED, CANCELLED
100
- timestamp REAL NOT NULL,
101
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
102
- );
103
-
104
- CREATE INDEX idx_trades_symbol ON trades(symbol);
105
- CREATE INDEX idx_trades_timestamp ON trades(timestamp);
106
- CREATE INDEX idx_orderbook_symbol_side ON order_book(symbol, side);
107
- ```
108
-
109
- ---
110
-
111
- ## Phase 3: FIX Protocol Improvements
112
-
113
- **Problem:** Basic FIX handling with no validation, no execution reports, no order management.
114
-
115
- ### Tasks
116
-
117
- - [x] **3.1** Add FIX message validation βœ…
118
- - Validate required fields (ClOrdID, Symbol, Side, OrderQty)
119
- - Validate field formats and ranges
120
- - Return Reject (35=3) for invalid messages
121
- - File: `fix_oeg/fix_oeg_server.py`
122
-
123
- - [x] **3.2** Send Execution Reports back to clients βœ…
124
- - Send 35=8 (ExecutionReport) for order acknowledgment
125
- - Send fill reports when trades occur
126
- - Requires Kafka consumer for trades topic in fix_oeg
127
- - File: `fix_oeg/fix_oeg_server.py`
128
-
129
- - [x] **3.3** Support Order Cancel Request (35=F) βœ…
130
- - Parse cancel requests
131
- - Publish to orders Kafka topic (with type=cancel)
132
- - Files: `fix_oeg/fix_oeg_server.py`
133
-
134
- - [x] **3.4** Support Order Cancel/Replace (35=G) βœ…
135
- - Parse modify requests
136
- - Implement price/quantity amendments
137
- - Files: `fix_oeg/fix_oeg_server.py`, `matcher/matcher.py`
138
-
139
- - [ ] **3.5** Add session-level validation (DEFERRED)
140
- - Sequence number checking handled by QuickFIX engine
141
- - Heartbeat/TestRequest handled automatically
142
- - File: `fix_oeg/fix_oeg_server.py`
143
-
144
- ### Execution Report Fields (35=8)
145
-
146
- ```
147
- Tag 35 = 8 (ExecutionReport)
148
- Tag 37 = OrderID (exchange-assigned)
149
- Tag 11 = ClOrdID (client order ID)
150
- Tag 17 = ExecID
151
- Tag 20 = ExecTransType (0=New)
152
- Tag 39 = OrdStatus (0=New, 1=PartialFill, 2=Filled)
153
- Tag 150 = ExecType (0=New, F=Trade)
154
- Tag 55 = Symbol
155
- Tag 54 = Side
156
- Tag 38 = OrderQty
157
- Tag 44 = Price
158
- Tag 32 = LastShares (fill qty)
159
- Tag 31 = LastPx (fill price)
160
- Tag 14 = CumQty
161
- Tag 151 = LeavesQty
162
- Tag 6 = AvgPx
163
- ```
164
-
165
- ---
166
-
167
- ## Phase 4: Enhanced Order Types
168
-
169
- **Problem:** Only limit orders supported.
170
-
171
- ### Tasks
172
-
173
- - [x] **4.1** Support Market Orders βœ…
174
- - Match immediately at best available price
175
- - No price field required (OrdType=1)
176
- - File: `matcher/matcher.py`
177
-
178
- - [x] **4.2** Support IOC (Immediate-or-Cancel) βœ…
179
- - TimeInForce=3: Fill what's available, cancel rest
180
- - File: `matcher/matcher.py`
181
-
182
- - [x] **4.3** Support FOK (Fill-or-Kill) βœ…
183
- - TimeInForce=4: Fill entire order or reject
184
- - File: `matcher/matcher.py`
185
-
186
- - [x] **4.4** Support GTC (Good-Till-Cancel) βœ…
187
- - TimeInForce=1: Persist until explicitly cancelled
188
- - Implemented via SQLite persistence (Phase 2)
189
- - File: `matcher/matcher.py`
190
-
191
- ---
192
-
193
- ## Phase 5: Monitoring & Observability
194
-
195
- ### Tasks
196
-
197
- - [x] **5.1** Add health check endpoints βœ…
198
- - `/health` endpoint for matcher, dashboard, frontend
199
- - Check Kafka/DB connectivity and service stats
200
- - Files: `matcher/matcher.py`, `dashboard/dashboard.py`, `frontend/frontend.py`
201
-
202
- - [x] **5.2** Add structured logging βœ…
203
- - JSON log format for parsing
204
- - Include correlation IDs
205
- - New file: `shared/logging_utils.py`
206
-
207
- - [x] **5.3** Add metrics endpoint βœ…
208
- - Order count, trade count, latency stats
209
- - Prometheus-compatible format (`/metrics` endpoint)
210
- - File: `matcher/matcher.py`
211
-
212
- - [ ] **5.4** Add Kafka lag monitoring (OPTIONAL)
213
- - Track consumer group lag
214
- - Alert on high lag
215
- - New file: `shared/monitoring.py`
216
-
217
- ---
218
-
219
- ## Phase 6: Testing & Quality
220
-
221
- ### Tasks
222
-
223
- - [x] **6.1** Add unit tests for matcher βœ…
224
- - Test matching logic (full/partial fills, price-time priority)
225
- - Test order types (market, limit, IOC, FOK)
226
- - Test cancel and amend operations
227
- - New file: `matcher/test_matcher.py`
228
-
229
- - [ ] **6.2** Add integration tests (OPTIONAL)
230
- - End-to-end order flow tests
231
- - FIX client to trade execution
232
- - New directory: `tests/`
233
-
234
- - [ ] **6.3** Add load testing script (OPTIONAL)
235
- - Generate high-volume order flow
236
- - Measure latency and throughput
237
- - New file: `tests/load_test.py`
238
-
239
- ---
240
-
241
- ## Implementation Order (Recommended)
242
-
243
- 1. **Phase 1** (Real-Time Dashboard) - Quick win, improves UX
244
- 2. **Phase 2** (SQLite Persistence) - Foundation for reliability
245
- 3. **Phase 3.1-3.2** (FIX Validation + Execution Reports) - Core trading functionality
246
- 4. **Phase 5.1** (Health Checks) - Operational necessity
247
- 5. **Phase 4.1** (Market Orders) - Common order type
248
- 6. **Phase 3.3** (Order Cancellation) - Essential for trading
249
- 7. Remaining phases as needed
250
-
251
- ---
252
-
253
- ## Quick Reference: Key Files
254
-
255
- | Component | Main File | Port |
256
- |-----------|-----------|------|
257
- | FIX Gateway | `fix_oeg/fix_oeg_server.py` | 5001 |
258
- | Matcher | `matcher/matcher.py` | 6000 |
259
- | Dashboard | `dashboard/dashboard.py` | 5005 |
260
- | Frontend | `frontend/frontend.py` | 5000 |
261
- | Config | `shared/config.py` | - |
262
-
263
- ---
264
-
265
- ## How to Continue
266
-
267
- When resuming work, tell Claude:
268
- 1. Which phase/task to work on (e.g., "Let's implement Phase 1.1")
269
- 2. Or ask Claude to pick the next logical task
270
-
271
- Each task is designed to be implementable in a single session.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README_OPTIQ.md DELETED
@@ -1,73 +0,0 @@
1
- # StockEx β€” Euronext/OPTIQ Trading System Emulator
2
-
3
- ## Overview
4
-
5
- **StockEx** is a real-time trading simulation platform inspired by Euronext OPTIQ. It provides a complete order flow from FIX protocol entry through matching engine to trade execution, with live visualization via a web dashboard.
6
-
7
- ## Architecture
8
-
9
- | Component | Description |
10
- |-----------|-------------|
11
- | **FIX OEG** | QuickFIX acceptor receiving FIX 4.4 orders |
12
- | **Kafka** | Message backbone (topics: `orders`, `snapshots`, `trades`) |
13
- | **Matcher** | Order matching engine with price-time priority |
14
- | **MDF** | Market Data Feeder publishing BBO snapshots |
15
- | **Dashboard** | Real-time web UI with SSE streaming |
16
-
17
- ## Data Flow
18
-
19
- ```
20
- FIX Client β†’ FIX OEG β†’ Kafka [orders] β†’ Matcher β†’ Kafka [trades]
21
- ↓
22
- MDF β†’ Kafka [snapshots] ───────────→ Dashboard (Flask)
23
- ```
24
-
25
- ## Dashboard Features
26
-
27
- - **Orders Panel** β€” Live order feed with Edit/Cancel actions
28
- - **Market Snapshot** β€” Best Bid/Ask with spread, mid price, and scrolling ticker
29
- - **Trades Panel** β€” Executed trades with value calculation
30
- - **Order Book** β€” Depth of book per symbol (bids/asks)
31
- - **Trade Chart** β€” Price and volume visualization
32
- - **Trading Statistics** β€” Volume, Value, VWAP per symbol with bar charts
33
-
34
- ![Trading Dashboard](screenshots/dashboard.png)
35
-
36
- ## Quick Start
37
-
38
- ```bash
39
- docker compose up --build
40
- ```
41
-
42
- Open dashboard: **http://localhost:5005**
43
-
44
- ## Directory Structure
45
-
46
- ```
47
- StockEx/
48
- β”œβ”€β”€ docker-compose.yml
49
- β”œβ”€β”€ fix_oeg/ # FIX Order Entry Gateway
50
- β”œβ”€β”€ fix-ui-client/ # FIX test clients
51
- β”œβ”€β”€ matcher/ # Order matching engine
52
- β”œβ”€β”€ md_feeder/ # Market data simulator
53
- β”œβ”€β”€ dashboard/ # Flask web dashboard
54
- β”œβ”€β”€ frontend/ # Manual order entry UI
55
- β”œβ”€β”€ shared/ # Common config and utilities
56
- └── shared_data/ # Securities and state files
57
- ```
58
-
59
- ## Configuration
60
-
61
- | Variable | Default | Description |
62
- |----------|---------|-------------|
63
- | `KAFKA_BOOTSTRAP` | kafka:9092 | Kafka broker address |
64
- | `TICK_SIZE` | 0.05 | Minimum price increment |
65
- | `ORDERS_PER_MIN` | 8 | MDF order generation rate |
66
-
67
- ## Requirements
68
-
69
- - Docker & Docker Compose
70
- - Ports: 5005 (Dashboard), 6000 (Matcher), 9092 (Kafka)
71
-
72
- ---
73
- *StockEx v1.0 β€” Trading Simulation Platform*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
StockEx_Developer_Guide.html DELETED
@@ -1,569 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <title>StockEx - Developer Guide</title>
6
- <style>
7
- @page { margin: 1.5cm; size: A4; }
8
- body { font-family: 'Segoe UI', Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.5; color: #333; font-size: 11pt; }
9
- h1 { color: #1a1a2e; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; font-size: 24pt; }
10
- h2 { color: #2e7d32; margin-top: 25px; border-bottom: 1px solid #ddd; padding-bottom: 5px; font-size: 16pt; page-break-after: avoid; }
11
- h3 { color: #1565c0; margin-top: 18px; font-size: 13pt; }
12
- h4 { color: #555; margin-top: 12px; font-size: 11pt; }
13
- table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 10pt; }
14
- th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
15
- th { background: #f5f5f5; font-weight: bold; }
16
- tr:nth-child(even) { background: #fafafa; }
17
- .header { text-align: center; margin-bottom: 25px; }
18
- .subtitle { color: #666; font-size: 14pt; }
19
- .version { color: #999; font-size: 10pt; }
20
- .section { page-break-inside: avoid; margin-bottom: 20px; }
21
- .highlight { background: #e8f5e9; padding: 12px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #4CAF50; }
22
- .info { background: #e3f2fd; padding: 12px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #2196F3; }
23
- .warning { background: #fff3e0; padding: 12px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #ff9800; }
24
- .code-block { background: #263238; color: #aed581; padding: 15px; border-radius: 5px; font-family: 'Consolas', monospace; font-size: 10pt; overflow-x: auto; margin: 10px 0; white-space: pre; }
25
- .json { background: #37474f; color: #80cbc4; }
26
- code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Consolas', monospace; font-size: 10pt; }
27
- .green { color: #2e7d32; }
28
- .red { color: #c62828; }
29
- .blue { color: #1565c0; }
30
- hr { border: none; border-top: 1px solid #ddd; margin: 25px 0; }
31
- .footer { text-align: center; color: #666; font-size: 9pt; margin-top: 30px; padding-top: 15px; border-top: 1px solid #ddd; }
32
- .toc { background: #fafafa; padding: 15px; border-radius: 5px; margin: 15px 0; columns: 2; }
33
- .toc ul { margin: 0; padding-left: 20px; }
34
- .toc li { margin: 4px 0; font-size: 10pt; }
35
- .arch-diagram { background: #f5f5f5; padding: 15px; border-radius: 5px; font-family: 'Consolas', monospace; white-space: pre; font-size: 9pt; overflow-x: auto; line-height: 1.3; }
36
- .module-box { border: 1px solid #ddd; border-radius: 6px; padding: 12px; margin: 12px 0; background: #fafafa; page-break-inside: avoid; }
37
- .module-box h4 { margin: 0 0 8px 0; color: #1a1a2e; border-bottom: 1px solid #eee; padding-bottom: 5px; }
38
- .port { display: inline-block; background: #e3f2fd; padding: 2px 8px; border-radius: 3px; font-family: monospace; font-size: 9pt; margin-right: 5px; }
39
- .tech { display: inline-block; background: #f3e5f5; padding: 2px 8px; border-radius: 3px; font-size: 9pt; margin-right: 5px; }
40
- .endpoint { font-family: monospace; background: #e8f5e9; padding: 2px 6px; border-radius: 3px; }
41
- .two-col { display: flex; gap: 20px; }
42
- .two-col > div { flex: 1; }
43
- .screenshot { text-align: center; margin: 15px 0; }
44
- .screenshot img { max-width: 100%; border: 1px solid #ddd; border-radius: 5px; }
45
- </style>
46
- </head>
47
- <body>
48
-
49
- <div class="header">
50
- <h1>StockEx Trading Platform</h1>
51
- <p class="subtitle">Developer & Technical Guide</p>
52
- <p class="version">Version 1.0 | Euronext OPTIQ Inspired</p>
53
- </div>
54
-
55
- <div class="toc">
56
- <strong>Contents</strong>
57
- <ul>
58
- <li>1. Overview</li>
59
- <li>2. Architecture</li>
60
- <li>3. Modules</li>
61
- <li>4. Database Persistence</li>
62
- <li>5. Data Flow Diagrams</li>
63
- <li>6. Kafka Topics</li>
64
- <li>7. Message Formats</li>
65
- <li>8. SSE Events</li>
66
- <li>9. Configuration</li>
67
- <li>10. Development</li>
68
- </ul>
69
- </div>
70
-
71
- <hr>
72
-
73
- <div class="section">
74
- <h2>1. Overview</h2>
75
- <p><strong>StockEx</strong> is a real-time trading simulation platform providing complete order-to-trade lifecycle emulation. Built with microservices architecture using Docker containers.</p>
76
-
77
- <div class="two-col">
78
- <div class="highlight">
79
- <strong>Core Features</strong>
80
- <ul style="margin:5px 0; padding-left:20px;">
81
- <li>FIX 4.4 protocol gateway</li>
82
- <li>Price-time priority matching</li>
83
- <li>Kafka event streaming</li>
84
- <li>SSE real-time dashboard</li>
85
- <li>SQLite persistence</li>
86
- </ul>
87
- </div>
88
- <div class="info">
89
- <strong>Tech Stack</strong>
90
- <ul style="margin:5px 0; padding-left:20px;">
91
- <li>Python 3.11 / Flask</li>
92
- <li>QuickFIX/Python</li>
93
- <li>Apache Kafka 7.5</li>
94
- <li>Docker Compose</li>
95
- <li>SQLite</li>
96
- </ul>
97
- </div>
98
- </div>
99
- </div>
100
-
101
- <div class="section">
102
- <h2>2. System Architecture</h2>
103
- <div class="arch-diagram">
104
- β”Œβ”€β”€β”€β”€β”€β”€β”€οΏ½οΏ½οΏ½β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
105
- β”‚ FIX UI Client β”‚ β”‚ FIX UI Client β”‚ β”‚ Frontend β”‚
106
- β”‚ (Port 5002) β”‚ β”‚ (Port 5003) β”‚ β”‚ (Port 5000) β”‚
107
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
108
- β”‚ FIX 4.4 β”‚ FIX 4.4 β”‚ HTTP/JSON
109
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
110
- β–Ό
111
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
112
- β”‚ FIX OEG (Port 5001) β”‚
113
- β”‚ QuickFIX Order Gateway β”‚
114
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
115
- β”‚ JSON
116
- β–Ό
117
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
118
- β”‚ APACHE KAFKA (Port 9092) β”‚
119
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
120
- β”‚ β”‚ orders β”‚ β”‚ trades β”‚ β”‚ snapshotsβ”‚ β”‚
121
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
122
- β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
123
- β”‚ β”‚ β”‚
124
- β–Ό β”‚ β–Ό
125
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
126
- β”‚ Matcher β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ MD Feeder β”‚
127
- β”‚ (Port 6000) β”‚ β”‚ (MDF) β”‚
128
- β”‚ β”‚ β”‚ β”‚
129
- β”‚ Order Book │◄──────────────────│ Price Sim β”‚
130
- β”‚ Trade Exec β”‚ β”‚ BBO Publish β”‚
131
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
132
- β”‚
133
- β–Ό REST API
134
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
135
- β”‚ Dashboard (Port 5005) β”‚
136
- β”‚ Orders β”‚ Trades β”‚ Book β”‚ Chart β”‚ Stats β”‚
137
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
138
- </div>
139
- </div>
140
-
141
- <div class="section">
142
- <h2>3. Module Specifications</h2>
143
-
144
- <div class="module-box">
145
- <h4>Kafka Message Broker</h4>
146
- <span class="port">9092</span> <span class="port">29092 (host)</span> <span class="tech">Confluent 7.5.0</span>
147
- <p>Central event bus. All order flow distributed via topics. Zookeeper (port 2181) for coordination.</p>
148
- </div>
149
-
150
- <div class="module-box">
151
- <h4>FIX Order Entry Gateway</h4>
152
- <span class="port">5001</span> <span class="tech">QuickFIX/Python</span>
153
- <p>FIX 4.4 acceptor. Receives NewOrderSingle (D), OrderCancelRequest (F), OrderCancelReplaceRequest (G). Normalizes to JSON β†’ Kafka <code>orders</code>.</p>
154
- </div>
155
-
156
- <div class="module-box">
157
- <h4>Matcher Engine</h4>
158
- <span class="port">6000</span> <span class="tech">Python/Flask/SQLite</span>
159
- <p>Consumes <code>orders</code>, matches with price-time priority, publishes <code>trades</code>. Maintains order book per symbol. SQLite persistence.</p>
160
- <table>
161
- <tr><th>Endpoint</th><th>Method</th><th>Description</th></tr>
162
- <tr><td><span class="endpoint">/orderbook/&lt;symbol&gt;</span></td><td>GET</td><td>Order book depth</td></tr>
163
- <tr><td><span class="endpoint">/trades</span></td><td>GET</td><td>Recent trades</td></tr>
164
- <tr><td><span class="endpoint">/health</span></td><td>GET</td><td>Health + stats</td></tr>
165
- </table>
166
- </div>
167
-
168
- <div class="module-box">
169
- <h4>Market Data Feeder (MDF)</h4>
170
- <span class="tech">Python</span>
171
- <p>Simulates market activity. 90% passive orders (book building), 10% aggressive (trades). Publishes BBO snapshots.</p>
172
- <p>Output: <code>orders</code> + <code>snapshots</code> topics</p>
173
- </div>
174
-
175
- <div class="module-box">
176
- <h4>Dashboard</h4>
177
- <span class="port">5005</span> <span class="tech">Flask/SSE/JavaScript</span>
178
- <p>Real-time web UI. Consumes Kafka + Matcher API. Server-Sent Events for live streaming. Edit/Cancel order management.</p>
179
- <table>
180
- <tr><th>Endpoint</th><th>Method</th><th>Description</th></tr>
181
- <tr><td><span class="endpoint">/stream</span></td><td>GET</td><td>SSE event stream</td></tr>
182
- <tr><td><span class="endpoint">/data</span></td><td>GET</td><td>Polling fallback</td></tr>
183
- <tr><td><span class="endpoint">/order/cancel</span></td><td>POST</td><td>Cancel order</td></tr>
184
- <tr><td><span class="endpoint">/order/amend</span></td><td>POST</td><td>Amend order</td></tr>
185
- </table>
186
- </div>
187
-
188
- <div class="module-box">
189
- <h4>FIX UI Clients</h4>
190
- <span class="port">5002</span> <span class="port">5003</span> <span class="tech">QuickFIX/Flask</span>
191
- <p>Web-based FIX initiators. Connect to FIX OEG for institutional order submission.</p>
192
- </div>
193
- </div>
194
-
195
- <div class="section">
196
- <h2>4. Database Persistence</h2>
197
-
198
- <div class="info">
199
- <strong>Storage:</strong> SQLite database at <code>/app/data/matcher.db</code><br>
200
- <strong>Docker Volume:</strong> <code>stockex_matcher_data</code> (survives container restarts)
201
- </div>
202
-
203
- <h4>4.1 Database Schema</h4>
204
-
205
- <div class="module-box">
206
- <h4>order_book β€” All Orders</h4>
207
- <table>
208
- <tr><th>Column</th><th>Type</th><th>Description</th></tr>
209
- <tr><td><code>id</code></td><td>INTEGER</td><td>Auto-increment primary key</td></tr>
210
- <tr><td><code>cl_ord_id</code></td><td>TEXT</td><td>Unique client order ID</td></tr>
211
- <tr><td><code>symbol</code></td><td>TEXT</td><td>Security (ALPHA, EXAE, etc.)</td></tr>
212
- <tr><td><code>side</code></td><td>TEXT</td><td>BUY / SELL</td></tr>
213
- <tr><td><code>price</code></td><td>REAL</td><td>Limit price</td></tr>
214
- <tr><td><code>quantity</code></td><td>INTEGER</td><td>Original order quantity</td></tr>
215
- <tr><td><code>remaining_qty</code></td><td>INTEGER</td><td>Unfilled quantity</td></tr>
216
- <tr><td><code>status</code></td><td>TEXT</td><td>OPEN / FILLED / CANCELLED</td></tr>
217
- <tr><td><code>timestamp</code></td><td>REAL</td><td>Order entry time (Unix)</td></tr>
218
- <tr><td><code>created_at</code></td><td>DATETIME</td><td>DB insert timestamp</td></tr>
219
- </table>
220
- </div>
221
-
222
- <div class="module-box">
223
- <h4>trades β€” Executed Trades</h4>
224
- <table>
225
- <tr><th>Column</th><th>Type</th><th>Description</th></tr>
226
- <tr><td><code>id</code></td><td>INTEGER</td><td>Auto-increment primary key</td></tr>
227
- <tr><td><code>symbol</code></td><td>TEXT</td><td>Traded security</td></tr>
228
- <tr><td><code>price</code></td><td>REAL</td><td>Execution price</td></tr>
229
- <tr><td><code>quantity</code></td><td>INTEGER</td><td>Traded quantity</td></tr>
230
- <tr><td><code>buy_order_id</code></td><td>TEXT</td><td>Buyer's cl_ord_id</td></tr>
231
- <tr><td><code>sell_order_id</code></td><td>TEXT</td><td>Seller's cl_ord_id</td></tr>
232
- <tr><td><code>timestamp</code></td><td>REAL</td><td>Trade time (Unix)</td></tr>
233
- <tr><td><code>created_at</code></td><td>DATETIME</td><td>DB insert timestamp</td></tr>
234
- </table>
235
- </div>
236
-
237
- <h4>4.2 Database Functions</h4>
238
- <table>
239
- <tr><th>Function</th><th>Description</th></tr>
240
- <tr><td><code>save_order(order)</code></td><td>Insert new order into order_book</td></tr>
241
- <tr><td><code>update_order_quantity(id, qty)</code></td><td>Update remaining_qty after partial fill</td></tr>
242
- <tr><td><code>cancel_order(cl_ord_id)</code></td><td>Set status = 'CANCELLED'</td></tr>
243
- <tr><td><code>save_trade(trade)</code></td><td>Insert executed trade</td></tr>
244
- <tr><td><code>get_open_orders(symbol, side)</code></td><td>Query open orders for matching</td></tr>
245
- <tr><td><code>load_order_books()</code></td><td>Restore order books on startup</td></tr>
246
- <tr><td><code>get_trades(symbol, limit)</code></td><td>Retrieve recent trades</td></tr>
247
- <tr><td><code>delete_filled_orders(days)</code></td><td>Cleanup old filled/cancelled orders</td></tr>
248
- </table>
249
-
250
- <h4>4.3 Indexes</h4>
251
- <div class="code-block">CREATE INDEX idx_trades_symbol ON trades(symbol);
252
- CREATE INDEX idx_trades_timestamp ON trades(timestamp);
253
- CREATE INDEX idx_orderbook_symbol_side ON order_book(symbol, side);
254
- CREATE INDEX idx_orderbook_status ON order_book(status);
255
- CREATE INDEX idx_orderbook_cl_ord_id ON order_book(cl_ord_id);</div>
256
- </div>
257
-
258
- <div class="section">
259
- <h2>5. Data Flow Diagrams</h2>
260
-
261
- <h4>4.1 Order Entry Flow</h4>
262
- <div class="arch-diagram">
263
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
264
- β”‚ FIX Client β”‚
265
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
266
- β”‚ FIX 4.4 NewOrderSingle (35=D)
267
- β–Ό
268
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
269
- β”‚ FIX OEG β”‚ Validate β†’ Normalize β†’ Generate cl_ord_id
270
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
271
- β”‚ JSON
272
- β–Ό
273
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
274
- β”‚ Kafka β”‚ Topic: orders
275
- β”‚ [orders] β”‚
276
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
277
- β”‚
278
- β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
279
- β–Ό β–Ό
280
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”ŒοΏ½οΏ½οΏ½β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
281
- β”‚Matcher β”‚ β”‚ Dashboard β”‚
282
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
283
- </div>
284
-
285
- <h4>4.2 Order Matching Flow</h4>
286
- <div class="arch-diagram">
287
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
288
- β”‚ Incoming Order β”‚
289
- β”‚ (from Kafka) β”‚
290
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
291
- β”‚
292
- β–Ό
293
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
294
- β”‚ Parse & Validateβ”‚
295
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
296
- β”‚
297
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
298
- β–Ό β–Ό
299
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
300
- β”‚ BUY Order β”‚ β”‚ SELL Order β”‚
301
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
302
- β”‚ β”‚
303
- β–Ό β–Ό
304
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
305
- β”‚ Check SELL book β”‚ β”‚ Check BUY book β”‚
306
- β”‚ for price ≀ bid β”‚ β”‚ for price β‰₯ ask β”‚
307
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
308
- β”‚ β”‚
309
- β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
310
- β–Ό β–Ό β–Ό β–Ό
311
- β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
312
- β”‚ Match β”‚ β”‚No Matchβ”‚ β”‚ Match β”‚ β”‚No Matchβ”‚
313
- β”‚ Found β”‚ β”‚ β”‚ β”‚ Found β”‚ β”‚ β”‚
314
- β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
315
- β”‚ β”‚ β”‚ β”‚
316
- β–Ό β–Ό β–Ό β–Ό
317
- β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
318
- β”‚Executeβ”‚ β”‚Add to β”‚ β”‚Executeβ”‚ β”‚Add to β”‚
319
- β”‚ Trade β”‚ β”‚BUY Bookβ”‚ β”‚ Trade β”‚ β”‚SELLBookβ”‚
320
- β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜
321
- β”‚ β”‚
322
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
323
- β–Ό
324
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
325
- β”‚Kafka:tradesβ”‚
326
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
327
- </div>
328
-
329
- <h4>4.3 Real-time Dashboard Flow</h4>
330
- <div class="arch-diagram">
331
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
332
- β”‚ BROWSER β”‚
333
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
334
- β”‚ β”‚ Dashboard UI β”‚ β”‚
335
- β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ β”‚
336
- β”‚ β”‚ β”‚ Orders β”‚ β”‚ Trades β”‚ β”‚ Book β”‚ β”‚ Statistics β”‚β”‚ β”‚
337
- β”‚ β”‚ β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ β”‚
338
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
339
- β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
340
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
341
- β”‚ β”‚ β”‚
342
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
343
- β”‚ β”‚ EventSource β”‚ SSE Connection β”‚
344
- β”‚ β”‚ /stream β”‚ β”‚
345
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
346
- β””β”€οΏ½οΏ½οΏ½β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
347
- β”‚ HTTP (SSE)
348
- β–Ό
349
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
350
- β”‚ DASHBOARD SERVER β”‚
351
- β”‚ β”‚
352
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
353
- β”‚ β”‚Kafka Consumer│─────▢│ SSE Broadcast│────▢│ Clients β”‚ β”‚
354
- β”‚ β”‚ (orders, β”‚ β”‚ Queue β”‚ β”‚ Queue[] β”‚ β”‚
355
- β”‚ β”‚ trades, β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
356
- β”‚ β”‚ snapshots) β”‚ β”‚
357
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
358
- β”‚ β”‚
359
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
360
- β”‚ β”‚ REST API │◀────▢│ Matcher β”‚ /orderbook, /trades β”‚
361
- β”‚ β”‚ /data β”‚ β”‚ Proxy β”‚ β”‚
362
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
363
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
364
- </div>
365
-
366
- <h4>4.4 Complete System Interaction</h4>
367
- <div class="arch-diagram">
368
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
369
- β”‚FIX Cli 1β”‚ β”‚FIX Cli 2β”‚ β”‚Frontend β”‚
370
- β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
371
- β”‚ β”‚ β”‚
372
- β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
373
- β”‚
374
- β–Ό
375
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
376
- β”‚ FIX OEG │◄──── FIX 4.4 Protocol
377
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
378
- β”‚
379
- β–Ό
380
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
381
- β”‚ KAFKA CLUSTER β”‚
382
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
383
- β”‚ β”‚orders β”‚β”‚trades β”‚β”‚snapshots β”‚ β”‚
384
- β”‚ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜β””β”€β”€β”€β–²β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”˜ β”‚
385
- β””β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”˜
386
- β”‚ β”‚ β”‚
387
- β”Œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”
388
- β”‚ β–Ό β”‚ β”‚ β”‚
389
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
390
- β”‚ β”‚MATCHER β”‚β”€β”€β”€β”€β”˜ β”‚ β”‚
391
- β”‚ β”‚ β”‚ β”‚ β”‚
392
- β”‚ β”‚ Book β”‚ β”‚ β”‚
393
- β”‚ β”‚ Match β”‚ β”‚ β”‚
394
- β”‚ β”‚ Trade β”‚ β”‚ β”‚
395
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
396
- β”‚ β–² β”‚ β”‚
397
- β”‚ β”‚ REST β”‚ β”‚
398
- β”‚ β”‚ β”‚ β”‚
399
- β”‚ β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β” β”‚
400
- β”‚ β”‚ DASHBOARD β”‚ β”‚
401
- β”‚ β”‚ (SSE + Kafka Consumer) β”‚ β”‚
402
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
403
- β”‚ β”‚
404
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
405
- β”‚ β”‚ MD FEEDER (MDF) β”‚β”€β”€β”˜
406
- β”‚ β”‚ Orders + Snapshots β”‚
407
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
408
- β”‚ DOCKER NETWORK
409
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
410
- </div>
411
-
412
- <h4>4.5 Message Sequence: New Order to Trade</h4>
413
- <div class="arch-diagram">
414
- FIX Client FIX OEG Kafka Matcher Dashboard
415
- β”‚ β”‚ β”‚ β”‚ β”‚
416
- │──35=D────▢│ β”‚ β”‚ β”‚
417
- β”‚NewOrder β”‚ β”‚ β”‚ β”‚
418
- β”‚ │──JSON────▢│ β”‚ β”‚
419
- β”‚ β”‚ [orders] β”‚ β”‚ β”‚
420
- β”‚ β”‚ │──consume──▢│ β”‚
421
- β”‚ β”‚ β”‚ β”‚ β”‚
422
- β”‚ β”‚ β”‚ │──match() β”‚
423
- β”‚ β”‚ β”‚ β”‚ β”‚
424
- β”‚ β”‚ │◀──trade────│ β”‚
425
- β”‚ β”‚ β”‚ [trades] β”‚ β”‚
426
- β”‚ β”‚ β”‚ β”‚ β”‚
427
- β”‚ β”‚ │──────────────consume───▢│
428
- β”‚ β”‚ β”‚ β”‚ β”‚
429
- β”‚ β”‚ β”‚ β”‚ render()
430
- β”‚ β”‚ β”‚ β”‚ β”‚
431
- </div>
432
- </div>
433
-
434
- <div class="section">
435
- <h2>6. Kafka Topics</h2>
436
- <table>
437
- <tr><th>Topic</th><th>Producers</th><th>Consumers</th><th>Content</th></tr>
438
- <tr><td><code>orders</code></td><td>FIX OEG, MDF, Frontend</td><td>Matcher, Dashboard</td><td>New/Cancel/Amend orders</td></tr>
439
- <tr><td><code>trades</code></td><td>Matcher</td><td>Dashboard, Consumer</td><td>Executed trades</td></tr>
440
- <tr><td><code>snapshots</code></td><td>MDF</td><td>Dashboard</td><td>BBO updates</td></tr>
441
- </table>
442
- </div>
443
-
444
- <div class="section">
445
- <h2>7. Message Formats</h2>
446
-
447
- <h4>Order (New)</h4>
448
- <div class="code-block json">{
449
- "symbol": "ALPHA",
450
- "side": "BUY",
451
- "price": 25.50,
452
- "quantity": 100,
453
- "cl_ord_id": "MDF-1234567890-1",
454
- "timestamp": 1234567890.123,
455
- "source": "MDF"
456
- }</div>
457
-
458
- <h4>Order (Cancel)</h4>
459
- <div class="code-block json">{
460
- "type": "cancel",
461
- "orig_cl_ord_id": "MDF-1234567890-1",
462
- "symbol": "ALPHA",
463
- "timestamp": 1234567890.456
464
- }</div>
465
-
466
- <h4>Order (Amend)</h4>
467
- <div class="code-block json">{
468
- "type": "amend",
469
- "orig_cl_ord_id": "MDF-1234567890-1",
470
- "cl_ord_id": "amend-1234567890",
471
- "symbol": "ALPHA",
472
- "quantity": 150,
473
- "price": 25.45,
474
- "timestamp": 1234567890.789
475
- }</div>
476
-
477
- <h4>Trade</h4>
478
- <div class="code-block json">{
479
- "symbol": "ALPHA",
480
- "price": 25.50,
481
- "quantity": 100,
482
- "buy_order_id": "order-123",
483
- "sell_order_id": "order-456",
484
- "timestamp": 1234567890.123
485
- }</div>
486
-
487
- <h4>Snapshot (BBO)</h4>
488
- <div class="code-block json">{
489
- "symbol": "ALPHA",
490
- "best_bid": 25.45,
491
- "best_ask": 25.55,
492
- "bid_size": 500,
493
- "ask_size": 300,
494
- "timestamp": 1234567890.123,
495
- "source": "MDF"
496
- }</div>
497
- </div>
498
-
499
- <div class="section">
500
- <h2>8. SSE Events</h2>
501
- <table>
502
- <tr><th>Event</th><th>Data</th><th>Trigger</th></tr>
503
- <tr><td><code>connected</code></td><td>{status}</td><td>Client connects</td></tr>
504
- <tr><td><code>init</code></td><td>{orders, bbos, trades}</td><td>Initial state dump</td></tr>
505
- <tr><td><code>order</code></td><td>Order JSON</td><td>New order received</td></tr>
506
- <tr><td><code>trade</code></td><td>Trade JSON</td><td>Trade executed</td></tr>
507
- <tr><td><code>snapshot</code></td><td>BBO JSON</td><td>Price update</td></tr>
508
- </table>
509
- </div>
510
-
511
- <div class="section">
512
- <h2>9. Configuration</h2>
513
- <table>
514
- <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
515
- <tr><td><code>KAFKA_BOOTSTRAP</code></td><td>kafka:9092</td><td>Broker address</td></tr>
516
- <tr><td><code>MATCHER_URL</code></td><td>http://matcher:6000</td><td>Matcher API</td></tr>
517
- <tr><td><code>TICK_SIZE</code></td><td>0.05</td><td>Min price increment</td></tr>
518
- <tr><td><code>ORDERS_PER_MIN</code></td><td>8</td><td>MDF rate</td></tr>
519
- <tr><td><code>KAFKA_RETRIES</code></td><td>30</td><td>Connection retries</td></tr>
520
- </table>
521
-
522
- <h4>Securities (shared_data/securities.txt)</h4>
523
- <div class="code-block">#SYMBOL start_price current_price
524
- ALPHA 25.00 25.00
525
- EXAE 42.00 42.00
526
- PEIR 18.50 18.50
527
- QUEST 12.75 12.75</div>
528
- </div>
529
-
530
- <div class="section">
531
- <h2>10. Development Commands</h2>
532
-
533
- <h4>Build & Run</h4>
534
- <div class="code-block">docker compose up --build # Start all
535
- docker compose up -d --build # Background
536
- docker compose logs -f dashboard # Follow logs
537
- docker compose down # Stop all</div>
538
-
539
- <h4>Reset Data</h4>
540
- <div class="code-block">docker compose down
541
- docker volume rm stockex_matcher_data
542
- docker compose up -d</div>
543
-
544
- <h4>Container Access</h4>
545
- <div class="code-block">docker exec -it matcher bash
546
- docker exec -it dashboard bash
547
- docker logs matcher --tail 50</div>
548
-
549
- <h4>API Testing</h4>
550
- <div class="code-block">curl http://localhost:6000/orderbook/ALPHA
551
- curl http://localhost:6000/trades
552
- curl http://localhost:5005/data</div>
553
- </div>
554
-
555
- <div class="screenshot">
556
- <img src="screenshots/dashboard.png" alt="StockEx Dashboard">
557
- <p style="font-size:10pt; color:#666;"><em>StockEx Trading Dashboard - Real-time Market View</em></p>
558
- </div>
559
-
560
- <hr>
561
-
562
- <div class="footer">
563
- <strong>StockEx Trading Platform v1.0</strong><br>
564
- Developer Guide | Euronext OPTIQ Inspired<br>
565
- <p style="margin-top:10px;">Print to PDF: Ctrl+P β†’ Save as PDF</p>
566
- </div>
567
-
568
- </body>
569
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
StockEx_Technical_Guide.md DELETED
@@ -1,814 +0,0 @@
1
- # StockEx Trading Platform
2
- ## Complete Technical & Developer Guide v1.0
3
-
4
- ---
5
-
6
- ## Table of Contents
7
-
8
- 1. [Introduction](#1-introduction)
9
- 2. [System Architecture](#2-system-architecture)
10
- 3. [Module Descriptions](#3-module-descriptions)
11
- 4. [Dashboard User Interface](#4-dashboard-user-interface)
12
- 5. [Data Flow & Messaging](#5-data-flow--messaging)
13
- 6. [Configuration](#6-configuration)
14
- 7. [Quick Reference](#7-quick-reference)
15
- 8. [Troubleshooting](#8-troubleshooting)
16
-
17
- ---
18
-
19
- ## 1. Introduction
20
-
21
- **StockEx** is a comprehensive real-time trading simulation platform inspired by Euronext OPTIQ. It provides a complete electronic trading ecosystem including order entry via FIX protocol, order matching engine, market data distribution, and live visualization through a web dashboard.
22
-
23
- ### Key Features
24
-
25
- - **FIX 4.4 Protocol Support** β€” Institutional-grade order entry via QuickFIX
26
- - **Real-time Order Matching** β€” Price-time priority matching engine with SQLite persistence
27
- - **Live Market Data Streaming** β€” Kafka-based event distribution
28
- - **Web Dashboard with SSE** β€” Real-time updates without page refresh
29
- - **Order Management** β€” Edit and Cancel capabilities from the UI
30
- - **Trading Analytics** β€” Volume, Value, VWAP statistics with visualizations
31
-
32
- ### Access URLs
33
-
34
- | Service | URL | Purpose |
35
- |---------|-----|---------|
36
- | Dashboard | http://localhost:5005 | Main trading view |
37
- | Frontend | http://localhost:5000 | Manual order entry |
38
- | FIX Client 1 | http://localhost:5002 | FIX order submission |
39
- | FIX Client 2 | http://localhost:5003 | FIX order submission |
40
- | Matcher API | http://localhost:6000 | REST API for order book/trades |
41
-
42
- ---
43
-
44
- ## 2. System Architecture
45
-
46
- ### High-Level Architecture Diagram
47
-
48
- ```
49
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
50
- β”‚ FIX UI Client β”‚ β”‚ FIX UI Client β”‚ β”‚ Frontend β”‚
51
- β”‚ (Port 5002) β”‚ β”‚ (Port 5003) β”‚ β”‚ (Port 5000) β”‚
52
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
53
- β”‚ β”‚ β”‚
54
- β”‚ FIX 4.4 β”‚ FIX 4.4 β”‚ HTTP
55
- β–Ό β–Ό β–Ό
56
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
57
- β”‚ FIX OEG (Port 5001) β”‚
58
- β”‚ QuickFIX Order Entry Gateway β”‚
59
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
60
- β”‚
61
- β–Ό Kafka [orders]
62
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
63
- β”‚ Apache Kafka (Port 9092) β”‚
64
- β”‚ Topics: orders, trades, snapshots β”‚
65
- β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
66
- β”‚ β”‚ β”‚
67
- β–Ό β–Ό β–Ό
68
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
69
- β”‚ Matcher β”‚ β”‚ MD Feeder β”‚ β”‚ Dashboard β”‚
70
- β”‚ (Port 6000) β”‚ β”‚ (MDF) β”‚ β”‚ (Port 5005) β”‚
71
- β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
72
- β”‚ Order Book β”‚ β”‚ Price Sim β”‚ β”‚ Web UI + SSE β”‚
73
- β”‚ Trade Match β”‚ β”‚ BBO Publish β”‚ β”‚ Real-time β”‚
74
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
75
- ```
76
-
77
- ### Component Summary
78
-
79
- | Component | Technology | Port | Function |
80
- |-----------|------------|------|----------|
81
- | Zookeeper | Confluent 7.5.0 | 2181 | Kafka coordination |
82
- | Kafka | Confluent 7.5.0 | 9092, 29092 | Message streaming |
83
- | FIX OEG | QuickFIX/Python | 5001 | FIX protocol gateway |
84
- | Matcher | Python/Flask/SQLite | 6000 | Order matching engine |
85
- | MDF | Python | - | Market data simulation |
86
- | Dashboard | Flask/SSE | 5005 | Real-time web UI |
87
- | Frontend | Flask | 5000 | Manual order entry |
88
- | FIX Clients | QuickFIX/Flask | 5002, 5003 | FIX order UI |
89
-
90
- ---
91
-
92
- ## 3. Module Descriptions
93
-
94
- ### 3.1 Zookeeper
95
-
96
- **Port:** 2181
97
-
98
- Apache Zookeeper provides distributed coordination for the Kafka cluster. It manages broker metadata, topic configurations, and cluster membership.
99
-
100
- - **Image:** confluentinc/cp-zookeeper:7.5.0
101
- - **Dependencies:** None
102
- - **Environment:** `ZOOKEEPER_CLIENT_PORT=2181`
103
-
104
- ---
105
-
106
- ### 3.2 Apache Kafka
107
-
108
- **Ports:** 9092 (internal), 29092 (host access)
109
-
110
- Apache Kafka serves as the central message backbone for the entire trading system. All order flow, trade executions, and market data are distributed through Kafka topics.
111
-
112
- - **Image:** confluentinc/cp-kafka:7.5.0
113
- - **Dependencies:** Zookeeper
114
-
115
- **Topics:**
116
-
117
- | Topic | Purpose | Producers | Consumers |
118
- |-------|---------|-----------|-----------|
119
- | `orders` | Order flow | FIX OEG, MDF, Frontend | Matcher, Dashboard |
120
- | `trades` | Executed trades | Matcher | Dashboard, Consumer |
121
- | `snapshots` | BBO updates | MDF | Dashboard, Snapshot Viewer |
122
-
123
- ---
124
-
125
- ### 3.3 FIX Order Entry Gateway (FIX OEG)
126
-
127
- **Port:** 5001
128
-
129
- The FIX OEG is a QuickFIX/Python acceptor that receives orders via FIX 4.4 protocol from institutional clients. It validates incoming FIX messages, normalizes them to JSON format, and publishes to the Kafka `orders` topic.
130
-
131
- **Supported FIX Messages:**
132
-
133
- | Message Type | Tag 35 | Description |
134
- |--------------|--------|-------------|
135
- | NewOrderSingle | D | New order submission |
136
- | OrderCancelRequest | F | Cancel existing order |
137
- | OrderCancelReplaceRequest | G | Modify existing order |
138
-
139
- **Key Functions:**
140
- - FIX session management (logon, heartbeat, logout)
141
- - Message validation and normalization
142
- - Order ID generation
143
- - Kafka publishing
144
-
145
- ---
146
-
147
- ### 3.4 FIX UI Clients
148
-
149
- **Ports:** 5002 (Client 1), 5003 (Client 2)
150
-
151
- Web-based FIX initiator clients that connect to the FIX OEG. They provide a user interface for submitting orders via FIX protocol, simulating institutional trading terminals.
152
-
153
- **Features:**
154
- - New Order Single submission
155
- - Order cancellation
156
- - Order amendment
157
- - Real-time execution reports
158
-
159
- **Configuration Files:**
160
- - `client1.cfg` β€” FIX session config for Client 1
161
- - `client2.cfg` β€” FIX session config for Client 2
162
-
163
- ---
164
-
165
- ### 3.5 Matcher (Order Matching Engine)
166
-
167
- **Port:** 6000
168
-
169
- The core matching engine that maintains order books for all securities. It consumes orders from Kafka, attempts to match them using price-time priority, and publishes resulting trades.
170
-
171
- **Matching Algorithm:** Price-Time Priority (FIFO)
172
- - Buy orders sorted by price descending (highest first)
173
- - Sell orders sorted by price ascending (lowest first)
174
- - Within same price level, earlier orders have priority
175
-
176
- **Persistence:** SQLite database (`/app/data/matcher.db`)
177
- - Order book survives container restarts
178
- - Volume-mapped for data durability
179
-
180
- **REST API Endpoints:**
181
-
182
- | Endpoint | Method | Description |
183
- |----------|--------|-------------|
184
- | `/orderbook/<symbol>` | GET | Get order book depth for symbol |
185
- | `/trades` | GET | Get recent trades |
186
- | `/health` | GET | Health check with statistics |
187
-
188
- **Order Book Response Example:**
189
- ```json
190
- {
191
- "symbol": "ALPHA",
192
- "bids": [
193
- {"price": 25.45, "quantity": 100, "cl_ord_id": "MDF-123"},
194
- {"price": 25.40, "quantity": 200, "cl_ord_id": "MDF-124"}
195
- ],
196
- "asks": [
197
- {"price": 25.55, "quantity": 150, "cl_ord_id": "MDF-125"},
198
- {"price": 25.60, "quantity": 100, "cl_ord_id": "MDF-126"}
199
- ]
200
- }
201
- ```
202
-
203
- ---
204
-
205
- ### 3.6 Market Data Feeder (MDF)
206
-
207
- **Port:** Internal only
208
-
209
- Simulates market data by generating random orders and publishing Best Bid/Offer (BBO) snapshots. Creates realistic market activity with configurable order rates and price movements.
210
-
211
- **Order Generation:**
212
- - **90% Passive Orders** β€” Placed away from mid price to build order book
213
- - **10% Aggressive Orders** β€” Cross the spread to generate trades
214
-
215
- **Price Simulation:**
216
- - Small random drift (Β±2 ticks) with 10% probability
217
- - Prices bounded by minimum (1.00)
218
- - Half spread: 0.10 (10 cents)
219
-
220
- **Output:**
221
- - Orders β†’ Kafka `orders` topic
222
- - Snapshots β†’ Kafka `snapshots` topic
223
-
224
- **Configuration:**
225
-
226
- | Variable | Default | Description |
227
- |----------|---------|-------------|
228
- | `ORDERS_PER_MIN` | 8 | Order generation rate |
229
- | `TICK_SIZE` | 0.05 | Minimum price increment |
230
-
231
- ---
232
-
233
- ### 3.7 Dashboard
234
-
235
- **Port:** 5005
236
-
237
- Real-time web dashboard providing comprehensive market visualization. Uses Server-Sent Events (SSE) for live streaming updates without page refresh.
238
-
239
- **Technology Stack:**
240
- - Backend: Python/Flask
241
- - Frontend: HTML5/CSS3/JavaScript
242
- - Streaming: Server-Sent Events (SSE)
243
- - Data: Kafka consumer + REST API calls to Matcher
244
-
245
- **Features:**
246
-
247
- | Panel | Description |
248
- |-------|-------------|
249
- | Orders | Live order feed with Edit/Cancel actions |
250
- | Market Snapshot | BBO table with scrolling ticker tape |
251
- | Trades | Executed trades with value calculation |
252
- | Order Book | Depth of book per symbol |
253
- | Trade Chart | Price line + volume bars visualization |
254
- | Trading Statistics | Aggregated metrics with bar charts |
255
-
256
- **SSE Events:**
257
-
258
- | Event | Data | Trigger |
259
- |-------|------|---------|
260
- | `connected` | Status | Initial connection |
261
- | `init` | Full state | On connect |
262
- | `order` | Order JSON | New order received |
263
- | `trade` | Trade JSON | Trade executed |
264
- | `snapshot` | BBO JSON | Price update |
265
-
266
- ---
267
-
268
- ### 3.8 Frontend (Manual Order Entry)
269
-
270
- **Port:** 5000
271
-
272
- Simple web interface for manual order submission. Allows users to enter orders directly without FIX protocol.
273
-
274
- **Features:**
275
- - Symbol selection dropdown
276
- - Side selection (BUY/SELL)
277
- - Price and quantity input
278
- - Submit order to Kafka
279
-
280
- ---
281
-
282
- ### 3.9 Consumer (Debug Utility)
283
-
284
- **Port:** Internal only
285
-
286
- Debug utility that consumes and logs messages from Kafka `trades` topic. Outputs trade information to console for monitoring.
287
-
288
- **Output Format:**
289
- ```
290
- TRADE: ALPHA - 100 @ 25.50
291
- TRADE: EXAE - 50 @ 42.10
292
- ```
293
-
294
- ---
295
-
296
- ### 3.10 Snapshot Viewer
297
-
298
- **Port:** Internal only
299
-
300
- Utility service that subscribes to the `snapshots` topic and logs BBO updates. Writes to log files in `/app/logs` for analysis.
301
-
302
- ---
303
-
304
- ## 4. Dashboard User Interface
305
-
306
- ### 4.1 Orders Panel
307
-
308
- Displays real-time incoming orders with full management capabilities.
309
-
310
- | Column | Description |
311
- |--------|-------------|
312
- | Symbol | Stock ticker (ALPHA, EXAE, PEIR, QUEST) |
313
- | Side | BUY (green) or SELL (red) |
314
- | Qty | Order quantity in shares |
315
- | Price | Limit price |
316
- | Source | Order origin (MDF, FIX, Manual) |
317
- | Time | Order timestamp |
318
- | Actions | Edit / Cancel buttons |
319
-
320
- **Order Actions:**
321
- - **Edit** β€” Opens modal to modify quantity and/or price (sends amend to Kafka)
322
- - **Cancel** β€” Sends cancel request to Kafka
323
- - **Row Selection** β€” Click row to select, use header buttons for actions
324
-
325
- ---
326
-
327
- ### 4.2 Market Snapshot
328
-
329
- Shows Best Bid/Offer (BBO) for all securities with real-time updates.
330
-
331
- | Column | Description |
332
- |--------|-------------|
333
- | Symbol | Security identifier |
334
- | Best Bid | Highest buy price (green) |
335
- | Best Ask | Lowest sell price (red) |
336
- | Spread | Ask - Bid difference |
337
- | Mid | Midpoint: (Bid + Ask) / 2 |
338
- | Updated | Last update timestamp |
339
-
340
- **Ticker Tape:**
341
- - Scrolling bar at bottom shows recent trades
342
- - β–² Green: Price up from previous
343
- - β–Ό Red: Price down from previous
344
- - ● Yellow: No change
345
- - Hover to pause scrolling
346
-
347
- ---
348
-
349
- ### 4.3 Trades Panel
350
-
351
- Lists all executed trades with calculated values.
352
-
353
- | Column | Description |
354
- |--------|-------------|
355
- | Symbol | Traded security |
356
- | Qty | Executed quantity |
357
- | Price | Execution price |
358
- | Value | Trade value (Qty Γ— Price) |
359
- | Time | Execution timestamp |
360
-
361
- ---
362
-
363
- ### 4.4 Order Book
364
-
365
- Displays full market depth for selected symbol.
366
-
367
- **Controls:**
368
- - **Symbol Dropdown** β€” Select security to view
369
- - **Refresh Button** β€” Manual refresh (auto-refreshes every 3 seconds)
370
-
371
- **Display:**
372
- - **Left Side:** Bid Qty | Bid Price (green, sorted price descending)
373
- - **Right Side:** Ask Price | Ask Qty (red, sorted price ascending)
374
- - **Header:** Shows count of bids and asks
375
-
376
- ---
377
-
378
- ### 4.5 Trade Chart
379
-
380
- Visual representation of trade activity over time.
381
-
382
- **Elements:**
383
- - **Green Line** β€” Price trend connecting trade execution prices
384
- - **Blue Bars** β€” Volume per trade
385
- - **Y-Axis** β€” Price scale
386
- - **X-Axis** β€” Trade sequence (last 100 trades)
387
-
388
- **Controls:**
389
- - Symbol dropdown to filter by security or view all
390
-
391
- ---
392
-
393
- ### 4.6 Trading Statistics
394
-
395
- Aggregated metrics calculated from all trades.
396
-
397
- | Metric | Description | Formula |
398
- |--------|-------------|---------|
399
- | Trades | Count of executed trades | COUNT(*) |
400
- | Volume | Total shares traded | Ξ£ Quantity |
401
- | Value | Total monetary value | Ξ£ (Qty Γ— Price) |
402
- | Start | First trade price | First price in session |
403
- | Last | Most recent price | Latest price |
404
- | VWAP | Volume-Weighted Average Price | Ξ£(Qty Γ— Price) / Ξ£ Qty |
405
-
406
- **Bar Charts:**
407
- - Grouped by symbol showing Volume (green) and Value (blue) side by side
408
- - Normalized to maximum value in dataset
409
-
410
- ---
411
-
412
- ## 5. Data Flow & Messaging
413
-
414
- ### 5.1 Order Entry Flow
415
-
416
- ```
417
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
418
- β”‚ FIX Client β”‚
419
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
420
- β”‚ FIX 4.4 NewOrderSingle (35=D)
421
- β–Ό
422
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
423
- β”‚ FIX OEG β”‚ Validate β†’ Normalize β†’ Generate cl_ord_id
424
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
425
- β”‚ JSON
426
- β–Ό
427
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
428
- β”‚ Kafka β”‚ Topic: orders
429
- β”‚ [orders] β”‚
430
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
431
- β”‚
432
- β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
433
- β–Ό β–Ό
434
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
435
- β”‚Matcher β”‚ β”‚ Dashboard β”‚
436
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
437
- ```
438
-
439
- ### 5.2 Order Matching Flow
440
-
441
- ```
442
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
443
- β”‚ Incoming Order β”‚
444
- β”‚ (from Kafka) β”‚
445
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
446
- β”‚
447
- β–Ό
448
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
449
- β”‚ Parse & Validateβ”‚
450
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
451
- β”‚
452
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
453
- β–Ό β–Ό
454
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
455
- β”‚ BUY Order β”‚ β”‚ SELL Order β”‚
456
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
457
- β”‚ β”‚
458
- β–Ό β–Ό
459
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
460
- β”‚ Check SELL book β”‚ β”‚ Check BUY book β”‚
461
- β”‚ for price ≀ bid β”‚ β”‚ for price β‰₯ ask β”‚
462
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
463
- β”‚ β”‚
464
- β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
465
- β–Ό β–Ό β–Ό β–Ό
466
- β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
467
- β”‚ Match β”‚ β”‚No Matchβ”‚ β”‚ Match β”‚ β”‚No Matchβ”‚
468
- β”‚ Found β”‚ β”‚ β”‚ β”‚ Found β”‚ β”‚ β”‚
469
- β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
470
- β”‚ β”‚ β”‚ β”‚
471
- β–Ό β–Ό β–Ό β–Ό
472
- β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
473
- β”‚Executeβ”‚ β”‚Add to β”‚ β”‚Executeβ”‚ β”‚Add to β”‚
474
- β”‚ Trade β”‚ β”‚BUY Bookβ”‚ β”‚ Trade β”‚ β”‚SELLBookβ”‚
475
- β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜
476
- β”‚ β”‚
477
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
478
- β–Ό
479
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
480
- β”‚Kafka:tradesβ”‚
481
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
482
- ```
483
-
484
- ### 5.3 Real-time Dashboard Flow
485
-
486
- ```
487
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
488
- β”‚ BROWSER β”‚
489
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
490
- β”‚ β”‚ Dashboard UI β”‚ β”‚
491
- β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ β”‚
492
- β”‚ β”‚ β”‚ Orders β”‚ β”‚ Trades β”‚ β”‚ Book β”‚ β”‚ Statistics β”‚β”‚ β”‚
493
- β”‚ β”‚ β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ β”‚
494
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
495
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
496
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
497
- β”‚ β”‚ EventSource β”‚ SSE Connection β”‚
498
- β”‚ β”‚ /stream β”‚ β”‚
499
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
500
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
501
- β”‚ HTTP (SSE)
502
- β–Ό
503
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
504
- β”‚ DASHBOARD SERVER οΏ½οΏ½
505
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
506
- β”‚ β”‚Kafka Consumer│─────▢│ SSE Broadcast│────▢│ Clients β”‚ β”‚
507
- β”‚ β”‚ (orders, β”‚ β”‚ Queue β”‚ β”‚ Queue[] β”‚ β”‚
508
- β”‚ β”‚ trades, β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
509
- β”‚ β”‚ snapshots) β”‚ β”‚
510
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
511
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
512
- ```
513
-
514
- ### 5.4 Complete System Interaction
515
-
516
- ```
517
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
518
- β”‚FIX Cli 1β”‚ β”‚FIX Cli 2β”‚ β”‚Frontend β”‚
519
- β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
520
- β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
521
- β–Ό
522
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
523
- β”‚ FIX OEG │◄──── FIX 4.4 Protocol
524
- β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
525
- β–Ό
526
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
527
- β”‚ KAFKA CLUSTER β”‚
528
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
529
- β”‚ β”‚orders β”‚β”‚trades β”‚β”‚snapshots β”‚ β”‚
530
- β”‚ β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜β””β”€β”€β”€β–²β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”˜ β”‚
531
- β””β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”˜
532
- β”‚ β”‚ β”‚
533
- β”Œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”
534
- β”‚ β–Ό β”‚ β”‚ β”‚
535
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
536
- β”‚ β”‚MATCHER β”‚β”€β”€β”€β”€β”˜ β”‚ β”‚
537
- β”‚ β”‚ Book β”‚ β”‚ β”‚
538
- β”‚ β”‚ Match β”‚ β”‚ β”‚
539
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
540
- β”‚ β–² β”‚ β”‚
541
- β”‚ β”‚ REST β”‚ β”‚
542
- β”‚ β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β” β”‚
543
- β”‚ β”‚ DASHBOARD β”‚ β”‚
544
- β”‚ β”‚ (SSE + Kafka Consumer) β”‚ β”‚
545
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
546
- β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
547
- β”‚ β”‚ MD FEEDER (MDF) β”‚β”€β”€β”˜
548
- β”‚ β”‚ Orders + Snapshots β”‚
549
- β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
550
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
551
- ```
552
-
553
- ### 5.5 Message Sequence: New Order to Trade
554
-
555
- ```
556
- FIX Client FIX OEG Kafka Matcher Dashboard
557
- β”‚ β”‚ β”‚ β”‚ β”‚
558
- │──35=D────▢│ β”‚ β”‚ β”‚
559
- β”‚NewOrder β”‚ β”‚ β”‚ β”‚
560
- β”‚ │──JSON────▢│ β”‚ β”‚
561
- β”‚ β”‚ [orders] β”‚ β”‚ β”‚
562
- β”‚ β”‚ │──consume──▢│ β”‚
563
- β”‚ β”‚ β”‚ │──match() β”‚
564
- β”‚ β”‚ │◀──trade────│ β”‚
565
- β”‚ β”‚ β”‚ [trades] β”‚ β”‚
566
- β”‚ β”‚ │──────────────consume───▢│
567
- β”‚ β”‚ β”‚ β”‚ render()
568
- ```
569
-
570
- ---
571
-
572
- ### 5.6 Message Formats
573
-
574
- **Order Message:**
575
- ```json
576
- {
577
- "symbol": "ALPHA",
578
- "side": "BUY",
579
- "price": 25.50,
580
- "quantity": 100,
581
- "cl_ord_id": "MDF-1234567890-1",
582
- "timestamp": 1234567890.123,
583
- "source": "MDF"
584
- }
585
- ```
586
-
587
- **Cancel Message:**
588
- ```json
589
- {
590
- "type": "cancel",
591
- "orig_cl_ord_id": "MDF-1234567890-1",
592
- "symbol": "ALPHA",
593
- "timestamp": 1234567890.456
594
- }
595
- ```
596
-
597
- **Amend Message:**
598
- ```json
599
- {
600
- "type": "amend",
601
- "orig_cl_ord_id": "MDF-1234567890-1",
602
- "cl_ord_id": "amend-1234567890",
603
- "symbol": "ALPHA",
604
- "quantity": 150,
605
- "price": 25.45,
606
- "timestamp": 1234567890.789
607
- }
608
- ```
609
-
610
- **Trade Message:**
611
- ```json
612
- {
613
- "symbol": "ALPHA",
614
- "price": 25.50,
615
- "quantity": 100,
616
- "buy_order_id": "order-123",
617
- "sell_order_id": "order-456",
618
- "timestamp": 1234567890.123
619
- }
620
- ```
621
-
622
- **Snapshot Message:**
623
- ```json
624
- {
625
- "symbol": "ALPHA",
626
- "best_bid": 25.45,
627
- "best_ask": 25.55,
628
- "bid_size": 500,
629
- "ask_size": 300,
630
- "timestamp": 1234567890.123,
631
- "source": "MDF"
632
- }
633
- ```
634
-
635
- ---
636
-
637
- ## 6. Configuration
638
-
639
- ### 6.1 Environment Variables
640
-
641
- | Variable | Default | Description |
642
- |----------|---------|-------------|
643
- | `KAFKA_BOOTSTRAP` | kafka:9092 | Kafka broker address |
644
- | `MATCHER_URL` | http://matcher:6000 | Matcher API endpoint |
645
- | `TICK_SIZE` | 0.05 | Minimum price increment |
646
- | `ORDERS_PER_MIN` | 8 | MDF order generation rate |
647
- | `SECURITIES_FILE` | /app/data/securities.txt | Securities configuration |
648
- | `KAFKA_RETRIES` | 30 | Connection retry attempts |
649
- | `KAFKA_RETRY_DELAY` | 2 | Seconds between retries |
650
-
651
- ### 6.2 Securities Configuration
652
-
653
- File: `shared_data/securities.txt`
654
-
655
- ```
656
- #SYMBOL <start_price> <current_price>
657
- ALPHA 25.00 25.00
658
- PEIR 18.50 18.50
659
- EXAE 42.00 42.00
660
- QUEST 12.75 12.75
661
- ```
662
-
663
- | Symbol | Start Price | Description |
664
- |--------|-------------|-------------|
665
- | ALPHA | 25.00 | Test Security A |
666
- | EXAE | 42.00 | Test Security B |
667
- | PEIR | 18.50 | Test Security C |
668
- | QUEST | 12.75 | Test Security D |
669
-
670
- ### 6.3 Docker Volumes
671
-
672
- | Volume | Path | Purpose |
673
- |--------|------|---------|
674
- | `matcher_data` | /app/data | SQLite database persistence |
675
- | `shared_data` | /app/data | Securities and order ID files |
676
- | `logs` | /app/logs | Snapshot viewer logs |
677
-
678
- ---
679
-
680
- ## 7. Quick Reference
681
-
682
- ### 7.1 Starting the System
683
-
684
- ```bash
685
- # Start all services
686
- docker compose up --build
687
-
688
- # Start in background
689
- docker compose up -d --build
690
-
691
- # View logs
692
- docker compose logs -f dashboard
693
- docker compose logs -f matcher
694
-
695
- # Stop all services
696
- docker compose down
697
-
698
- # Reset matcher database
699
- docker volume rm stockex_matcher_data
700
- ```
701
-
702
- ### 7.2 Dashboard Actions
703
-
704
- | Action | How To |
705
- |--------|--------|
706
- | View order book depth | Select symbol from Order Book dropdown |
707
- | Edit an order | Click Edit button on order row |
708
- | Cancel an order | Click Cancel button on order row |
709
- | Filter trade chart | Select symbol from Trade Chart dropdown |
710
- | Pause ticker tape | Hover mouse over the ticker |
711
- | Select order row | Click on the row |
712
- | Bulk actions | Select row, use header Edit/Cancel buttons |
713
-
714
- ### 7.3 Connection Status
715
-
716
- | Status | Indicator | Meaning |
717
- |--------|-----------|---------|
718
- | Live | ● Green | Connected to SSE stream |
719
- | Connecting | ● Yellow | Establishing connection |
720
- | Disconnected | ● Red | Connection lost, auto-reconnecting |
721
-
722
- ### 7.4 API Endpoints
723
-
724
- **Matcher API (Port 6000):**
725
-
726
- | Endpoint | Method | Description |
727
- |----------|--------|-------------|
728
- | `/orderbook/<symbol>` | GET | Order book for symbol |
729
- | `/trades` | GET | Recent trades list |
730
- | `/health` | GET | Service health status |
731
-
732
- **Dashboard API (Port 5005):**
733
-
734
- | Endpoint | Method | Description |
735
- |----------|--------|-------------|
736
- | `/` | GET | Main dashboard page |
737
- | `/data` | GET | Current state (polling fallback) |
738
- | `/stream` | GET | SSE event stream |
739
- | `/orderbook/<symbol>` | GET | Proxy to matcher |
740
- | `/order/cancel` | POST | Cancel order request |
741
- | `/order/amend` | POST | Amend order request |
742
-
743
- ---
744
-
745
- ## 8. Troubleshooting
746
-
747
- ### Orders/Trades Panel Empty
748
-
749
- 1. Check Kafka connection:
750
- ```bash
751
- docker logs dashboard | grep "Kafka"
752
- ```
753
- 2. Verify MDF is producing:
754
- ```bash
755
- docker logs stockex-md_feeder-1 --tail 10
756
- ```
757
- 3. Restart dashboard:
758
- ```bash
759
- docker restart dashboard
760
- ```
761
-
762
- ### Order Book Empty
763
-
764
- 1. Check matcher is receiving orders:
765
- ```bash
766
- docker logs matcher | grep "received"
767
- ```
768
- 2. Verify matcher Kafka consumer connected:
769
- ```bash
770
- docker logs matcher | grep "consumer connected"
771
- ```
772
- 3. Reset matcher database if corrupted:
773
- ```bash
774
- docker compose down
775
- docker volume rm stockex_matcher_data
776
- docker compose up -d
777
- ```
778
-
779
- ### Connection Status Shows Disconnected
780
-
781
- 1. SSE stream timeout β€” will auto-reconnect after 3 seconds
782
- 2. Check dashboard container is running:
783
- ```bash
784
- docker ps | grep dashboard
785
- ```
786
- 3. Check browser console for errors (F12)
787
-
788
- ### Prices Going Negative or Extreme
789
-
790
- 1. Old bug in MDF β€” update to latest version
791
- 2. Reset securities file:
792
- ```
793
- #SYMBOL <start_price> <current_price>
794
- ALPHA 25.00 25.00
795
- PEIR 18.50 18.50
796
- EXAE 42.00 42.00
797
- QUEST 12.75 12.75
798
- ```
799
- 3. Restart MDF and matcher:
800
- ```bash
801
- docker restart stockex-md_feeder-1 matcher
802
- ```
803
-
804
- ---
805
-
806
- ## Document Information
807
-
808
- - **Version:** 1.0
809
- - **Platform:** StockEx Trading Simulation
810
- - **Inspired by:** Euronext OPTIQ
811
-
812
- ---
813
-
814
- *StockEx v1.0 β€” Trading Simulation Platform*
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
StockEx_User_Guide.html DELETED
@@ -1,428 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8">
5
- <title>StockEx User Guide</title>
6
- <style>
7
- @page { margin: 2cm; }
8
- body { font-family: Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; line-height: 1.6; color: #333; }
9
- h1 { color: #1a1a2e; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; }
10
- h2 { color: #2e7d32; margin-top: 30px; border-bottom: 1px solid #ddd; padding-bottom: 5px; page-break-after: avoid; }
11
- h3 { color: #1565c0; margin-top: 20px; }
12
- h4 { color: #555; margin-top: 15px; }
13
- table { width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 14px; }
14
- th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
15
- th { background: #f5f5f5; font-weight: bold; }
16
- .header { text-align: center; margin-bottom: 30px; }
17
- .subtitle { color: #666; font-size: 18px; }
18
- .section { page-break-inside: avoid; margin-bottom: 25px; }
19
- .highlight { background: #e8f5e9; padding: 15px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #4CAF50; }
20
- .info { background: #e3f2fd; padding: 15px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #2196F3; }
21
- .warning { background: #fff3e0; padding: 15px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #ff9800; }
22
- .green { color: #2e7d32; }
23
- .red { color: #c62828; }
24
- .blue { color: #1565c0; }
25
- .screenshot { text-align: center; margin: 20px 0; }
26
- .screenshot img { max-width: 100%; border: 1px solid #ddd; border-radius: 5px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
27
- hr { border: none; border-top: 1px solid #ddd; margin: 30px 0; }
28
- .footer { text-align: center; color: #666; font-size: 12px; margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; }
29
- .toc { background: #fafafa; padding: 20px; border-radius: 5px; margin: 20px 0; }
30
- .toc ul { margin: 0; padding-left: 20px; }
31
- .toc li { margin: 5px 0; }
32
- .arch-diagram { background: #f5f5f5; padding: 20px; border-radius: 5px; font-family: monospace; white-space: pre; font-size: 12px; overflow-x: auto; }
33
- code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: monospace; }
34
- .module-box { border: 1px solid #ddd; border-radius: 8px; padding: 15px; margin: 15px 0; background: #fafafa; }
35
- .module-box h4 { margin-top: 0; color: #1a1a2e; }
36
- .port { display: inline-block; background: #e3f2fd; padding: 2px 8px; border-radius: 3px; font-family: monospace; font-size: 12px; }
37
- </style>
38
- </head>
39
- <body>
40
-
41
- <div class="header">
42
- <h1>StockEx Trading Dashboard</h1>
43
- <p class="subtitle">Complete User & Technical Guide v1.0</p>
44
- </div>
45
-
46
- <div class="toc">
47
- <strong>Table of Contents</strong>
48
- <ul>
49
- <li>1. Introduction</li>
50
- <li>2. System Architecture</li>
51
- <li>3. Module Descriptions</li>
52
- <li>4. Dashboard User Interface</li>
53
- <li>5. Data Flow & Messaging</li>
54
- <li>6. Configuration</li>
55
- <li>7. Quick Reference</li>
56
- </ul>
57
- </div>
58
-
59
- <hr>
60
-
61
- <div class="section">
62
- <h2>1. Introduction</h2>
63
- <p><strong>StockEx</strong> is a comprehensive real-time trading simulation platform inspired by Euronext OPTIQ. It provides a complete electronic trading ecosystem including order entry, matching engine, market data distribution, and live visualization.</p>
64
-
65
- <div class="highlight">
66
- <strong>Key Features:</strong>
67
- <ul>
68
- <li>FIX 4.4 protocol support for institutional order entry</li>
69
- <li>Real-time order matching with price-time priority</li>
70
- <li>Live market data streaming via Kafka</li>
71
- <li>Web-based dashboard with Server-Sent Events (SSE)</li>
72
- <li>Order management (Edit/Cancel) capabilities</li>
73
- <li>Trading analytics and statistics</li>
74
- </ul>
75
- </div>
76
-
77
- <div class="info">
78
- <strong>Access URLs:</strong><br>
79
- Dashboard: <code>http://localhost:5005</code><br>
80
- Frontend (Order Entry): <code>http://localhost:5000</code><br>
81
- FIX Gateway: <code>localhost:5001</code><br>
82
- Matcher API: <code>http://localhost:6000</code>
83
- </div>
84
- </div>
85
-
86
- <div class="screenshot">
87
- <img src="screenshots/dashboard.png" alt="Trading Dashboard">
88
- <p><em>StockEx Trading Dashboard - Real-time Market View</em></p>
89
- </div>
90
-
91
- <div class="section">
92
- <h2>2. System Architecture</h2>
93
-
94
- <div class="arch-diagram">
95
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
96
- β”‚ FIX UI Client β”‚ β”‚ FIX UI Client β”‚ β”‚ Frontend β”‚
97
- β”‚ (Port 5002) β”‚ β”‚ (Port 5003) β”‚ β”‚ (Port 5000) β”‚
98
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
99
- β”‚ β”‚ β”‚
100
- β”‚ FIX 4.4 β”‚ FIX 4.4 β”‚ HTTP
101
- β–Ό β–Ό β–Ό
102
- β”Œβ”€οΏ½οΏ½β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
103
- β”‚ FIX OEG (Port 5001) β”‚
104
- β”‚ QuickFIX Order Entry Gateway β”‚
105
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
106
- β”‚
107
- β–Ό Kafka [orders]
108
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
109
- β”‚ Apache Kafka (Port 9092) β”‚
110
- β”‚ Topics: orders, trades, snapshots β”‚
111
- β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
112
- β”‚ β”‚ β”‚
113
- β–Ό β–Ό β–Ό
114
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
115
- β”‚ Matcher β”‚ β”‚ MD Feeder β”‚ β”‚ Dashboard β”‚
116
- β”‚ (Port 6000) β”‚ β”‚ (MDF) β”‚ β”‚ (Port 5005) β”‚
117
- β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
118
- β”‚ Order Book β”‚ β”‚ Price Sim β”‚ β”‚ Web UI + SSE β”‚
119
- β”‚ Trade Match β”‚ β”‚ BBO Publish β”‚ β”‚ Real-time β”‚
120
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
121
- </div>
122
- </div>
123
-
124
- <div class="section">
125
- <h2>3. Module Descriptions</h2>
126
-
127
- <div class="module-box">
128
- <h4>Zookeeper</h4>
129
- <p><span class="port">Port 2181</span></p>
130
- <p>Apache Zookeeper provides distributed coordination for the Kafka cluster. It manages broker metadata, topic configurations, and cluster membership.</p>
131
- <table>
132
- <tr><th>Function</th><td>Kafka cluster coordination</td></tr>
133
- <tr><th>Technology</th><td>Confluent Zookeeper 7.5.0</td></tr>
134
- <tr><th>Dependencies</th><td>None</td></tr>
135
- </table>
136
- </div>
137
-
138
- <div class="module-box">
139
- <h4>Kafka Message Broker</h4>
140
- <p><span class="port">Port 9092</span> <span class="port">Port 29092 (host)</span></p>
141
- <p>Apache Kafka serves as the central message backbone for the entire system. All order flow, trade executions, and market data are distributed through Kafka topics.</p>
142
- <table>
143
- <tr><th>Function</th><td>Message streaming and event distribution</td></tr>
144
- <tr><th>Technology</th><td>Confluent Kafka 7.5.0</td></tr>
145
- <tr><th>Topics</th><td><code>orders</code>, <code>trades</code>, <code>snapshots</code></td></tr>
146
- <tr><th>Dependencies</th><td>Zookeeper</td></tr>
147
- </table>
148
- </div>
149
-
150
- <div class="module-box">
151
- <h4>FIX Order Entry Gateway (FIX OEG)</h4>
152
- <p><span class="port">Port 5001</span></p>
153
- <p>The FIX OEG is a QuickFIX/Python acceptor that receives orders via FIX 4.4 protocol from institutional clients. It validates incoming messages, normalizes them to JSON format, and publishes to the Kafka <code>orders</code> topic.</p>
154
- <table>
155
- <tr><th>Function</th><td>FIX protocol order reception and normalization</td></tr>
156
- <tr><th>Technology</th><td>QuickFIX/Python</td></tr>
157
- <tr><th>Protocol</th><td>FIX 4.4</td></tr>
158
- <tr><th>Message Types</th><td>NewOrderSingle (D), OrderCancelRequest (F), OrderCancelReplaceRequest (G)</td></tr>
159
- <tr><th>Output</th><td>Kafka <code>orders</code> topic</td></tr>
160
- </table>
161
- </div>
162
-
163
- <div class="module-box">
164
- <h4>FIX UI Clients</h4>
165
- <p><span class="port">Port 5002 (Client 1)</span> <span class="port">Port 5003 (Client 2)</span></p>
166
- <p>Web-based FIX initiator clients that connect to the FIX OEG. They provide a user interface for submitting orders via FIX protocol, simulating institutional trading terminals.</p>
167
- <table>
168
- <tr><th>Function</th><td>FIX order submission interface</td></tr>
169
- <tr><th>Technology</th><td>QuickFIX/Python + Flask</td></tr>
170
- <tr><th>Features</th><td>New Order, Cancel, Amend</td></tr>
171
- <tr><th>Connection</th><td>FIX 4.4 to FIX OEG</td></tr>
172
- </table>
173
- </div>
174
-
175
- <div class="module-box">
176
- <h4>Matcher (Order Matching Engine)</h4>
177
- <p><span class="port">Port 6000</span></p>
178
- <p>The core matching engine that maintains order books for all securities. It consumes orders from Kafka, attempts to match them using price-time priority, and publishes resulting trades. Provides REST API for order book and trade queries.</p>
179
- <table>
180
- <tr><th>Function</th><td>Order matching, trade execution, order book management</td></tr>
181
- <tr><th>Technology</th><td>Python/Flask with SQLite persistence</td></tr>
182
- <tr><th>Algorithm</th><td>Price-Time Priority (FIFO)</td></tr>
183
- <tr><th>Input</th><td>Kafka <code>orders</code> topic</td></tr>
184
- <tr><th>Output</th><td>Kafka <code>trades</code> topic</td></tr>
185
- <tr><th>API Endpoints</th><td><code>/orderbook/&lt;symbol&gt;</code>, <code>/trades</code>, <code>/health</code></td></tr>
186
- </table>
187
- </div>
188
-
189
- <div class="module-box">
190
- <h4>Market Data Feeder (MDF)</h4>
191
- <p><span class="port">Internal</span></p>
192
- <p>Simulates market data by generating random orders and publishing Best Bid/Offer (BBO) snapshots. Creates realistic market activity with configurable order rates and price movements.</p>
193
- <table>
194
- <tr><th>Function</th><td>Market simulation and BBO publishing</td></tr>
195
- <tr><th>Technology</th><td>Python</td></tr>
196
- <tr><th>Output</th><td>Kafka <code>orders</code> and <code>snapshots</code> topics</td></tr>
197
- <tr><th>Order Mix</th><td>90% passive (book building), 10% aggressive (trades)</td></tr>
198
- <tr><th>Rate</th><td>Configurable (default: 8 orders/minute)</td></tr>
199
- </table>
200
- </div>
201
-
202
- <div class="module-box">
203
- <h4>Dashboard</h4>
204
- <p><span class="port">Port 5005</span></p>
205
- <p>Real-time web dashboard providing comprehensive market visualization. Uses Server-Sent Events (SSE) for live streaming updates without page refresh. Displays orders, trades, market snapshots, order book depth, and trading statistics.</p>
206
- <table>
207
- <tr><th>Function</th><td>Real-time market visualization and order management</td></tr>
208
- <tr><th>Technology</th><td>Python/Flask + JavaScript</td></tr>
209
- <tr><th>Streaming</th><td>Server-Sent Events (SSE)</td></tr>
210
- <tr><th>Input</th><td>Kafka topics + Matcher REST API</td></tr>
211
- <tr><th>Features</th><td>Orders, Trades, BBO, Order Book, Charts, Statistics</td></tr>
212
- </table>
213
- </div>
214
-
215
- <div class="module-box">
216
- <h4>Frontend (Manual Order Entry)</h4>
217
- <p><span class="port">Port 5000</span></p>
218
- <p>Simple web interface for manual order submission. Allows users to enter orders directly without FIX protocol, useful for testing and demonstration.</p>
219
- <table>
220
- <tr><th>Function</th><td>Manual order entry interface</td></tr>
221
- <tr><th>Technology</th><td>Python/Flask</td></tr>
222
- <tr><th>Output</th><td>Kafka <code>orders</code> topic</td></tr>
223
- </table>
224
- </div>
225
-
226
- <div class="module-box">
227
- <h4>Consumer (Debug)</h4>
228
- <p><span class="port">Internal</span></p>
229
- <p>Debug utility that consumes and logs messages from Kafka topics. Useful for monitoring message flow and troubleshooting.</p>
230
- </div>
231
-
232
- <div class="module-box">
233
- <h4>Snapshot Viewer</h4>
234
- <p><span class="port">Internal</span></p>
235
- <p>Utility service that subscribes to the <code>snapshots</code> topic and logs BBO updates. Writes to log files for analysis.</p>
236
- </div>
237
- </div>
238
-
239
- <div class="section">
240
- <h2>4. Dashboard User Interface</h2>
241
-
242
- <h3>4.1 Orders Panel</h3>
243
- <p>Displays real-time incoming orders with full management capabilities.</p>
244
- <table>
245
- <tr><th>Column</th><th>Description</th></tr>
246
- <tr><td>Symbol</td><td>Stock ticker (ALPHA, EXAE, PEIR, QUEST)</td></tr>
247
- <tr><td>Side</td><td><span class="green">BUY</span> (green) or <span class="red">SELL</span> (red)</td></tr>
248
- <tr><td>Qty</td><td>Order quantity in shares</td></tr>
249
- <tr><td>Price</td><td>Limit price</td></tr>
250
- <tr><td>Source</td><td>Order origin (MDF, FIX, Manual)</td></tr>
251
- <tr><td>Time</td><td>Order timestamp</td></tr>
252
- <tr><td>Actions</td><td><span class="blue">Edit</span> / <span class="red">Cancel</span> buttons</td></tr>
253
- </table>
254
- <p><strong>Row Selection:</strong> Click any row to select, then use header buttons for bulk actions.</p>
255
-
256
- <h3>4.2 Market Snapshot</h3>
257
- <p>Shows Best Bid/Offer (BBO) for all securities with real-time updates.</p>
258
- <table>
259
- <tr><th>Column</th><th>Description</th></tr>
260
- <tr><td>Symbol</td><td>Security identifier</td></tr>
261
- <tr><td>Best Bid</td><td>Highest buy price <span class="green">(green)</span></td></tr>
262
- <tr><td>Best Ask</td><td>Lowest sell price <span class="red">(red)</span></td></tr>
263
- <tr><td>Spread</td><td>Ask - Bid difference</td></tr>
264
- <tr><td>Mid</td><td>Midpoint: (Bid + Ask) / 2</td></tr>
265
- <tr><td>Updated</td><td>Last update timestamp</td></tr>
266
- </table>
267
- <p><strong>Ticker Tape:</strong> Scrolling bar at bottom displays recent trades with price direction (β–² green up, β–Ό red down).</p>
268
-
269
- <h3>4.3 Trades Panel</h3>
270
- <p>Lists all executed trades with calculated values.</p>
271
- <table>
272
- <tr><th>Column</th><th>Description</th></tr>
273
- <tr><td>Symbol</td><td>Traded security</td></tr>
274
- <tr><td>Qty</td><td>Executed quantity</td></tr>
275
- <tr><td>Price</td><td>Execution price</td></tr>
276
- <tr><td>Value</td><td>Trade value (Qty Γ— Price)</td></tr>
277
- <tr><td>Time</td><td>Execution timestamp</td></tr>
278
- </table>
279
-
280
- <h3>4.4 Order Book</h3>
281
- <p>Displays full market depth for selected symbol.</p>
282
- <ul>
283
- <li>Select symbol from dropdown menu</li>
284
- <li>Click <strong>Refresh</strong> to update (auto-refreshes every 3 seconds)</li>
285
- <li><span class="green">Bid Qty / Bid Price</span> β€” Buy orders sorted by price (highest first)</li>
286
- <li><span class="red">Ask Price / Ask Qty</span> β€” Sell orders sorted by price (lowest first)</li>
287
- <li>Header shows total bid and ask counts</li>
288
- </ul>
289
-
290
- <h3>4.5 Trade Chart</h3>
291
- <p>Visual representation of trade activity over time.</p>
292
- <ul>
293
- <li><span class="green">Green line</span> β€” Price trend connecting trade prices</li>
294
- <li><span class="blue">Blue bars</span> β€” Volume per trade</li>
295
- <li>Dropdown to filter by symbol or view all</li>
296
- <li>Displays last 100 trades</li>
297
- </ul>
298
-
299
- <h3>4.6 Trading Statistics</h3>
300
- <p>Aggregated metrics calculated from all trades.</p>
301
- <table>
302
- <tr><th>Metric</th><th>Description</th></tr>
303
- <tr><td>Trades</td><td>Number of executed trades</td></tr>
304
- <tr><td>Volume</td><td>Total shares traded</td></tr>
305
- <tr><td>Value</td><td>Total monetary value (Ξ£ Qty Γ— Price)</td></tr>
306
- <tr><td>Start</td><td>First trade price (opening)</td></tr>
307
- <tr><td>Last</td><td>Most recent trade price</td></tr>
308
- <tr><td>VWAP</td><td>Volume-Weighted Average Price</td></tr>
309
- </table>
310
- <p><strong>Bar Charts:</strong> Visual comparison showing <span class="green">Volume</span> and <span class="blue">Value</span> per symbol side by side.</p>
311
- </div>
312
-
313
- <div class="section">
314
- <h2>5. Data Flow & Messaging</h2>
315
-
316
- <h3>5.1 Kafka Topics</h3>
317
- <table>
318
- <tr><th>Topic</th><th>Producers</th><th>Consumers</th><th>Content</th></tr>
319
- <tr><td><code>orders</code></td><td>FIX OEG, MDF, Frontend</td><td>Matcher, Dashboard</td><td>New orders, cancels, amends</td></tr>
320
- <tr><td><code>trades</code></td><td>Matcher</td><td>Dashboard, Consumer</td><td>Executed trades</td></tr>
321
- <tr><td><code>snapshots</code></td><td>MDF</td><td>Dashboard, Snapshot Viewer</td><td>BBO updates</td></tr>
322
- </table>
323
-
324
- <h3>5.2 Message Formats</h3>
325
-
326
- <h4>Order Message</h4>
327
- <div class="arch-diagram" style="font-size: 11px;">{
328
- "symbol": "ALPHA",
329
- "side": "BUY",
330
- "price": 25.50,
331
- "quantity": 100,
332
- "cl_ord_id": "MDF-1234567890-1",
333
- "timestamp": 1234567890.123,
334
- "source": "MDF"
335
- }</div>
336
-
337
- <h4>Trade Message</h4>
338
- <div class="arch-diagram" style="font-size: 11px;">{
339
- "symbol": "ALPHA",
340
- "price": 25.50,
341
- "quantity": 100,
342
- "buy_order_id": "order-123",
343
- "sell_order_id": "order-456",
344
- "timestamp": 1234567890.123
345
- }</div>
346
-
347
- <h4>Snapshot Message</h4>
348
- <div class="arch-diagram" style="font-size: 11px;">{
349
- "symbol": "ALPHA",
350
- "best_bid": 25.45,
351
- "best_ask": 25.55,
352
- "bid_size": 500,
353
- "ask_size": 300,
354
- "timestamp": 1234567890.123,
355
- "source": "MDF"
356
- }</div>
357
- </div>
358
-
359
- <div class="section">
360
- <h2>6. Configuration</h2>
361
-
362
- <table>
363
- <tr><th>Variable</th><th>Default</th><th>Description</th></tr>
364
- <tr><td><code>KAFKA_BOOTSTRAP</code></td><td>kafka:9092</td><td>Kafka broker address</td></tr>
365
- <tr><td><code>MATCHER_URL</code></td><td>http://matcher:6000</td><td>Matcher API endpoint</td></tr>
366
- <tr><td><code>TICK_SIZE</code></td><td>0.05</td><td>Minimum price increment</td></tr>
367
- <tr><td><code>ORDERS_PER_MIN</code></td><td>8</td><td>MDF order generation rate</td></tr>
368
- <tr><td><code>SECURITIES_FILE</code></td><td>/app/data/securities.txt</td><td>Securities configuration</td></tr>
369
- </table>
370
-
371
- <h3>6.1 Supported Securities</h3>
372
- <table>
373
- <tr><th>Symbol</th><th>Start Price</th><th>Description</th></tr>
374
- <tr><td>ALPHA</td><td>25.00</td><td>Test Security A</td></tr>
375
- <tr><td>EXAE</td><td>42.00</td><td>Test Security B</td></tr>
376
- <tr><td>PEIR</td><td>18.50</td><td>Test Security C</td></tr>
377
- <tr><td>QUEST</td><td>12.75</td><td>Test Security D</td></tr>
378
- </table>
379
- </div>
380
-
381
- <div class="section">
382
- <h2>7. Quick Reference</h2>
383
-
384
- <h3>7.1 Starting the System</h3>
385
- <div class="highlight">
386
- <code>docker compose up --build</code>
387
- </div>
388
-
389
- <h3>7.2 Service URLs</h3>
390
- <table>
391
- <tr><th>Service</th><th>URL</th><th>Purpose</th></tr>
392
- <tr><td>Dashboard</td><td>http://localhost:5005</td><td>Main trading view</td></tr>
393
- <tr><td>Frontend</td><td>http://localhost:5000</td><td>Manual order entry</td></tr>
394
- <tr><td>FIX Client 1</td><td>http://localhost:5002</td><td>FIX order submission</td></tr>
395
- <tr><td>FIX Client 2</td><td>http://localhost:5003</td><td>FIX order submission</td></tr>
396
- <tr><td>Matcher API</td><td>http://localhost:6000</td><td>REST API</td></tr>
397
- </table>
398
-
399
- <h3>7.3 Dashboard Actions</h3>
400
- <table>
401
- <tr><th>Action</th><th>How To</th></tr>
402
- <tr><td>View order book depth</td><td>Select symbol from Order Book dropdown</td></tr>
403
- <tr><td>Edit an order</td><td>Click <span class="blue">Edit</span> button on order row</td></tr>
404
- <tr><td>Cancel an order</td><td>Click <span class="red">Cancel</span> button on order row</td></tr>
405
- <tr><td>Filter trade chart</td><td>Select symbol from Trade Chart dropdown</td></tr>
406
- <tr><td>Pause ticker tape</td><td>Hover mouse over the ticker</td></tr>
407
- <tr><td>Select multiple orders</td><td>Click rows, use header Edit/Cancel buttons</td></tr>
408
- </table>
409
-
410
- <h3>7.4 Connection Status Indicators</h3>
411
- <table>
412
- <tr><th>Status</th><th>Indicator</th><th>Meaning</th></tr>
413
- <tr><td>Live</td><td><span class="green">● Green</span></td><td>Connected to real-time stream</td></tr>
414
- <tr><td>Connecting</td><td><span style="color:#ffc107;">● Yellow</span></td><td>Establishing connection</td></tr>
415
- <tr><td>Disconnected</td><td><span class="red">● Red</span></td><td>Connection lost, auto-reconnecting</td></tr>
416
- </table>
417
- </div>
418
-
419
- <hr>
420
-
421
- <div class="footer">
422
- <p><strong>StockEx v1.0</strong> β€” Trading Simulation Platform</p>
423
- <p>Inspired by Euronext OPTIQ</p>
424
- <p style="margin-top: 15px; font-size: 11px;">To create PDF: Open in browser β†’ Print (Ctrl+P) β†’ Save as PDF</p>
425
- </div>
426
-
427
- </body>
428
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dashboard/templates/index - Copy (6).html DELETED
@@ -1,198 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8"/>
5
- <title>Trading Dashboard</title>
6
- <style>
7
- body { font-family: Arial, sans-serif; margin: 20px; background: #f7f7f7; }
8
- h2 { margin: 5px 0; }
9
- .container { display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px; }
10
- .panel {
11
- background: #fff;
12
- border-radius: 8px;
13
- padding: 10px;
14
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
15
- height: 400px;
16
- display: flex;
17
- flex-direction: column;
18
- }
19
- .panel pre {
20
- flex-grow: 1;
21
- overflow-y: scroll;
22
- background: #fafafa;
23
- padding: 10px;
24
- margin: 0;
25
- border-radius: 6px;
26
- font-size: 13px;
27
- white-space: pre;
28
- font-family: monospace;
29
- }
30
- table {
31
- width: 100%;
32
- border-collapse: collapse;
33
- font-size: 13px;
34
- }
35
- th, td {
36
- border: 1px solid #ccc;
37
- padding: 4px;
38
- text-align: center;
39
- }
40
- .updated {
41
- animation: flash 1s ease-in-out;
42
- }
43
- @keyframes flash {
44
- from { background: yellow; }
45
- to { background: transparent; }
46
- }
47
- </style>
48
- </head>
49
- <body>
50
- <h1>πŸ“Š Trading Dashboard</h1>
51
- <div class="container">
52
- <div class="panel">
53
- <h2>πŸ“ Orders</h2>
54
- <pre id="orders"></pre>
55
- </div>
56
-
57
- <div class="panel">
58
- <h2>πŸ’Ή BBOs (from Order Book API)</h2>
59
- <pre id="book"></pre>
60
- </div>
61
-
62
- <div class="panel">
63
- <h2>🀝 Trades</h2>
64
- <pre id="trades"></pre>
65
- </div>
66
-
67
- <div class="panel">
68
- <h2>πŸ“– Full Order Book</h2>
69
- <label for="symbol-select">Select symbol:</label>
70
- <select id="symbol-select" onchange="refresh()"></select>
71
- <div id="full-book" style="flex-grow:1; overflow-y:scroll;"></div>
72
- </div>
73
-
74
- <div class="panel">
75
- <h2>πŸ“Š Market Snapshots (BBO)</h2>
76
- <table id="bbos-table">
77
- <thead>
78
- <tr>
79
- <th>Symbol</th>
80
- <th>Best Bid</th>
81
- <th>Best Ask</th>
82
- <th>Timestamp</th>
83
- <th>Source</th>
84
- </tr>
85
- </thead>
86
- <tbody id="bbos-body"></tbody>
87
- </table>
88
- </div>
89
- </div>
90
-
91
- <script>
92
- function fmtOrder(o) {
93
- const sym = (o.symbol ?? "?").padEnd(6);
94
- const side = (o.type ?? o.side ?? "?").padEnd(6);
95
- const qty = String(o.quantity ?? o.qty ?? "?").padStart(6);
96
- const price = o.price !== undefined ? Number(o.price).toFixed(2).padStart(8) : " - ";
97
- const src = (o.source ?? "?").padEnd(5);
98
- const ts = o.timestamp ? new Date(o.timestamp * 1000).toLocaleTimeString() : "-";
99
- return `${sym} | ${side} | ${qty} @ ${price} | ${src} | ${ts}\n`;
100
- }
101
-
102
- function fmtTrade(t) {
103
- const sym = (t.symbol ?? "?").padEnd(6);
104
- const qty = String(t.quantity ?? t.qty ?? "-").padStart(6);
105
- const price = t.price !== undefined ? Number(t.price).toFixed(2).padStart(8) : " - ";
106
- const ts = t.timestamp ? new Date(t.timestamp * 1000).toLocaleTimeString() : "-";
107
- return `${sym} | ${qty} x ${price} | ${ts}\n`;
108
- }
109
-
110
- function renderOrderBook(book) {
111
- const bids = (book.buy || []).sort((a,b) => b.price - a.price);
112
- const asks = (book.sell || []).sort((a,b) => a.price - b.price);
113
- const maxRows = Math.max(bids.length, asks.length);
114
- let html = "<table><tr><th>Bid Size</th><th>Bid Price</th><th>Ask Price</th><th>Ask Size</th></tr>";
115
- for (let i=0; i<maxRows; i++) {
116
- const b = bids[i] || {};
117
- const a = asks[i] || {};
118
- html += `<tr>
119
- <td>${b.quantity ?? ""}</td>
120
- <td>${b.price !== undefined ? Number(b.price).toFixed(2) : ""}</td>
121
- <td>${a.price !== undefined ? Number(a.price).toFixed(2) : ""}</td>
122
- <td>${a.quantity ?? ""}</td>
123
- </tr>`;
124
- }
125
- html += "</table>";
126
- return html;
127
- }
128
-
129
- async function refresh() {
130
- try {
131
- const r = await fetch("/data");
132
- const data = await r.json();
133
-
134
- // Orders
135
- document.getElementById("orders").textContent =
136
- data.orders.slice().reverse().map(fmtOrder).join("");
137
-
138
- // Trades
139
- document.getElementById("trades").textContent =
140
- data.trades.slice().reverse().map(fmtTrade).join("");
141
-
142
- // Book JSON dump
143
- document.getElementById("book").textContent =
144
- JSON.stringify(data.book ?? {}, null, 2);
145
-
146
- // Snapshots β†’ fill table
147
- const tbody = document.getElementById("bbos-body");
148
- tbody.innerHTML = "";
149
- for (const [symbol, snap] of Object.entries(data.bbos)) {
150
- const row = document.createElement("tr");
151
- row.innerHTML = `
152
- <td>${symbol}</td>
153
- <td>${snap.best_bid !== null ? Number(snap.best_bid).toFixed(2) : "-"}</td>
154
- <td>${snap.best_ask !== null ? Number(snap.best_ask).toFixed(2) : "-"}</td>
155
- <td>${snap.timestamp ? new Date(snap.timestamp*1000).toLocaleTimeString() : "-"}</td>
156
- <td>${snap.source ?? "-"}</td>
157
- `;
158
- row.classList.add("updated");
159
- setTimeout(() => row.classList.remove("updated"), 1000);
160
- tbody.appendChild(row);
161
- }
162
-
163
- // Populate dropdown once
164
- const sel = document.getElementById("symbol-select");
165
- if (!sel.options.length) {
166
- const symbols = [...new Set([
167
- ...Object.keys(data.bbos),
168
- ...(data.book?.buy || []).map(o => o.symbol),
169
- ...(data.book?.sell || []).map(o => o.symbol)
170
- ])];
171
- symbols.forEach(sym => {
172
- const opt = document.createElement("option");
173
- opt.value = sym;
174
- opt.textContent = sym;
175
- sel.appendChild(opt);
176
- });
177
- }
178
-
179
- // Render order book for selected symbol
180
- const sym = sel.value;
181
- if (sym) {
182
- const bookForSymbol = {
183
- buy: (data.book?.buy || []).filter(o => o.symbol === sym),
184
- sell: (data.book?.sell || []).filter(o => o.symbol === sym),
185
- };
186
- document.getElementById("full-book").innerHTML = renderOrderBook(bookForSymbol);
187
- }
188
-
189
- } catch(e) {
190
- console.error("Refresh error", e);
191
- }
192
- }
193
-
194
- setInterval(refresh, 2000);
195
- refresh();
196
- </script>
197
- </body>
198
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dashboard/templates/index_Matcher.html DELETED
@@ -1,194 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8"/>
5
- <title>Trading Dashboard</title>
6
- <style>
7
- body { font-family: Arial, sans-serif; margin: 20px; background: #f7f7f7; }
8
- h2 { margin: 5px 0; }
9
- .container { display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px; }
10
- .panel {
11
- background: #fff;
12
- border-radius: 8px;
13
- padding: 10px;
14
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
15
- height: 400px;
16
- display: flex;
17
- flex-direction: column;
18
- }
19
- .panel pre {
20
- flex-grow: 1;
21
- overflow-y: scroll;
22
- background: #fafafa;
23
- padding: 10px;
24
- margin: 0;
25
- border-radius: 6px;
26
- font-size: 13px;
27
- white-space: pre;
28
- font-family: monospace;
29
- }
30
- table {
31
- width: 100%;
32
- border-collapse: collapse;
33
- font-size: 13px;
34
- }
35
- th, td {
36
- border: 1px solid #ccc;
37
- padding: 4px;
38
- text-align: center;
39
- }
40
- .updated {
41
- animation: flash 1s ease-in-out;
42
- }
43
- @keyframes flash {
44
- from { background: yellow; }
45
- to { background: transparent; }
46
- }
47
- </style>
48
- </head>
49
- <body>
50
- <h1>πŸ“Š Trading Dashboard</h1>
51
- <div class="container">
52
- <div class="panel">
53
- <h2>πŸ“ Orders</h2>
54
- <pre id="orders"></pre>
55
- </div>
56
-
57
- <div class="panel">
58
- <h2>πŸ’Ή BBOs (from Order Book API)</h2>
59
- <pre id="book"></pre>
60
- </div>
61
-
62
- <div class="panel">
63
- <h2>🀝 Trades</h2>
64
- <pre id="trades"></pre>
65
- </div>
66
-
67
- <div class="panel">
68
- <h2>πŸ“– Full Order Book</h2>
69
- <label for="symbol-select">Select symbol:</label>
70
- <select id="symbol-select" onchange="refresh()"></select>
71
- <div id="full-book" style="flex-grow:1; overflow-y:scroll;"></div>
72
- </div>
73
-
74
- <div class="panel">
75
- <h2>πŸ“Š Market Snapshots (BBO)</h2>
76
- <table id="bbos-table">
77
- <thead>
78
- <tr>
79
- <th>Symbol</th>
80
- <th>Best Bid</th>
81
- <th>Best Ask</th>
82
- <th>Timestamp</th>
83
- </tr>
84
- </thead>
85
- <tbody id="bbos-body"></tbody>
86
- </table>
87
- </div>
88
- </div>
89
-
90
- <script>
91
- function fmtOrder(o) {
92
- const sym = o.symbol ?? "?";
93
- const side = o.type ?? o.side ?? "?";
94
- const price = o.price !== undefined ? Number(o.price).toFixed(2) : "?";
95
- const qty = o.quantity ?? o.qty ?? "?";
96
- return `${sym} | ${side} | ${qty} @ ${price}\n`;
97
- }
98
-
99
- function fmtTrade(t) {
100
- const sym = (t.symbol ?? "?").padEnd(6);
101
- const qty = String(t.quantity ?? t.qty ?? "-").padStart(6);
102
- const price = t.price !== undefined ? Number(t.price).toFixed(2).padStart(8) : " - ";
103
- const ts = t.timestamp ? new Date(t.timestamp * 1000).toLocaleTimeString() : "-";
104
- return `${sym} | ${qty} x ${price} | ${ts}\n`;
105
- }
106
-
107
- function renderOrderBook(book) {
108
- const bids = (book.buy || []).sort((a,b) => b.price - a.price);
109
- const asks = (book.sell || []).sort((a,b) => a.price - b.price);
110
- const maxRows = Math.max(bids.length, asks.length);
111
- let html = "<table><tr><th>Bid Size</th><th>Bid Price</th><th>Ask Price</th><th>Ask Size</th></tr>";
112
- for (let i=0; i<maxRows; i++) {
113
- const b = bids[i] || {};
114
- const a = asks[i] || {};
115
- html += `<tr>
116
- <td>${b.quantity ?? ""}</td>
117
- <td>${b.price !== undefined ? Number(b.price).toFixed(2) : ""}</td>
118
- <td>${a.price !== undefined ? Number(a.price).toFixed(2) : ""}</td>
119
- <td>${a.quantity ?? ""}</td>
120
- </tr>`;
121
- }
122
- html += "</table>";
123
- return html;
124
- }
125
-
126
- async function refresh() {
127
- try {
128
- const r = await fetch("/data");
129
- const data = await r.json();
130
-
131
- // Orders
132
- document.getElementById("orders").textContent =
133
- data.orders.slice().reverse().map(fmtOrder).join("");
134
-
135
- // Trades
136
- document.getElementById("trades").textContent =
137
- data.trades.slice().reverse().map(fmtTrade).join("");
138
-
139
- // Book JSON dump
140
- document.getElementById("book").textContent =
141
- JSON.stringify(data.book, null, 2);
142
-
143
- // Snapshots β†’ fill table
144
- const tbody = document.getElementById("bbos-body");
145
- tbody.innerHTML = "";
146
- for (const [symbol, snap] of Object.entries(data.bbos)) {
147
- const row = document.createElement("tr");
148
- row.innerHTML = `
149
- <td>${symbol}</td>
150
- <td>${snap.best_bid !== null ? Number(snap.best_bid).toFixed(2) : "-"}</td>
151
- <td>${snap.best_ask !== null ? Number(snap.best_ask).toFixed(2) : "-"}</td>
152
- <td>${snap.timestamp ? new Date(snap.timestamp*1000).toLocaleTimeString() : "-"}</td>
153
- `;
154
- row.classList.add("updated");
155
- setTimeout(() => row.classList.remove("updated"), 1000);
156
- tbody.appendChild(row);
157
- }
158
-
159
- // Populate dropdown once
160
- const sel = document.getElementById("symbol-select");
161
- if (!sel.options.length) {
162
- const symbols = [...new Set([
163
- ...Object.keys(data.bbos),
164
- ...(data.book.buy || []).map(o => o.symbol),
165
- ...(data.book.sell || []).map(o => o.symbol)
166
- ])];
167
- symbols.forEach(sym => {
168
- const opt = document.createElement("option");
169
- opt.value = sym;
170
- opt.textContent = sym;
171
- sel.appendChild(opt);
172
- });
173
- }
174
-
175
- // Render order book for selected symbol
176
- const sym = sel.value;
177
- if (sym) {
178
- const bookForSymbol = {
179
- buy: (data.book.buy || []).filter(o => o.symbol === sym),
180
- sell: (data.book.sell || []).filter(o => o.symbol === sym),
181
- };
182
- document.getElementById("full-book").innerHTML = renderOrderBook(bookForSymbol);
183
- }
184
-
185
- } catch(e) {
186
- console.error("Refresh error", e);
187
- }
188
- }
189
-
190
- setInterval(refresh, 2000);
191
- refresh();
192
- </script>
193
- </body>
194
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
matcher/matcher - Copy.py DELETED
@@ -1,135 +0,0 @@
1
- ο»Ώimport threading, time, json
2
- from flask import Flask, jsonify
3
- from kafka import KafkaConsumer, KafkaProducer
4
- from kafka.errors import NoBrokersAvailable
5
-
6
- BOOTSTRAP = 'kafka:9092'
7
-
8
- import json, sys, datetime
9
- def jlog(event_type, payload):
10
- out = {
11
- "ts": datetime.datetime.utcnow().isoformat() + "Z",
12
- "component": "matcher",
13
- "event": event_type,
14
- "payload": payload
15
- }
16
- sys.stdout.write(json.dumps(out, default=str) + "\n")
17
- sys.stdout.flush()
18
-
19
- app = Flask(__name__)
20
-
21
- order_book = {'buy': [], 'sell': []}
22
- trades = []
23
- producer = None
24
-
25
- def create_kafka_producer(retries=20, delay=2):
26
- global producer
27
- for i in range(retries):
28
- try:
29
- producer = KafkaProducer(
30
- bootstrap_servers=BOOTSTRAP,
31
- value_serializer=lambda v: json.dumps(v).encode('utf-8')
32
- )
33
- print('Producer connected')
34
- return
35
- except NoBrokersAvailable:
36
- print('Producer: Kafka not ready, retry', i)
37
- time.sleep(delay)
38
- raise RuntimeError('Producer: cannot connect to Kafka')
39
-
40
- def create_kafka_consumer(topic='orders', retries=20, delay=2):
41
- for i in range(retries):
42
- try:
43
- consumer = KafkaConsumer(
44
- topic,
45
- bootstrap_servers=BOOTSTRAP,
46
- value_deserializer=lambda m: json.loads(m.decode('utf-8')),
47
- auto_offset_reset='earliest',
48
- enable_auto_commit=True,
49
- group_id='matcher-group'
50
- )
51
- print('Consumer connected to topic', topic)
52
- return consumer
53
- except NoBrokersAvailable:
54
- print('Consumer: Kafka not ready, retry', i)
55
- time.sleep(delay)
56
- raise RuntimeError('Consumer: cannot connect to Kafka')
57
-
58
- def find_match(order):
59
- side = order['type']
60
- opp = 'sell' if side == 'buy' else 'buy'
61
- candidates = [o for o in order_book[opp] if o['symbol'] == order['symbol'] and o['quantity'] > 0]
62
- if not candidates:
63
- return None
64
- # best-price matching: for buy -> lowest sell price; for sell -> highest buy price
65
- if side == 'buy':
66
- valid = [o for o in candidates if o['price'] <= order['price']]
67
- if not valid: return None
68
- best_price = min(o['price'] for o in valid)
69
- bests = [o for o in valid if o['price'] == best_price]
70
- return min(bests, key=lambda x: x['timestamp'])
71
- else:
72
- valid = [o for o in candidates if o['price'] >= order['price']]
73
- if not valid: return None
74
- best_price = max(o['price'] for o in valid)
75
- bests = [o for o in valid if o['price'] == best_price]
76
- return min(bests, key=lambda x: x['timestamp'])
77
-
78
- def process_order(order):
79
- print('Processing order:', order)
80
- while order['quantity'] > 0:
81
- matched = find_match(order)
82
- if not matched:
83
- order_book[order['type']].append(order.copy())
84
- print('Added to book:', order)
85
- return
86
- traded_qty = min(order['quantity'], matched['quantity'])
87
- trade_price = matched['price']
88
- trade = {
89
- 'buy_order_id': order['order_id'] if order['type']=='buy' else matched['order_id'],
90
- 'sell_order_id': matched['order_id'] if order['type']=='buy' else order['order_id'],
91
- 'symbol': order['symbol'],
92
- 'price': trade_price,
93
- 'quantity': traded_qty,
94
- 'timestamp': time.time()
95
- }
96
- trades.append(trade)
97
- if producer:
98
- producer.send('trades', value=trade)
99
- producer.flush()
100
- print('TRADE:', trade)
101
- order['quantity'] -= traded_qty
102
- matched['quantity'] -= traded_qty
103
- if matched['quantity'] == 0:
104
- try:
105
- order_book['sell' if order['type']=='buy' else 'buy'].remove(matched)
106
- print('Removed matched from book:', matched)
107
- except ValueError:
108
- pass
109
-
110
- def consumer_loop():
111
- consumer = create_kafka_consumer('orders')
112
- for msg in consumer:
113
- try:
114
- order = msg.value
115
- order['price'] = float(order['price'])
116
- order['quantity'] = int(order['quantity'])
117
- order.setdefault('timestamp', time.time())
118
- process_order(order)
119
- except Exception as e:
120
- print('Error processing message:', e)
121
-
122
- @app.route('/book')
123
- def get_book():
124
- # present best bids/asks sorted
125
- return jsonify(order_book)
126
-
127
- @app.route('/trades')
128
- def get_trades():
129
- return jsonify(trades)
130
-
131
- if __name__ == '__main__':
132
- create_kafka_producer()
133
- t = threading.Thread(target=consumer_loop, daemon=True)
134
- t.start()
135
- app.run(host='0.0.0.0', port=6000)