{"id":3059,"date":"2026-06-23T10:00:00","date_gmt":"2026-06-23T10:00:00","guid":{"rendered":"https:\/\/oddspapi.io\/blog\/?p=3059"},"modified":"2026-06-15T15:22:14","modified_gmt":"2026-06-15T15:22:14","slug":"real-time-odds-feed-market-making","status":"publish","type":"post","link":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/","title":{"rendered":"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture"},"content":{"rendered":"<h2>Market Makers Need Two Things: A Reference Price and a Venue<\/h2>\n<p>Most trading infrastructure treats those as separate problems. You build one integration for a pricing data feed and another for the exchange where you actually quote. In sports prediction markets, that means separate API calls to Pinnacle for the sharp reference price and to Polymarket or Kalshi for the depth-of-book \u2014 two feeds, two polling loops, two staleness risks.<\/p>\n<p>OddsPapi&#8217;s B2B feed solves this at the schema level. A single <code>\/odds<\/code> call returns sportsbook prices (Pinnacle, SBOBet, Singbet) alongside exchange depth-of-book ladders (<code>exchangeMeta<\/code>) for Polymarket, Kalshi, and Betfair \u2014 in the same JSON object, keyed by bookmaker slug. Your signal loop reads from one source. Your stale detection checks one feed.<\/p>\n<p>This post builds a market making signal loop using live data from the 2026 World Cup. It is post 3 in the OddsPapi B2B series \u2014 <a href=\"https:\/\/oddspapi.io\/blog\/sportsbook-odds-feed-websocket-pipeline\/\">post 1<\/a> covered the WebSocket feed architecture, <a href=\"https:\/\/oddspapi.io\/blog\/clv-sportsbook-line-audit\/\">post 2<\/a> covered CLV line auditing.<\/p>\n<h2>What Market Making Looks Like in Practice<\/h2>\n<p>A market maker on Polymarket or Kalshi does not pick sides. They quote both bid and ask inside the current spread, collect the bid-ask margin on each matched trade, and hedge directional risk using the sportsbook feed as the reference. The profit is not &#8220;I think Spain wins&#8221; \u2014 it is &#8220;I can quote Spain YES at 92.5\u00a2 bid \/ 93.5\u00a2 ask while the market is at 92\u00a2\/94\u00a2, and I collect 1\u00a2 per matched round-trip.&#8221;<\/p>\n<p>Three things control whether that is profitable:<\/p>\n<ol>\n<li><strong>Is the reference price accurate?<\/strong> If you&#8217;re quoting relative to a stale Pinnacle price, your spread is anchored to yesterday&#8217;s market.<\/li>\n<li><strong>Is the exchange spread wide enough to quote inside?<\/strong> On a 1\u00a2 spread, you cannot make a market \u2014 there is no room. On a 3-5\u00a2 spread, there is.<\/li>\n<li><strong>Is the feed live?<\/strong> If a bookmaker&#8217;s feed goes stale, your reference is unreliable and your risk is unhedged. You need to know instantly.<\/li>\n<\/ol>\n<p>All three are solvable with the right feed. Here is what that looks like with real data.<\/p>\n<h2>Live Data: Spain vs Cape Verde, World Cup 2026<\/h2>\n<p>Fixture ID <code>id1000001666456994<\/code> (Spain vs Cape Verde, Group Stage, Jun 15 2026). A single <code>\/odds<\/code> call returned <strong>17 bookmakers<\/strong> including Pinnacle (the sharp reference), Kalshi, and Polymarket (the exchange venues).<\/p>\n<h3>Step 1: Get All Books in One Call<\/h3>\n<pre class=\"wp-block-code\"><code>import requests\n\nAPI_KEY = \"YOUR_API_KEY\"\nBASE_URL = \"https:\/\/api.oddspapi.io\/v4\"\n\nr = requests.get(f\"{BASE_URL}\/odds\", params={\n    \"apiKey\": API_KEY,\n    \"fixtureId\": \"id1000001666456994\",\n    \"bookmakers\": \"pinnacle,kalshi,polymarket,betfair-ex,sbobet\",\n})\nbooks = r.json().get(\"bookmakerOdds\", {})\nprint(f\"Books returned: {list(books.keys())}\")\n# Output: ['bet365', 'betmgm', 'kalshi', 'sbobet', 'pinnacle', 'polymarket', ...]\n# 17 bookmakers in one call\n<\/code><\/pre>\n<h3>Step 2: Extract the Sharp Reference (Pinnacle De-Vigged)<\/h3>\n<pre class=\"wp-block-code\"><code>def devig_market(outcomes_dict):\n    prices = {oid: od[\"players\"][\"0\"][\"price\"]\n              for oid, od in outcomes_dict.items()\n              if od.get(\"players\", {}).get(\"0\", {}).get(\"active\")}\n    total_imp = sum(1\/p for p in prices.values())\n    overround = (total_imp - 1) * 100\n    fair = {oid: (1\/p) \/ total_imp for oid, p in prices.items()}\n    return fair, overround\n\n# Pinnacle 1X2 (market 101) on Spain vs Cape Verde\npin_outcomes = books[\"pinnacle\"][\"markets\"][\"101\"][\"outcomes\"]\nfair, vig = devig_market(pin_outcomes)\nprint(f\"Pinnacle overround: {vig:.2f}%\")\nfor oid, prob in fair.items():\n    raw = pin_outcomes[oid][\"players\"][\"0\"][\"price\"]\n    print(f\"  Outcome {oid}: raw={raw:.3f}, no-vig fair={1\/prob:.3f} ({prob*100:.1f}%)\")\n<\/code><\/pre>\n<p>Live output on Spain vs Cape Verde:<\/p>\n<pre class=\"wp-block-code\"><code>Pinnacle overround: 2.01%\n  Outcome 101 (Spain):      raw=1.083, no-vig fair=1.105 (90.5%)\n  Outcome 102 (Draw):       raw=15.5,  no-vig fair=15.812 (6.3%)\n  Outcome 103 (Cape Verde): raw=31.0,  no-vig fair=31.624 (3.2%)\n<\/code><\/pre>\n<h3>Step 3: Read the Exchange Depth-of-Book<\/h3>\n<p>For exchange-type bookmakers (Polymarket, Kalshi, Betfair), the <code>players[\"0\"]<\/code> object includes an <code>exchangeMeta<\/code> field with a full depth-of-book ladder. <code>back<\/code> and <code>lay<\/code> are lists of price levels (best first), each with <code>price<\/code> (decimal odds), <code>size<\/code> (total volume at that level), <code>limit<\/code> (available to match), and <code>cents<\/code> (native share price, 0-1).<\/p>\n<pre class=\"wp-block-code\"><code>def read_ladder(books, slug, market_id, outcome_id):\n    try:\n        outcome = (books[slug][\"markets\"][market_id]\n                         [\"outcomes\"][outcome_id][\"players\"][\"0\"])\n        em = outcome.get(\"exchangeMeta\") or {}\n        return {\n            \"price\": outcome.get(\"price\"),\n            \"active\": outcome.get(\"active\"),\n            \"back\": em.get(\"back\", []),\n            \"lay\": em.get(\"lay\", []),\n        }\n    except (KeyError, TypeError):\n        return None\n\n# Kalshi Spain (outcome 101)\nkalshi_spain = read_ladder(books, \"kalshi\", \"101\", \"101\")\nprint(\"Kalshi Spain back ladder:\")\nfor level in kalshi_spain[\"back\"][:3]:\n    print(f\"  {level['cents']*100:.0f}c  {level['price']:.3f}  ${level['size']:,.0f} available\")\n\nprint(\"Kalshi Spain lay (NO) ladder:\")\nfor level in kalshi_spain[\"lay\"][:3]:\n    print(f\"  {level['cents']*100:.0f}c  {level['price']:.3f}  ${level['size']:,.0f} available\")\n<\/code><\/pre>\n<p>Live output:<\/p>\n<pre class=\"wp-block-code\"><code>Kalshi Spain back ladder (YES \u2014 betting Spain wins):\n  93c  1.075  $4,772,928 available\n  94c  1.064  $1,852,102 available\n  95c  1.053  $1,301,446 available\n\nKalshi Spain lay (NO \u2014 betting Spain does NOT win):\n   8c  12.500  $104,667 available\n   9c  11.111  $319,527 available\n  10c  10.000  $177,318 available\n<\/code><\/pre>\n<p>Reading the spread: Spain YES trades at 93\u00a2. The NO side trades at 8\u00a2 (implying Spain wins 92% of the time at that level). Effective YES ask implied from NO: ~92\u00a2. Bid-ask spread: <strong>1\u00a2<\/strong> \u2014 93\u00a2 bid \/ 92\u00a2 implied ask. In decimal odds that is 1.075 bid \/ 1.087 implied ask.<\/p>\n<p>Compare to Polymarket&#8217;s Spain back:<\/p>\n<pre class=\"wp-block-code\"><code>Polymarket Spain back ladder:\n  92c  1.087  $1,647,509 available\n  93c  1.075  $1,650,604 available\n  94c  1.064  $1,789,496 available\n<\/code><\/pre>\n<p>Polymarket is quoting Spain at 92\u00a2 best back \u2014 slightly below Kalshi&#8217;s 93\u00a2. Both are above the Pinnacle no-vig fair of 90.5\u00a2 (1.105), which means the prediction markets are pricing Spain as a slightly stronger favourite than Pinnacle. The spread between the sharpest exchange price (93\u00a2) and the Pinnacle no-vig anchor (90.5\u00a2) is the market maker&#8217;s working range.<\/p>\n<h2>The Market Making Signal Loop<\/h2>\n<pre class=\"wp-block-code\"><code>import requests, time\n\nAPI_KEY = \"YOUR_API_KEY\"\nBASE_URL = \"https:\/\/api.oddspapi.io\/v4\"\n\nMIN_SPREAD_CENTS = 1.5    # only make markets when spread >= 1.5c\nMIN_EDGE_VS_FAIR = 0.005  # only back if exchange price > Pinnacle fair + 0.5%\n\ndef pinnacle_fair(outcomes_dict):\n    prices = {}\n    for oid, od in outcomes_dict.items():\n        p0 = od.get(\"players\", {}).get(\"0\", {})\n        if p0.get(\"active\") and p0.get(\"price\"):\n            prices[oid] = p0[\"price\"]\n    total = sum(1\/p for p in prices.values())\n    return {oid: (1\/p) \/ total for oid, p in prices.items()}\n\ndef mm_signal(fair_probs, back_ladder, lay_ladder, outcome_id):\n    if not back_ladder:\n        return None\n\n    fair_prob = fair_probs.get(outcome_id)\n    if not fair_prob:\n        return None\n\n    fair_price = 1 \/ fair_prob\n    fair_cents = fair_prob   # share price equivalent\n\n    best_back_cents = back_ladder[0][\"cents\"]\n    best_back_price = back_ladder[0][\"price\"]\n    best_back_size = back_ladder[0][\"size\"]\n\n    # Derive implied YES ask from NO ladder (if available)\n    if lay_ladder:\n        no_best_cents = lay_ladder[0][\"cents\"]\n        implied_yes_ask_cents = 1.0 - no_best_cents\n        spread_cents = best_back_cents - implied_yes_ask_cents\n    else:\n        spread_cents = None\n\n    # Edge: exchange back price vs Pinnacle fair\n    edge_vs_fair = (best_back_price \/ fair_price) - 1\n\n    return {\n        \"outcome_id\": outcome_id,\n        \"fair_price\": round(fair_price, 3),\n        \"fair_cents\": round(fair_cents * 100, 1),\n        \"exchange_back\": best_back_price,\n        \"exchange_back_cents\": round(best_back_cents * 100, 1),\n        \"exchange_size\": best_back_size,\n        \"spread_cents\": round(spread_cents * 100, 2) if spread_cents else None,\n        \"edge_vs_fair_pct\": round(edge_vs_fair * 100, 2),\n        \"signal\": (\n            \"QUOTE\" if (spread_cents and spread_cents * 100 >= MIN_SPREAD_CENTS\n                        and edge_vs_fair > MIN_EDGE_VS_FAIR)\n            else \"WATCH\"\n        ),\n    }\n\ndef run_mm_loop(fixture_id, market_id=\"101\", poll_seconds=5):\n    print(f\"Starting MM loop on fixture {fixture_id}, market {market_id}\")\n    stale = {}\n\n    while True:\n        r = requests.get(f\"{BASE_URL}\/odds\", params={\n            \"apiKey\": API_KEY, \"fixtureId\": fixture_id,\n            \"bookmakers\": \"pinnacle,kalshi,polymarket\",\n        })\n        if r.status_code != 200:\n            time.sleep(poll_seconds)\n            continue\n\n        books = r.json().get(\"bookmakerOdds\", {})\n\n        # Pinnacle reference (check stale via bookmakerChangedAt freshness)\n        if \"pinnacle\" not in books:\n            print(\"Pinnacle not present \u2014 pausing\")\n            time.sleep(poll_seconds)\n            continue\n\n        pin_outcomes = books[\"pinnacle\"][\"markets\"].get(market_id, {}).get(\"outcomes\", {})\n        fair = pinnacle_fair(pin_outcomes)\n\n        for exchange in [\"kalshi\", \"polymarket\"]:\n            if exchange not in books:\n                continue\n            ex_market = books[exchange][\"markets\"].get(market_id, {})\n            ex_outcomes = ex_market.get(\"outcomes\", {})\n\n            for oid in fair:\n                if oid not in ex_outcomes:\n                    continue\n                p0 = ex_outcomes[oid][\"players\"][\"0\"]\n                em = p0.get(\"exchangeMeta\") or {}\n                signal = mm_signal(\n                    fair_probs=fair,\n                    back_ladder=em.get(\"back\", []),\n                    lay_ladder=em.get(\"lay\", []),\n                    outcome_id=oid,\n                )\n                if signal and signal[\"signal\"] == \"QUOTE\":\n                    print(f\"[{exchange}] Outcome {oid}: QUOTE signal\")\n                    print(f\"  Fair: {signal['fair_cents']}c ({signal['fair_price']})\")\n                    print(f\"  Exchange back: {signal['exchange_back_cents']}c, edge={signal['edge_vs_fair_pct']:+.2f}%\")\n                    print(f\"  Spread: {signal['spread_cents']}c, size=${signal['exchange_size']:,.0f}\")\n\n        time.sleep(poll_seconds)\n\n# Run on Spain vs Cape Verde\nrun_mm_loop(\"id1000001666456994\")\n<\/code><\/pre>\n<h2>staleOdds: The Kill Switch for Your Positions<\/h2>\n<p>A market maker&#8217;s risk is directional exposure. If you have open YES positions on Spain and the Pinnacle feed goes stale, your reference price is frozen \u2014 you cannot know if the market has moved against you. The correct response is to widen your quotes immediately (to reduce trade probability) or withdraw them entirely.<\/p>\n<p>The <code>staleOdds<\/code> flag on the B2B WebSocket feed (<a href=\"https:\/\/docs.oddspapi.io\/\">docs.oddspapi.io<\/a>) delivers this signal at the bookmaker level. On the REST API, you can approximate it by tracking <code>bookmakerChangedAt<\/code> \u2014 the timestamp of the last price change from the source bookmaker:<\/p>\n<pre class=\"wp-block-code\"><code>from datetime import datetime, timezone, timedelta\n\nSTALE_THRESHOLD_SECONDS = 120   # treat as stale if unchanged for 2 minutes\n\ndef is_stale(outcome_obj, threshold_s=STALE_THRESHOLD_SECONDS):\n    changed_at = outcome_obj.get(\"bookmakerChangedAt\")\n    if not changed_at:\n        return True  # unknown = treat as stale\n    last = datetime.fromisoformat(changed_at.replace(\"Z\", \"+00:00\"))\n    age = (datetime.now(timezone.utc) - last).total_seconds()\n    return age > threshold_s\n\n# In the signal loop:\np0 = pin_outcomes.get(oid, {}).get(\"players\", {}).get(\"0\", {})\nif is_stale(p0):\n    print(f\"Pinnacle {oid} is stale \u2014 withdrawing quotes\")\n    continue\n<\/code><\/pre>\n<p>On the B2B WebSocket feed, the <code>bookmakers<\/code> channel delivers <code>staleOdds: true<\/code> the moment the aggregation layer detects a connectivity problem \u2014 no polling required. For a market making system running 24\/7 on live in-play markets, the WebSocket signal is materially faster than any polling approximation. See the <a href=\"https:\/\/oddspapi.io\/blog\/sportsbook-odds-feed-websocket-pipeline\/\">WebSocket feed architecture post<\/a> for the full stale detection pattern.<\/p>\n<h2>What the Depth Tells You Beyond the Spread<\/h2>\n<p>The back ladder is not just a spread signal \u2014 it tells you how much you can trade at each price level before moving the market. On Spain vs Cape Verde at kickoff window, the Kalshi Spain YES depth was:<\/p>\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Price level<\/th>\n<th>Share price<\/th>\n<th>Volume available<\/th>\n<th>vs Pinnacle fair<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>1.075 (best)<\/td>\n<td>93\u00a2<\/td>\n<td>$4,772,928<\/td>\n<td>+2.5c above fair (90.5\u00a2)<\/td>\n<\/tr>\n<tr>\n<td>1.064 (2nd)<\/td>\n<td>94\u00a2<\/td>\n<td>$1,852,102<\/td>\n<td>+3.5c above fair<\/td>\n<\/tr>\n<tr>\n<td>1.053 (3rd)<\/td>\n<td>95\u00a2<\/td>\n<td>$1,301,446<\/td>\n<td>+4.5c above fair<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Over $7M of liquidity within 4.5\u00a2 of fair on a single WC game outcome. For a market maker sizing positions against this ladder, <code>limit<\/code> (available to match) is the relevant field \u2014 not <code>size<\/code> (total volume at the level). A level with $4.7M size but only $24K limit means most of the size is already matched; you can only take the residual.<\/p>\n<pre class=\"wp-block-code\"><code>def available_at_level(level):\n    return level.get(\"limit\", 0)   # NOT \"size\" \u2014 size includes already-matched volume\n\ndef total_available_to_price(back_ladder, max_price_decimal):\n    return sum(\n        available_at_level(lvl) for lvl in back_ladder\n        if lvl[\"price\"] >= max_price_decimal\n    )\n\n# How much can I back Spain at 1.064 or better on Kalshi?\ntotal = total_available_to_price(kalshi_spain[\"back\"], max_price_decimal=1.064)\nprint(f\"Available to back Spain at >=1.064 on Kalshi: ${total:,.0f}\")\n<\/code><\/pre>\n<h2>350+ Books as the Price Discovery Engine<\/h2>\n<p>The market making signal above uses Pinnacle as the single reference. In practice, you want a consensus across multiple sharp books to reduce the risk of a single-book move triggering a false signal. Pull Pinnacle, SBOBet, and Singbet in the same call \u2014 all three are in the OddsPapi catalog \u2014 and use the median no-vig fair as your anchor:<\/p>\n<pre class=\"wp-block-code\"><code>def consensus_fair(books, market_id, outcome_id, sharp_books=(\"pinnacle\", \"sbobet\", \"singbet\")):\n    fair_probs = []\n    for slug in sharp_books:\n        if slug not in books:\n            continue\n        outcomes = books[slug].get(\"markets\", {}).get(market_id, {}).get(\"outcomes\", {})\n        fair, _ = devig_market(outcomes)\n        if outcome_id in fair:\n            fair_probs.append(fair[outcome_id])\n\n    if not fair_probs:\n        return None\n    # Median consensus \u2014 less sensitive to a single book moving\n    sorted_probs = sorted(fair_probs)\n    n = len(sorted_probs)\n    return sorted_probs[n \/\/ 2]\n<\/code><\/pre>\n<p>With OddsPapi&#8217;s 200+ bookmakers in a single feed, a three-sharp consensus is one API call. Without the aggregated feed, it would be three separate integrations with three separate rate limits and three staleness clocks to manage.<\/p>\n<h2>Overlap with the Arb Bot Pattern<\/h2>\n<p>Market making and arbitrage use the same underlying data but different logic. The <a href=\"https:\/\/oddspapi.io\/blog\/arbitrage-betting-bot-python\/\">arb bot<\/a> looks for simultaneous mispricing across books that guarantees a profit regardless of outcome. A market maker does not wait for a guaranteed spread \u2014 they quote continuously and manage directional risk through hedging, not lock-in.<\/p>\n<p>The practical difference: an arb opportunity appears and disappears in seconds. A market making spread exists continuously \u2014 the money is made on volume of matched trades, not on single locked-in positions. Both strategies depend on sub-second reference price updates; the arb dies if the price moves before you execute, and the MM&#8217;s hedge leg fails if the reference goes stale while positions are open.<\/p>\n<p>The <a href=\"https:\/\/oddspapi.io\/blog\/polymarket-api-kalshi-api-vs-sportsbooks-the-developers-guide\/\">Polymarket and Kalshi vs sportsbooks guide<\/a> covers the exchange architecture in more depth if you are new to how prediction market order books work.<\/p>\n<h2>Frequently Asked Questions<\/h2>\n<h3>What is market making in sports prediction markets?<\/h3>\n<p>Market making means continuously quoting both sides (buy and sell \/ back and lay) on an exchange, collecting the bid-ask spread on each matched trade. The market maker uses a sharp sportsbook feed (Pinnacle no-vig) as the reference price and hedges directional risk rather than picking winners.<\/p>\n<h3>How do I read the exchangeMeta back\/lay ladder?<\/h3>\n<p>The <code>back<\/code> list contains price levels at which you can back (bet YES) an outcome. Each level has <code>price<\/code> (decimal odds), <code>cents<\/code> (native share price, 0-1), <code>size<\/code> (total volume at the level), and <code>limit<\/code> (available to match \u2014 use this, not size, for position sizing). The <code>lay<\/code> list is the NO side. Best available price is always the first element.<\/p>\n<h3>How many exchange bookmakers does OddsPapi cover?<\/h3>\n<p>Polymarket, Kalshi, Betfair Exchange, SX Bet, ProphetX, Limitless, Myriad, Matchbook, and several Asian exchanges are in the catalog. Coverage varies by sport and fixture \u2014 prediction markets (Polymarket, Kalshi) are deepest on major events like the World Cup, elections, and high-profile US sports.<\/p>\n<h3>What is the staleOdds flag and why does it matter for market making?<\/h3>\n<p>When OddsPapi loses connectivity to a source bookmaker, it sets <code>staleOdds: true<\/code> on the <code>bookmakers<\/code> WebSocket channel for that bookmaker. For a market maker using Pinnacle as the reference, a stale Pinnacle feed means your anchor price is frozen \u2014 you cannot safely price your quotes. The correct response is to widen or withdraw quotes until the flag clears.<\/p>\n<h3>Does this require the B2B feed or can I use the free tier?<\/h3>\n<p>The <code>\/odds<\/code> endpoint with <code>exchangeMeta<\/code> depth-of-book is accessible on the free v4 API key. The B2B WebSocket feed (docs.oddspapi.io) adds real-time push delivery (no polling), the <code>staleOdds<\/code> flag on the bookmakers channel, and higher throughput \u2014 necessary for a production market making system running on live in-play markets. The polling approach shown here works for testing and strategy development.<\/p>\n<p><script type=\"application\/ld+json\">\n{\n  \"@context\": \"https:\/\/schema.org\",\n  \"@type\": \"FAQPage\",\n  \"mainEntity\": [\n    {\n      \"@type\": \"Question\",\n      \"name\": \"What is market making in sports prediction markets?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Market making means continuously quoting both sides (buy and sell) on an exchange, collecting the bid-ask spread on each matched trade. The market maker uses a sharp sportsbook feed (Pinnacle no-vig) as the reference price and hedges directional risk.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How do I read the exchangeMeta back\/lay ladder?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"The back list contains price levels to back (bet YES). Each level has price (decimal odds), cents (share price 0-1), size (total volume), and limit (available to match \u2014 use this for position sizing). Best available is always the first element.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"How many exchange bookmakers does OddsPapi cover?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"Polymarket, Kalshi, Betfair Exchange, SX Bet, ProphetX, Limitless, Myriad, Matchbook, and several Asian exchanges are in the catalog. Coverage is deepest on major events like the World Cup and elections.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"What is the staleOdds flag and why does it matter for market making?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"When OddsPapi loses connectivity to a bookmaker, it sets staleOdds: true on the WebSocket bookmakers channel. For a market maker using Pinnacle as reference, a stale Pinnacle means your anchor is frozen and quotes should be widened or withdrawn.\"\n      }\n    },\n    {\n      \"@type\": \"Question\",\n      \"name\": \"Does this require the B2B feed or can I use the free tier?\",\n      \"acceptedAnswer\": {\n        \"@type\": \"Answer\",\n        \"text\": \"The \/odds endpoint with exchangeMeta depth-of-book is on the free v4 API. The B2B WebSocket adds real-time push delivery, the staleOdds flag, and higher throughput \u2014 needed for production systems on live in-play markets.\"\n      }\n    }\n  ]\n}\n<\/script><\/p>\n<p><!--\nFocus Keyphrase: prediction market making\nSEO Title: Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture\nMeta Description: Build a market making signal loop using Pinnacle no-vig as reference and Polymarket\/Kalshi depth-of-book. Live World Cup data, stale detection, Python tutorial.\nSlug: real-time-odds-feed-market-making\n--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Build a market making signal loop using Pinnacle no-vig as reference and Polymarket\/Kalshi depth-of-book. Live World Cup data, stale detection, Python tutorial.<\/p>\n","protected":false},"author":2,"featured_media":3061,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[72,9,34,11,71],"class_list":["post-3059","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to-guides","tag-b2b","tag-odds-api","tag-prediction-markets","tag-python","tag-websocket"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.4 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture | OddsPapi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture | OddsPapi Blog\" \/>\n<meta property=\"og:description\" content=\"Build a market making signal loop using Pinnacle no-vig as reference and Polymarket\/Kalshi depth-of-book. Live World Cup data, stale detection, Python tutorial.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\" \/>\n<meta property=\"og:site_name\" content=\"OddsPapi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-23T10:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp\" \/>\n\t<meta property=\"og:image:width\" content=\"2560\" \/>\n\t<meta property=\"og:image:height\" content=\"1429\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/webp\" \/>\n<meta name=\"author\" content=\"Odds API Writer\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/oddspapi.io\/logo-v2.webp\" \/>\n<meta name=\"twitter:creator\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:site\" content=\"@oddspapiapi\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Odds API Writer\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"12 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\"},\"author\":{\"name\":\"Odds API Writer\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\"},\"headline\":\"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture\",\"datePublished\":\"2026-06-23T10:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\"},\"wordCount\":1379,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp\",\"keywords\":[\"B2B\",\"Odds API\",\"Prediction Markets\",\"Python\",\"WebSocket\"],\"articleSection\":[\"How To Guides\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\",\"name\":\"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture | OddsPapi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp\",\"datePublished\":\"2026-06-23T10:00:00+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp\",\"width\":2560,\"height\":1429,\"caption\":\"Market Making - OddsPapi API Blog\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/oddspapi.io\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#website\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"name\":\"OddsPapi\",\"description\":\"Sports Odds API Tutorials &amp; Guides\",\"publisher\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\"},\"alternateName\":\"Odds Papi\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/oddspapi.io\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#organization\",\"name\":\"OddsPapi\",\"url\":\"https:\/\/oddspapi.io\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"contentUrl\":\"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png\",\"width\":135,\"height\":135,\"caption\":\"OddsPapi\"},\"image\":{\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/x.com\/oddspapiapi\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13\",\"name\":\"Odds API Writer\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g\",\"caption\":\"Odds API Writer\"},\"url\":\"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture | OddsPapi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/","og_locale":"en_US","og_type":"article","og_title":"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture | OddsPapi Blog","og_description":"Build a market making signal loop using Pinnacle no-vig as reference and Polymarket\/Kalshi depth-of-book. Live World Cup data, stale detection, Python tutorial.","og_url":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/","og_site_name":"OddsPapi Blog","article_published_time":"2026-06-23T10:00:00+00:00","og_image":[{"width":2560,"height":1429,"url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp","type":"image\/webp"}],"author":"Odds API Writer","twitter_card":"summary_large_image","twitter_image":"https:\/\/oddspapi.io\/logo-v2.webp","twitter_creator":"@oddspapiapi","twitter_site":"@oddspapiapi","twitter_misc":{"Written by":"Odds API Writer","Est. reading time":"12 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#article","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/"},"author":{"name":"Odds API Writer","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13"},"headline":"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture","datePublished":"2026-06-23T10:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/"},"wordCount":1379,"commentCount":0,"publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp","keywords":["B2B","Odds API","Prediction Markets","Python","WebSocket"],"articleSection":["How To Guides"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/","url":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/","name":"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture | OddsPapi Blog","isPartOf":{"@id":"https:\/\/oddspapi.io\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage"},"thumbnailUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp","datePublished":"2026-06-23T10:00:00+00:00","breadcrumb":{"@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#primaryimage","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2026\/06\/real-time-odds-feed-market-making-scaled.webp","width":2560,"height":1429,"caption":"Market Making - OddsPapi API Blog"},{"@type":"BreadcrumbList","@id":"https:\/\/oddspapi.io\/blog\/real-time-odds-feed-market-making\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/oddspapi.io\/blog\/"},{"@type":"ListItem","position":2,"name":"Real-Time Odds Feed for Market Makers: Exchange Depth and Spread Capture"}]},{"@type":"WebSite","@id":"https:\/\/oddspapi.io\/blog\/#website","url":"https:\/\/oddspapi.io\/blog\/","name":"OddsPapi","description":"Sports Odds API Tutorials &amp; Guides","publisher":{"@id":"https:\/\/oddspapi.io\/blog\/#organization"},"alternateName":"Odds Papi","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/oddspapi.io\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/oddspapi.io\/blog\/#organization","name":"OddsPapi","url":"https:\/\/oddspapi.io\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","contentUrl":"https:\/\/oddspapi.io\/blog\/wp-content\/uploads\/2025\/11\/oddspapi.png","width":135,"height":135,"caption":"OddsPapi"},"image":{"@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/oddspapiapi"]},{"@type":"Person","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/b6f21e649c4f556f0a95c23a0f1efa13","name":"Odds API Writer","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/oddspapi.io\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/33b204f24af3d02e35b25ae730c0536121ca6a783fdb196e7611c9e49fcd13eb?s=96&d=mm&r=g","caption":"Odds API Writer"},"url":"https:\/\/oddspapi.io\/blog\/author\/andy-lavelle\/"}]}},"_links":{"self":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3059","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/comments?post=3059"}],"version-history":[{"count":1,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3059\/revisions"}],"predecessor-version":[{"id":3060,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/posts\/3059\/revisions\/3060"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media\/3061"}],"wp:attachment":[{"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/media?parent=3059"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/categories?post=3059"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/oddspapi.io\/blog\/wp-json\/wp\/v2\/tags?post=3059"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}