Arbitrage Examples and Validation Errors
This page collects complete arbitrage strategies (platform: arbitrage) for both venue pairs: fixed and rolling hedged locks, inverted labels, daily temperature buckets, inline selectors with a Kalshi fee override, a directional buy and custom rules. It ends with common validation errors and what to change for each. For what each key does, see All arbitrage parameters.
Example strategies
Each example below passes Studio's checks. Markets, IDs and numbers are illustrative placeholders. Ask Studio's search for real markets, and choose your own limits.
Fixed markets on Kalshi and Polymarket US
One pinned market on each venue, with matching labels.
version: 1
platform: arbitrage
strategy: complementary
strategy_name: Fed October decision - Kalshi vs Polymarket US
strategy_name_origin: user
venues:
a:
platform: kalshi
market:
ticker: KXFEDDECISION-26OCT-C25 # illustrative
b:
platform: polymarket_us
market:
slug: fed-cuts-rates-25bps-october-2026 # illustrative
mapping:
a_yes_equals: b_yes # both markets ask the same question
risk:
max_position_usdc: 25 # per lock, not in total
min_net_edge_bps: 150 # 1.5 cents per contract after estimated fees
max_leg_slippage_bps: 150
max_unhedged_seconds: 15
loop:
interval: 30- Each loop the Bot prices Kalshi YES + Polymarket US NO and Kalshi NO + Polymarket US YES at 1 contract, and locks the better one when its net edge is at least 150 bps.
- With the deploy limits Studio pre-fills (1 contract and $1 per order), each lock is 1 contract; see
risk.max_position_usdc. - With 150 bps of slippage, Kalshi's limit gains a cent only for asks of about 34¢ and up.
- If the hedge fails, fills a different quantity or takes longer than 15 seconds, the Bot halts. Being fixed-market, it stays halted.
- When the Kalshi market closes, the Bot stops.
Rolling 15-minute crypto on Kalshi and Polymarket
Both legs roll every window, paired by close time; see Rolling markets.
version: 1
platform: arbitrage
strategy: complementary
venues:
a:
platform: kalshi
market:
series_ticker: KXBTC15M
b:
platform: polymarket
market:
recurring:
kind: updown
asset: btc
interval: 15m # follows btc-up-or-down-15m
mapping:
a_yes_equals: b_yes # Up on Polymarket matches YES on Kalshi
risk:
max_position_usdc: 50
min_net_edge_bps: 200
max_leg_slippage_bps: 150
max_unhedged_seconds: 20
accept_resolution_source_mismatch: true # the venues settle on different price sources
loop:
interval: 15- Each window, the Bot pairs the Kalshi market that closes within 120 seconds of the current Polymarket market. If no Kalshi market matches, or more than one does, the loop skips.
- The Polymarket leg always goes first, and each lock must reach the market minimum, usually 5 shares. The deploy dialog raises Max contracts per order, Max dollars per order and Max open contracts to at least 5 for this pair.
- Polymarket fees use the market's live rate. If it can't be read, the loop skips.
accept_resolution_source_mismatch: truerecords that you accept that the two venues settle on different price sources. It doesn't change how the Bot trades.
Inverted labels on Polymarket
Kalshi's "Celtics win" against Polymarket's "Will the Lakers beat the Celtics?". Kalshi YES is Polymarket NO, so each lock is YES + YES or NO + NO. See mapping.a_yes_equals.
version: 1
platform: arbitrage
strategy: complementary
venues:
a:
platform: kalshi
market:
ticker: KXNBAGAME-26NOV12LALBOS-BOS # Celtics win (illustrative)
b:
platform: polymarket
market:
slug: will-the-lakers-beat-the-celtics-nov-12 # label only
condition_id: "0x5f3c2a9e1b7d4c6a8e0f2b4d6c8a0e2f4b6d8c0a2e4f6b8d0c2a4e6f8b0d2c4a"
yes_token_id: "71321045679252212594626385532706912750332728571942532289631379312455583992563"
no_token_id: "48331043336612883890938759509493159234755048973500640148014422747788308965732"
neg_risk: false
tick_size: "0.01"
min_order_size: "5"
mapping:
a_yes_equals: b_no # Kalshi YES (Celtics win) = Polymarket NO (Lakers don't win)
risk:
max_position_usdc: 25
min_net_edge_bps: 150
max_leg_slippage_bps: 150
max_unhedged_seconds: 15
loop:
interval: 30- The IDs are placeholders. Studio's Polymarket search returns the real condition ID, token IDs, price step and minimum order size.
- The yes token is "Lakers win", so the locks are Kalshi YES + Polymarket YES and Kalshi NO + Polymarket NO. Exactly one leg of each pays $1.
min_order_size: "5"is the size every lock must reach.tick_sizeis used only if the live price step can't be read.
Daily temperature buckets on Kalshi and Polymarket US
A rolling daily pair that trades only the 77 to 78 °F bucket each day, pinned with bucket.bounds.
version: 1
platform: arbitrage
strategy: complementary
venues:
a:
platform: kalshi
market:
series_ticker: KXHIGHNY
b:
platform: polymarket_us
market:
recurring:
kind: daily
event_slug_prefix: temp-nychigh
mapping:
a_yes_equals: b_yes
bucket:
select: pinned_bounds
bounds:
gte: 77
lte: 78
risk:
max_position_usdc: 25
min_net_edge_bps: 150
max_leg_slippage_bps: 150
max_unhedged_seconds: 20
accept_resolution_source_mismatch: true
loop:
interval: 30- Each day the Bot finds the current
temp-nychigh-YYYY-MM-DDevent, matches its buckets to the Kalshi markets that close within 120 seconds, and trades only the pair covering 77 to 78. - If that bucket has no matching pair that day, every loop skips.
- Remove the
bucketblock (or useselect: all) to let the Bot re-screen every matched bucket each loop instead.
Inline selectors and a Kalshi fee override
The compact selector form, a bucket chosen by even odds, and a flat Kalshi fee estimate.
version: 1
platform: arbitrage
strategy: complementary
strategy_name: Miami daily high - bucket nearest 50/50
strategy_name_origin: generated
venues:
a:
platform: kalshi
series_ticker: KXHIGHMIA # directly on the leg, no market block
b:
platform: polymarket_us
recurring:
kind: daily
event_slug_prefix: temp-miahigh
mapping:
a_yes_equals: b_yes
bucket:
select: nearest_even_odds
fees:
a:
flat_bps: 700 # Kalshi estimate: 7% of notional
risk:
max_position_usdc: 20
min_net_edge_bps: 100
max_leg_slippage_bps: 100
max_unhedged_seconds: 20
accept_resolution_source_mismatch: true
loop:
interval: 30nearest_even_oddspicks the matched bucket whose Kalshi midpoint is closest to 50¢, once per daily event, and stays on it until the event rolls.flat_bps: 700estimates Kalshi's fee as 0.07 × price per contract, unrounded. At 1 contract that's above the built-in estimate from about 30¢ up, and can be below it at lower prices.- Polymarket US has no 5-share minimum, so the pre-filled deploy limits of 1 contract and $1 per order are enough to trade.
Directional buy on one venue
Not arbitrage: a single-leg buy on Polymarket US. See directional for how it decides to buy.
version: 1
platform: arbitrage
strategy: directional
venues:
a:
platform: kalshi
market:
ticker: KXFEDDECISION-26OCT-C25
b:
platform: polymarket_us
market:
slug: fed-cuts-rates-25bps-october-2026
mapping:
a_yes_equals: b_yes # required, not used by directional
params:
leg: b # buy on Polymarket US
side: yes
max_price: 0.40
size: 1
risk:
max_position_usdc: 10 # position cap: $10 ÷ 0.40 = 25 contracts
min_net_edge_bps: 200
loop:
interval: 60- The Bot buys 1 YES contract when the ask is at or below 0.40 and (0.40 − ask) × 10,000, minus the fee in bps, is at least 200. At 0.37 it buys; at 0.38 it skips.
- It buys again every 60 seconds while that holds, until it holds 25 YES contracts in that market, then skips and logs "directional buy blocked by max_position". It never sells them.
- Max open contracts and Max daily notional traded from the deploy dialog can stop it sooner. With the limits Studio pre-fills for a $10 budget (10 open contracts, $10 a day), it stops at 10 contracts.
max_leg_slippage_bpsis left out, so it defaults to 25.
Custom rules on a rolling pair
Clear orders once a window is nearly decided, pause when the Kalshi balance is low, and lock only when Kalshi YES is cheaper than Polymarket YES. See custom for how rules run.
version: 1
platform: arbitrage
strategy: custom
strategy_name: ETH 15m - lock only when Kalshi YES is cheaper
strategy_name_origin: generated
venues:
a:
platform: kalshi
market:
series_ticker: KXETH15M
b:
platform: polymarket
market:
series_slug: eth-up-or-down-15m
mapping:
a_yes_equals: b_yes
risk:
max_position_usdc: 30
min_net_edge_bps: 150
max_leg_slippage_bps: 100
max_unhedged_seconds: 20
accept_resolution_source_mismatch: true
loop:
interval: 15
rules:
- name: window_nearly_decided_clear_orders
when:
any:
- {field: leg_b.yes_ask, op: ">=", value: 0.95}
- {field: leg_b.no_ask, op: ">=", value: 0.95}
action: cancel_all
- name: kalshi_balance_low
when:
all:
- {field: leg_a.balance, op: "<", value: 10}
action: skip
- name: lock_when_kalshi_yes_is_cheaper
when:
all:
- {field: leg_a.yes_ask, op: "<", value_field: leg_b.yes_ask}
- {field: fee_b, op: "<=", value: 0.02}
- {field: net_edge_bps, op: ">=", value: 200}
action: execute_lock- Rules run from the top, and the first match acts and ends the loop.
- Rule 1 cancels this Bot's resting orders on Kalshi and in the current Polymarket market, never ones you placed by hand.
- Rule 3 compares two live values with
value_field, and checks that the Polymarket fee for 1 contract is at most $0.02. These conditions only decide whether to try.execute_lockstill picks the better lock pair itself and appliesmin_net_edge_bps.
Common validation errors
These are common messages from Studio's checks on an arbitrage document, and what to change for each.
| Message | What to change |
|---|---|
version: unsupported arbitrage DSL version 2 (want 1) | Use version: 1. |
platform: arbitrage DSL must set platform: arbitrage | Use platform: arbitrage. The pair comes from venues.b.platform. |
strategy: unknown arbitrage strategy "lock" (want complementary|directional|custom) | Use one of the three strategies. |
venues.a.platform: leg A platform must be kalshi | Leg A is always Kalshi. |
venues.b.platform: leg B platform must be polymarket or polymarket_us | Use one of the two supported leg-B venues. |
venues.a.market: leg A must set a market selector | Add ticker or series_ticker. The same message exists for leg B. |
venues.a.market: Kalshi arbitrage leg must use either market.ticker or market.series_ticker, not both | Keep one. Check for an inline key next to a nested one. |
venues.b.market: regular Polymarket arbitrage leg must set condition_id, yes_token_id, and no_token_id, … | Add all three, or use series_slug or recurring. |
venues.b.market.neg_risk: negative-risk/multi-outcome Polymarket markets are not supported for arbitrage | Pick a binary market, or remove neg_risk: true. |
market.recurring.asset: must be one of btc, eth, sol, xrp, doge, hype, or bnb (same for market.recurring.interval) | Use a listed asset or interval. On a Polymarket leg B the path has no venues.b. prefix. |
venues.b.market: Polymarket US arbitrage leg cannot use regular Polymarket condition/token, … | Remove the regular-Polymarket keys. Use slug or recurring. |
venues.b.market.recurring.kind: Polymarket US recurring selector requires kind: daily | Add kind: daily. |
venues.b.market.recurring.event_slug_prefix: Polymarket US recurring selector requires event_slug_prefix … | Add the event slug without its date, such as temp-nychigh. |
venues.b.market: use either market.slug or market.recurring for Polymarket US, not both | Keep one. |
venues.a.market.series_ticker: recurring Polymarket US leg B requires a rolling Kalshi series_ticker on leg A … | Use series_ticker on leg A. |
venues: leg A rolls with market.series_ticker but regular Polymarket leg B is pinned to one market, … (or the reverse, leg B rolls …) | Make both legs roll, or both fixed. See Rolling markets. |
venues.b.market.min_order_size: must be a positive number | Write a number such as 5. |
mapping.a_yes_equals: must be b_yes or b_no | Set a_yes_equals. |
risk.max_position_usdc: must be > 0 | Set a per-lock budget above 0. |
loop.interval: must be >= 10 seconds (prevents API rate limiting) | Use 10 or more. |
fees.b: set flat_bps or fee_rate_bps, not both | Keep one key per leg. |
fees.a.fee_rate_bps: is a Polymarket fee input and cannot be used on the Kalshi leg A; … | Use fees.a.flat_bps, or remove fees.a. |
risk.min_net_edge_bps: must be a whole number, got 12.5 (any whole-number key) | Drop the decimals. |
bucket.bounds.gte: must be finite | Write a number, not .nan or .inf. |
bucket: bucket requires a recurring Polymarket US leg B (market.recurring with kind: daily) | Remove bucket, or use a rolling daily Polymarket US leg. |
bucket.select: unknown bucket selector "best" … | Use all, nearest_even_odds, max_volume or pinned_bounds. |
bucket.bounds: pinned_bounds requires bounds with gte and/or lte … | Add bounds.gte, bounds.lte or both. |
bucket.bounds: bounds is only used with select: pinned_bounds | Remove bounds, or use select: pinned_bounds. |
rules: custom arbitrage strategy requires at least one rule | Add a rule, or use complementary. |
rules[0].when.all[0].field: unknown arbitrage state field "price" | Use one of the arbitrage fields. |
rules[0].action: unknown arbitrage action "buy_yes" | Use execute_lock, cancel_all or skip. |
rules[0].when.all[0].value: must be a number (got 0.5); remove the quotes | Write the number without quotes. |
rules[0].action: buy@a requires explicit side and limit price rule params, … | Use execute_lock, cancel_all or skip. |
risk.accept_resolution_source_mismatch: DSL lists a resolution-source mismatch or candidate-only caveat; … | Studio only. Accept the listed settlement difference in chat, or choose a pair that settles the same way. |
For how validation works across venues, see the Strategy Reference overview.