<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dozor</title>
    <description>The latest articles on DEV Community by Dozor (dozor).</description>
    <link>https://dev.to/dozor</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15127%2F5c00ff19-e89e-4b39-aa9a-bb0b0056f55f.png</url>
      <title>DEV Community: Dozor</title>
      <link>https://dev.to/dozor</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dozor"/>
    <language>en</language>
    <item>
      <title>Streaming Polymarket and Kalshi Order Books over WebSockets: 7 Lessons from Production</title>
      <dc:creator>Dozor | Esports Surebet Scanner</dc:creator>
      <pubDate>Sat, 10 Oct 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/dozor/streaming-polymarket-and-kalshi-order-books-over-websockets-7-lessons-from-production-3khf</link>
      <guid>https://dev.to/dozor/streaming-polymarket-and-kalshi-order-books-over-websockets-7-lessons-from-production-3khf</guid>
      <description>&lt;p&gt;We run a live arbitrage scanner that prices esports markets on &lt;a href="https://dozor.bet/en/bookmakers/polymarket" rel="noopener noreferrer"&gt;Polymarket&lt;/a&gt; and &lt;a href="https://dozor.bet/en/bookmakers/kalshi" rel="noopener noreferrer"&gt;Kalshi&lt;/a&gt; against 25 sportsbooks. Both exchanges are read through WebSocket order-book channels in Go (&lt;code&gt;gorilla/websocket&lt;/code&gt;). A wrong top-of-book here is not a cosmetic bug: the price becomes decimal odds, the odds become an arb, and the arb is someone's real money. These are the lessons that cost us the most to learn.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. On Polymarket, trust the server's best bid/ask, not the book you rebuilt
&lt;/h2&gt;

&lt;p&gt;Polymarket's public &lt;code&gt;/ws/market&lt;/code&gt; channel sends a &lt;code&gt;book&lt;/code&gt; snapshot and then &lt;code&gt;price_change&lt;/code&gt; deltas that also carry the server's &lt;code&gt;best_bid&lt;/code&gt;/&lt;code&gt;best_ask&lt;/code&gt;. The obvious design is to apply deltas to a local book and read the top of it. We measured that on a live channel — 1,200 tokens, 5 minutes, 24.6k deltas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;About &lt;strong&gt;1% of deltas&lt;/strong&gt; left our locally rebuilt book with a different top-of-book than the server reported, and the divergence was sticky — it did not converge by itself.&lt;/li&gt;
&lt;li&gt;We checked who was right by forcing a fresh snapshot: of 106 divergences, the new &lt;code&gt;book&lt;/code&gt; confirmed the &lt;strong&gt;server's best in 87 cases&lt;/strong&gt; and our local book in one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the price comes from the server's &lt;code&gt;best_bid&lt;/code&gt;/&lt;code&gt;best_ask&lt;/code&gt;, and the divergence itself became a &lt;strong&gt;desync detector&lt;/strong&gt;: if levels disagree with the server, depth from that book is not trusted and the book is rebuilt.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Resync by unsubscribe + subscribe — a plain re-subscribe does nothing
&lt;/h2&gt;

&lt;p&gt;To rebuild one token's book we unsubscribe and subscribe it again; the server sends a fresh &lt;code&gt;book&lt;/code&gt; in &lt;strong&gt;89 ms on average (119 ms max)&lt;/strong&gt;. Re-subscribing without unsubscribing first does not work: the channel answers with the text &lt;code&gt;NO NEW ASSETS&lt;/code&gt; and sends no snapshot.&lt;/p&gt;

&lt;p&gt;Resyncs are batched (every 500 ms, at most 200 tokens per tick) with a per-token cooldown, so a burst of divergences cannot turn into a storm of subscription frames. One more limit we hit: a subscribe/unsubscribe message carries at most &lt;strong&gt;500 asset IDs&lt;/strong&gt;; anything past that is dropped without an error.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Know who answers your heartbeat
&lt;/h2&gt;

