Engineering

Polymarket Kalshi Arbitrage Bot: Cross-Venue Setup

A cross-venue arbitrage bot between Polymarket and Kalshi sounds easy and almost never is. The market pairs that line up, the funding rails, the latency math, and how the edge decays in real time.

Last reviewed · Jamal Okafor, Poly Syncer

A Polymarket Kalshi arbitrage bot is the kind of project that sounds boring on paper and almost always turns out hard in practice. The two venues list overlapping markets on elections, macro releases and a slice of sports, but they settle in different currencies, on different rails, under different regulators, with different fee schedules and different fill latencies. A naive bot that ignores any of those mismatches will quote correctly, fire correctly, and still lose money because the cash leg is stuck two banking days behind the crypto leg. This guide is the cross-venue setup it is realistic to ship as a small team: which market pairs actually overlap, where funding latency dominates raw spread, how to budget end-to-end latency, what to model for resolution risk, the spread detection loop in pseudocode, and the honest reasons most of these bots fail.

The shape of cross-venue arbitrage between Polymarket and Kalshi

The pitch is simple. Polymarket runs a Polygon-based CLOB denominated in USDC, with anonymous wallet-level access and almost no fiat plumbing. Kalshi runs a CFTC-regulated event-contract exchange denominated in US dollars, with KYC, ACH funding and a domestic clearing model. When both venues list a contract on the same underlying event, their implied probabilities should agree net of fees and funding cost. When they do not, a Polymarket Kalshi arbitrage bot can short the rich leg, buy the cheap leg, and harvest the convergence at resolution.

That is the textbook picture. Reality is messier because the two legs do not behave symmetrically. A Polymarket fill clears on Polygon in a few seconds. A Kalshi fill is matched in milliseconds inside the exchange but the bot is now holding a position in a separate brokerage account, in a separate currency, with a separate margin model. The only way to rebalance is to wire USD between the two venues, which is days of float. So in practice the bot is not flat after a paired fill. It is long-one-leg-on-Polymarket and short-one-leg-on-Kalshi until the underlying event resolves, sometimes weeks later. Every line of the design has to absorb that fact.

The general two-leg execution shape borrows heavily from the single-venue case covered in how to build a Polymarket arbitrage bot. The cross-venue layer adds three things on top: a market-pair mapper, a funding scheduler, and a resolution-risk model. Strip any of the three out and the bot becomes a directional bet wearing arbitrage clothing.

Which market pairs actually overlap

The first engineering problem is that Polymarket and Kalshi do not list identical contracts. Even when both venues cover the same event, the contract wording, the cutoff time, and the resolution source can differ. The bot needs an explicit, hand-curated pair table; pulling pairs by string similarity is how operators end up arbitraging two contracts that are subtly different and lose at resolution.

The clusters where genuine overlap is common:

For each pair, the bot stores a record with both market identifiers, the wording-normalisation note, the resolution source for each side, the cutoff timestamps, and a manual confidence score. Pairs below a threshold confidence are flagged for human review before they become eligible for the spread detection loop.

The funding logistics problem: USDC on chain vs USD ACH

If there is one section of this guide that newcomers underestimate, it is funding. The mechanical arbitrage is a two-leg fill. The operational arbitrage is moving money between Polygon USDC and a US dollar Kalshi balance, in both directions, fast enough that captured edge is not eaten by float.

The relevant rails:

DimensionPolymarketKalshi
Settlement currencyUSDC on PolygonUS dollars in brokerage account
Funding railBridge or on-ramp to USDC, then deposit to CLOBACH transfer or wire from US bank
Funding latencySeconds to minutes once USDC is in wallet1 to 3 business days for ACH, hours for wire
Fee structure0.75 percent taker, 0 maker (typical CLOB schedule)Per-contract trading fee schedule, see Kalshi docs
Market typesMostly binary, some categoricalBinary and bucketed scalar
KYC and accessWallet-level, no US residentsFull KYC, US residents permitted
ResolutionUMA optimistic oracleInternal resolution committee under CFTC oversight

The implication for the bot is that capital cannot be rebalanced fill-by-fill. Instead the operator pre-positions a working balance on each venue, sized to the expected directional drawdown over a holding period of one to two weeks. If the bot fires ten pairs in a week that all go long on Polymarket and short on Kalshi, the Kalshi USD balance is being drawn down faster than ACH can top it up. The funding scheduler watches both balances, predicts the next two business days of consumption, and triggers a top-up wire if either side falls below a configured floor.

