Arbitrage Venues and Selectors
This page covers the venues block of an arbitrage strategy (platform: arbitrage): how leg A (Kalshi) and leg B (Polymarket or Polymarket US) each pick a market, how the two legs stay lined up when their markets roll, and which selectors save only as a draft. For the two venue pairs and how they differ, see Supported venue pairs; for every venue key at a glance, see All arbitrage parameters.
Each leg is a venue plus a market selector:
venues:
a:
platform: kalshi
market:
series_ticker: KXHIGHNY
b:
platform: polymarket_us
market:
recurring:
kind: daily
event_slug_prefix: temp-nychighWhich selector keys work depends on the leg's venue. A key that belongs to another venue is rejected. Search placeholders save as a draft that can't deploy yet; see Draft selectors.
Which selector keys each leg accepts
| Selector key | Kalshi (leg A) | Polymarket (leg B) | Polymarket US (leg B) |
|---|---|---|---|
ticker | Deployone fixed market | Rejected | Rejected |
series_ticker | Deployrolling series | Rejected | Rejected |
event_ticker | Draft onlysearch placeholder | Rejected | Rejected |
query | Draft onlysearch placeholder | Draft onlysearch placeholder | Draft onlysearch placeholder |
slug | Rejected | Label onlyneeds condition and token IDs to deploy | Deployone fixed market |
event_slug | Rejected | Draft onlysearch placeholder | Draft onlysearch placeholder |
series_slug | Rejected | Deployrolling crypto series | Rejected |
condition_id + yes_token_id + no_token_id | Rejected | Deployone fixed market; all three together | Rejected |
recurring | Rejected | Deploykind updown, asset, interval; event_slug_prefix ignored | Deploykind daily, event_slug_prefix; leg A must use series_ticker |
neg_risk, tick_size, min_order_size | Rejected | Optionalneg_risk must be false | Rejectedeven neg_risk: false |
selection | Rejecteduse bucket.select | Rejected | Rejected |
To deploy, each leg needs one deployable selector:
- Kalshi:
tickerorseries_ticker, not both. - Polymarket:
condition_idwith both token IDs, orseries_slug, orrecurring. - Polymarket US:
slugorrecurring, not both.
Inline or nested selectors
Selector keys can sit under market, or directly on the leg. These two are the same:
venues:
a:
platform: kalshi
market:
ticker: KXFEDDECISION-26OCT-C25venues:
a:
platform: kalshi
ticker: KXFEDDECISION-26OCT-C25If one leg uses both forms, they're merged key by key, and a key under market wins without a warning. Mixing can also leave two conflicting selectors: an inline ticker plus a nested series_ticker keeps both and can't deploy ("must use either market.ticker or market.series_ticker, not both"). Use one form per leg. Error messages name the path as venues.a.market or venues.b.market either way. (Regular-Polymarket recurring errors are the exception: they start at market.recurring… or market.series_slug.)
Unknown keys inside a leg are ignored, so a misspelled selector is only caught when it leaves the leg with no selector at all.
Kalshi leg A
tickerpins one Kalshi market. When that market has closed or settled and its book is empty, the Bot stops.series_tickerfollows a repeating series, such as a 15-minute crypto series or a daily temperature series. The Bot uses the series' open market that closes soonest, preferring markets with quotes. When that market closes, the Bot waits for the next one. How it lines up with leg B is covered in Rolling markets.
The ticker also sets the Kalshi fee estimate; see Built-in fee estimates.
market.selection, which single-venue Kalshi strategies use to pick a bracket, is rejected here. For bucketed daily series, use a rolling Polymarket US leg with bucket.
Polymarket leg B
A fixed Polymarket leg needs all three of condition_id, yes_token_id and no_token_id. Studio's Polymarket search finds them. The other keys are optional:
slugis a readable label. On its own it can't deploy, and Studio saves the strategy as a draft.neg_risk: falseis optional documentation.neg_risk: trueis rejected: negative-risk, multi-outcome markets aren't supported.tick_sizeis a fallback. The Bot reads each token's live price step and uses this value only if that read fails. Default"0.01". Studio doesn't check this value here, so a typo such as"0.5"saves. Copy it from Studio's Polymarket search.min_order_sizeis the market's smallest order, default 5. Every lock must reach it. The deploy dialog's Max contracts per order must be at least this size. The dialog raises it to at least 5 for this pair, so if a market's minimum is higher, raise the limit or every lock is skipped. Write a positive number (5or"5"); anything else, such as"abc"or0, is rejected when you save.
mapping.a_yes_equals is read against yes_token_id: "leg-B YES" means whichever outcome that token is. If you swap the two token IDs, flip the mapping too.
A rolling Polymarket leg follows a repeating crypto up/down series. Write the series slug yourself, or let recurring build it. These two are the same:
venues:
b:
platform: polymarket
market:
recurring:
kind: updown # the only kind on Polymarket; optional
asset: btc
interval: 15mvenues:
b:
platform: polymarket
market:
series_slug: btc-up-or-down-15masset | Also accepts |
|---|---|
btc | bitcoin |
eth | ethereum, ether |
sol | solana |
xrp | ripple |
doge | dogecoin |
hype | hyperliquid |
bnb | binance |
interval | Also accepts | Series it follows |
|---|---|---|
5m | 5min, 5mins | <asset>-up-or-down-5m |
15m | 15min, 15mins | <asset>-up-or-down-15m |
1h | hour, hourly | <asset>-up-or-down-hourly (solana- for sol) |
4h | none | <asset>-up-or-down-4h |
1d | day, daily | <asset>-up-or-down-daily (solana- for sol, dogecoin- for doge) |
Case doesn't matter, and spaces and hyphens in interval are ignored, so 15 min works too.
How a rolling Polymarket leg behaves:
- The Bot trades the series' active market that ends soonest. From 10 seconds before that market ends, it re-checks the series on every loop, and moves to the next market once the current one has ended.
- The market must be binary (Yes/No or Up/Down) and not negative-risk. On Up/Down markets, leg-B YES is Up.
- If you set
series_slugand token IDs, the series wins. If you setseries_slugandrecurring, they must name the same series. event_slug_prefixbelongs to Polymarket US and is ignored here.- A line that reads exactly
recurring: trueorrecurring: yesis ignored. If that was the leg's only selector, the leg has none and is rejected. Any other single value, such asrecurring: false, rejects the save with a YAML error. Use a fullrecurringblock orseries_slug.
Polymarket US leg B
slugpins one Polymarket US market. Many slugs include a date, so a pinned slug stops trading once that market closes.recurringwithkind: dailyandevent_slug_prefixfollows a daily event family, such as a city's daily high temperature. Each day's event has several bucket markets, which the Bot matches to the Kalshi series' markets. See Buckets.
venues:
a:
platform: kalshi
market:
series_ticker: KXHIGHNY # required with a rolling daily leg B
b:
platform: polymarket_us
market:
recurring:
kind: daily
event_slug_prefix: temp-nychigh # no date, no market prefixRules for a rolling daily leg:
- Leg A must use
series_ticker, so both legs roll together. - Don't combine it with
slug. event_slug_prefixis the event slug without its date. The Bot adds-YYYY-MM-DDfor yesterday, today and tomorrow (UTC), and uses the open event whose markets close soonest, at least a minute from now. Don't include the date, or thetc-prefix that the event's market slugs carry.assetandintervalare rejected here.
Polymarket US also rejects the regular-Polymarket keys: condition_id, the token IDs, neg_risk (even false), tick_size, min_order_size and series_slug.
A rolling daily leg with a fixed Kalshi ticker is rejected:
# Rejected: a rolling daily Polymarket US leg needs a rolling Kalshi series on leg A
# error: recurring Polymarket US leg B requires a rolling Kalshi series_ticker on leg A so both legs roll together
version: 1
platform: arbitrage
strategy: complementary
venues:
a:
platform: kalshi
market:
ticker: KXHIGHNY-26SEP27-B77.5
b:
platform: polymarket_us
market:
recurring:
kind: daily
event_slug_prefix: temp-nychigh
mapping:
a_yes_equals: b_yes
risk:
max_position_usdc: 25
loop:
interval: 30Rolling markets
How the two legs line up depends on which of them roll:
| Leg A | Leg B | How the legs are paired |
|---|---|---|
ticker | Polymarket token IDs, or a Polymarket US slug | Both fixed. You choose the pair. |
series_ticker | Polymarket series_slug or recurring | Each window, the Kalshi market that closes within 120 seconds of the Polymarket market. If none does, or more than one does, the loop skips. |
series_ticker | Polymarket US slug | The Kalshi market that closes within 120 seconds of the Polymarket US market. After that market closes, leg B can't roll. |
series_ticker | Polymarket US recurring | Buckets are matched every day. See Buckets. |
ticker | Polymarket US recurring | Rejected. |
series_ticker | Polymarket token IDs | Rejected. Only Kalshi would roll, so the legs would trade different windows. |
ticker | Polymarket series_slug or recurring | Rejected. Only leg B would roll, so later windows wouldn't match. |
As a second line of defense, once either leg rolls the Bot checks right before placing each lock that both legs' markets close within 120 seconds of each other. If they don't, or either close time is unknown, it skips the lock and logs "refusing arbitrage lock". Two fixed legs are never checked: you chose that pair.
A rolling leg next to a fixed one is rejected. Here the Kalshi series rolls to a new 15-minute market every window, while leg B stays on one Polymarket market:
# Rejected: a rolling Kalshi series can't be paired with a fixed Polymarket market
# error: leg A rolls with market.series_ticker but regular Polymarket leg B is pinned to one market
version: 1
platform: arbitrage
strategy: complementary
venues:
a:
platform: kalshi
market:
series_ticker: KXBTC15M
b:
platform: polymarket
market:
condition_id: "0x5f2c0e8a4b1d9c3e7a6f0b2d4c8e1a3f5b7d9c0e2a4f6b8d0c2e4a6f8b0d2c4e"
yes_token_id: "71321045863081256108345919573942158337210856419052614930512845961230987654321"
no_token_id: "52114209337610457788219334650091827364509128374650192837465019283746501928374"
mapping:
a_yes_equals: b_yes
risk:
max_position_usdc: 25
loop:
interval: 30A Kalshi series that lists several markets with the same close time, such as daily temperature bands, can't be paired with a Polymarket series or a Polymarket US slug. The Bot skips and logs that the series "cannot be auto-paired". Use a rolling daily Polymarket US leg with bucket matching, or pin ticker to one band and pair it with a fixed leg-B market.
Draft selectors
query (either leg), Kalshi event_ticker and leg-B event_slug are search placeholders. So is a Polymarket slug without token IDs. Studio's AI uses them while it's still looking for the exact market. Studio saves the strategy as a draft, and you can't deploy it until each leg has a deployable selector and no placeholder is left.
A leftover placeholder blocks deploy even next to a deployable selector:
# Rejected: this can't deploy while the event_ticker search placeholder is still set (Studio keeps it as a draft)
# error: Kalshi query and event_ticker are discovery-only
version: 1
platform: arbitrage
strategy: complementary
venues:
a:
platform: kalshi
market:
ticker: KXFEDDECISION-26OCT-C25
event_ticker: KXFEDDECISION-26OCT
b:
platform: polymarket_us
market:
slug: fed-cuts-rates-25bps-october-2026
mapping:
a_yes_equals: b_yes
risk:
max_position_usdc: 25
loop:
interval: 30To fix it, ask Studio to resolve the exact markets and remove the search fields.