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

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

DecisionAlternativeWhy
Rust backendA scripting languageTwo concurrent feeds with matching on the hot path
WebSocket market dataPollingPolling either lags or trips rate limits
SQLiteA server databaseThe working set is small and the deployment stays single-process
Separate frontendServer-rendered pagesThe 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.

Search this site