None of this is exotic, but it is operational work the on-chain-only single-venue bot never has to do. A common shortcut is to overcapitalise: hold twice the working balance on each side and accept the lower return on capital in exchange for never being rail-limited. That is the right call for the first six months of running a Polymarket Kalshi arbitrage bot; only after the funding flow is fully observed should the operator try to optimise for tighter balances.

Latency budget breakdown

The latency story is where the cross-venue case diverges sharply from the single-venue case. A Polymarket-only bot lives in a 1.7 to 3.6 second end-to-end window. The cross-venue bot has two parallel windows, on two venues, with two different gating constraints, and the slower window dictates whether the trade is even worth attempting.

Cross-venue latency budget — Polymarket fill vs Kalshi fill vs funding rebalance

Latency budget for a cross-venue Polymarket Kalshi arbitrage bot A horizontal comparison of three latency horizons. The Polymarket fill leg takes roughly 1.7 to 3.6 seconds end-to-end. The Kalshi fill leg takes roughly 200 to 600 milliseconds. The funding rebalance via ACH takes 1 to 3 business days. The chart shows that the trading legs are well within the pattern close window of 5 to 30 minutes but the funding rebalance is orders of magnitude slower and dominates capital efficiency. Two trading legs (seconds scale) Polymarket fill 1.7–3.6 s end-to-end Kalshi 200–600 ms Slower leg = Polymarket. Fire Kalshi second to confirm fill there is reachable. Funding rebalance (days scale, not seconds) ACH between bank and Kalshi: 1–3 business days Pattern close window: 5–30 minutes. The bot is trade-latency-rich and funding-latency-poor.
Cross-venue latency lives on two scales. The trading legs measure in seconds and milliseconds and fit comfortably inside the pattern close window; the funding rebalance measures in business days and is the binding constraint on capital efficiency. The design lesson is that minimising trade latency is necessary but not sufficient. The bot also has to schedule funding so that the slow leg never starves the fast leg.

Two design rules fall out of the chart. First, fire the slower trading leg first. In this pairing that is Polymarket, because Polygon submission and confirmation dominate the millisecond-to-second window. If the Polymarket leg fills, the Kalshi leg is almost always reachable inside its own much shorter window. Firing Kalshi first leaves the bot with a US dollar position whose hedge is still in flight; if Polymarket fails, unwinding on Kalshi is fast but the bot has consumed liquidity for nothing. Second, the funding scheduler is a separate process, not an inline check. Inline checks add useless milliseconds to every signal; the scheduler runs every few minutes against working-balance forecasts and tops up before the trading path ever notices a shortfall.

Settlement and resolution risk differences

Settlement-source mismatch is the single largest hidden risk in a cross-venue book. Polymarket resolves through the UMA optimistic oracle: a proposer asserts the outcome, a dispute window opens, and absent challenge the result settles on chain. Kalshi resolves through an internal committee, supervised by the CFTC, that publishes a determination on a clear schedule. The two processes can disagree.

The most common disagreement is timing. A Kalshi macro contract often resolves the same day the official release lands. A Polymarket equivalent may take longer if the proposer waits, or shorter if a proposer fires immediately on a wire headline. A bot that assumes both sides resolve simultaneously will measure mark-to-market PnL incorrectly during the gap and may panic-unwind a leg that was about to settle in its favour.

The deeper disagreement is interpretation. Two contracts with subtly different wording can resolve opposite ways. The 2024 cycle had a handful of contracts where Kalshi resolved on certified totals while a Polymarket equivalent resolved on AP call, and in tight races those resolution timestamps fell on different days and occasionally on different outcomes. The Polymarket Kalshi arbitrage bot has to treat each pair's resolution sources as a first-class field, not a footnote, and the operator should be willing to flag pairs whose wording is not byte-identical as higher-risk.

Concretely, the resolution-risk model assigns each pair a probability that the two contracts resolve oppositely conditional on the underlying being close. For pairs with identical wording and identical sources this probability is essentially zero. For pairs with different sources it can be one to five percent. The bot's edge calculation has to subtract that probability times the maximum loss, not treat the pair as a riskless converge.

The spread detection loop

The detector reads cached top-of-book from both venues, normalises the Kalshi book into Polymarket-equivalent probabilities, applies the fee and funding-cost adjustments, and tests whether the net edge clears the configured threshold. The shape is similar to the YES/NO arbitrage detector covered in detail in the Polymarket arbitrage bot guide, but with cross-venue adjustments. A practical sketch:

# detector.py — cross-venue spread loop for Polymarket Kalshi arbitrage bot
from dataclasses import dataclass

