跳到正文
DEV Community· BotCookery·· 9 小時前AI 評分58

Polymarket 與 Kalshi 套利監察器只找到一個 2¢ 價差,扣除實際手續費後每份合約虧損 1.4¢

Our Polymarket-Kalshi arbitrage monitor found one 2¢ gap. After real fees it lost 1.4¢

AI 導讀

BotCookery 的套利監察器以唯讀方式測試 Kalshi 和 Polymarket US 的三組市場配對,唯一找到的 2¢ 表面價差扣除實際手續費後,每份合約虧損 1.4¢。文章指出,監察器還需按實際交易數量計算兩邊手續費和訂單簿深度,並核對合約結算來源與規則,以免把結算條件不同的市場當成套利。更新版通過 9 項測試,仍只讀取公開資料,不會下單。

正文

Originally published on BotCookery, free build recipes for AI-built trading and prediction-market bots. Written with AI assistance. Every number and log line below comes from read-only runs against the live Kalshi and Polymarket US public APIs on the afternoon of Oct 25, 2026. The monitor has no order code at all. Nothing here is financial advice.

"Risk-free arbitrage between Polymarket and Kalshi" is one of the most common bot pitches around. The idea is simple: if YES on one venue plus NO on the other costs less than $1.00, one of them has to pay $1.00, so you lock in the difference.

It only works if two things are true at once: the combined cost is under $1.00 after fees, and both contracts settle on exactly the same outcome. Our recipe builds an alerts-only monitor that measures both, for market pairs you map by hand. It never places, previews or cancels an order.

On Oct 25 we pasted the Oct 22 version of the prompt into a clean folder, let an AI coding tool build it, and ran it read-only on three real pairs: Magic at Knicks (NBA), Chiefs at Seahawks (NFL), and the 83-84°F bucket for Miami's Oct 26 high temperature. The skip rules, the stale-date rule and the "price is always YES" math were right. Six other things weren't.

Problem 1: the recommended Kalshi host answered 403

external-api.kalshi.com is the host Kalshi's docs recommend. From our network it answered 403, so the first run skipped all three pairs and reported nothing. The other documented host, api.elections.kalshi.com, worked fine.

The fix is boring but necessary: try the recommended host, switch once on a 403, and keep whichever works.

Kalshi https://external-api.kalshi.com/trade-api/v2 answered 403; switching to https://api.elections.kalshi.com/trade-api/v2

Problem 2: placeholder fees made a losing trade look break-even