&lt;p&gt;On Polymarket, protocol-level ping frames are answered — but by the Cloudflare edge, not by the origin. A dead stream behind a live edge keeps answering control pings, so a read deadline extended by those pongs would keep a frozen book "alive". We use the channel's application-level heartbeat instead: a text &lt;code&gt;PING&lt;/code&gt; every 10 s, a text &lt;code&gt;PONG&lt;/code&gt; back from the channel itself, and a 30 s read deadline. Writes get their own 5 s deadline — without it, a full send buffer hangs the book forever and silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. On Kalshi, server pings must extend your read deadline
&lt;/h2&gt;

&lt;p&gt;Kalshi does the opposite: the server sends control pings every 10 seconds, and those pings are the sign of a live channel. Our read loop extended the 30 s deadline only on data frames. &lt;code&gt;gorilla/websocket&lt;/code&gt;'s default ping handler replies with a pong but does not touch the read deadline.&lt;/p&gt;

&lt;p&gt;Most of the week that did not matter — order books change often enough. Then, every Thursday during Kalshi's maintenance window (07:00–09:00 UTC, when trading pauses), books stop changing, and the connection dropped every &lt;strong&gt;30–80 seconds&lt;/strong&gt; with &lt;code&gt;i/o timeout&lt;/code&gt;. Each reconnect cleared the cache and re-fetched about a thousand snapshots. It was not a regression; it had happened every Thursday since we moved Kalshi to WebSockets. The fix is four lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;pong&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PingHandler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetPingHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appData&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetReadDeadline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readWait&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pong&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;appData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A downstream guard that holds the last good snapshot when a book suddenly empties kept the drops off users' screens — which is also why it took weeks to notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Kalshi's &lt;code&gt;seq&lt;/code&gt; is per subscription, not per market
&lt;/h2&gt;

&lt;p&gt;We subscribed &lt;code&gt;orderbook_delta&lt;/code&gt; to 30 tickers at once. The 30 &lt;code&gt;orderbook_snapshot&lt;/code&gt; messages arrived with &lt;code&gt;seq&lt;/code&gt; 1 to 30, and every delta after that, across all tickers, continued one shared sequence (31, 32, 33…). A gap in &lt;code&gt;seq&lt;/code&gt; therefore means the whole subscription is suspect: we rebuild the subscription, not one market. Partial recovery would be a guess, not documented behaviour.&lt;/p&gt;

&lt;p&gt;The WebSocket handshake is signed: an RSA-PSS signature of &lt;code&gt;timestamp_ms + "GET" + "/trade-api/ws/v2"&lt;/code&gt; in the &lt;code&gt;KALSHI-ACCESS-*&lt;/code&gt; headers. An unsigned request gets &lt;code&gt;401&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Use WebSockets only where they pay off
&lt;/h2&gt;

&lt;p&gt;Measured traffic on Kalshi: 30 tickers produced 158 deltas in 25 s — 6.3 messages/s, about 0.2 per ticker. For the ~640 open esports markets that day, that extrapolates to roughly &lt;strong&gt;135 messages/s&lt;/strong&gt; in one subscription on one connection, far from needing sharding.&lt;/p&gt;

&lt;p&gt;Market structure is a different story. &lt;code&gt;market_lifecycle_v2&lt;/code&gt; cannot be filtered by ticker, so it streams the lifecycle of the whole exchange — weather, politics, economics — and its messages carry no tag that says "esports". We kept structure on REST (&lt;code&gt;/series?category=Sports&lt;/code&gt;, then &lt;code&gt;/events&lt;/code&gt; per series, refreshed every 15 s) and moved only the order books to WebSockets, which is where the REST budget was going.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Decimals, stale books and one reconnect loop
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decimals, not floats.&lt;/strong&gt; Odds are computed as &lt;code&gt;1/best&lt;/code&gt;, so a rounding error becomes a phantom arb. Prices are compared as decimals, not strings: the server does not guarantee the same text for one price in a snapshot and in a later delta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A stale book is worse than none.&lt;/strong&gt; On disconnect we drop the books instead of keeping them. For Polymarket the scanner then falls back to the slower Gamma listing price until fresh snapshots arrive; a frozen book would quote prices that no longer exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One reconnect loop for everyone.&lt;/strong&gt; Polymarket, Kalshi and a third WebSocket book each had their own copy of the lifecycle and backoff code (1 s, doubling to 30 s). A fix in one copy did not reach the others, so they now share one package.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this feeds
&lt;/h2&gt;

