How to Remove the Bookmaker Margin from Odds

Turn bookmaker odds into fair probabilities: the proportional and power methods, worked examples and Python code.

By the HoopsAPI team · Updated

To remove the bookmaker margin, convert each price to an implied probability (1 / odds) and rescale the probabilities so they add up to exactly 100%: either proportionally, or with the power method, which takes more of the margin from the longshot. The margin (also called the vig, juice or overround) is why implied probabilities add up to more than 100%; this guide shows both methods with worked examples and Python code, and when each is right.

What are implied probability and the margin?

For decimal odds o, the implied probability is 1 / o. Take a basketball moneyline with the favourite at 1.50 and the underdog at 2.60:

  • Favourite: 1 / 1.50 = 66.7%
  • Underdog: 1 / 2.60 = 38.5%
  • Total (the “book”): 105.1%, so the margin is 5.1%

Fair probabilities must add up to exactly 100%. The question is how to share the extra 5.1% between the two sides, and that is where the methods differ.

How does the proportional method work?

Divide each implied probability by the book total. Every outcome is scaled down by the same factor, so the margin is assumed to be spread in proportion to each side's probability.

SideOddsImpliedFair probabilityFair odds
Favourite1.5066.7%63.4%1.577
Underdog2.6038.5%36.6%2.733

It is simple and good enough when the two sides are close in price, such as a main handicap or total line.

How does the power method work?

Bookmakers do not load their margin evenly. They take proportionally more from longshots, because bettors systematically overbet them (the favourite-longshot bias). The power method models this: find the exponent k for which the implied probabilities raised to k add up to exactly 1.

fair probability
p_i = (1 / o_i) ^ k     where   sum over outcomes of (1 / o_i) ^ k = 1

For the same prices, k = 1.083. Raising a small probability to a power above 1 shrinks it relatively more than a large one, so the underdog gives up more of the margin:

SideOddsProportional fair oddsPower fair odds
Favourite1.501.5771.551
Underdog2.602.7332.814

The gap between the methods grows with the price difference. For an even handicap line at 1.91 / 1.91 they agree exactly; for a heavy moneyline favourite at 1.10 against 7.00 they can disagree by several percent on the underdog. The power method is the better default for moneylines and any market with uneven prices.

How do I remove the margin in Python?

devig.py
def proportional(odds):
    book = sum(1 / o for o in odds)
    return [(1 / o) / book for o in odds]


def power(odds, tolerance=1e-10):
    """Fair probabilities: find k with sum((1/o)^k) == 1 by bisection."""
    lo, hi = 1.0, 3.0  # k >= 1 whenever the book has a positive margin
    while hi - lo > tolerance:
        k = (lo + hi) / 2
        if sum((1 / o) ** k for o in odds) > 1:
            lo = k
        else:
            hi = k
    return [(1 / o) ** k for o in odds]


odds = [1.50, 2.60]
print([round(1 / p, 3) for p in proportional(odds)])  # [1.577, 2.733]
print([round(1 / p, 3) for p in power(odds)])         # [1.551, 2.814]

Both functions work for any number of outcomes, so the same code also handles markets with more than two.

Which bookmaker's odds should I de-vig?

Removing the margin gives you that bookmaker's view of the probabilities, which is only as good as the bookmaker. For a reference price, use the sharpest market available: a low-margin bookmaker that takes large bets from professionals, or an exchange. Then compare other bookmakers' raw prices against that fair price: any price above it is, by that measure, positive expected value. Our comparison of basketball bookmakers shows how much margins vary between bookmakers and leagues.

Two practical rules:

  • De-vig both sides of the same line. A handicap of -4.5 and one of -5.5 are different markets. The API returns every line with both sides together, so pair prices by line.
  • Use prices captured at the same moment. Pairing a fresh price with a stale one from the other side produces a meaningless margin. Each game in the API carries updated_at, and each bookmaker captured_at.

Where can I get the odds?

HoopsAPI returns moneyline, handicap and totals odds from multiple bookmakers for every half and quarter, already paired by line. Fetch a game and de-vig each bookmaker's main line:

example
import requests

r = requests.get(
    "https://hoopsapi.com/api/v1/games",
    params={"competition": "NBA"},
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
game = r.json()["games"][0]
for bookmaker, markets in game["periods"]["ft"].items():
    ml = markets.get("moneyline")
    if ml:
        fair = power([ml["home"], ml["away"]])
        print(bookmaker, [round(1 / p, 3) for p in fair])

Try it with real data

The free plan includes 10 requests a day across every league and market. No credit card.