POLY_TAKER = 0.0075        # 0.75% Polymarket CLOB taker fee
KALSHI_FEE = 0.0035        # illustrative Kalshi per-contract fee, see kalshi.com/docs
FUNDING_COST = 0.0020      # opportunity cost of locked capital over expected hold
MIN_NET_EDGE = 0.012       # require at least 1.2 cents per share net
RES_RISK_DEFAULT = 0.01    # 1% chance of opposite resolution, identical wording

@dataclass
class Quote:
    bid: float
    ask: float
    bid_size: float
    ask_size: float

@dataclass
class Pair:
    poly_id: str
    kalshi_id: str
    wording_identical: bool
    res_risk: float       # 0..1 prob of opposite resolution

def cross_venue_edge(pair: Pair, poly: Quote, kalshi: Quote):
    # Pattern: Polymarket YES rich vs Kalshi YES cheap
    # Sell Polymarket YES (hit bid), buy Kalshi YES (lift ask)
    gross_a = poly.bid - kalshi.ask
    fees_a  = POLY_TAKER * poly.bid + KALSHI_FEE * kalshi.ask
    net_a   = gross_a - fees_a - FUNDING_COST - pair.res_risk

    # Pattern: Polymarket YES cheap vs Kalshi YES rich
    gross_b = kalshi.bid - poly.ask
    fees_b  = POLY_TAKER * poly.ask + KALSHI_FEE * kalshi.bid
    net_b   = gross_b - fees_b - FUNDING_COST - pair.res_risk

    if net_a >= MIN_NET_EDGE and net_a > net_b:
        size = min(poly.bid_size, kalshi.ask_size)
        return ("sell_poly_buy_kalshi", net_a, size)
    if net_b >= MIN_NET_EDGE:
        size = min(poly.ask_size, kalshi.bid_size)
        return ("buy_poly_sell_kalshi", net_b, size)
    return None

async def loop(pairs, books):
    # books is a live cache fed by two websocket listeners
    async for pair_id in books.updates():
        pair = pairs[pair_id]
        signal = cross_venue_edge(pair, books.poly[pair.poly_id], books.kalshi[pair.kalshi_id])
        if signal:
            await coordinator.fire_two_leg(pair, signal)

Four properties of this loop matter. First, the funding-cost term is not a fee, it is the opportunity cost of holding the legs until resolution; it depends on the expected holding period and the bot's hurdle rate and is set per pair, not globally. Second, the resolution-risk term punishes pairs with subtly different wording; this is what stops the bot from greedily trading the most attractive pairs, which are usually attractive precisely because someone else has spotted the wording mismatch. Third, the size is capped at top-of-book on both sides. Sweeping deeper is possible but each level on each venue has its own fee math and the simple form above stops being safe. Fourth, the detector does not fire orders; it returns signals that the coordinator inspects against latency, funding and risk gates before submitting. The maker-vs-taker decision on the Polymarket leg specifically deserves its own modelling pass against the schedule documented in the Polymarket maker and taker fees breakdown, since making on one side and taking on the other can almost double net edge but trades fill certainty for latency.

Realistic edge expectations and decay

The honest version of cross-venue edge is that it is wider than single-venue edge but decays faster once observed. A new pair launches and runs at 3 to 8 cents of gross spread for the first hours, sometimes the first days. Within two or three days of liquid trading the spread typically compresses to 0.5 to 2 cents, which is below the all-in cost of the cross-venue trade for most retail operators once funding is priced in. The bot that captures the early window is fine. The bot that arrives late and tries to grind 1-cent gross spreads is paying the fee schedule to be flat.

What this means operationally is that the bot's monitor list has to be broad and its activation has to be early. Every newly-listed pair should be on the watchlist from listing day, not from the point where it shows up in third-party scanners. The market-pair table is maintained by hand precisely so that new pairs are added the same week they list, with their wording reviewed before the first signal is allowed to fire.

A second source of decay is participant entry. Once a pair is large enough to attract more than two arbitrageurs, the spread does not just compress, it becomes asymmetric: one side fills aggressively, the other lags, and the bots that arrive last eat single-leg risk on the lagging side. The defence is to size down as the pair matures and to widen the minimum net edge threshold for that pair specifically. Pairs that ran on 2 cents net at launch may need 1.2 to 1.5 cents minimum once mature, simply because the residual edge cannot absorb fill-rate degradation.

For context on what a healthy book even looks like before applying any of this, the structural notes in the Polymarket order book explainer describe the venue's liquidity shape; reading the Kalshi book against that mental model is the cleanest way to spot pairs where one venue is structurally thicker and therefore the directional risk of a single-leg failure sits naturally on one side.

