Prediction markets / price comparison
Kalshi and Polymarket vs sportsbooks in one call
Kalshi and Polymarket can lead a sportsbook comparison on the raw quote, then fall behind once you include the trading fee. This NBA response shows the order change, with every price tied to its book and timestamp.
Request the books and the fee fields together
Select the moneyline with markets=h2h, name the books, and ask for oddsFormat=decimal. fees=taker adds net_price without changing price. Authentication uses the X-API-Key header.
curl --fail-with-body -H "X-API-Key: $ODDSLINK_API_KEY" \
"https://oddslink.app/v1/sports/nba/odds?markets=h2h&bookmakers=kalshi,polymarket,draftkings,fanduel,pinnacle&fees=taker&oddsFormat=decimal"The response is an array of fixtures. Each fixture has bookmakers, each book has markets, and each market has outcomes. The same outcome name identifies the side within that fixture. See the fee field definitions for supported schedules and rounding.
The fee flips the leading price
Read on 31 October 2026 at about 22:22 UTC: Oklahoma City Thunder at Houston Rockets, scheduled for 2026-11-01T00:30:00Z. Fixture id: nba:houstonrockets-oklahomacitythunder:2026-11-01. These are recorded quotes from that read, rather than a current offer.
| Book | Thunder raw | Thunder after fee | Rockets raw | Rockets after fee | last_update (UTC) |
|---|---|---|---|---|---|
| Kalshi | 2.27 | 2.184 | 1.75 | 1.701 | 22:21:56 |
| Polymarket | 2.27 | 2.208 | 1.75 | 1.715 | 21:48:18 |
| DraftKings | 2.20 | 2.20 | 1.70 | 1.70 | 22:21:50 |
| FanDuel | 2.24 | 2.24 | 1.68 | 1.68 | 22:21:50 |
| Pinnacle | 2.23 | 2.23 | 1.75 | 1.75 | 22:21:50 |
On the Thunder, Kalshi and Polymarket both show 2.27 before fees. Kalshi's per-contract coefficient is 0.07; Polymarket's is 0.05. Their returned net prices are 2.184 and 2.208. FanDuel at 2.24 then leads the recorded comparison, followed by Pinnacle at 2.23.
On Houston, Pinnacle's 1.75 exceeds Kalshi's 1.701 and Polymarket's 1.715 after fees. Keep the raw and net columns separate. The decimal raw quote is rounded to two places, while net_price has three; recalculating from the displayed raw number can differ from the returned net quote.
A shared fixture id solves the join. You still need equivalent settlement rules, the same full-game contract and enough available size before comparing executable orders.
Read the raw price, net price and timestamp in Python
import json, os
from urllib.parse import urlencode
from urllib.request import Request, urlopen
params = urlencode({
"markets": "h2h",
"bookmakers": "kalshi,polymarket,draftkings,fanduel,pinnacle",
"fees": "taker",
"oddsFormat": "decimal",
})
request = Request("https://oddslink.app/v1/sports/nba/odds?" + params,
headers={"X-API-Key": os.environ["ODDSLINK_API_KEY"]})
with urlopen(request, timeout=20) as response:
fixtures = json.load(response)
for fixture in fixtures:
for book in fixture["bookmakers"]:
for market in book["markets"]:
if market["key"] != "h2h" or market.get("period"):
continue
fee = market.get("fees")
for outcome in market["outcomes"]:
raw = outcome.get("price")
if raw is None:
continue
net = outcome.get("net_price")
# Missing net_price is not proof of a zero fee.
comparable = net if net is not None else (
raw if fee and fee["model"] == "included" else None)
print(fixture["id"], book["key"], outcome["name"],
"raw", raw, "net", comparable,
"last_update", market.get("last_update"))The script reads current NBA fixtures when you run it; it does not replay the October read. It prints an unknown comparison value when the fee fields cannot establish a net quote. Resolve those books' fee terms before sorting by price. An absent net_price alone cannot justify a zero-fee assumption.
Keep size kinds and timestamps attached
The recorded Thunder sizes were 8,807.4 at Kalshi and 134,170.59 at Polymarket, both resting_depth. FanDuel showed 12,097 as posted_limit. Resting depth is the amount at the quoted price; a posted limit is the book's stated maximum. Neither promises that an order will execute unchanged.
Read size.kind before using size.amount, and preserve an absent size as unknown. Keep the market's last_update next to the price. The timestamps in the table belong to the recorded response and are all UTC.
For fee arithmetic across repeated bets, see the long-term cost of exchange fees.
Questions
Can Kalshi, Polymarket and sportsbooks share a response?
Yes. Select their bookmaker keys on the odds route. Prices sit under the same fixture id, market key and outcome names. Check the contract and settlement rules before treating them as interchangeable.
Does fees=taker replace price?
No. It adds net_price alongside price for supported fee schedules and adds a fees object to the market. The parameter requires Starter or above.
Does an absent net_price mean the book charges nothing?
No. An unsupported book or date can have neither fee field. With fees.model equal to included, the quoted price already includes the fee. Check the published schedule and your account terms.
Compare the same fixture across books.
Use the documented odds route for sportsbook and prediction market prices. Add fees=taker on Starter or above to request published taker fees.
More from the blog
Exchange fees / expected value
How exchange fees change the price and erode long-term gains
Hold the bets and the pre-fee expected gain constant, then measure what each fee takes over 1,000 bets, including negative expected returns.
Read the post →API evaluation / testing
How to pick an odds API: a developer's test checklist
What to test during an odds API trial, how to measure each requirement, and what evidence to keep.
Read the post →NFL / line movement
NFL line movement explained: openers, steam and key numbers
Openers, closing lines, steam, key numbers 3 and 7, and why each book posts its own number. With real charts from last week's NFL games.
Read the post →Prediction markets / odds formats
Prediction market odds explained: convert a price to American odds
Turn a 62¢ contract into American odds and implied probability, and see how a market's spread and fees compare with sportsbook vig.
Read the post →CLV / +EV / no-vig
CLV and +EV explained: closing line value and no-vig fair odds
Remove the vig from a two-way market, price expected value, and measure CLV against the book's own closing line. Every step worked.
Read the post →