&lt;p&gt;These caches feed a canonical market model shared with the sportsbook adapters; a matcher maps the same match across 27 books, and a server-side engine recomputes arbs on every cycle, net of each market's exchange fee. The data side of the project is in our previous post, &lt;a href="https://dev.to/dozor/polymarket-kalshi-arbitrage-vs-sportsbooks-30-days-of-esports-data-1dc2"&gt;Polymarket &amp;amp; Kalshi Arbitrage vs Sportsbooks: 30 Days of Esports Data&lt;/a&gt;. The scanner itself: &lt;a href="https://dozor.bet/en/prediction-markets" rel="noopener noreferrer"&gt;prediction market arbitrage&lt;/a&gt;; the price conversion it relies on: &lt;a href="https://dozor.bet/en/odds-converter" rel="noopener noreferrer"&gt;odds converter&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Dozor is an esports arbitrage scanner covering 25 sportsbooks plus Polymarket and Kalshi. It is an information tool, not a bookmaker. 18+.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>websocket</category>
      <category>polymarket</category>
      <category>kalshi</category>
    </item>
    <item>
      <title>Polymarket &amp; Kalshi Arbitrage vs Sportsbooks: 30 Days of Esports Data</title>
      <dc:creator>Dozor | Esports Surebet Scanner</dc:creator>
      <pubDate>Tue, 06 Oct 2026 13:51:39 +0000</pubDate>
      <link>https://dev.to/dozor/polymarket-kalshi-arbitrage-vs-sportsbooks-30-days-of-esports-data-1dc2</link>
      <guid>https://dev.to/dozor/polymarket-kalshi-arbitrage-vs-sportsbooks-30-days-of-esports-data-1dc2</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; Over 30 days (Sep 6 – Oct 6, 2026) our scanner logged about &lt;strong&gt;4.87 million&lt;/strong&gt; arbitrage opportunities in esports across 27 books: sportsbooks, crypto sportsbooks and two prediction markets, Polymarket and Kalshi.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;About 1 in 6 arbs (15.9%)&lt;/strong&gt; had a Polymarket or Kalshi leg. Polymarket alone appeared in 642,253, Kalshi in 138,017.&lt;/li&gt;
&lt;li&gt;Arbs &lt;strong&gt;between Polymarket and Kalshi themselves are rare&lt;/strong&gt;: 4,435 in a month, 0.09% of all arbs. The other side of a prediction-market arb is almost always a sportsbook.&lt;/li&gt;
&lt;li&gt;Arbs with an exchange leg &lt;strong&gt;last longer&lt;/strong&gt;. In live play, 23.9% of them survive 10 seconds or more, versus 14.4% for sportsbook-only arbs. The median lifetime of a Polymarket arb is 4.1 s, twice the overall 2.0 s.&lt;/li&gt;
&lt;li&gt;In prematch, exchange arbs pay more: median ROI &lt;strong&gt;2.30%&lt;/strong&gt; versus 1.54% for sportsbook-only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below: the dataset, how we measured it, the tables, and a short look at the pipeline that produces the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as an arb
&lt;/h2&gt;

