Polkas
A prediction-market arbitrage system that watches Kalshi and Polymarket for opportunities, with an Axum backend, WebSocket market data and a TypeScript frontend.
- Role
- Author
- Status
- Active
- Stack
- Rust, TypeScript
- Repository
- github.com/pwnholic/polkas
Overview
Polkas monitors the Kalshi and Polymarket prediction markets and looks for arbitrage opportunities between them. It is an end-to-end system rather than a library: an Axum/Tokio backend, an HTTP API, WebSocket market data, market matching, SQLite persistence, and a React/TypeScript frontend.
Problem
The same real-world question is listed on two venues at two prices. Finding the discrepancy is a query; acting on it requires both books to be current, the two markets to be identified as the same question, and the resulting signal to reach a human before it expires.
Constraints
- Two venues, two APIs. Nothing can be assumed to be shared beyond the concept of a market.
- Streaming first. Polling two venues at a useful frequency is both slow and rate-limited, so the books are maintained from WebSocket feeds.
- Matching is the hard part. An arbitrage pair is a judgement about equivalence, and a wrong match is worse than a missed one.
Architecture
Kalshi feed ─┐
├─► market match ─► opportunity ─► SQLite ─► HTTP API ─► WebSocket ─► UI
Polymarket ──┘
(WebSocket) (Rust, Axum/Tokio) (React/TypeScript)Persistence sits between detection and presentation so that a restart does not lose the history that makes a signal interpretable.
Technical decisions
| Decision | Alternative | Why |
|---|---|---|
| Rust backend | A scripting language | Two concurrent feeds with matching on the hot path |
| WebSocket market data | Polling | Polling either lags or trips rate limits |
| SQLite | A server database | The working set is small and the deployment stays single-process |
| Separate frontend | Server-rendered pages | The UI is a live view, not a document |
Tradeoffs
Matching heuristics produce false positives, and a false positive in an arbitrage tool is expensive; the system therefore surfaces a signal for a human rather than executing it. Keeping the frontend separate also means two runtimes and two dependency trees for one tool.
Results
The repository documents this functionality. I am not quoting returns, fill rates or latency figures - the honest statement is that it detects candidate pairs and presents them, and that its usefulness depends on market conditions I have not measured systematically.
Lessons learned
In market data, the failure that matters is a stale book that still looks alive. Treating “last update time” as part of the data, rather than as monitoring, is what makes an arbitrage signal trustworthy.