Add FIX UI Client to HuggingFace Space via nginx reverse proxy
Browse files- Add fix_server_hf.cfg and client_hf.cfg with localhost paths for single-container use
- Add nginx.conf routing port 7860: / β dashboard (5000), /fix/ β fix-ui-client (5002)
- Update Dockerfile: add nginx, QuickFIX build deps, copy fix_oeg and fix-ui-client code
- Update entrypoint.sh: start fix_oeg, fix-ui-client, nginx before dashboard
- Add "FIX UI" nav link in dashboard pointing to /fix/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile +24 -4
- client_hf.cfg +21 -0
- dashboard/templates/index.html +1 -0
- entrypoint.sh +16 -2
- fix_server_hf.cfg +26 -0
- nginx.conf +45 -0
Dockerfile
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# HuggingFace Spaces β StockEx Trading Demo
|
| 2 |
-
# Single container: Kafka (KRaft) + Matcher + MD Feeder + Dashboard
|
| 3 |
|
| 4 |
FROM python:3.11-slim
|
| 5 |
|
|
@@ -8,10 +8,15 @@ 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)
|
|
@@ -23,11 +28,12 @@ RUN wget -q \
|
|
| 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
|
|
@@ -46,9 +52,23 @@ COPY md_feeder/mdf_simulator.py /app/mdf_simulator.py
|
|
| 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
|
|
|
|
| 1 |
# HuggingFace Spaces β StockEx Trading Demo
|
| 2 |
+
# Single container: Kafka (KRaft) + Matcher + MD Feeder + Dashboard + FIX OEG + FIX UI Client
|
| 3 |
|
| 4 |
FROM python:3.11-slim
|
| 5 |
|
|
|
|
| 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 + nginx + C++ build tools (for QuickFIX)
|
| 12 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 13 |
default-jre-headless \
|
| 14 |
wget \
|
| 15 |
+
nginx \
|
| 16 |
+
build-essential \
|
| 17 |
+
libssl-dev \
|
| 18 |
+
zlib1g-dev \
|
| 19 |
+
libbz2-dev \
|
| 20 |
&& rm -rf /var/lib/apt/lists/*
|
| 21 |
|
| 22 |
# Download Apache Kafka 3.7.0 (KRaft mode β no ZooKeeper needed)
|
|
|
|
| 28 |
&& mv /opt/kafka_2.13-${KAFKA_VERSION} /opt/kafka \
|
| 29 |
&& rm /tmp/kafka.tgz
|
| 30 |
|
| 31 |
+
# Install Python dependencies (quickfix compiles from source β allow extra time)
|
| 32 |
RUN pip install --no-cache-dir \
|
| 33 |
kafka-python==2.0.2 \
|
| 34 |
Flask==2.2.5 \
|
| 35 |
+
requests==2.31.0 \
|
| 36 |
+
quickfix
|
| 37 |
|
| 38 |
# ββ Application code (flat layout matching /app container paths) ββββββββββββββ
|
| 39 |
WORKDIR /app
|
|
|
|
| 52 |
COPY dashboard/dashboard.py /app/dashboard.py
|
| 53 |
COPY dashboard/templates/ /app/templates/
|
| 54 |
|
| 55 |
+
# FIX OEG (Order Entry Gateway)
|
| 56 |
+
COPY fix_oeg/fix_oeg_server.py /app/fix_oeg/fix_oeg_server.py
|
| 57 |
+
COPY fix_oeg/FIX44.xml /app/fix_oeg/FIX44.xml
|
| 58 |
+
# Use HF-specific config (localhost paths instead of container service names)
|
| 59 |
+
COPY fix_server_hf.cfg /app/fix_oeg/fix_server.cfg
|
| 60 |
+
|
| 61 |
+
# FIX UI Client
|
| 62 |
+
COPY fix-ui-client/fix-ui-client.py /app/fix_ui/fix_ui_client.py
|
| 63 |
+
COPY fix-ui-client/templates/ /app/fix_ui/templates/
|
| 64 |
+
COPY client_hf.cfg /app/fix_ui/client_hf.cfg
|
| 65 |
+
|
| 66 |
# ββ Kafka KRaft configuration βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 67 |
COPY kafka-kraft.properties /opt/kafka/config/kraft/server.properties
|
| 68 |
|
| 69 |
+
# ββ nginx configuration βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 70 |
+
COPY nginx.conf /etc/nginx/nginx.conf
|
| 71 |
+
|
| 72 |
# ββ Startup script ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 73 |
COPY entrypoint.sh /entrypoint.sh
|
| 74 |
RUN chmod +x /entrypoint.sh
|
client_hf.cfg
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[DEFAULT]
|
| 2 |
+
ConnectionType=initiator
|
| 3 |
+
StartTime=00:00:00
|
| 4 |
+
EndTime=23:59:59
|
| 5 |
+
UseLocalTime=Y
|
| 6 |
+
HeartBtInt=30
|
| 7 |
+
FileStorePath=/tmp/fix_store
|
| 8 |
+
FileLogPath=/tmp/fix_log
|
| 9 |
+
UseDataDictionary=Y
|
| 10 |
+
DataDictionary=/app/fix_oeg/FIX44.xml
|
| 11 |
+
ReconnectInterval=5
|
| 12 |
+
ResetOnLogon=Y
|
| 13 |
+
ResetOnLogout=Y
|
| 14 |
+
ResetOnDisconnect=Y
|
| 15 |
+
|
| 16 |
+
[SESSION]
|
| 17 |
+
BeginString=FIX.4.4
|
| 18 |
+
SenderCompID=CLIENT1
|
| 19 |
+
TargetCompID=FIX_SERVER
|
| 20 |
+
SocketConnectHost=localhost
|
| 21 |
+
SocketConnectPort=5001
|
dashboard/templates/index.html
CHANGED
|
@@ -163,6 +163,7 @@
|
|
| 163 |
<span id="session-badge" class="status idle"><span class="dot"></span><span id="session-text">IDLE</span></span>
|
| 164 |
<button onclick="startDay()" class="btn-day btn-start">Start of Day</button>
|
| 165 |
<button onclick="endDay()" class="btn-day btn-end">End of Day</button>
|
|
|
|
| 166 |
</h1>
|
| 167 |
<div class="container">
|
| 168 |
|
|
|
|
| 163 |
<span id="session-badge" class="status idle"><span class="dot"></span><span id="session-text">IDLE</span></span>
|
| 164 |
<button onclick="startDay()" class="btn-day btn-start">Start of Day</button>
|
| 165 |
<button onclick="endDay()" class="btn-day btn-end">End of Day</button>
|
| 166 |
+
<a href="/fix/" style="margin-left:auto; padding:4px 14px; background:#6c757d; color:#fff; border-radius:20px; font-size:12px; font-weight:bold; text-decoration:none;">FIX UI</a>
|
| 167 |
</h1>
|
| 168 |
<div class="container">
|
| 169 |
|
entrypoint.sh
CHANGED
|
@@ -6,7 +6,9 @@ KAFKA_DIR=/opt/kafka
|
|
| 6 |
export PYTHONPATH=/app
|
| 7 |
export KAFKA_BOOTSTRAP=localhost:9092
|
| 8 |
export MATCHER_URL=http://localhost:6000
|
| 9 |
-
export PORT=
|
|
|
|
|
|
|
| 10 |
|
| 11 |
# ββ Kafka (KRaft) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
echo "[startup] Formatting Kafka storage (KRaft)..."
|
|
@@ -53,5 +55,17 @@ sleep 8
|
|
| 53 |
echo "[startup] Starting MD Feeder..."
|
| 54 |
python3 /app/mdf_simulator.py &
|
| 55 |
|
| 56 |
-
echo "[startup] Starting
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
exec python3 /app/dashboard.py
|
|
|
|
| 6 |
export PYTHONPATH=/app
|
| 7 |
export KAFKA_BOOTSTRAP=localhost:9092
|
| 8 |
export MATCHER_URL=http://localhost:6000
|
| 9 |
+
export PORT=5000
|
| 10 |
+
export FIX_CONFIG=/app/fix_ui/client_hf.cfg
|
| 11 |
+
export UI_PORT=5002
|
| 12 |
|
| 13 |
# ββ Kafka (KRaft) βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 14 |
echo "[startup] Formatting Kafka storage (KRaft)..."
|
|
|
|
| 55 |
echo "[startup] Starting MD Feeder..."
|
| 56 |
python3 /app/mdf_simulator.py &
|
| 57 |
|
| 58 |
+
echo "[startup] Starting FIX OEG on port 5001..."
|
| 59 |
+
(cd /app/fix_oeg && python3 /app/fix_oeg/fix_oeg_server.py) &
|
| 60 |
+
sleep 3
|
| 61 |
+
|
| 62 |
+
echo "[startup] Starting FIX UI Client on port 5002..."
|
| 63 |
+
python3 /app/fix_ui/fix_ui_client.py &
|
| 64 |
+
sleep 2
|
| 65 |
+
|
| 66 |
+
# ββ nginx (reverse proxy: port 7860 β dashboard:5000 + fix-ui:5002) βββββββββββ
|
| 67 |
+
echo "[startup] Starting nginx on port 7860..."
|
| 68 |
+
nginx
|
| 69 |
+
|
| 70 |
+
echo "[startup] Starting Dashboard on port 5000..."
|
| 71 |
exec python3 /app/dashboard.py
|
fix_server_hf.cfg
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[DEFAULT]
|
| 2 |
+
ConnectionType=acceptor
|
| 3 |
+
SocketAcceptPort=5001
|
| 4 |
+
SocketReuseAddress=Y
|
| 5 |
+
StartTime=00:00:00
|
| 6 |
+
EndTime=23:59:59
|
| 7 |
+
UseLocalTime=Y
|
| 8 |
+
UseDataDictionary=Y
|
| 9 |
+
DataDictionary=/app/fix_oeg/FIX44.xml
|
| 10 |
+
FileStorePath=/tmp/fix_store
|
| 11 |
+
FileLogPath=/tmp/fix_log
|
| 12 |
+
HeartBtInt=30
|
| 13 |
+
ValidateUserDefinedFields=N
|
| 14 |
+
ResetOnLogon=Y
|
| 15 |
+
ResetOnLogout=Y
|
| 16 |
+
ResetOnDisconnect=Y
|
| 17 |
+
|
| 18 |
+
[SESSION]
|
| 19 |
+
BeginString=FIX.4.4
|
| 20 |
+
SenderCompID=FIX_SERVER
|
| 21 |
+
TargetCompID=CLIENT1
|
| 22 |
+
|
| 23 |
+
[SESSION]
|
| 24 |
+
BeginString=FIX.4.4
|
| 25 |
+
SenderCompID=FIX_SERVER
|
| 26 |
+
TargetCompID=CLIENT2
|
nginx.conf
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker_processes 1;
|
| 2 |
+
events { worker_connections 256; }
|
| 3 |
+
|
| 4 |
+
http {
|
| 5 |
+
include /etc/nginx/mime.types;
|
| 6 |
+
default_type application/octet-stream;
|
| 7 |
+
access_log off;
|
| 8 |
+
|
| 9 |
+
server {
|
| 10 |
+
listen 7860;
|
| 11 |
+
|
| 12 |
+
# SSE β disable buffering for the dashboard's /stream endpoint
|
| 13 |
+
location /stream {
|
| 14 |
+
proxy_pass http://localhost:5000/stream;
|
| 15 |
+
proxy_set_header Host $host;
|
| 16 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 17 |
+
proxy_buffering off;
|
| 18 |
+
proxy_cache off;
|
| 19 |
+
proxy_read_timeout 3600s;
|
| 20 |
+
chunked_transfer_encoding on;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
# FIX UI Client β served under /fix/
|
| 24 |
+
location /fix/ {
|
| 25 |
+
proxy_pass http://localhost:5002/;
|
| 26 |
+
proxy_set_header Host $host;
|
| 27 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 28 |
+
# Rewrite absolute links generated by Flask url_for()
|
| 29 |
+
sub_filter 'href="/' 'href="/fix/';
|
| 30 |
+
sub_filter 'action="/' 'action="/fix/';
|
| 31 |
+
sub_filter_once off;
|
| 32 |
+
sub_filter_types text/html;
|
| 33 |
+
# Rewrite Location headers on Flask redirects
|
| 34 |
+
proxy_redirect / /fix/;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
# Dashboard β everything else
|
| 38 |
+
location / {
|
| 39 |
+
proxy_pass http://localhost:5000;
|
| 40 |
+
proxy_set_header Host $host;
|
| 41 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 42 |
+
proxy_buffering off;
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|