&lt;p&gt;An arbitrage (a "surebet") is a set of bets on every outcome of the same market, placed at different books, where the implied probabilities add up to less than 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1/k1 + 1/k2 &amp;lt; 1          ROI = 1 / (1/k1 + 1/k2) - 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Team A to win the match at 2.10 on a sportsbook, Team B to win at a price that converts to 2.05 decimal on Polymarket (a 48.8¢ share). &lt;code&gt;1/2.10 + 1/2.05 = 0.964&lt;/code&gt;, so the arb returns about 3.7% whatever the result. Prediction-market prices are probabilities, so they need converting first: &lt;a href="https://dozor.bet/en/odds-converter" rel="noopener noreferrer"&gt;odds converter&lt;/a&gt;, &lt;a href="https://dozor.bet/en/calculator" rel="noopener noreferrer"&gt;surebet calculator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dataset and method
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Window:&lt;/strong&gt; 30 days, Sep 6 – Oct 6, 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage:&lt;/strong&gt; esports only — CS2, Dota 2, League of Legends, Valorant, Rainbow Six and smaller titles. 25 sportsbooks and crypto sportsbooks plus Polymarket and Kalshi. Full list with per-book stats: &lt;a href="https://dozor.bet/en/bookmakers" rel="noopener noreferrer"&gt;bookmakers&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lifetime&lt;/strong&gt; of an arb = last time it was seen minus first time it was seen. All books are read at the same time and every arb is recomputed on each cycle, so lifetime has a resolution of about 2 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ROI&lt;/strong&gt; = the best ROI the arb reached during its life.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caveats.&lt;/strong&gt; The set of books changed during the window, so daily volumes are not a market trend. A slower scanner sees fewer short-lived arbs and a longer median. An arb seen in a single snapshot is often a timing difference between two quotes rather than a real mispricing — which is why we show &lt;a href="https://dozor.bet/en/glossary/price-age" rel="noopener noreferrer"&gt;price age&lt;/a&gt; on every leg.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finding 1: prediction markets are in 1 of 6 arbs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Arbs, 30 days&lt;/th&gt;
&lt;th&gt;Share of all arbs&lt;/th&gt;
&lt;th&gt;Live&lt;/th&gt;
&lt;th&gt;Prematch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;All books&lt;/td&gt;
&lt;td&gt;~4.87M&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;81%&lt;/td&gt;
&lt;td&gt;19%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With Polymarket&lt;/td&gt;
&lt;td&gt;642,253&lt;/td&gt;
&lt;td&gt;13.2%&lt;/td&gt;
&lt;td&gt;64%&lt;/td&gt;
&lt;td&gt;36%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With Kalshi&lt;/td&gt;
&lt;td&gt;138,017&lt;/td&gt;
&lt;td&gt;2.8%&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;35%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Polymarket + Kalshi together&lt;/td&gt;
&lt;td&gt;4,435&lt;/td&gt;
&lt;td&gt;0.09%&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things stand out. Exchanges are relatively stronger in prematch: a third of their arbs happen before the match starts, versus a fifth across all books. And Polymarket and Kalshi rarely disagree with each other on esports — when they are mispriced, they are mispriced against sportsbooks. If you are looking for "Polymarket vs Kalshi arbitrage" in esports, the volume is on the &lt;a href="https://dozor.bet/en/prediction-markets" rel="noopener noreferrer"&gt;prediction market vs sportsbook&lt;/a&gt; side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: exchange arbs live longer
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Arb type&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Median life&lt;/th&gt;
&lt;th&gt;90th percentile&lt;/th&gt;
&lt;th&gt;Lasting ≥10 s&lt;/th&gt;
&lt;th&gt;Lasting ≥60 s&lt;/th&gt;
&lt;th&gt;Median ROI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sportsbooks only&lt;/td&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;2.0 s&lt;/td&gt;
&lt;td&gt;14.2 s&lt;/td&gt;
&lt;td&gt;14.4%&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;td&gt;3.37%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With an exchange leg&lt;/td&gt;
&lt;td&gt;Live&lt;/td&gt;
&lt;td&gt;2.0 s&lt;/td&gt;
&lt;td&gt;24.4 s&lt;/td&gt;
&lt;td&gt;23.9%&lt;/td&gt;
&lt;td&gt;2.9%&lt;/td&gt;
&lt;td&gt;3.09%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sportsbooks only&lt;/td&gt;
&lt;td&gt;Prematch&lt;/td&gt;
&lt;td&gt;14.1 s&lt;/td&gt;
&lt;td&gt;93.5 s&lt;/td&gt;
&lt;td&gt;55.9%&lt;/td&gt;
&lt;td&gt;17.8%&lt;/td&gt;
&lt;td&gt;1.54%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With an exchange leg&lt;/td&gt;
&lt;td&gt;Prematch&lt;/td&gt;
&lt;td&gt;10.3 s&lt;/td&gt;
&lt;td&gt;177.4 s&lt;/td&gt;
&lt;td&gt;51.7%&lt;/td&gt;
&lt;td&gt;17.3%&lt;/td&gt;
&lt;td&gt;2.30%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In live play the median is the same, but the tail is much fatter: an arb with a Polymarket or Kalshi leg is &lt;strong&gt;1.7x more likely&lt;/strong&gt; to survive 10 seconds and &lt;strong&gt;2.4x more likely&lt;/strong&gt; to survive a minute. Across both modes, 44.6% of exchange arbs live 5 seconds or more, versus 29.7% for sportsbook-only arbs.&lt;/p&gt;

