Skip to main content

Golf Odds in Python: Compare Bookmaker Prices with pandas

Golf Odds in Python: Compare Bookmaker Prices with pandas

This tutorial pulls odds for the 2027 Masters from several bookmakers with Python, puts them in a pandas table, and finds the best price on each golfer. The same code works for any golf tournament on the PGA Tour, DP World Tour, LIV Golf or the majors. Every sample below was run against the live API on 7 November 2026, and the outputs are the real responses from that day.

The data comes from the Odds-API.io golf outrights endpoints. What they cover, which bookmakers price which markets and how ties are settled is on the Golf Outrights API page. This post is about using the data.

Setup

Install the Python SDK with the pandas extra, and put your API key in the ODDS_API_KEY environment variable. The golf endpoints need the Outrights add-on on a paid plan; without it the SDK raises AddOnRequiredError with a link to add it.

pip install "odds-api-io[pandas]"
export ODDS_API_KEY=your_key

Find the tournament

get_outrights lists the tournaments for a tour, sorted by start date. The majors have their own league, majors, so the Masters is one call away:

import os

from odds_api import OddsAPIClient
from odds_api.dataframes import outright_odds_to_dataframe

client = OddsAPIClient(api_key=os.environ["ODDS_API_KEY"])

majors = client.get_outrights(sport="golf", league="majors")
masters = next(t for t in majors if t["name"].startswith("Masters"))
print(masters["id"], masters["name"], masters["date"], masters["bookmakerCount"])
1008 Masters Tournament 2027 2027-04-08T11:00:00Z 36

bookmakerCount is how many of the bookmakers on your plan price the tournament right now. Five months out it is already 36, because futures markets for the majors open early.

Fetch prices from several bookmakers

get_outright_odds takes the tournament id and up to 30 bookmakers from your plan. outright_odds_to_dataframe flattens the response into one row per bookmaker, market and golfer:

books = ["DraftKings", "FanDuel", "BetMGM", "Bet365", "William Hill", "Betfair Exchange", "Kalshi", "Polymarket", "Paddy Power"]
odds = client.get_outright_odds(masters["id"], books)
df = outright_odds_to_dataframe(odds)
print(df.groupby("bookmaker")["market"].unique())
bookmaker
Bet365              [Winner, Top 5 (Incl. Ties), Top 10 (Incl. Ties)]
BetMGM                                                       [Winner]
Betfair Exchange                                             [Winner]
DraftKings                                                   [Winner]
FanDuel                                                      [Winner]
Kalshi                                                       [Winner]
Paddy Power                                                  [Winner]
William Hill                                                 [Winner]
Name: market, dtype: object

Not every bookmaker prices every market this early. Winner is everywhere; Top 5, 10 and 20 and Make Cut fill in closer to the tournament. Prices are decimal odds, already converted to floats by the dataframe helper. The exchange rows also carry layOdds, and Make Cut rows carry yes and no instead of odds.

Best Winner price per golfer

Each golfer has one participantId and one name across all bookmakers, so a pivot table lines the prices up without any name matching:

winner = df[df["market"] == "Winner"]
table = winner.pivot_table(index="label", columns="bookmaker", values="odds")
table["best"] = table.max(axis=1)
table["best_book"] = table.drop(columns="best").idxmax(axis=1)
table["books"] = table.drop(columns=["best", "best_book"]).count(axis=1)
print(table.sort_values("best").head(10)[["best", "best_book", "books"]])
bookmaker            best         best_book  books
label                                             
Scottie Scheffler   5.556            Kalshi      8
Rory McIlroy       12.500            Kalshi      8
Jon Rahm           22.727            Kalshi      8
Ludvig Aberg       23.000  Betfair Exchange      8
Cameron Young      24.000  Betfair Exchange      7
Xander Schauffele  26.000  Betfair Exchange      8
Bryson DeChambeau  27.778            Kalshi      8
Tommy Fleetwood    35.714            Kalshi      8
Collin Morikawa    41.667            Kalshi      8
Matt Fitzpatrick   43.478            Kalshi      8

Kalshi or the Betfair Exchange has the longest price on every one of the ten favourites. Two things to keep in mind: exchange prices are before commission and Kalshi prices are before its trading fees, and Cameron Young is listed by seven of the eight bookmakers, so check the books column before trusting a best price.

How much margin each bookmaker builds in

Turn each price into an implied probability with 1 / odds and add them up per bookmaker. A fair book over the whole field sums to 1.00; anything above that is the bookmaker's margin, which in outright markets is much larger than on a single match:

implied = 1 / winner.dropna(subset=["odds"]).groupby(["bookmaker", "label"])["odds"].first()
margin = implied.groupby("bookmaker").agg(golfers="count", total="sum").round(2)
print(margin.sort_values("golfers", ascending=False))
                  golfers  total