The first build used a flat 1¢ per contract per venue as a placeholder. That doesn't match either venue's formula. Both fees scale with p × (1 - p), so they're largest near 50¢:

  • Kalshi taker: fee_multiplier × 0.07 × C × P × (1 - P), rounded up to the cent. The multiplier comes from GET /series/{series_ticker}.
  • Polymarket US taker: 0.0695 × C × p × (1 - p), rounded to the cent (banker's rounding).

On these books the real fees were 1.4-1.8¢ per contract on each leg. Here's the old build's first cycle at 3:41 PM ET:

!!! WARNING: fees.yaml has placeholder values. THESE EDGES ARE NOT REAL. !!!
Magic @ Knicks, Oct 25 (Orlando wins) [K YES + PM short] cost 0.2900+0.7200=1.0100 edge_after_fees -0.0300 depth 261080
Magic @ Knicks, Oct 25 (Orlando wins) [K NO + PM long] cost 0.7200+0.2900=1.0100 edge_after_fees -0.0300 depth 12550
Chiefs @ Seahawks, Oct 25 (Seattle ticker vs Chiefs-long) [K YES + PM long] cost 0.5900+0.4150=1.0050 edge_after_fees -0.0250 depth 1504150
Chiefs @ Seahawks, Oct 25 (Seattle ticker vs Chiefs-long) [K NO + PM short] cost 0.4200+0.5875=1.0075 edge_after_fees -0.0275 depth 651638
Miami high Oct 26, 83-84F [K YES + PM short] cost 0.6300+0.4100=1.0400 edge_after_fees -0.0600 depth 37
Miami high Oct 26, 83-84F [K NO + PM long] cost 0.3800+0.6000=0.9800 edge_after_fees +0.0000 depth 12
SKIP Stale test pair: rules_checked_on is 24 days old (>14)

That last Miami line is the only gross gap we saw all afternoon: Kalshi NO at $0.38 plus Polymarket US YES at $0.60 = $0.98. With placeholders it looks like a wash. Now the real math for 12 contracts:

  • Kalshi: 0.07 × 12 × 0.38 × 0.62 = $0.198, rounded up to $0.20
  • Polymarket US: 0.0695 × 12 × 0.6008 × 0.3992 = $0.20 (0.6008 is the 12-contract average, see problem 3)
  • Total per contract: 0.38 + 0.6008 + 0.40 / 12 = 1.0142

So the edge is -1.4¢ a contract. The one "arbitrage" of the afternoon loses money before you even think about timing. The new prompt computes both published formulas per leg, at the size you'd actually trade, keeps the coefficients in fees.yaml with a checked_on date, and warns when that date is over 30 days old.

Problem 3: "depth" meant the top level only

Polymarket US showed 11.66 contracts at $0.60. Twelve contracts don't fit in 11.66, so the real 12-contract average was $0.6008. A few minutes later someone had taken most of that offer, and the same 12 contracts averaged $0.6109.

The monitor now walks each book across levels, volume-weighted, for the size you'd trade, and logs "too thin" if either side can't fill it. One more trap: in Polymarket US's /bbo response, askDepth and bidDepth are not contract counts. Take sizes from /book.

Problem 4: the Miami gap wasn't an arbitrage anyway

Even with a positive edge, that pair would have been a bet, not an arbitrage. On Oct 25 Kalshi's Miami high-temperature market settled "according to The Weather Company". Polymarket US's settled on the National Weather Service's daily climate report. Same city, same day, same bucket, two different sources. They can disagree by a degree, and one degree moves a bucket.

The NFL pair differed too. Both venues paid $0.50 on a tie, but on a postponement Kalshi went to a fair price after 48 hours while Polymarket US waited up to two weeks.

The old prompt had a free-text notes field, which let us type "same bucket, same day" and move on. The new one makes you prove it:

  • Each pair gets kalshi_source, polymarket_source, tie_and_delay, rules_match (default false) and rules_hash.
  • --show-rules prints Kalshi's rules_primary, rules_secondary and the series settlement_sources next to the Polymarket US description, with one sha256 of the combined text.
  • A pair is skipped unless rules_match is true and the live hash still equals the saved one. If either venue edits its rules, the pair stops until you re-read them.

We tested that last part by changing one saved fingerprint:

SKIP Chiefs @ Seahawks, Oct 25 (Seattle): live rules hash 4acddea46b19a75e != saved 0000000000000000; re-read with --show-rules

Problem 5: it couldn't tell you how long a gap lasted

The first build would have re-alerted every 30 seconds while a gap stayed open and never logged when it closed. Gap duration is the number that matters most if you ever wanted to act by hand, so now it alerts once and logs a CLOSED line with how many seconds it lasted and the best edge seen.

Problem 6: piped output stayed empty

Our first 100-second run, piped through tee, printed nothing. Python buffers stdout when it isn't a terminal. Every line now prints with flush=True.

One near-miss: which end of Kalshi's book is the best bid

Kalshi's GET /markets/{ticker}/orderbook returns bids only, as [price, size] pairs sorted ascending, so the best bid is the last element. The YES ask is $1 minus the best NO bid. Our build happened to get this right, but the old prompt never said it, and taking the first element gives you a $0.99 ask. The new prompt spells it out and has a test for it.

After the fixes

Fresh rebuild from the new prompt, 9 tests passing, 3:46 PM ET, 10 contracts, the real pairs file:

Kalshi https://external-api.kalshi.com/trade-api/v2 answered 403; switching to https://api.elections.kalshi.com/trade-api/v2
Magic @ Knicks, Oct 25 (Orlando wins) [K YES + PM short] x10: K 0.2900 fee $0.15 | PM 0.7200 fee $0.14 | edge -0.0390/contract
Magic @ Knicks, Oct 25 (Orlando wins) [K NO + PM long] x10: K 0.7200 fee $0.15 | PM 0.2900 fee $0.14 | edge -0.0390/contract
SKIP Chiefs @ Seahawks, Oct 25 (Seattle): rules_match is not true
SKIP Miami high Oct 26, 83-84F: rules_match is not true

To check the alert path and the math, we ran a copy with every pair forced to rules_match: true, 12 contracts and a deliberately negative margin so alerts would fire (Miami lines, 3:48-3:51 PM ET):

ALERT Miami high Oct 26, 83-84F [K NO + PM long] x12: K 0.3800 fee $0.20 | PM 0.6008 fee $0.20 | edge -0.0142/contract
CLOSED Miami high Oct 26, 83-84F [K NO + PM long] after 73s, best edge -0.0142 (edge -0.0242)
Miami high Oct 26, 83-84F [K NO + PM long] x12: K 0.3800 fee $0.20 | PM 0.6109 fee $0.20 | edge -0.0242/contract

That's the whole story of the afternoon in three lines: the only gap was negative after fees, it got worse when the offer was taken, and the monitor logged exactly how long it lasted.

Built this before Oct 25? The follow-up prompt

If you built an earlier version, paste this into the same project:

Update my alerts-only arbitrage monitor with these fixes. Still no order
code of any kind; public reads only.

1. Kalshi host: try https://external-api.kalshi.com/trade-api/v2 first;
   on a 403, switch once to https://api.elections.kalshi.com/trade-api/v2
   and keep it. KALSHI_BASE still overrides both.
2. Check the Kalshi book math: arrays are bids sorted ASCENDING, so the
   best bid is the LAST element. YES ask = 1 - last NO bid, NO ask =
   1 - last YES bid. Taking the first element gives a $0.99 ask.
3. Replace the placeholder fees with the real formulas, computed per leg
   for the size you'd trade:
   Kalshi taker = fee_multiplier x 0.07 x C x P x (1-P), rounded UP to
   the cent (fee_multiplier from GET /series/{series_ticker}).
   Polymarket US taker = 0.0695 x C x p x (1-p), banker's rounding to the
   cent. Keep the coefficients in fees.yaml with a checked_on date and
   warn if it's older than 30 days.
4. Price min_size contracts by walking each book across levels
   (volume-weighted). If either side can't fill it, log "too thin".
   Take sizes from /book; /bbo askDepth and bidDepth are not sizes.
5. Add kalshi_source, polymarket_source, tie_and_delay, rules_match
   (default false) and rules_hash to each pair. Add --show-rules to print
   Kalshi rules_primary + rules_secondary + series settlement_sources
   next to the Polymarket US description, with a sha256 of both. Skip a
   pair unless rules_match is true and the live hash equals rules_hash.
6. Alert once when a gap opens; log a "closed" line with how many
   seconds it lasted and its best edge. Print with flush=True.
7. Tests for each of the above.

The full build prompt, the step-by-step for mapping pairs by hand, and the safety checklist are on the recipe page.

Takeaways if you're building one

  • Use the real fee formulas at your real size. Both fees peak near 50¢. A flat placeholder will show you gaps that don't exist.
  • Walk the book. Top-of-book size is often smaller than the trade, and it moves within minutes.
  • Read both rulebooks for every pair, and fingerprint them. "Same event" isn't the same contract if the settlement source, tie rule or postponement rule differs.
  • Log how long gaps last. That, plus size and edge after fees, is the honest answer to "do arbitrage bots work?"
  • Keep it private. Kalshi's Developer Agreement limits API data to your own trading and bars sharing it with third parties without written permission, so no public arb dashboards or paid alert channels.

What we verified, and what we didn't

From the recipe page: Verified working on live data in read-only mode, Oct 25, 2026. Ran read-only against the live Kalshi and Polymarket US public APIs on the afternoon of Oct 25, for several cycles on three hand-mapped pairs (one NBA game, one NFL game, one Miami temperature bucket), plus a forced-alert drill and a changed-rules drill. No gap that survived fees appeared, so no real alert fired. The Telegram and desktop alerts were not tested and no message was sent.

Fee coefficients and contract rules are as we read them on Oct 25. They change, so check both venues' fee pages and rules before you trust any number here.

來源:DEV Community · dev.to