&lt;p&gt;By book, the median lifetime is 4.1 s for Polymarket and 4.0 s for Kalshi, against 2.0 s for all arbs. Arbs lasting a minute or more make up 8.3% of Polymarket arbs and 6.9% of Kalshi arbs, against 4.5% overall. Per-book pages: &lt;a href="https://dozor.bet/en/bookmakers/polymarket" rel="noopener noreferrer"&gt;Polymarket&lt;/a&gt;, &lt;a href="https://dozor.bet/en/bookmakers/kalshi" rel="noopener noreferrer"&gt;Kalshi&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our reading (a hypothesis, not a measured cause): sportsbooks reprice esports lines with trading teams and feeds, while exchange prices move only when someone trades. A stale order sits in the book until it is taken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: the pairs that produce them
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pair&lt;/th&gt;
&lt;th&gt;Arbs&lt;/th&gt;
&lt;th&gt;Lasting ≥60 s&lt;/th&gt;
&lt;th&gt;Median life&lt;/th&gt;
&lt;th&gt;Median ROI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dozor.bet/en/pairs/olimp-polymarket" rel="noopener noreferrer"&gt;Olimp – Polymarket&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;135,476&lt;/td&gt;
&lt;td&gt;7,738&lt;/td&gt;
&lt;td&gt;6.1 s&lt;/td&gt;
&lt;td&gt;2.81%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dozor.bet/en/pairs/duel-polymarket" rel="noopener noreferrer"&gt;Duel – Polymarket&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;80,260&lt;/td&gt;
&lt;td&gt;11,062&lt;/td&gt;
&lt;td&gt;4.2 s&lt;/td&gt;
&lt;td&gt;1.96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://dozor.bet/en/pairs/onexbet-polymarket" rel="noopener noreferrer"&gt;1xBet – Polymarket&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;69,719&lt;/td&gt;
&lt;td&gt;5,437&lt;/td&gt;
&lt;td&gt;4.0 s&lt;/td&gt;
&lt;td&gt;2.96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BetBoom – Polymarket&lt;/td&gt;
&lt;td&gt;39,014&lt;/td&gt;
&lt;td&gt;3,849&lt;/td&gt;
&lt;td&gt;6.4 s&lt;/td&gt;
&lt;td&gt;3.91%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marathon – Polymarket&lt;/td&gt;
&lt;td&gt;37,755&lt;/td&gt;
&lt;td&gt;2,575&lt;/td&gt;
&lt;td&gt;6.1 s&lt;/td&gt;
&lt;td&gt;3.47%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kalshi – Olimp&lt;/td&gt;
&lt;td&gt;25,469&lt;/td&gt;
&lt;td&gt;1,413&lt;/td&gt;
&lt;td&gt;4.2 s&lt;/td&gt;
&lt;td&gt;2.82%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All pairs with daily-updated stats: &lt;a href="https://dozor.bet/en/pairs" rel="noopener noreferrer"&gt;pairs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 4: which esports titles
&lt;/h2&gt;