bookmaker                       
DraftKings             98   1.70
FanDuel                88   1.58
Paddy Power            88   1.55
BetMGM                 83   1.56
Betfair Exchange       83   1.20
William Hill           82   1.65
Bet365                 55   1.43
Kalshi                 38   0.92

The sportsbooks that list most of the field sum to between 1.43 and 1.70, so a large share of every Winner price is margin. The Betfair Exchange comes in at 1.20 over 83 golfers. Kalshi totals 0.92 only because it lists 38 golfers: the longshots it leaves out would push it over 1.00. Count the golfers before comparing totals, or compare books on the same set of names.

Top 10 and Top 10 (Incl. Ties)

Top 5, 10 and 20 come in two versions. The plain market settles a tie on the boundary by dead heat rules, so part of the stake is lost. The Incl. Ties market pays ties in full and is priced shorter. Five months out, Bet365 is the only one of these bookmakers with a Top 10 market on the Masters, so this sample uses the PGA Tour event that was in play when we ran it:

pga = client.get_outrights(sport="golf", league="pga-tour")
week = next(t for t in pga if t["bookmakerCount"] > 0)
week_df = outright_odds_to_dataframe(client.get_outright_odds(week["id"], books))
top10 = week_df[week_df["market"].isin(["Top 10", "Top 10 (Incl. Ties)"])]
pair = top10.pivot_table(index=["label", "bookmaker"], columns="market", values="odds").dropna()
golfer = pair.index.get_level_values("label")[0]
print(week["name"], golfer)
print(pair.loc[golfer])
World Wide Technology Championship 2026 Adam Svensson
market      Top 10  Top 10 (Incl. Ties)
bookmaker                              
DraftKings    19.0                14.25
FanDuel       26.0                16.00

The same golfer is 19.0 to finish in the top 10 at DraftKings under dead heat rules and 14.25 with ties paid in full. Comparing a Top 10 price at one bookmaker with a Top 10 (Incl. Ties) price at another would make the first look far better than it is. The dropna() keeps only bookmakers that price both versions for that golfer.

After the tournament

get_outright_results returns each golfer's final position, whether they made the cut, and the winners of every market as lists of participantIds. This is the Mexico Open, the first tournament settled through the API:

results = client.get_outright_results(1000)
print(results["name"], results["status"])
print(results["results"][:3])
print({market: len(ids) for market, ids in results["winners"].items()})
Mexico Open 2026 settled
[{'label': 'Andrew Novak', 'participantId': 991570904131, 'position': 1, 'tied': False, 'madeCut': True, 'status': 'finished'}, {'label': 'Kristoffer Ventura', 'participantId': 993035649881, 'position': 2, 'tied': True, 'madeCut': True, 'status': 'finished'}, {'label': 'Steven Fisk', 'participantId': 993893124464, 'position': 2, 'tied': True, 'madeCut': True, 'status': 'finished'}]
{'Make Cut': 68, 'Top 10': 10, 'Top 10 (Incl. Ties)': 10, 'Top 20': 22, 'Top 20 (Incl. Ties)': 22, 'Top 5': 5, 'Top 5 (Incl. Ties)': 5, 'Winner': 1}

Two golfers tied for second, and the Top 20 list has 22 names because of a tie on the boundary. Since the ids match the odds, settling a position is a membership check: df["participant_id"].isin(results["winners"]["Top 10"]). Dead heat reductions differ by bookmaker, so apply your bookmaker's rule to boundary ties. Until a tournament is settled, results and winners are empty.

FAQ

Can I get golf odds in Python without the SDK?

Yes. The SDK wraps three REST endpoints, /v3/outrights, /v3/outrights/odds and /v3/outrights/results, so requests or any HTTP client works the same way. The outrights guide has the raw requests and responses.

Which golf tournaments can I get odds for?

PGA Tour, DP World Tour and LIV Golf individual stroke-play events, and the four majors: the Masters, the PGA Championship, the U.S. Open and The Open. Majors are usually priced months ahead.

How do I match golfers across bookmakers?

Use participantId. It is the same for a golfer at every bookmaker and in the results, and label is the name as listed in /v3/participants, whatever spelling the bookmaker uses.

Is there historical golf odds data?

Not yet. The endpoints serve current prices only, so store the responses yourself if you want a price history.

Next steps

The Golf Outrights API page lists all 47 bookmakers with the markets each one prices and the tournaments priced now. The outrights guide has every parameter and error code.

Get live and pre-match odds from 450+ bookmakers

OddsNotifier and OddsHub run on the same real-time feed you get with the API.

See what's built with Odds API →