Quant · 2026

Polymarket–Kalshi Cross-Venue Arb

Polymarket and Kalshi often list contracts on the same real-world event but price them differently. This engine watches both, links matching contracts, and simulates trading the gap. It runs in shadow mode — paper-trading every opportunity it would have taken — until the numbers earn a live promotion.

Quant Python FastAPI SQLAlchemy PostgreSQL
Tests passing76+
Config knobs50+
ModeShadow · gated live
PersistenceLedger + positions
Shadow vs live PnL illustrative
Fig. 1. Illustrative — not a real backtest. Run shadow long enough to trust the spreads and fees, then flip to live with a small cap.

What it is

Two competing prediction markets, same underlying events, different prices. Sometimes the gap is bigger than the trading fees on both sides — that's an arbitrage. The engine looks for those gaps continuously, but it doesn't actually trade until I've watched it work in pretend mode for long enough to trust it.

The reason for the patience: real fees are always worse than your spreadsheet says, real fills aren't free, and one bad assumption in shadow mode would have looked great on paper and lost real money. So shadow first, gated live promotion, small caps when it does go live.

How it works

Polymarket and Kalshi don't share contract IDs, so before any pricing happens the engine has to decide which Polymarket contract corresponds to which Kalshi one. A multi-stage gate — resolution time, domain, market type, entity overlap — runs before semantic similarity, with a hand-curated lookup catching what the gate misses.

  • Shadow ledger. Every opportunity the engine would have taken is logged with fill assumptions and fee modelling. Persists across restarts so the simulated PnL doesn't reset on every redeploy.
  • Risk profiles. Liquidity filters, max position, spread thresholds, time-in-force — all configurable per profile.
  • FastAPI control plane. Endpoints to inspect state, toggle modes, and force-flatten if something gets weird.
  • Compliance matrix. An explicit table of what shadow results must show before live mode is allowed for a given profile. Promotion is manual.

Python end-to-end. SQLAlchemy over Postgres for ledger and positions, pandas for analytical queries, 50+ config knobs so I can A/B profile changes without code edits. This is the simpler Python predecessor to the Go arbitrage engine — same problem, two venues instead of one cross-asset pair.

Where it's at

Shadow mode by default; 76+ tests passing on the engine. Live trading is gated behind manual promotion and small caps. Shadow PnL is what the engine would have done, not what it would have filled — the fee and slippage models are conservative guesses, and real execution will be worse. That's why I'm not in a hurry to flip the switch.