&lt;p&gt;CS2 dominates everywhere, but exchanges lean towards slower titles. League of Legends is 18% of Polymarket arbs and 26% of Kalshi arbs, against about 7% across all books. Dota 2 is 20% of Polymarket arbs. Slower games mean fewer price jumps per minute, which fits the longer lifetimes above. More on titles: &lt;a href="https://dozor.bet/en/esports" rel="noopener noreferrer"&gt;esports arbitrage&lt;/a&gt;, &lt;a href="https://dozor.bet/en/blog/live-vs-prematch-surebets" rel="noopener noreferrer"&gt;live vs prematch&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the pipeline works
&lt;/h2&gt;

&lt;p&gt;For the developers reading this, a short version of what produces these numbers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One adapter per book.&lt;/strong&gt; Each sportsbook is read by its own process (Python) and normalized into a canonical market model: &lt;code&gt;(kind, scope, line)&lt;/code&gt; — match winner, map winner, handicap, total, rounds, kills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prediction markets in Go.&lt;/strong&gt; Polymarket markets are discovered through the Gamma API and priced from the CLOB WebSocket order book; Kalshi comes in through its REST API and the v2 WebSocket feed. Exchange prices are converted from probabilities to decimal odds before they meet sportsbook lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event matching.&lt;/strong&gt; The same match has different names, IDs and start times on 27 books. A matcher (Go) resolves them into one event using team-name normalization, tournament rules and a rules file that humans and strong automatic evidence can update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-side arb engine.&lt;/strong&gt; Arbs are computed in one place, on the server, for every matched market on every cycle. When an arb disappears, its first/last seen time, max ROI, market kind and books go into a history table — the source of this article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery.&lt;/strong&gt; The browser gets a compact WebSocket stream with ROI, the age of every quote and a direct link to the event, because in live play the question is not "is there an arb" but "is it still there".&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practical notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Liquidity is the limit on exchanges.&lt;/strong&gt; Polymarket and Kalshi do not limit winning accounts, but an arb is only as large as the depth at the quoted price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fees and withdrawals differ.&lt;/strong&gt; See &lt;a href="https://dozor.bet/en/blog/polymarket-fees-and-withdrawal" rel="noopener noreferrer"&gt;Polymarket fees and withdrawal&lt;/a&gt; and &lt;a href="https://dozor.bet/en/blog/polymarket-or-a-bookmaker" rel="noopener noreferrer"&gt;Polymarket or a bookmaker&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sportsbooks limit arbers.&lt;/strong&gt; The exchange is often the leg that stays open longest; the sportsbook leg is the one that gets restricted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check quote age before you click.&lt;/strong&gt; A 10% "arb" is more often a stale line than a gift.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Step-by-step guide: &lt;a href="https://dozor.bet/en/blog/how-to-arbitrage-polymarket-kalshi" rel="noopener noreferrer"&gt;how to arbitrage Polymarket and Kalshi&lt;/a&gt;. Lifetime study across all books: &lt;a href="https://dozor.bet/en/blog/how-long-do-surebets-last" rel="noopener noreferrer"&gt;how long do surebets last&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Data from &lt;a href="https://dozor.bet/en" rel="noopener noreferrer"&gt;Dozor&lt;/a&gt;, an esports arbitrage scanner covering 25 sportsbooks plus Polymarket and Kalshi; free plan without a card. Dozor is an information tool, not a bookmaker. 18+. Betting involves risk of loss, and sportsbooks may restrict arbitrage accounts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>datascience</category>
      <category>showdev</category>
      <category>polymarket</category>
      <category>go</category>
    </item>
  </channel>
</rss>