Why most cross-venue arbitrage bots fail

Five failure modes account for most cross-venue bot deaths.

Treating the pair table as a search problem. Builders try to auto-match Polymarket and Kalshi contracts by string similarity and end up with pairs that look right and resolve differently. The pair table has to be hand-curated. There is no shortcut.

Ignoring funding float. The bot calculates net edge against a same-day flat assumption and never models the one to three business days of float on the Kalshi side. Net edge that looks like 1.5 cents is actually negative once cost-of-capital is priced over a two-week hold.

Symmetric latency assumptions. Operators assume both legs fill in similar time and fire them in any order. The reality is that the Polymarket leg has 10x the variance of the Kalshi leg, and firing Kalshi first regularly leaves the bot with a stranded position when Polymarket fails. The discipline is always to fire the slower leg first and gate the second leg on confirmation of the first.

Resolution mismatch denial. Builders flag wording differences in the design review and then ship anyway, assuming "close enough" pairs will resolve the same. They mostly do. The cases where they do not are exactly the close races where the position was largest, which is how the bot loses a year of edge in a single resolution event.

Regulatory naivete. Polymarket is geofenced from US residents. Kalshi is US-only in practice. A single operator running both legs from the same jurisdiction is, at minimum, in unclear regulatory territory. None of this is legal advice and the legal layer is outside the scope of this engineering guide, but builders who do not consult counsel before deploying production capital into a Polymarket Kalshi arbitrage bot find out the hard way that the technology was the easy part.

For builders who have shipped a single-venue Polymarket bot and are sizing up the cross-venue jump, the honest summary is this. The trading code is a straightforward extension of the single-venue case. The pair table is a few weeks of manual work that has to be redone every cycle. The funding plumbing is an operational system, not a feature. The resolution-risk model is the part most builders skip and is the part that decides whether the year is profitable. Build all four or do not build any of it.

Frequently asked questions

How is a Polymarket Kalshi arbitrage bot different from a single-venue Polymarket arbitrage bot?

The single-venue case is a latency game inside a few seconds with both legs settling on the same chain in the same currency. The cross-venue case keeps that fast trading layer but adds funding logistics in business days, an explicit market-pair table, and a resolution-risk model for cases where the two venues might disagree at settlement. Trade latency stops being the binding constraint; funding latency and resolution risk dominate edge.

Which market categories overlap most reliably between Polymarket and Kalshi?

US elections and political-control markets, macro releases like CPI and FOMC decisions, and a thin slice of season-long sports awards. Geopolitical one-offs occasionally produce wide spreads but the pairs are short-lived. In every category the bot needs hand-curated pairs because contract wording, cutoff times, and resolution sources differ subtly even when the underlying event is the same.

How much working capital does a cross-venue bot need on each side?

Enough to cover one to two weeks of expected directional drawdown without triggering an ACH top-up under stress. For a bot firing five to ten pairs per week at $500 to $2,000 per side, that is typically $20,000 to $50,000 of working balance on each venue. Overcapitalising during the first six months is correct; the funding flow has to be observed before the operator tries to run tighter.

Which leg should fire first?

The Polymarket leg, because Polygon submission and confirmation have higher variance than the Kalshi internal match. Firing Polymarket first and gating the Kalshi submission on confirmation of the Polymarket fill keeps single-leg risk on the lower-variance side. Firing Kalshi first is a common newcomer mistake and is responsible for a meaningful share of stranded positions.

What is the realistic edge after fees, funding, and resolution risk?

For a well-tuned bot on hand-curated pairs the all-in net edge is typically 0.8 to 2 cents per share on mature pairs and 3 to 6 cents on freshly-listed pairs in the first hours. Annualised return on the deployed working balance lands in the 5 to 15 percent range for retail-sized operators once funding, fees, and the resolution-risk premium are subtracted. Anything claiming dramatically higher is either using a different cost model or ignoring resolution risk.

Where can I read the Kalshi API and fee details?

The authoritative reference is the official documentation at kalshi.com/docs, which lists the contract types, the API surface, and the current fee schedule. The Polymarket counterpart is the Polymarket developer documentation; a cross-venue bot needs both open in front of the engineer for the duration of the build.

About the author

Jamal Okafor is an execution engineer on the Poly Syncer team. He focuses on low-latency order routing, cross-venue plumbing, and the unglamorous funding logistics that decide whether a research-grade arbitrage idea actually survives contact with two real exchanges.