Add HuggingFace Spaces deployment
Browse files- Dockerfile: single-container build (Kafka KRaft + Matcher + MD Feeder + Dashboard)
- entrypoint.sh: ordered startup β Kafka ready-check, topic creation, Python services
- kafka-kraft.properties: single-node KRaft config (no ZooKeeper)
- README.md: HF Space metadata + project overview
- dashboard.py: read PORT from env var (default 5000, HF uses 7860)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile +58 -0
- README.md +47 -0
- dashboard/dashboard.py +2 -1
- entrypoint.sh +57 -0
- kafka-kraft.properties +20 -0
Dockerfile
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HuggingFace Spaces β StockEx Trading Demo
|
| 2 |
+
# Single container: Kafka (KRaft) + Matcher + MD Feeder + Dashboard
|
| 3 |
+
|
| 4 |
+
FROM python:3.11-slim
|
| 5 |
+
|
| 6 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 7 |
+
ENV PYTHONUNBUFFERED=1
|
| 8 |
+
# Limit Kafka JVM heap to fit free-tier RAM
|
| 9 |
+
ENV KAFKA_HEAP_OPTS="-Xmx400m -Xms256m"
|
| 10 |
+
|
| 11 |
+
# Install Java (required by Kafka) + wget
|
| 12 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 13 |
+
default-jre-headless \
|
| 14 |
+
wget \
|
| 15 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 16 |
+
|
| 17 |
+
# Download Apache Kafka 3.7.0 (KRaft mode β no ZooKeeper needed)
|
| 18 |
+
ARG KAFKA_VERSION=3.7.0
|
| 19 |
+
RUN wget -q \
|
| 20 |
+
"https://archive.apache.org/dist/kafka/${KAFKA_VERSION}/kafka_2.13-${KAFKA_VERSION}.tgz" \
|
| 21 |
+
-O /tmp/kafka.tgz \
|
| 22 |
+
&& tar -xzf /tmp/kafka.tgz -C /opt \
|
| 23 |
+
&& mv /opt/kafka_2.13-${KAFKA_VERSION} /opt/kafka \
|
| 24 |
+
&& rm /tmp/kafka.tgz
|
| 25 |
+
|
| 26 |
+
# Install Python dependencies
|
| 27 |
+
RUN pip install --no-cache-dir \
|
| 28 |
+
kafka-python==2.0.2 \
|
| 29 |
+
Flask==2.2.5 \
|
| 30 |
+
requests==2.31.0
|
| 31 |
+
|
| 32 |
+
# ββ Application code (flat layout matching /app container paths) ββββββββββββββ
|
| 33 |
+
WORKDIR /app
|
| 34 |
+
|
| 35 |
+
COPY shared/ /app/shared/
|
| 36 |
+
COPY shared_data/securities.txt /app/data/securities.txt
|
| 37 |
+
|
| 38 |
+
# Matcher service
|
| 39 |
+
COPY matcher/matcher.py /app/matcher.py
|
| 40 |
+
COPY matcher/database.py /app/database.py
|
| 41 |
+
|
| 42 |
+
# MD Feeder service
|
| 43 |
+
COPY md_feeder/mdf_simulator.py /app/mdf_simulator.py
|
| 44 |
+
|
| 45 |
+
# Dashboard service
|
| 46 |
+
COPY dashboard/dashboard.py /app/dashboard.py
|
| 47 |
+
COPY dashboard/templates/ /app/templates/
|
| 48 |
+
|
| 49 |
+
# ββ Kafka KRaft configuration βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 50 |
+
COPY kafka-kraft.properties /opt/kafka/config/kraft/server.properties
|
| 51 |
+
|
| 52 |
+
# ββ Startup script ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 53 |
+
COPY entrypoint.sh /entrypoint.sh
|
| 54 |
+
RUN chmod +x /entrypoint.sh
|
| 55 |
+
|
| 56 |
+
EXPOSE 7860
|
| 57 |
+
|
| 58 |
+
CMD ["/entrypoint.sh"]
|
README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: StockEx Trading Demo
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
app_port: 7860
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# StockEx β Kafka-based Stock Exchange Simulator
|
| 12 |
+
|
| 13 |
+
A real-time stock exchange simulation built with **Apache Kafka**, **Python**, and **Flask**.
|
| 14 |
+
|
| 15 |
+
## Architecture
|
| 16 |
+
|
| 17 |
+
| Service | Description |
|
| 18 |
+
|---|---|
|
| 19 |
+
| **Matcher** | Order matching engine (price-time priority, limit/market orders) |
|
| 20 |
+
| **MD Feeder** | Synthetic market data generator, drives order flow |
|
| 21 |
+
| **Dashboard** | Real-time trading dashboard with SSE streaming |
|
| 22 |
+
|
| 23 |
+
## Features
|
| 24 |
+
|
| 25 |
+
- **Live Order Book** β best bid/ask with depth
|
| 26 |
+
- **Trade Feed** β real-time executions with SSE push
|
| 27 |
+
- **Market Snapshot** β BBO per symbol with ticker tape
|
| 28 |
+
- **Trade Chart** β live price/volume chart
|
| 29 |
+
- **Price History** β OHLCV candlestick chart (1H / 8H / 1D / 1W / 1M)
|
| 30 |
+
- **Start / End of Day** β reset prices, pause/resume simulation
|
| 31 |
+
- **Order Management** β cancel and amend resting orders
|
| 32 |
+
|
| 33 |
+
## Securities
|
| 34 |
+
|
| 35 |
+
`ALPHA` Β· `PEIR` Β· `EXAE` Β· `QUEST` Β· `NBG`
|
| 36 |
+
|
| 37 |
+
## Stack
|
| 38 |
+
|
| 39 |
+
- Apache Kafka (KRaft mode, no ZooKeeper)
|
| 40 |
+
- Python 3.11 Β· Flask Β· kafka-python
|
| 41 |
+
- SQLite (matcher persistence + OHLCV history)
|
| 42 |
+
- Canvas API candlestick charts
|
| 43 |
+
- Server-Sent Events for real-time UI updates
|
| 44 |
+
|
| 45 |
+
## Source
|
| 46 |
+
|
| 47 |
+
GitHub: [github.com/Bonum/StockEx](https://github.com/Bonum/StockEx)
|
dashboard/dashboard.py
CHANGED
|
@@ -428,4 +428,5 @@ def stream():
|
|
| 428 |
|
| 429 |
|
| 430 |
if __name__ == "__main__":
|
| 431 |
-
|
|
|
|
|
|
| 428 |
|
| 429 |
|
| 430 |
if __name__ == "__main__":
|
| 431 |
+
port = int(os.getenv("PORT", "5000"))
|
| 432 |
+
app.run(host="0.0.0.0", port=port, debug=True, use_reloader=False)
|
entrypoint.sh
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
KAFKA_DIR=/opt/kafka
|
| 5 |
+
|
| 6 |
+
export PYTHONPATH=/app
|
| 7 |
+
export KAFKA_BOOTSTRAP=localhost:9092
|
| 8 |
+
export MATCHER_URL=http://localhost:6000
|
| 9 |
+
export PORT=7860
|
| 10 |
+
|
| 11 |
+
# ββ Kafka (KRaft) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
+
echo "[startup] Formatting Kafka storage (KRaft)..."
|
| 13 |
+
CLUSTER_ID=$($KAFKA_DIR/bin/kafka-storage.sh random-uuid)
|
| 14 |
+
$KAFKA_DIR/bin/kafka-storage.sh format \
|
| 15 |
+
-t "$CLUSTER_ID" \
|
| 16 |
+
-c $KAFKA_DIR/config/kraft/server.properties \
|
| 17 |
+
--ignore-formatted
|
| 18 |
+
|
| 19 |
+
echo "[startup] Starting Kafka..."
|
| 20 |
+
$KAFKA_DIR/bin/kafka-server-start.sh $KAFKA_DIR/config/kraft/server.properties &
|
| 21 |
+
KAFKA_PID=$!
|
| 22 |
+
|
| 23 |
+
echo "[startup] Waiting for Kafka to be ready..."
|
| 24 |
+
RETRIES=30
|
| 25 |
+
until $KAFKA_DIR/bin/kafka-topics.sh \
|
| 26 |
+
--list --bootstrap-server localhost:9092 &>/dev/null 2>&1; do
|
| 27 |
+
RETRIES=$((RETRIES - 1))
|
| 28 |
+
if [ $RETRIES -le 0 ]; then
|
| 29 |
+
echo "[startup] ERROR: Kafka did not start in time"
|
| 30 |
+
exit 1
|
| 31 |
+
fi
|
| 32 |
+
sleep 3
|
| 33 |
+
echo "[startup] Still waiting for Kafka... ($RETRIES retries left)"
|
| 34 |
+
done
|
| 35 |
+
echo "[startup] Kafka ready."
|
| 36 |
+
|
| 37 |
+
# Create topics
|
| 38 |
+
for TOPIC in orders trades snapshots control; do
|
| 39 |
+
$KAFKA_DIR/bin/kafka-topics.sh \
|
| 40 |
+
--create --if-not-exists \
|
| 41 |
+
--topic "$TOPIC" \
|
| 42 |
+
--partitions 1 \
|
| 43 |
+
--replication-factor 1 \
|
| 44 |
+
--bootstrap-server localhost:9092
|
| 45 |
+
done
|
| 46 |
+
echo "[startup] Topics created."
|
| 47 |
+
|
| 48 |
+
# ββ Python services ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 49 |
+
echo "[startup] Starting Matcher on port 6000..."
|
| 50 |
+
python3 /app/matcher.py &
|
| 51 |
+
sleep 8
|
| 52 |
+
|
| 53 |
+
echo "[startup] Starting MD Feeder..."
|
| 54 |
+
python3 /app/mdf_simulator.py &
|
| 55 |
+
|
| 56 |
+
echo "[startup] Starting Dashboard on port 7860..."
|
| 57 |
+
exec python3 /app/dashboard.py
|
kafka-kraft.properties
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
process.roles=broker,controller
|
| 2 |
+
node.id=1
|
| 3 |
+
controller.quorum.voters=1@localhost:9093
|
| 4 |
+
|
| 5 |
+
listeners=PLAINTEXT://localhost:9092,CONTROLLER://localhost:9093
|
| 6 |
+
inter.broker.listener.name=PLAINTEXT
|
| 7 |
+
advertised.listeners=PLAINTEXT://localhost:9092
|
| 8 |
+
controller.listener.names=CONTROLLER
|
| 9 |
+
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
| 10 |
+
|
| 11 |
+
log.dirs=/tmp/kafka-logs
|
| 12 |
+
num.partitions=1
|
| 13 |
+
offsets.topic.replication.factor=1
|
| 14 |
+
transaction.state.log.replication.factor=1
|
| 15 |
+
transaction.state.log.min.isr=1
|
| 16 |
+
auto.create.topics.enable=true
|
| 17 |
+
|
| 18 |
+
log.retention.hours=24
|
| 19 |
+
log.segment.bytes=104857600
|
| 20 |
+
log.retention.check.interval.ms=300000
|