Bookmaker Margin Analytics: Track Vig Trends Across 350+ Books (Python)
If you run a book, your margin is your revenue. The overround baked into every market (the vig) is the number that pays the bills. But a margin you never benchmark is a margin you are guessing at. Set it too fat and sharp customers route around you; set it too thin and you leave money on the table. The only honest reference point is the sharp market, measured not once but across the whole pricing window.
This guide shows operators and trading desks how to turn that into a programmatic workflow: compute overround from any book, pull a full Pinnacle vig timeline from free historical odds, and benchmark your own margin against the sharpest book in the market across 350+ books. Every number below was pulled live before publishing.
Why a Single Snapshot Lies
A vig calculator that reads today’s prices answers “what is my margin right now.” That is a spot check, not analytics. Real margin management is a time-series question:
- Does your margin hold as the line moves? A disciplined book keeps its overround roughly constant from open through to close, even as the underlying prices swing hard.
- How does it compare at open vs in-play? Margins typically compress as a market matures and liquidity arrives. If yours does not, you are mispricing the window.
- Where does it sit against the sharp floor? Pinnacle runs one of the tightest margins in the industry. The gap between your overround and theirs is your competitive exposure, customer by customer.
You cannot answer any of those from one snapshot. You need history, and OddsPapi gives it away on the free tier.
The Old Way vs The OddsPapi Way
| Task | Old Way | OddsPapi |
|---|---|---|
| Today’s margin | Manual spreadsheet spot check | One /odds call, computed |
| Margin over time | Log prices yourself for weeks | Free /historical-odds, full price history |
| Sharp benchmark | Eyeball Pinnacle by hand | Pinnacle in the same response |
| Coverage | The books you can scrape | 350+ books, one schema |
Step 1: Overround From Any Set of Prices
Overround is the sum of the implied probabilities. Subtract 1 and you have the margin. This works for any market, two-way or three-way.
def overround(decimal_prices: list[float]) -> float:
"""Return the bookmaker margin (%) baked into a set of decimal odds."""
implied = sum(1 / p for p in decimal_prices)
return round((implied - 1) * 100, 2)
# Pinnacle on a pre-game soccer 1X2 market
print(overround([2.03, 4.50, 2.98])) # -> 5.04 (a tight, sharp margin)
print(overround([2.05, 3.80, 2.75])) # -> 11.46 (a fat retail margin)
Those two lines are the same match. The first is Pinnacle, the second is a major retail book. The retail book is holding more than double the margin on identical outcomes. That gap is the whole point of this post.
Step 2: Build a Vig Timeline From Free Historical Odds
The /historical-odds endpoint returns the full price history of a fixture. Unlike the live /odds endpoint, the top key is bookmakers and each outcome’s players["0"] is a list of snapshots, not a single price. Max 3 books per call; auth is the apiKey query parameter.
We pulled Pinnacle’s history on a Norwegian fixture (Raufoss IL vs Sogndal) and got 313 snapshots per outcome spanning a full week from market open through to in-play.
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.oddspapi.io/v4"
params = {
"apiKey": API_KEY,
"fixtureId": "id1000002267018014",
"bookmakers": "pinnacle", # max 3 per call
}
data = requests.get(f"{BASE}/historical-odds", params=params).json()
# Full Time Result = market 101; outcomes 101 Home, 102 Draw, 103 Away
outcomes = data["bookmakers"]["pinnacle"]["markets"]["101"]["outcomes"]
history = {oid: outcomes[oid]["players"]["0"] for oid in ("101", "102", "103")}
print(len(history["101"]), "snapshots") # -> 313
Each outcome moves on its own clock, so to compute overround at any instant you forward-fill the last known price of each leg onto a shared timeline, then sum the implied probabilities.
def price_at(snapshots, t):
"""Last known price at or before timestamp t."""
last = None
for s in snapshots:
if s["active"] is False:
continue
if s["createdAt"] <= t:
last = s["price"]
else:
break
return last
# Union of all timestamps, then sample the overround across the window
stamps = sorted({s["createdAt"] for leg in history.values() for s in leg})
for t in (stamps[0], stamps[len(stamps)//2], stamps[-1]):
legs = [price_at(history[o], t) for o in ("101", "102", "103")]
if all(legs):
print(t[:19], legs, f"{overround(legs)}%")
The result is the finding that matters to an operator:
2026-06-14T14:12:16 [2.93, 3.61, 2.2] 7.29% # market open
2026-06-21T14:28:56 [1.662, 3.9, 4.62] 7.45% # in-play, prices swinging
2026-06-21T15:32:44 [1.358, 3.99, 11.95] 7.07% # latest
Look at what the prices did: the home leg collapsed from 2.93 to 1.36 as the match developed, the away leg ballooned past 11.0. The odds moved violently. The margin barely flinched, holding inside a 7.0 to 7.5 percent band the entire week. That discipline is exactly what a sharp book sells, and it is the benchmark you measure your own book against.
Step 3: Benchmark Your Book Against the Field
Now scan every book on a fixture and rank them by margin. This is the operator’s mirror: it shows where your overround sits in the competitive set. We ran it pre-game on a 12-book Icelandic fixture (Valur vs Keflavik).
odds = requests.get(f"{BASE}/odds",
params={"apiKey": API_KEY,
"fixtureId": "id1000018868194688"}).json()
ranked = []
for slug, book in odds["bookmakerOdds"].items():
try:
o = book["markets"]["101"]["outcomes"]
legs = [o[x]["players"]["0"] for x in ("101", "102", "103")]
except KeyError:
continue
if all(p["active"] and p["price"] for p in legs):
ranked.append((overround([p["price"] for p in legs]), slug))
for margin, slug in sorted(ranked):
print(f"{margin:6.2f}% {slug}")
5.04% pinnacle <- the sharp floor
6.13% draftkings
7.68% fanduel
8.34% sbobet
9.62% betmgm
10.53% hardrockbet
10.90% betrivers
11.46% bet365 <- 2.3x Pinnacle's margin
Pinnacle anchors the market at 5.04 percent. The retail pack clusters between 9 and 11.5 percent. If you are operating in that pack, this is your map: bettors who price-shop will always find the thinner book, so your fat margin only holds with customers who do not compare. Knowing the exact spread, per market and per fixture, is the difference between a deliberate margin strategy and an accidental one.
A Note on Cross-Sport Comparison
Margins vary by sport, but compare like with like. A three-way soccer market (Home/Draw/Away) carries a structurally higher summed overround than a two-way market simply because it has an extra outcome. Pinnacle’s two-way basketball moneyline on AS Monaco vs Paris ran 4.21 percent against the 5.04 percent it held on the three-way soccer market above. To track vig trends across sports, normalise per outcome or hold the market structure constant; never put a two-way and a three-way overround in the same column. For the underlying margin mechanics, see our vig calculator.
From Snapshot to Strategy
Two endpoints give an operator a full margin analytics loop: /odds for the live competitive snapshot and the free /historical-odds for the time-series. Wire them on a schedule and you have:
- Free historical odds across every fixture, so you backtest your margin policy against real closing markets instead of guessing.
- 350+ bookmakers in one schema, so the sharp floor and the full retail set are always one call away.
- A repeatable benchmark you can run per sport, per market, and per pricing window.
Pair this with our consensus odds calculator to de-vig against the whole field, the CLV line audit to grade your closing prices, and the historical odds CSV export to push the timeline into your own warehouse. The pricing pipeline that feeds all of it is covered in the WebSocket feed guide.
Frequently Asked Questions
How do I calculate a bookmaker’s margin in Python?
Sum the implied probabilities of every outcome (1 divided by each decimal price), subtract 1, and multiply by 100. For Pinnacle’s 2.03 / 4.50 / 2.98 on a soccer 1X2 market that is 5.04 percent. The same formula works for two-way and three-way markets.
What is the difference between vig and overround?
They describe the same thing. Overround is the amount by which a book’s implied probabilities exceed 100 percent; the vig (or juice) is the operator’s margin that overround represents. A 5 percent overround means a 5 percent built-in margin.
Can I track how a bookmaker’s margin changes over time?
Yes. Use the free /historical-odds endpoint, which returns the full list of price snapshots per outcome. Forward-fill each leg onto a shared timeline and compute the overround at each point. A sharp book like Pinnacle typically holds its margin within a tight band across the whole window.
Why does OddsPapi historical odds nest prices as a list?
The historical endpoint returns price history, so players["0"] is a list of snapshots (each with a price and createdAt), not the single current price the live /odds endpoint returns. The top-level key is also bookmakers, not bookmakerOdds. The endpoint accepts a maximum of 3 bookmakers per call.
Which bookmaker has the lowest margin to benchmark against?
Pinnacle consistently runs one of the tightest margins in the market and is the standard sharp benchmark. In the 12-book example above it held 5.04 percent where retail books ran 9 to 11.5 percent on the identical fixture.
Benchmark Your Margin
Stop guessing where your overround sits. Get a free OddsPapi API key, pull the sharp market’s full history, and measure your margin against it across every fixture you price.