Update README: message flow chart, full deployment guide, project structure
Browse files- ASCII architecture overview (always renders) + Mermaid message flow diagram
- Document all 10 securities with names and start prices
- Deployment: HF live demo, deploy-your-own Space (step-by-step), Docker Compose, bare Python
- CI/CD table documenting GitHub Actions workflows
- Project structure tree
- Updated features list (Suspend/Resume, unified Price Chart, mobile responsive, FIX UI)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README.md
CHANGED
|
@@ -11,37 +11,286 @@ app_port: 7860
|
|
| 11 |
# StockEx β Kafka-based Stock Exchange Simulator
|
| 12 |
|
| 13 |
A real-time stock exchange simulation built with **Apache Kafka**, **Python**, and **Flask**.
|
|
|
|
| 14 |
|
| 15 |
-
|
|
|
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
## Features
|
| 24 |
|
| 25 |
-
- **Live Order Book**
|
| 26 |
-
- **Trade Feed**
|
| 27 |
-
- **Market Snapshot**
|
| 28 |
-
- **
|
| 29 |
-
- **
|
| 30 |
-
- **
|
| 31 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
## Securities
|
| 34 |
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
## Stack
|
| 38 |
|
| 39 |
-
- Apache Kafka (KRaft mode
|
| 40 |
-
- Python 3.11 Β· Flask Β· kafka-python
|
| 41 |
-
-
|
| 42 |
-
-
|
| 43 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
-
##
|
| 46 |
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# StockEx β Kafka-based Stock Exchange Simulator
|
| 12 |
|
| 13 |
A real-time stock exchange simulation built with **Apache Kafka**, **Python**, and **Flask**.
|
| 14 |
+
Includes a FIX 4.4 order gateway, live matching engine, SSE-streamed dashboard, and candlestick charts.
|
| 15 |
|
| 16 |
+
π **Live demo:** [huggingface.co/spaces/RayMelius/StockEx](https://huggingface.co/spaces/RayMelius/StockEx)
|
| 17 |
+
π¦ **Source:** [github.com/Bonum/StockEx](https://github.com/Bonum/StockEx)
|
| 18 |
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## Architecture Overview
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
+
β Single Docker Container β
|
| 26 |
+
β β
|
| 27 |
+
β nginx :7860 (reverse proxy) β
|
| 28 |
+
β / β Dashboard :5000 β
|
| 29 |
+
β /fix/ β FIX UI :5002 β
|
| 30 |
+
β β
|
| 31 |
+
β βββββββββββββββ ββββββββββββββββ βββββββββββββββββββββ β
|
| 32 |
+
β β MD Feeder β β FIX OEG β β FIX UI Client β β
|
| 33 |
+
β β (simulator) β β :5001 β β Flask :5002 β β
|
| 34 |
+
β ββββββββ¬βββββββ ββββββββ¬ββββββββ βββββββββββ¬ββββββββββ β
|
| 35 |
+
β β β β FIX 4.4 β
|
| 36 |
+
β β ββββββββββββββ β β
|
| 37 |
+
β βΌ βΌ β β
|
| 38 |
+
β βββββββββββββββββββββββββββββββββββββββββββββββ β β
|
| 39 |
+
β β Apache Kafka (KRaft) β β β
|
| 40 |
+
β β orders β trades β snapshots β control β β β
|
| 41 |
+
β ββββββββββββββββββββ¬βββββββββββββββββββββββββββ β β
|
| 42 |
+
β β β β
|
| 43 |
+
β βββββββββββββ β β
|
| 44 |
+
β βΌ β β
|
| 45 |
+
β βββββββββββββββ ββββββββββββββββββββββββββββ β β
|
| 46 |
+
β β Matcher β β Dashboard Flask :5000 βββββ β
|
| 47 |
+
β β Flask:6000 β β SSE + REST API β β
|
| 48 |
+
β β SQLite DB β β SQLite OHLCV history β β
|
| 49 |
+
β βββββββββββββββ ββββββββββββββββββββββββββββ β
|
| 50 |
+
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## Message Flow
|
| 56 |
+
|
| 57 |
+
```mermaid
|
| 58 |
+
flowchart TB
|
| 59 |
+
Browser("π Browser")
|
| 60 |
+
|
| 61 |
+
subgraph Container["Docker Container"]
|
| 62 |
+
nginx["nginx :7860"]
|
| 63 |
+
|
| 64 |
+
subgraph Services["Python Services"]
|
| 65 |
+
Dashboard["Dashboard\n:5000"]
|
| 66 |
+
Matcher["Matcher\n:6000"]
|
| 67 |
+
MDF["MD Feeder"]
|
| 68 |
+
FIXOEG["FIX OEG\n:5001"]
|
| 69 |
+
FIXUI["FIX UI Client\n:5002"]
|
| 70 |
+
end
|
| 71 |
+
|
| 72 |
+
subgraph KafkaTopics["Apache Kafka (KRaft)"]
|
| 73 |
+
Ko[("orders")]
|
| 74 |
+
Kt[("trades")]
|
| 75 |
+
Ks[("snapshots")]
|
| 76 |
+
Kc[("control")]
|
| 77 |
+
end
|
| 78 |
+
|
| 79 |
+
DB[("SQLite")]
|
| 80 |
+
end
|
| 81 |
+
|
| 82 |
+
Browser -- "HTTP / SSE" --> nginx
|
| 83 |
+
nginx -- "/" --> Dashboard
|
| 84 |
+
nginx -- "/fix/" --> FIXUI
|
| 85 |
+
|
| 86 |
+
FIXUI -- "FIX 4.4\nNewOrderSingle" --> FIXOEG
|
| 87 |
+
FIXOEG -- "order" --> Ko
|
| 88 |
+
FIXOEG -- "ExecReport" --> FIXUI
|
| 89 |
+
|
| 90 |
+
MDF -- "synthetic orders\n& snapshots" --> Ko
|
| 91 |
+
MDF -- "BBO snapshots" --> Ks
|
| 92 |
+
|
| 93 |
+
Dashboard -- "start/stop\nsuspend/resume" --> Kc
|
| 94 |
+
Kc -- "control signal" --> MDF
|
| 95 |
+
|
| 96 |
+
Ko -- "limit / market orders" --> Matcher
|
| 97 |
+
Matcher -- "fills" --> Kt
|
| 98 |
+
Matcher -- "BBO updates" --> Ks
|
| 99 |
+
Matcher --- DB
|
| 100 |
+
|
| 101 |
+
Ko --> Dashboard
|
| 102 |
+
Kt --> Dashboard
|
| 103 |
+
Ks --> Dashboard
|
| 104 |
+
Kt -- "OHLCV\n1-min buckets" --> DB
|
| 105 |
+
|
| 106 |
+
Dashboard -- "SSE stream\n(orders/trades/snapshots)" --> Browser
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Services
|
| 112 |
+
|
| 113 |
+
| Service | Port | Description |
|
| 114 |
+
|---|---|---|
|
| 115 |
+
| **nginx** | 7860 | Reverse proxy β single public port |
|
| 116 |
+
| **Dashboard** | 5000 | Flask app, SSE streaming, session control, OHLCV history |
|
| 117 |
+
| **Matcher** | 6000 | Price-time priority matching engine, REST API, SQLite persistence |
|
| 118 |
+
| **MD Feeder** | β | Synthetic market data generator; responds to start/stop/suspend/resume |
|
| 119 |
+
| **FIX OEG** | 5001 | FIX 4.4 acceptor β translates FIX messages to Kafka orders |
|
| 120 |
+
| **FIX UI Client** | 5002 | Browser UI for sending FIX orders and viewing execution reports |
|
| 121 |
+
|
| 122 |
+
---
|
| 123 |
|
| 124 |
## Features
|
| 125 |
|
| 126 |
+
- **Live Order Book** β best bid/ask with full depth per symbol
|
| 127 |
+
- **Trade Feed** β real-time executions pushed via Server-Sent Events
|
| 128 |
+
- **Market Snapshot** β BBO table + scrolling ticker tape
|
| 129 |
+
- **Price Chart** β candlestick + close-price line + volume bars; Live / 1H / 8H / 1D / 1W / 1M periods
|
| 130 |
+
- **All-Symbols View** β normalised % change chart comparing all securities on one axis
|
| 131 |
+
- **Trading Statistics** β per-symbol trade count, volume, value, VWAP, bar chart
|
| 132 |
+
- **Start / End of Day** β resets opening prices, starts/stops MD simulation
|
| 133 |
+
- **Suspend / Resume** β pauses order generation without ending the session
|
| 134 |
+
- **Order Management** β cancel and amend resting orders from the dashboard
|
| 135 |
+
- **FIX UI Client** β send NewOrderSingle via FIX 4.4, view execution reports at `/fix/`
|
| 136 |
+
- **Mobile Responsive** β single-column layout on phones and tablets
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
|
| 140 |
## Securities
|
| 141 |
|
| 142 |
+
| Symbol | Name | Start Price |
|
| 143 |
+
|---|---|---|
|
| 144 |
+
| ALPHA | Alpha Bank | β¬24.95 |
|
| 145 |
+
| PEIR | Piraeus Bank | β¬18.05 |
|
| 146 |
+
| EXAE | Athens Exchange Group | β¬42.05 |
|
| 147 |
+
| QUEST | Quest Holdings | β¬12.60 |
|
| 148 |
+
| NBG | National Bank of Greece | β¬18.05 |
|
| 149 |
+
| ATTIKA | Attika Bank | β¬3.95 |
|
| 150 |
+
| INTKA | Intertech | β¬3.95 |
|
| 151 |
+
| LAMDA | Lamda Development | β¬3.95 |
|
| 152 |
+
| AEG | AEG | β¬3.95 |
|
| 153 |
+
| AAAK | AAAK | β¬3.95 |
|
| 154 |
+
|
| 155 |
+
---
|
| 156 |
|
| 157 |
## Stack
|
| 158 |
|
| 159 |
+
- **Apache Kafka 3.7** (KRaft mode β no ZooKeeper)
|
| 160 |
+
- **Python 3.11** Β· Flask 2.2 Β· kafka-python 2.0
|
| 161 |
+
- **QuickFIX** (FIX 4.4 protocol, compiled from C++)
|
| 162 |
+
- **SQLite** β matcher order/trade persistence + OHLCV history
|
| 163 |
+
- **Canvas 2D API** β candlestick charts rendered client-side
|
| 164 |
+
- **Server-Sent Events** β real-time push to browser (no WebSocket)
|
| 165 |
+
- **nginx** β reverse proxy with `sub_filter` URL rewriting for `/fix/`
|
| 166 |
+
|
| 167 |
+
---
|
| 168 |
+
|
| 169 |
+
## Deployment
|
| 170 |
+
|
| 171 |
+
### Option 1 β Use the live HuggingFace Space (zero setup)
|
| 172 |
+
|
| 173 |
+
Open: **https://huggingface.co/spaces/RayMelius/StockEx**
|
| 174 |
+
|
| 175 |
+
No account or installation required.
|
| 176 |
+
|
| 177 |
+
---
|
| 178 |
+
|
| 179 |
+
### Option 2 β Deploy your own HuggingFace Space
|
| 180 |
+
|
| 181 |
+
> Pushes to your GitHub `main` branch auto-deploy to HuggingFace via GitHub Actions.
|
| 182 |
+
|
| 183 |
+
**Step 1 β Fork the repository**
|
| 184 |
+
|
| 185 |
+
```bash
|
| 186 |
+
# On GitHub: click Fork on https://github.com/Bonum/StockEx
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
**Step 2 β Create a HuggingFace Space**
|
| 190 |
+
|
| 191 |
+
1. Go to [huggingface.co/new-space](https://huggingface.co/new-space)
|
| 192 |
+
2. Choose **Docker** SDK
|
| 193 |
+
3. Set `App port` to `7860`
|
| 194 |
+
4. Note your Space URL: `https://huggingface.co/spaces/<your-username>/StockEx`
|
| 195 |
+
|
| 196 |
+
**Step 3 β Get a HuggingFace write token**
|
| 197 |
+
|
| 198 |
+
1. Go to [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)
|
| 199 |
+
2. Create a token with **Write** permission
|
| 200 |
+
3. Copy the token (starts with `hf_β¦`)
|
| 201 |
+
|
| 202 |
+
**Step 4 β Add the token as a GitHub Secret**
|
| 203 |
+
|
| 204 |
+
1. In your fork: **Settings β Secrets and variables β Actions**
|
| 205 |
+
2. Click **New repository secret**
|
| 206 |
+
3. Name: `HF_TOKEN` Β· Value: your token from Step 3
|
| 207 |
+
|
| 208 |
+
**Step 5 β Update the Space URL in the workflow** *(if your username differs)*
|
| 209 |
+
|
| 210 |
+
Edit `.github/workflows/deploy-hf.yml`:
|
| 211 |
+
```yaml
|
| 212 |
+
git remote add huggingface https://<your-hf-username>:${HF_TOKEN}@huggingface.co/spaces/<your-hf-username>/StockEx.git
|
| 213 |
+
```
|
| 214 |
+
|
| 215 |
+
**Step 6 β Push**
|
| 216 |
+
|
| 217 |
+
```bash
|
| 218 |
+
git push origin main
|
| 219 |
+
```
|
| 220 |
+
|
| 221 |
+
GitHub Actions runs the CI tests, then pushes to HuggingFace. The Space builds (~5β15 min on first run due to QuickFIX compilation) and goes live automatically on every subsequent push.
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
+
### Option 3 β Local with Docker Compose
|
| 226 |
+
|
| 227 |
+
**Prerequisites:** Docker Desktop
|
| 228 |
+
|
| 229 |
+
```bash
|
| 230 |
+
git clone https://github.com/Bonum/StockEx.git
|
| 231 |
+
cd StockEx
|
| 232 |
+
docker-compose up
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
| URL | Service |
|
| 236 |
+
|---|---|
|
| 237 |
+
| http://localhost:5000 | Trading Dashboard |
|
| 238 |
+
| http://localhost:5001 | FIX OEG (TCP, not HTTP) |
|
| 239 |
+
| http://localhost:5002 | FIX UI Client |
|
| 240 |
+
| http://localhost:6000 | Matcher REST API |
|
| 241 |
+
|
| 242 |
+
---
|
| 243 |
+
|
| 244 |
+
### Option 4 β Local without Docker
|
| 245 |
+
|
| 246 |
+
**Prerequisites:** Python 3.11+, Apache Kafka running on `localhost:9092`
|
| 247 |
+
|
| 248 |
+
```bash
|
| 249 |
+
git clone https://github.com/Bonum/StockEx.git
|
| 250 |
+
cd StockEx
|
| 251 |
+
pip install kafka-python Flask requests quickfix
|
| 252 |
+
|
| 253 |
+
# In separate terminals:
|
| 254 |
+
export PYTHONPATH=$(pwd)
|
| 255 |
+
export KAFKA_BOOTSTRAP=localhost:9092
|
| 256 |
+
export MATCHER_URL=http://localhost:6000
|
| 257 |
+
|
| 258 |
+
python matcher/matcher.py # terminal 1
|
| 259 |
+
python md_feeder/mdf_simulator.py # terminal 2
|
| 260 |
+
python dashboard/dashboard.py # terminal 3
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
Then open http://localhost:5000.
|
| 264 |
+
|
| 265 |
+
---
|
| 266 |
+
|
| 267 |
+
## CI / CD
|
| 268 |
+
|
| 269 |
+
| Trigger | Action |
|
| 270 |
+
|---|---|
|
| 271 |
+
| Push to `main` | Run matcher unit tests + Docker build check |
|
| 272 |
+
| Push to `main` (tests pass) | Auto-deploy to HuggingFace Spaces |
|
| 273 |
+
| Pull request to `main` | Run tests only |
|
| 274 |
+
|
| 275 |
+
GitHub Actions workflows: `.github/workflows/ci.yml` Β· `.github/workflows/deploy-hf.yml`
|
| 276 |
+
|
| 277 |
+
---
|
| 278 |
|
| 279 |
+
## Project Structure
|
| 280 |
|
| 281 |
+
```
|
| 282 |
+
StockEx/
|
| 283 |
+
βββ matcher/ # Matching engine + SQLite persistence
|
| 284 |
+
βββ md_feeder/ # Synthetic market data generator
|
| 285 |
+
βββ dashboard/ # Flask dashboard + SSE + OHLCV history
|
| 286 |
+
β βββ templates/ # Single-page trading UI
|
| 287 |
+
βββ fix_oeg/ # FIX 4.4 Order Entry Gateway
|
| 288 |
+
βββ fix-ui-client/ # FIX browser UI
|
| 289 |
+
βββ shared/ # Shared config + Kafka utils
|
| 290 |
+
βββ shared_data/ # securities.txt (symbol list + prices)
|
| 291 |
+
βββ Dockerfile # HuggingFace / single-container build
|
| 292 |
+
βββ docker-compose.yml # Local multi-container dev setup
|
| 293 |
+
βββ entrypoint.sh # Container startup (Kafka β services β nginx)
|
| 294 |
+
βββ kafka-kraft.properties
|
| 295 |
+
βββ nginx.conf # Reverse proxy config
|
| 296 |
+
```
|