Architecture
Polymarket vs Kalshi: multi-venue architecture in polybot
Two prediction markets, two totally different technical stacks. How polybot abstracts them behind a single venue interface — and what leaks through anyway.
Published Apr 13, 2026
Polymarket and Kalshi are both prediction markets. From a trader’s perspective, they’re similar. From a software perspective, they are entirely different species. This guide walks through what polybot’s venue abstraction looks like, and where the abstraction intentionally leaks.
The two stacks, side by side
| Polymarket | Kalshi | |
|---|---|---|
| Venue type | On-chain CLOB (Polygon) | Centralised exchange |
| Collateral | USDC on Polygon | USD from bank account |
| Auth | EIP-712 signed messages, proxy wallet | API key + HMAC |
| Market discovery | GraphQL + on-chain events | REST + FIX-adjacent API |
| Order submission | Signed payload to py-clob-client → CLOB | POST to /v2/orders |
| Cancel semantics | On-chain transaction (or soft cancel) | REST DELETE |
| Fill notifications | WebSocket + EVM event logs | WebSocket only |
| Fees | Zero maker/taker; gas on cancel | Tiered fee schedule |
| Resolution | Oracle (UMA optimistic) | Kalshi-run resolution committee |
| Regulation | Restricted in US; DEX legally elsewhere | CFTC-regulated |
The BaseVenue interface
polybot’s unification point is src/polybot/venues/base.py. The interface is deliberately small:
class BaseVenue(Protocol):
async def list_markets(self, filters: MarketFilters) -> list[Market]: ...
async def get_market(self, market_id: str) -> Market: ...
async def subscribe_book(self, market_id: str) -> AsyncIterator[BookUpdate]: ...
async def submit_order(self, order: Order) -> OrderAck: ...
async def cancel_order(self, order_id: str) -> CancelAck: ...
async def positions(self) -> list[Position]: ...
async def balance(self) -> Balance: ...
Strategies never import polymarket.py or kalshi.py. They take a BaseVenue and call its methods.
Where the abstraction holds
- Order lifecycle. Submit → ack → fill → settle is the same model for both.
- Market shape. Binary YES/NO with prices in
[0, 1]maps cleanly both ways. - Position accounting. Net YES shares, average price, realised/unrealised P&L are the same primitives.
- Risk. The risk service operates on
Positionobjects; doesn’t know or care which venue they came from.
Where the abstraction leaks
- Collateral is not fungible. $1000 in Polymarket USDC is not $1000 you can trade on Kalshi. Cross-venue arbitrage needs separate accounts. polybot’s
Balanceis a per-venue object; strategies that want cross-venue capital have to allocate explicitly. - Cancel cost differs by orders of magnitude. On Polymarket, canceling 50 orders costs gas. On Kalshi, canceling 50 orders costs nothing. Spread-farming logic that naively reprices frequently is a disaster on Polymarket and fine on Kalshi. polybot exposes a
cancel_cost_estimatemethod on each venue; market-making code reads it. - Identity semantics. A market on Polymarket has a
conditionId(a keccak hash of the market’s outcome set). Kalshi uses a human-readable ticker. polybot uses an opaquemarket_idstring; the mapping to each venue’s native identifier lives in the venue module. - Latency. Polymarket’s CLOB WebSocket lags on-chain events by 200–800ms. Kalshi is consistently under 100ms. Strategies that rely on tight latency must read
market.venue.latency_classand skip Polymarket if they need sub-100ms. - Resolution timing. Polymarket resolution via UMA has a dispute window (measured in hours). Kalshi resolution is immediate at outcome declaration. Strategies with post-resolution logic (e.g.
resolution_arb) account for this per-venue.
Unified risk across venues
The risk service in src/polybot/services/risk.py aggregates exposure across venues with per-venue USD-equivalent translation. Key invariants:
- Total exposure across all venues is capped by a global USD.
- Per-category exposure (e.g. “politics”) is capped too, so a single narrative can’t blow up your book even if it’s split across venues.
- A venue going dark (WebSocket drops, API returns 500s repeatedly) triggers a soft halt on new orders on that venue while existing positions are tracked normally.
What this lets you actually do
With a single polybot start, you can run:
arbitrageon Polymarket (within-venue YES/NO arb),stat_arbacross Polymarket and Kalshi on correlated macro markets,spread_farmon Polymarket only (Kalshi’s fees make it uneconomic),ai_modelon both, with per-venue plugin configs,
all under one risk budget, with one dashboard, one MCP server. The abstraction’s job is to make that configuration boring. Mostly, it does.
Lessons for your own multi-venue system
- Don’t try to hide cost differences. Expose
cancel_cost,latency_class, and per-venue fees as first-class fields on the venue object. - Don’t normalise identity prematurely. Keep opaque IDs; maintain venue-specific mappings internally.
- Do unify the risk model. Exposure, drawdown, loss limits are the one thing strategies should never think about per-venue.
- Accept the leak. Abstractions leak. Document where, and stop pretending otherwise.
Need an agent system built like this?
Cryptuon builds production AI agents, MCP integrations, and trading systems. polybot is our open-source showcase.