Why Devigging One Book Isn't a Fair Price
September 25, 2026

Every price a sportsbook publishes has the book's margin baked into it. If you are comparing prices, ranking bets, or feeding a model, you need that margin out before the numbers mean anything.
The arithmetic is short enough to write in ten lines. The part people get wrong is what the result actually represents.
The calculation everyone starts with
Take a two-sided market — a moneyline, a spread, a total. Convert each side's American price to an implied probability, add them, and see how far past 100% the pair lands. That excess is the vig.
def american_to_prob(odds):
o = int(odds)
return -o / (-o + 100) if o < 0 else 100 / (o + 100)
def prob_to_american(p):
return f"{-round(100 * p / (1 - p))}" if p > 0.5 else f"+{round(100 * (1 - p) / p)}"
A standard -110 both ways:
-110 -> 110 / 210 = 0.5238 (52.38%)
-110 -> 110 / 210 = 0.5238 (52.38%)
total = 1.0476 -> 4.76% overround
Divide each side by that total and the pair sums to 1. Both sides come back at 50%, which converts to +100 — even money. That 4.76% was the book's cut.
Now a market where the book is not symmetrical. Home at -140, away at +120:
-140 -> 140 / 240 = 0.5833 (58.33%)
+120 -> 100 / 220 = 0.4545 (45.45%)
total = 1.0379 -> 3.79% overround
normalised: 0.5621 / 0.4379 -> -128 / +128
So the fair line on that market is roughly -128 and +128, against a posted -140 and +120. That is the whole method, and it is correct as far as it goes.
You need both sides, and the API gives you the pointer
The calculation needs the complement. Every odd carries an opposingOddID for exactly this:
{
"oddID": "points-home-game-ml-home",
"opposingOddID": "points-away-game-ml-away",
"betTypeID": "ml",
"sideID": "home",
"fairOdds": "-128",
"bookOdds": "-140"
}
Follow that pointer rather than constructing the opposite oddID by hand. The {statID}-{statEntityID}-{periodID}-{betTypeID}-{sideID} pattern is predictable enough that you could build the complement yourself for a two-way market, and on those you would usually be right.
Three-way markets are where that breaks. On an ml3way moneyline the opposite of home is not away — it is away+draw, because a bet on the home team loses on a draw as well as a defeat. The bet type carries six sides in total: home, away, draw, home+draw, away+draw and not_draw. You would not guess that pairing, and if you devig home against away on a soccer match you have normalised two prices that do not sum to one proposition.
The same pairing catches people out at settlement, where getting it wrong marks two losers on a drawn match. We cover that in grading bets programmatically.
Odds arrive as strings, so -140 is "-140". Cast before you do arithmetic on it and keep the leading + when you render.
Where this stops being a fair price
Here is the part that matters. What you just computed is not the fair price. It is that book's no-vig opinion.
Devigging removes the margin. It does not remove the lean. If a book is shaded because it is holding one-sided action, or because it prices a market lazily, or because its customers reliably bet one way, that bias survives the normalisation untouched — you have simply rescaled it to sum to 100%.
Two consequences.
The first is that a single-book devig tells you nothing about whether that book is out of line, because you have used the book as its own reference. Comparing a price to its own devigged version is circular.
The second is subtler and catches prop tools especially: books disagree on the line as well as the price. One book at 26.5 and another at 27.5 on the same player are not two opinions about the same proposition. Normalising probabilities across a line difference produces a number that looks precise and means nothing. Read the overUnder or spread on each entry, not just the odds.
Doing it across books
The better estimate uses many books. Every market carries every book's price in byBookmaker, keyed the same way:
curl "https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true" \
-H "x-api-key: YOUR_API_KEY"
"byBookmaker": {
"betmgm": { "odds": "-118", "overUnder": "224.5", "available": true },
"draftkings": { "odds": "-115", "overUnder": "224.5", "available": true },
"prizepicks": { "odds": "+100", "overUnder": "224.5", "available": true }
}
Devig each book's pair, then combine. Two practical warnings before you write that loop.
Not every book prices every market, so your denominator moves between markets and between minutes. Read available on each entry and treat a missing book as normal rather than as an error.
And a plain average is the wrong combiner. Books are not equally informative — a low-margin book like Pinnacle that moves early carries more signal than a recreational book following it — and weighting them equally throws that away. Filter to the books pricing the same line, then weight rather than average.
What fairOdds already does
Every odd ships with the consensus already computed:
| Field | What it is |
|---|---|
fairOdds | Consensus across every book pricing the market, vig removed |
bookOdds | Consensus across those books with the margin left in |
fairOverUnder / fairSpread | The consensus line, not just the price |
openFairOdds / openBookOdds | The same pair when the market first opened |
So the ten lines above are worth writing once, to satisfy yourself that the numbers land where they should, and then not maintaining. Compute a market by hand, compare it to fairOdds, and you will see the difference between a single-book devig and a cross-book consensus on the same market — which is the point this whole post is making.
Our consensus odds guide covers the methodology behind those two fields in detail, including how books are weighted.
openFairOdds is the one people miss. Holding the opening fair price next to the current one gives you the market's own revision, which is usually a better feature for a model than either number alone. Per-book open and close values are available too, with includeOpenCloseOdds=true on the request — they are excluded by default to keep responses small.
Where the distinction pays
For positive EV work, the entire calculation is a book's price against a fair reference. Use a single-book devig and you are measuring a book against itself. Use fairOdds and you are measuring it against everyone.
For odds comparison, the vig-free number is what lets you say a price is genuinely better rather than just differently juiced. Two books at -110 and -105 are not four points apart in value once you account for what each is holding on the other side.
For arbitrage you want the posted prices, not the fair ones — but the fair price tells you which side of the arb is doing the work, and whether the gap is real or a stale line about to move.
The short version
Devigging is easy and worth understanding. Just be clear about what falls out of it: the margin comes off, the book's opinion stays on. If you need a reference price rather than one book's view of itself, that has to come from across the market, and fairOdds is that number already computed.
The odds data type reference lists every field discussed here, and handling odds covers reading byBookmaker in code.
Frequently asked questions
What does devigging mean?
How do I convert American odds to an implied probability?
Is a devigged price the same as a fair price?
Do I need to calculate this myself?
How do I get both sides of a market?
Why does devigging across books need care?
Can I see the opening fair odds as well as the current ones?
Get the fair price, not one book's view of itself
fairOdds and bookOdds on every market, across 85+ books, in one request.