Real-time arbitrage detection across Kalshi and Polymarket prediction markets. C++ WebSocket ingestion, semantic matching pipeline, and interactive D3.js graph visualization — all running locally for $0/month.
C++, Python, TypeScript, ZeroMQ, D3.js, Next.js, sentence-transformers, scikit-learn, NumPy, WebSockets
https://www.youtube.com/watch?v=3uMMXEfT-_Y
flowchart LR
subgraph ingestion ["C++ Ingestion"]
K[Kalshi WS] --> N[Normalizer]
P[Polymarket WS] --> N
N --> E[ZMQ Publisher]
R[Async REST\nEnrichment] -.-> N
end
subgraph pipeline ["Python Pipeline"]
S[Event Store\ndict + numpy] --> TF[Time Filter]
TF --> SM[Semantic Match\n.70 cosine]
SM --> CL[k-Means\nClustering]
CL --> G[(graph.json)]
end
subgraph frontend ["Next.js Frontend"]
GR[D3.js Force Graph\nL0 Clusters · L1 Nodes]
SB[Live Sidebar\n50K+ contracts]
FI[Filters\nSource · Expiry · Search]
end
E -->|ZMQ PUB/SUB| S
E -->|ZMQ → WS relay| SB
G -->|/api/graph| GR
FI --> GR
FI --> SB
-
Ingest — C++ spawns concurrent WebSocket feeds for Kalshi and Polymarket. REST bootstrap paginates through all open markets, then WebSocket streams pick up live price changes. Events are normalized into a unified schema and published over ZeroMQ.
-
Match — Python subscribes to ZMQ, embeds each event title using
all-MiniLM-L6-v2(384-dim, local), and runs cosine similarity against all cross-platform events. Candidates are filtered by time proximity and scored by expiry urgency. Matches and clusters are written tograph.json. -
Visualize — Next.js serves a custom Node.js server that bridges ZMQ to browser WebSockets. D3.js renders a force-directed graph with two levels of detail: cluster bubbles (L0) and individual contracts (L1). Arbitrage edges are drawn in red with similarity and disparity percentages. Filters for source, time-to-expiry, and search apply to both the sidebar and graph simultaneously.
- C++ toolchain — CMake 3.20+, C++17 compiler, OpenSSL 3.x
- System libraries —
brew install zeromq(macOS) orapt install libzmq3-dev(Linux) - Python 3.10+
- Node.js 18+
- Kalshi API key + RSA private key (for authenticated feeds)
# Clone
git clone https://github.com/<you>/delta.git && cd delta
# C++ ingestion
make ingestion-configure # cmake
make ingestion-build # compile
# Python pipeline
make pipeline-install # creates venv, installs deps
# Frontend
cd frontend && npm install && cd ..Create config.json in the project root:
{
"kalshi_api_key": "YOUR_KALSHI_API_KEY",
"kalshi_private_key_path": "./private_key.pem"
}Place your Kalshi RSA private key at the path above. Polymarket feeds are unauthenticated.
All settings can also be set via environment variables (KALSHI_API_KEY, KALSHI_PRIVATE_KEY_PATH, ZMQ_BIND_ADDRESS, etc.).
Run each in a separate terminal:
# Terminal 1 — Ingestion (C++)
make ingestion
# Terminal 2 — Matching pipeline (Python)
make pipeline
# Terminal 3 — Frontend (Next.js + ZMQ relay)
make client-devOpen http://localhost:3000. The sidebar streams live contracts. The graph populates once the pipeline produces its first clustering pass (~60s or 1000 events).
delta/
├── ingestion/ C++ WebSocket feeds + normalizer + ZMQ emitter
│ ├── include/ Event schema, config, feed headers
│ └── src/ Kalshi feed, Polymarket feed, normalizer, IPC
├── pipeline/ Python semantic matching + clustering
│ ├── steps/ Time filter, semantic match, k-means clustering
│ ├── graph/ Graph schema + atomic JSON manager
│ ├── models/ Pydantic event model
│ └── store.py In-memory event store with embedding index
├── frontend/ Next.js + D3.js visualization
│ ├── components/ GraphCanvas, LiveSidebar, EventCard, Tooltip
│ ├── lib/ Types, filters, WebSocket hook
│ └── server.ts Custom Node.js server (ZMQ→WS relay)
└── Makefile Build/run commands for all three layers
MIT