← Plugin catalog
Data & Analytics

BirdieAPI

BirdieAPI v1.0.0

Golf data and stats

Full description ↓

View on ChatGPT ↗

Save this plugin to Following and return to review observed changes. Follow up to 10 plugins free.

Description

From the marketplace listing

BirdieAPI connects ChatGPT and Codex to golf data from BirdieAPI. It covers the PGA Tour, LPGA, LIV, DP World Tour and Korn Ferry Tour with leaderboards, scorecards, course history and scoring stats. Ask for tournament schedules and leaderboards, scorecards, course history, player results and season stats, head-to-head comparisons, player ratings, season leaders and outright winner odds. The plugin looks the answer up with read-only tools and reports what the data says, including the season, date and source of each number. The first time you use it, you sign in with your BirdieAPI account. Limits: BirdieAPI is read-only. It cannot place wagers, accept or track bets, or edit your account. Odds and prop lines are shown as information about the market, not as advice. Data comes from public sources and the BirdieAPI pipeline and is not guaranteed to be error-free or current to the minute, so do not rely on it as the sole basis for financial decisions. Ratings shown are BirdieAPI's own Elo-style ratings, not official rankings. Not affiliated with any tour or tournament. Bundled skills teach the model how to choose the right tool for a question and how to explain results.

Language: English · Automatically detected from descriptions.

Competitor profile

From the developer’s listing and package; capabilities are not independently tested. Package observed Nov 3, 2026.

Declared capabilities

  • Look up golfers and tournaments across major tours
  • Read schedules, leaderboards and scorecards
  • Read player season stats and results

Declared limitations

  • Place a wager: wagering is not supported
  • Out of coverage: another sport
  • Account and billing change: not supported

Review scenarios; not an exhaustive list.

Cost & access

BirdieAPI does not sell anything through ChatGPT. The plugin is read-only: it has no checkout, purchase, subscription or credit top-up flow.

Terms may have changed since this observation.

Starter prompt

List the 2025 major championships.

View package source →

Keywords

Files & skills

File archives

Plugin package19 files · 76.6 KBBrowse files →
Skill instructions
birdieapi-tool9.26 KB

View saved version →

---
name: birdieapi-tool
description: "Default data source for the BirdieAPI skills. Routes every call to the 14 BirdieAPI tools for PGA Tour, LPGA, LIV, DP World Tour and Korn Ferry Tour tournaments, leaderboards, scorecards, courses, player results, ratings and the winner prices of the four men's majors. Use when any other skill needs golf data, or when the user asks which tool to call, what a call costs, or how to connect."
metadata:
  version: 1.0.0
  author: BirdieAPI
  user-invocable: false
---

# BirdieAPI Tool

> Connection: this plugin reaches the data through its MCP server, which the user connects with a normal OAuth sign-in. Never ask the user for an API key, token or password, and never put one in a tool argument.


Every other skill delegates data retrieval here. This skill owns the 14 tools, the credit costs, the league switch and the response envelope.

**Coverage (from birdieapi.com/data and the league pages, check them before quoting a number):**

| Dataset | What is there |
|---------|---------------|
| PGA Tour leaderboards and results | Every player in every field since 2001 |
| PGA Tour hole-by-hole scores | About 99% of PGA Tour rounds since 2001, with par and yards per hole |
| PGA Tour tournament stat lines | Driving distance and accuracy, greens in regulation, putts per green, sand saves, from 2011 |
| Major winner odds | The four men's majors, weekly snapshots from 2021 (plus the 2020 U.S. Open) |
| LPGA, LIV, DP World Tour | Leaderboards, round scores and the rating. No hole-by-hole, no odds |
| Korn Ferry Tour | Leaderboards, round scores and hole-by-hole from 2007. No odds |

Data is public ESPN data loaded and checked by BirdieAPI. It is historical and next-morning, not a live feed.

## Connecting

- **Response envelope:** every REST response is wrapped as `{"data": {...}}`. Unwrap `data` before reading fields.

## When to Use Each Tool

| Task | Tool | Credits |
|------|------|---------|
| Find tournament ids for a season, winners, courses, majors | `list_tournaments` | 1 |
| What is on this week and next, defending champion | `get_schedule` | 1 |
| Find a player id from a name | `search_players` | 1 |
| Full results of one tournament | `get_leaderboard` | 2 |
| How each hole of a course played | `get_course` | 2 |
| One player's hole-by-hole card | `get_scorecard` | 2 |
| Who is playing best, on one rating | `get_ratings` | 2 |
| Player overview, career totals, last ten results | `get_player` | 5 |
| Player's tournament-by-tournament rounds and strokes vs field | `get_player_results` | 5 |
| Player's season: par 3/4/5 splits, birdies, driving, greens | `get_player_stats` | 5 |
| Player's record at one event or course across years | `get_course_history` | 5 |
| Two players in every tournament both played | `get_head_to_head` | 5 |
| Season leaders in one stat | `get_leaders` | 5 |
| Major winner prices by sportsbook, opening and latest | `get_winner_odds` | 5 |

## The league switch

Most tools take `league`: `golf` (the default, the PGA Tour), `lpga`, `liv`, `dpwt`, `kornferry`.

- `get_scorecard` takes only `golf` or `kornferry`.
- `get_winner_odds` has no `league` argument. It holds the four men's majors only.
- The `sg_par3`, `sg_par4`, `sg_par5` stats exist for the PGA Tour and Korn Ferry Tour only. Other tours return null.
- Ratings are measured within one tour. A 2.0 on the LPGA and a 2.0 on the PGA Tour are not on one scale.
- Player ids are ESPN's and are shared across tours, so a player's results on two tours join on one id.

## Identifiers and seasons

- `tournament_id`, `player_id` and `course_id` are strings. Never guess one: get it from `list_tournaments`, `get_schedule`, `search_players` or `get_leaderboard`.
- `season` is a calendar year (a September 2025 event is season 2025). Exceptions from the site: the 2020 U.S. Open and the November 2020 Masters are season 2021; Korn Ferry 2020 and 2021 are one combined season stored as 2020.
- Paged tools (`list_tournaments`, `get_player_results`) return `next_offset`. Pass it as `offset` for the next page.

## Tool Selection Guide

| You Need | Use This | Not This |
|----------|----------|----------|
| "What is on this week?" | `get_schedule` | `list_tournaments` (a whole season) |
| "Who won the 2024 PGA Championship?" | `list_tournaments` with `season` and `major` | `get_leaderboard` (2 credits for a winner you can read in a row) |
| "Full results of the Masters" | `get_leaderboard` | `get_player_results` per player |
| "Find Rory McIlroy's id" | `search_players` | `get_player` (needs the id) |
| "How has Scheffler played this year?" | `get_player_stats` with `season` | `get_player` (career overview) |
| "Best par-5 scorers of 2025" | `get_leaders` with `stat` `sg_par5` | `get_player_stats` per golfer |
| "Which holes at Augusta were hardest?" | `get_course` | `get_scorecard` |
| "Scheffler's round 1 hole by hole" | `get_scorecard` | `get_course` |
| "How does he do at Pebble Beach?" | `get_course_history` | `get_player_results` filtered by hand |
| "Scheffler versus McIlroy" | `get_head_to_head` | two `get_player_results` calls |
| "Who is best right now?" | `get_ratings` | `get_leaders` (a season stat) |
| "What was he priced at for the Masters?" | `get_winner_odds` | nothing else has prices |

## Calling Patterns

Example calls in the skills are written as the tool name followed by its JSON arguments. Over MCP pass the arguments object. Over REST send it as the body:

```
search_players {"query": "scheffler"}
get_ratings {"limit": 5}
get_winner_odds {"tournament_id": "401580344", "limit": 5}
```



Real response, unwrapped from `{"data": ...}` and trimmed:

```
players[0]: player_id "9478", name "Scottie Scheffler", country "USA", tournaments 167, wins 23, first_season 2014, last_season 2026
```

Chain the ids: `search_players` gives `player_id`, `list_tournaments` gives `tournament_id`, and the 2-credit and 5-credit tools take those.

## Errors and empty results

- A spent credit balance or a lapsed sign-in returns an error rather than data. Tell the user which.
- An empty list for a season usually means the league has no data there (for example the LPGA between 2013 and 2019). Check the league page before assuming a bug.
- A null `sg_par3`, `sg_par4` or `sg_par5` means the tour is not the PGA Tour or Korn Ferry Tour.
- Do not retry a failed call in a loop. Each attempt can spend credits.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Resolve a name to an id | 1 | `search_players` |
| One tournament leaderboard | 2 | whole field unless `limit` is set |
| One player card | 2 | `get_scorecard` |
| One player's season | 5 | `get_player_stats` |
| One major's winner market, 200 golfers | 5 | `limit` does not change the price |
| A full 10-year course-history table for 40 golfers | 200 | 5 per golfer; warn first |

A 500-credit month is about 100 five-credit calls a month. Tell the user the running total on any multi-call plan.

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_strokes_gained`, `get_sg_categories` | `get_ratings`, `get_player_results` (`vs_field`, `adj_vs_field`) and `get_leaders` with `sg_par3/4/5`. There is no shot-level data, so no off-the-tee, approach or putting split |
| `get_live_scores`, `get_live_leaderboard` | Not available. Data is next-morning; Data is next-morning, so check `status` from `list_tournaments` (scheduled, live, final) before reading a leaderboard as final |
| `get_odds`, `get_matchup_odds`, `get_top_10_odds` | Only `get_winner_odds`, and only for the four men's majors |
| `get_world_ranking` | Not available. Use `get_ratings` (our own rating) |
| `get_team_standings` | Not available. LIV has individual results only |
| `get_weather`, `get_tee_times`, `get_pairings` | Not available |
| `get_field` | A field is the `players` list of `get_leaderboard` once a tournament has results |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Call it "strokes gained" | The rating is the site's own strokes-versus-field number built from scores, not the official shot-level stat | Say "strokes versus the field"; see `strokes-vs-field` |
| Pass a name as `player_id` | IDs are numeric strings | `search_players` first |
| Pull `get_course_history` for a whole field | 5 credits each | Shortlist with `get_ratings` first |
| Quote the latest winner price as the closing line | For past majors it can be up to a week before the first tee | Call it the latest pregame snapshot |
| Compare an LPGA rating to a PGA Tour rating | Each is relative to its own tour | Compare within a tour |

## BYOD

If the user brings their own round scores or odds, skip the tools and spend no credits. Minimum columns: rounds need `player`, `tournament`, `round`, `strokes`, `course`, `date`; odds need `player`, `book`, `american_price`, `timestamp`. Say when a column had to be inferred.

## What to Do Next

| What You Retrieved | Next Action | Skill |
|--------------------|-------------|-------|
| Tournament list or leaderboard | Read the result, find the course | `tournament-lookup` |
| Player id | Scout the player | `player-scouting` |
| Ratings or round-level strokes vs field | Interpret and compare | `strokes-vs-field` |
| Hole data | Par-type and hole analysis | `hole-scoring` |
| Winner prices | Compare books, movement | `odds-explorer` |
course-fit7.18 KB

View saved version →

---
name: course-fit
description: "Measures how a golfer has played at one tournament or one course across every year, and how much of that record is course fit rather than general form. Use when the user asks who plays well at Augusta or Pebble Beach, how a player's record at an event looks, whether course history matters, or how to blend course form with overall rating. Do not use for ranking current form on its own -- see strokes-vs-field. Do not use for how the holes of a course played -- see hole-scoring. Do not use for turning fit into a win probability -- see outright-model."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Course Fit

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `get_course_history` (5 credits) returns a player's record at one tournament or one course across years. `get_course` (2) describes the course. `get_ratings` (2) gives the baseline.

You are an expert in separating "this player is good" from "this player is good here". Golf course history is real but noisy, and most of it is just general quality. Your job is to measure the part that is left over and say how sure you are.

## When to Use

- "How has Scottie Scheffler done at the Masters?"
- "Who has the best record at Pebble Beach since 2010?"
- Comparing a field's course records before a tournament
- Deciding how much weight course history gets in a model
- Checking whether a hot player has a poor record at this venue

## When NOT to Use

- Current form or the rating -- see `strokes-vs-field`
- Hole difficulty and setup -- see `hole-scoring`
- Final probabilities -- see `outright-model`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_course_history` | One player at one tournament (any edition's `tournament_id`, every year returned) or at one course (`course_id`, across tournaments): finishes, rounds, `vs_field`, summary | 5 |
| `get_course` | Par and yards per hole, how each hole played, for one tournament's course | 2 |
| `list_tournaments` | Find the tournament id and `course_id` values | 1 |
| `get_ratings` | The player's overall rating for the baseline | 2 |
| `get_player_results` | Raw rounds if you want your own course filter | 5 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_course_fit`, `get_fit_score` | Compute it from `get_course_history` and `get_ratings` as below |
| A whole field's course history in one call | One `get_course_history` call per player; shortlist first |
| `get_course_stats_by_year` | `get_course` per edition |
| `get_course_conditions`, `get_weather` | Not available |

## How It Works

**Step 1: Pick tournament or course.** Pass `tournament_id` of any year to get every edition of that event. Pass `course_id` to get every tournament played on that course (useful for a course that hosts different events). Get ids from `list_tournaments`; its `courses[]` lists each tournament's `course_id`.

**Step 2: Pull the record.**

```
get_course_history {"player_id": "9478", "tournament_id": "401580344"}
```

Real response (trimmed), Scottie Scheffler at the Masters:

```
summary: starts 7, wins 2, top_tens 5, made_cut 7, average_finish 7.9, vs_field 2.78
editions: 2026  finish 2   vs_field 3.30   rounds 70 74 65 68
          2025  finish 4   vs_field 2.68   rounds 68 71 72 69
          2024  finish 1   vs_field 4.57   rounds 66 72 71 68
          2023  finish T10 vs_field 2.01
          2022  finish 1   vs_field 4.36
          2021  finish T18 vs_field 1.23
```

Here `vs_field` is the player's average strokes versus the field per round at that event. Seven starts is 28 rounds.

**Step 3: Compare with the baseline.** The question is not whether 2.78 is good. It is whether it is better than he is in general. Pull his rating (or his `vs_field` over a comparable window):

```
get_ratings {"limit": 50}
```

A simple fit measure for player `i` at event `e`:

```
fit_raw = mean(event vs_field) - overall level at the time of those rounds
```

For a fair "overall level" use the rating as it stood before each edition (`get_ratings` with `as_of` per year), not today's rating. Using today's rating for a 2021 start leaks the future.

**Step 4: Shrink.** Course history is a small sample. Blend it toward zero:

```
fit = n_rounds / (n_rounds + k) * fit_raw
```

`k` is the number of rounds at which course history deserves half weight. Do not hard-code one. Treat `k` as a parameter, fit it with `walk-forward-validation`, and report the value used. A starting assumption of a few dozen rounds is a guess, not a finding.

**Step 5: Check what the fit is made of.** A record can come from the course's design (long par 5s, firm greens) or from a few hot weeks. Use `get_course` to profile the holes and `hole-scoring` to see whether the player's par-type split lines up with what the course rewards. If a course has four reachable par 5s and the player's `sg_par5` is elite, that is a mechanism. A good record with no mechanism is lower confidence.

**Step 6: Treat course changes as breaks.** A course can be redesigned, or a tournament can move venues. `courses` is listed per edition; if the venue changed, split the record at the change. Do not mix.

## How much does course history matter

Say what is known and what is not. In general, course-specific skill exists in golf but is small relative to general skill, which is why shrinkage is the right default. The size for a given course and tour should be measured, not assumed: run the shrinkage fit on past editions with `walk-forward-validation` and see whether adding `fit` improves out-of-sample log loss on winners or on finishing scores. If it does not, drop it.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| One player's record at one event | 5 | |
| Ten-player shortlist | 50 | |
| Whole field of 90 | 450 | do not; shortlist by rating first |
| Baseline ratings at one date | 2 | |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Rank players by average finish at the course | Finish depends on field size and luck | Use `vs_field` per round |
| Add course history without a baseline | It double-counts general skill | Subtract the overall level first |
| Use today's rating as the baseline for a 2019 start | Leakage | `as_of` before each edition |
| Trust 2 starts | 8 rounds is noise | Shrink and show `n_rounds` |
| Ignore a venue move | Different course | Split the record |
| Call a player a "course horse" on one win | One week of luck | Require multiple editions |

## Output Format

```
Scottie Scheffler at the Masters (Augusta National), 7 starts
Wins 2, top tens 5, average finish 7.9, strokes versus the field +2.78 a round.
Baseline rating before each start: see get_ratings as_of.
Shrunk fit estimate: +X.XX a round (n = 28 rounds, k = <value used>)
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| A fit number per golfer | Feed it into a win model | `outright-model` |
| A mechanism question | Look at hole splits | `hole-scoring` |
| Fit used as a feature | Check for leakage | `feature-engineering` |
| Want to validate the weight | Out-of-sample test | `walk-forward-validation` |
dispatch5.06 KB

View saved version →

---
name: dispatch
description: "Routes golf analytics requests to the right 2-3 skills. Load this FIRST when a golf data request comes in and you are unsure which skills to activate. Prevents loading all 16 skills when only 2-3 are needed. Do not use when the request clearly names one skill's job, such as devigging a price -- load odds-analysis directly."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Skill Dispatch Guide

Match the request to a pattern below, load those skills, and stop. Always load `birdieapi-tool` alongside any skill that needs live data.

## Scope check first

BirdieAPI covers the PGA Tour, LPGA, LIV, DP World Tour and Korn Ferry Tour as historical and next-morning data. The only odds are the winner prices of the four men's majors. If the user wants live scores, tee times, weather, round or matchup odds, top-10 markets or each-way terms, say it is not in the data and offer the nearest thing that is.

## First-use persona detection

| Signal | Persona | Start With |
|--------|---------|-----------|
| "Who won last week?" / "What is on this week?" | **Explorer** | `tournament-lookup` |
| "How is Scheffler playing?" | **Scout** | `player-scouting` + `strokes-vs-field` |
| "Who should I back at the Masters?" | **Odds reader** | `tournament-preview` + `odds-explorer` |
| "Build me a model of who wins" | **Builder** | `outright-model` + `feature-engineering` |
| "Is my model any good?" | **Validator** | `walk-forward-validation` + `probability-calibration` |
| "What does a +2000 price mean?" | **Odds reader** | `odds-analysis` |

## Request patterns -> skills

### "Who won?" / "Results of the 2024 Masters" / "What is on this week?"
1. `tournament-lookup`
2. `birdieapi-tool`: `list_tournaments`, `get_schedule`, `get_leaderboard`

### "Tell me about Rory McIlroy" / "Compare Scheffler and Schauffele"
1. `player-scouting`
2. `strokes-vs-field` if the user wants form or ratings

### "Who is playing best?" / "What is a rating of 2.0?" / "Field-strength adjusted"
1. `strokes-vs-field`
2. `birdieapi-tool`: `get_ratings`, `get_player_results`

### "Who is best on par 5s?" / "Which holes play hardest?" / "Birdie rate"
1. `hole-scoring`
2. `course-fit` if a specific course is involved

### "Who plays well at Augusta?" / "Course history" / "Course fit"
1. `course-fit`
2. `hole-scoring` for the hole-level view of the course

### "Preview the PGA Championship" / "Who are the contenders?"
1. `tournament-preview`
2. `odds-explorer` for the market side
3. `course-fit` if there is time and the credit budget

### "What are the odds?" / "Which book has the best price on Scheffler?" / "How did the price move?"
1. `odds-explorer`
2. `odds-analysis` for implied probability and devigging

### "Devig this" / "What is 2000 worth as a probability?" / "Overround"
1. `odds-analysis`

### "Build a winner-probability model" / "Simulate the Open"
1. `outright-model`
2. `feature-engineering` for inputs
3. `walk-forward-validation` before trusting any output

### "Walk-forward" / "Is k-fold okay?" / "Leakage"
1. `walk-forward-validation`
2. `feature-engineering`

### "Are my probabilities calibrated?" / "Brier" / "Log loss"
1. `probability-calibration`

### "How do I call this?" / "What does it cost?" / "Which tool?"
1. `birdieapi-tool`

### "How does the LPGA / LIV / DP World Tour / Korn Ferry work here?"
1. `birdieapi-tool` for the `league` argument and what each tour holds
2. `tournament-lookup` or `strokes-vs-field` for the actual question
3. Remember: no hole-by-hole outside the PGA Tour and Korn Ferry Tour, no odds outside the four men's majors

### "Who will win the [major]?"
1. `tournament-preview` for the picture
2. `outright-model` for probabilities with a sensitivity range
3. `odds-explorer` for the market, then `odds-analysis` to devig it
4. Do not name a pick without the caveats from `outright-model`

## Common chains

| Goal | Chain |
|------|-------|
| Preview a major | `tournament-lookup` -> `course-fit` -> `tournament-preview` -> `odds-explorer` |
| Build and test a model | `feature-engineering` -> `outright-model` -> `walk-forward-validation` -> `probability-calibration` |

## Things the data cannot answer

Say this first, then route to the nearest skill:

| Request | Not in the data | Nearest skill |
|---------|------------------|---------------|
| Live scores, tee times, weather | Next-morning data only | `tournament-lookup` for the finished rounds |
| Strokes gained off the tee, approach, putting | No shot data | `hole-scoring`, `strokes-vs-field` |
| Round, matchup or top-10 odds | Only major winner prices | `odds-explorer` |
| Each-way terms | Not held | `odds-analysis` for the outright |
| LIV team standings | Individual results only | `tournament-lookup` |

## Credit-aware routing

Before a multi-call plan, add up the credits from `birdieapi-tool` and tell the user. A preview is about 40 to 60 credits. A backtest across every major in the odds archive is one `get_winner_odds` call per major (5 credits each) plus the leaderboards (2 each).

## When NOT to Use This Guide

Single-skill requests do not need dispatch. "Devig +450 / +900 / +1200" goes straight to `odds-analysis`.
feature-engineering8.51 KB

View saved version →

---
name: feature-engineering
description: "Builds leak-free model features for golf from BirdieAPI data: ratings as of a date, rolling strokes versus the field, field-strength adjusted form, course history with shrinkage, par-type splits, field and event features, with rules for time order and target definition. Use when the user is building a golf model and asks what features to use, how to compute form, how to avoid leakage, or how to structure a training table per golfer per tournament. Do not use for validating the model -- see walk-forward-validation. Do not use for the rating explanation -- see strokes-vs-field. Do not use for simulating tournaments -- see outright-model."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Feature Engineering

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `get_player_results` (5 credits per player) is the workhorse: every round with `adj_vs_field`. `get_ratings` (2) with `as_of` supplies point-in-time ratings. `get_leaderboard` (2) supplies finishes.

You are an expert in building point-in-time features for golf. Your goal is a training table where every feature for a golfer-tournament row was knowable before round 1 of that tournament, and where the target is something with enough observations to learn from.

## When to Use

- "What features should I use to predict who wins?"
- "How do I compute form without leaking the future?"
- "How should I lay out the training table?"
- Deciding whether course history, par-type splits or recent form belong in the model

## When NOT to Use

- Splitting data and scoring -- see `walk-forward-validation`
- Explaining the rating -- see `strokes-vs-field`
- Running the simulation -- see `outright-model`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_player_results` | Round-level `strokes`, `field_avg`, `vs_field`, `adj_vs_field`, finish, per tournament | 5 |
| `get_ratings` | Rating at `as_of` | 2 |
| `get_leaderboard` | The whole field, finishing order, cut status, rounds | 2 |
| `get_course_history` | Record at an event or course | 5 |
| `get_player_stats` | Season hole scoring, `sg_par*`, ball-striking lines | 5 |
| `list_tournaments` | The event calendar and `start_date` for time ordering | 1 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_features`, `get_training_data` | Build the table yourself from the calls above |
| `get_world_ranking_history` | `get_ratings` at different `as_of` dates |
| `get_weather`, `get_tee_times`, `get_wave` | Not available; do not feature them |
| `get_field` for a future event | Not available |

## The table

One row per golfer per tournament. Order by `start_date`.

| Column | Source | Note |
|--------|--------|------|
| `rating_asof` | `get_ratings` with `as_of` = day before round 1 | the main feature |
| `weighted_rounds` | same | confidence in the rating |
| `form_8`, `form_24` | `get_player_results`, last 8 and 24 rounds before this event | `adj_vs_field` mean |
| `form_trend` | `form_8 - form_24` | |
| `par3_split`, `par4_split`, `par5_split` | `get_player_stats` for the prior season | `sg_par*`, score-based |
| `course_fit` | `get_course_history`, shrunk (see `course-fit`) | only editions before this one |
| `n_prior_starts_event` | same | |
| `days_since_last` | `list_tournaments` / results dates | |
| `field_mean_rating`, `field_size` | the field's ratings | event feature, shared by all rows |
| `is_major`, `tour` | `list_tournaments` | |
| target | see below | |

## Targets

Winners are rare: one per event. Train on targets with enough data, then convert.

- **Best:** round-level `adj_vs_field` for each golfer-round. Thousands of rows per season. Predict the mean, estimate the spread, simulate winners (see `outright-model`).
- **Good:** made cut (binary), top 10 (binary), finishing percentile.
- **Last:** winner (binary). Fewer than one positive per hundred rows. Use only for evaluation, not training, unless you pool many events.

## How It Works

**Step 1: Choose the unit and the clock.** The clock is the tournament `start_date`. Every feature for a row uses data strictly before it.

**Step 2: Rolling form with a shift.**

```python
import pandas as pd

# rounds: one row per golfer-round, columns: player_id, start_date, round, adj_vs_field
rounds = rounds.sort_values(["player_id", "start_date", "round"])
g = rounds.groupby("player_id")["adj_vs_field"]
rounds["form_8"] = g.transform(lambda s: s.shift(1).rolling(8, min_periods=4).mean())
```

`shift(1)` is the leakage guard: a round's own score is never in its own feature. But within a tournament, round 3 can see round 2 of the same event, which is not available before round 1 of a pre-tournament model. For pre-tournament features, aggregate by tournament first and shift by tournament:

```python
per_event = rounds.groupby(["player_id", "start_date"])["adj_vs_field"].mean().reset_index()
per_event["form_2ev"] = (per_event.groupby("player_id")["adj_vs_field"]
                         .transform(lambda s: s.shift(1).rolling(2, min_periods=1).mean()))
```

**Step 3: Point-in-time ratings.** Pull `get_ratings` with `as_of` for each event date, keyed to the day before round 1. One call returns up to 500 players, so one call per event. For 30 events that is 60 credits.

```
get_ratings {"as_of": "2024-04-04", "limit": 500, "min_rounds": 20}
```

**Step 4: Course history inside the clock.** For event edition `E`, include only editions before `E`. The summary returned by `get_course_history` covers every edition, so do not use `summary` for training rows; recompute from `editions` filtered to earlier dates.

**Step 5: Season-level stats.** `get_player_stats` returns a full season. For an event in April 2024 use 2023, not 2024. The 2024 line already contains the event.

**Step 6: Field features.** Field mean rating and size are event-level. For the cut and for the winner probability, the strength of the competition matters as much as a golfer's own number.

## Leakage checklist

| Leak | How it happens | Fix |
|------|----------------|-----|
| Today's rating on a past row | `get_ratings` without `as_of` | `as_of` day before |
| Season stats for the same season | `get_player_stats` season = event year | use the prior season |
| `get_course_history` summary | Includes the target edition | rebuild from `editions` |
| Rolling window without shift | Row includes its own round | `shift(1)` and tournament-level shifts |
| Random split | Same tournament in train and test | split by date |
| Field proxy from results | Using the finished leaderboard as the pre-event field | the priced list or the user's list |

## Cross-tour features

Ids are shared across tours, so a LIV golfer's PGA Tour history joins on `player_id`. Ratings are not comparable across tours. Add `tour` as a feature, or fit separately, and treat cross-tour golfers with wider uncertainty.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Ratings for 30 events | 60 | one call per event |
| Rounds for 100 golfers | 500 | one call per golfer; exceeds a 500-credit month |
| Course history for the top 20 in one event | 100 | |

If 500 credits a month is the budget, train on ratings and leaderboards only (2 credits each) and add rounds for a shortlist.

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Predict the winner label directly | About 1 positive per 100 rows | Predict round scores, simulate |
| Use finishing position as the target | Depends on field size | Use strokes versus the field or percentile |
| Hand-pick features after seeing test results | Selection leakage | Fix the list before the test period |
| Standardise on the full dataset | Test statistics leak into train | Fit scalers in the training window |
| Many highly correlated form windows | Overfit with few tournaments | Two or three windows |

## Output Format

```
Training table: <N> golfer-events, <first season>-<last season>, one row per golfer per event
Features: rating_asof, weighted_rounds, form_8, form_24, par5_split, course_fit (shrunk), field_mean_rating
Target: mean adj_vs_field per round (regression), winners kept for evaluation
Leakage checks: ratings as_of day before round 1; season stats from prior season; no random split
```

## What to Do Next

| What You Built | Next Action | Skill |
|----------------|-------------|-------|
| A feature table | Validate in time order | `walk-forward-validation` |
| A model | Simulate winners | `outright-model` |
| Probabilities | Check calibration | `probability-calibration` |
hole-scoring7.35 KB

View saved version →

---
name: hole-scoring
description: "Analyses golf scoring at hole level: par 3, par 4 and par 5 averages and strokes-versus-field splits (sg_par3, sg_par4, sg_par5), birdie and bogey rates, how each hole of a course played, and one player's hole-by-hole scorecard. Use when the user asks who is best on par 5s, which holes play hardest, how a course was set up, what a player shot on each hole, or what kind of scorer a golfer is. Do not use for tournament-wide results -- see tournament-lookup. Do not use for the official shot-level strokes gained categories -- they are not in the data. Do not use for a player's record at a course across years -- see course-fit."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Hole Scoring

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `get_player_stats` (5 credits) has the par-type splits. `get_course` (2) has how each hole played. `get_scorecard` (2) has one player's holes. `get_leaders` (5) ranks a season.

You are an expert in how golf is scored hole by hole. Your goal is to describe a player's or a course's scoring profile from strokes alone, and to be exact about what strokes can and cannot reveal.

## What is in the data

- Strokes on each hole for about 99% of PGA Tour rounds since 2001, with par and yards as the course was set up that week. Korn Ferry Tour has hole-by-hole from 2007.
- **Not** in the data for LPGA, LIV and DP World Tour: those have round totals only.
- No shot locations, no shot lengths, no categories. A par 5 number cannot say whether a player reached in two or laid up and wedged close.
- `sg_par3`, `sg_par4`, `sg_par5` are strokes versus the field on that hole type, field-strength adjusted, PGA Tour and Korn Ferry Tour only. Score-based, not shot data. The three add up to the round's strokes versus the field.

## When to Use

- "Who is the best par-5 scorer in 2025?"
- "Which holes at Augusta played hardest?"
- "Show Scheffler's Thursday round hole by hole."
- Birdie machine versus bogey avoider
- Course setup: par and yards per hole, scoring average, birdie and bogey rates

## When NOT to Use

- Overall form rating -- see `strokes-vs-field`
- A player at one event over years -- see `course-fit`
- Tournament results -- see `tournament-lookup`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_player_stats` | A season's hole scoring: birdies and bogeys per round, par 3/4/5 averages, `sg_par*` | 5 |
| `get_leaders` | Season leaders on `par3_average`, `par4_average`, `par5_average`, `sg_par3`, `sg_par4`, `sg_par5`, `birdies_per_round`, `bogey_avoidance` | 5 |
| `get_course` | Per hole: par, yards, scoring average, to par, birdie, par, bogey and double rates, difficulty rank | 2 |
| `get_scorecard` | One player's holes for every round: strokes, par, yards, result, out and in | 2 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_hole_stats`, `get_hole` | `get_course` returns every hole |
| `get_shot_data`, `get_shot_tracking` | Not available |
| `get_strokes_gained_putting` | Not available; `putts_per_gir` is a stat line (PGA Tour, from 2011) |
| Scorecards for LPGA, LIV, DPWT | `get_scorecard` accepts `golf` and `kornferry` only |

## How It Works

**Step 1: A player's profile.**

```
get_player_stats {"player_id": "9478", "season": 2025}
```

Real response (trimmed), Scottie Scheffler 2025, 84 rounds with holes:

```
birdies_per_round 4.762   eagles_per_round 0.202   bogeys_or_worse_per_round 1.917   par_rate 0.6177
par3_average 2.983   par4_average 3.889   par5_average 4.428
sg_par3 0.576   sg_par4 2.32   sg_par5 0.957
```

Read it: the three splits sum to 3.853, which matches his `adj_vs_field` of 3.86 in the same response, and the par-4 split carries about 60% of it. Par 4s are about ten holes a round and par 3s and 5s about four each, so per hole the par-3 split (0.58 over four holes) is the weakest and par 4 and par 5 are close (about 0.23 and 0.24 a hole). That is arithmetic on his splits, not shot data.

**Step 2: Rank a season on a hole type.**

```
get_leaders {"stat": "sg_par5", "season": 2025, "limit": 3}
```

Real response: Scheffler 0.957 (84 rounds), Schauffele 0.88 (64), McIlroy 0.807 (58). Default minimum is 40 rounds. Par 3 and par 5 numbers rest on fewer holes per round, so they move more round to round. Use a full season, and treat gaps under about 0.1 as ties.

**Step 3: How a course played.**

```
get_course {"tournament_id": "401580344"}
```

Real response (2024 Masters, Augusta National, par 72, 7,555 yards, 298 rounds, scoring average 73.91). For the 11th: par 4, 520 yards, scoring average 4.386, bogey rate 0.2852, double or worse 0.0738, `difficulty_rank` 1. For the 13th: par 5, 545 yards, scoring average 4.758, birdie 0.3624, `difficulty_rank` 16. For tournaments on more than one course, pass `course_id`.

Course difficulty: sort holes by `to_par`, take the top and bottom five, and split by par type. Do not compare scoring averages across different weeks without setting them against the field.

**Step 4: One player's hole-by-hole.**

```
get_scorecard {"tournament_id": "401580344", "player_id": "9478"}
```

Real response (round 1): 66, out 34, in 32, `field_avg` 73.43, `vs_field` 7.43. Each hole has `par`, `yards`, `strokes`, `result` (par, birdie, bogey). Round 1: birdies on 2 (par 5), 6, 12, 13, 15 and 16. Summarise by par type:

```python
import collections
agg = collections.defaultdict(lambda: [0, 0])   # par -> [strokes, holes]
for r in card["rounds"]:
    for h in r["holes"]:
        agg[h["par"]][0] += h["strokes"]; agg[h["par"]][1] += 1
avg_by_par = {p: s / n for p, (s, n) in agg.items()}
```

**Step 5: Scorer type.** Birdies per round measures aggression; `bogey_avoidance` measures mistakes. Two golfers at the same rating can differ in both. For setups with penal rough or firm greens the bogey-avoider tends to hold up better; say this as a hypothesis for `course-fit` to test, not a rule.

## Pitfalls

| Pitfall | Why it bites |
|---------|--------------|
| Calling `sg_par5` an approach or driving stat | It only says the player scored better than the field on par 5s |
| Using one tournament's par-3 average | Four holes a round, 16 holes total: very noisy |
| Comparing hole averages across course setups | Yards, pins and weather change |
| Dividing `sg_par*` by holes and quoting as per-shot | They are per round totals |
| Expecting hole data for LPGA, LIV, DPWT | Round scores only |
| Using `get_scorecard` on a match-play event | No stroke-play holes |

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Player season profile | 5 | |
| Season leaders, one stat | 5 | |
| Course hole table | 2 | |
| One player's tournament card | 2 | all four rounds |
| Profile + course + card | 9 | a typical "how does he fit this course" check |

## Output Format

```
Scottie Scheffler, 2025 PGA Tour (84 rounds)
Strokes versus the field by hole type (score-based): par 3 +0.58, par 4 +2.32, par 5 +0.96 per round.
Birdies per round 4.76, bogeys or worse 1.92. Par 4 scoring average 3.889.
No shot data: these show where strokes were saved, not how.
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| A course with scoreable par 5s | Does the player's par-5 split fit? | `course-fit` |
| A scoring profile | Overall form | `strokes-vs-field` |
| Want features for a model | Build inputs | `feature-engineering` |
odds-analysis8.09 KB

View saved version →

---
name: odds-analysis
description: "Converts golf outright prices to implied probability, measures the overround of a many-runner winner market, and devigs it with the proportional, power and Shin methods. Use when the user asks what a price means as a probability, how much margin a golf outright carries, how to remove the vig from a field of 100+ golfers, what the true win chance of a favourite or longshot is, or how a price should be read. Do not use for finding the best book or movement -- see odds-explorer. Do not use for two-way or each-way markets; each-way terms are not in the data."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Odds Analysis

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `get_winner_odds` (5 credits) supplies prices. The method below is arithmetic you run yourself.
> With the user's own prices, no credits are used.

You are an expert in turning prices into probabilities. Golf outrights are the hard case: one winner among about 90 to 156 runners, margins that tend to sit in the longshots, and a sum of implied probabilities far above 1. Do this carefully or every downstream edge is wrong.

## When to Use

- "What is +400 as a probability?"
- "How much margin is in this market?"
- "What is Scheffler's true win probability once I take the vig out?"
- Choosing between proportional, power and Shin devigging for a big field
- Checking whether a user's model probability for a golfer is even in range

## When NOT to Use

- Finding the best book or reading movement -- see `odds-explorer`
- Two-way markets (matchups, round bets) and each-way terms -- not in the data; the same formulas apply to the user's own two-way lines with a different normalisation

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_winner_odds` | Winner prices by golfer and book with raw implied probability | 5 |

Everything else here is calculation.

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_devigged_odds`, `get_true_probability` | Compute from `get_winner_odds` with the methods below |
| `get_each_way_terms` | Not available; do not assume terms |
| `get_odds` for non-majors | Not available |

## Step 1: Price to implied probability

American price `a`:
- positive: `p = 100 / (a + 100)`. +400 gives 0.200. +1000 gives 0.0909.
- negative: `p = |a| / (|a| + 100)`. -150 gives 0.600.

`get_winner_odds` already returns this as `implied_prob`. It is the raw probability and it includes the book's margin. It is not a belief.

Decimal odds `d = a/100 + 1` for positive `a`. Profit per unit stake `b = d - 1`.

## Step 2: Overround of the whole field

Sum the raw implied probabilities of every golfer at one book at one snapshot:

```
overround = sum(p_i) - 1
```

Real example: 2024 Masters, DraftKings latest pregame price, all 115 golfers priced.

```
get_winner_odds {"tournament_id": "401580344", "limit": 200, "bookmaker": "draftkings"}
```

```
golfers priced: 115
sum of latest implied probabilities: 1.642   (overround 64%)
sum of opening implied probabilities: 1.632
top of the board (raw): Scheffler 20.0% (+400), McIlroy 9.1% (+1000), Rahm 7.7% (+1200),
                        Koepka 5.3% (+1800), Schauffele 4.8% (+2000), Spieth 4.3% (+2200)
tail: Olazabal +500000 (0.02%)
```

A 64% overround is not a 64% hold on every bet. It is the sum of many small margins, and most of it sits in the longshots. A book shading a +20000 to +15000 adds little in absolute probability but a hundred such prices add up.

Do not report a "vig of 164%". Say: "the 115 prices sum to 1.64, so staking every golfer in proportion to his implied probability would return about 61 cents per dollar" (1/1.642 = 0.609). That is the average across the board, and the margin is far smaller on the favourite than in the tail.

## Step 3: Devig

Three methods. For an outright with a long tail, only the second and third are defensible.

**Proportional (multiplicative):** `q_i = p_i / sum(p)`. Divides every golfer by 1.642. It cuts the favourite and the longshot by the same percentage, so it understates favourites and overstates longshots when the margin is concentrated in the tail.

**Power:** find `k > 1` such that `sum(p_i^k) = 1`, then `q_i = p_i^k`. Because `p < 1`, raising to a power shrinks small probabilities more, which matches margin concentrated in longshots. Solve `k` by bisection.

**Shin:** solves for a proportion `z` of insider money and sets
`q_i = (sqrt(z^2 + 4(1 - z) p_i^2 / S) - z) / (2(1 - z))` with `S = sum(p)`, choosing `z` so `sum(q_i) = 1`. It is the usual choice when the field is large and favourite-longshot bias is visible.

Real comparison for the same market (computed from the 115 DraftKings prices):

| Golfer | Raw | Proportional | Power (k = 1.142) | Shin (z = 0.0075) |
|--------|-----|--------------|-------------------|-------------------|
| Scheffler | 20.0% | 12.2% | 15.9% | 15.3% |
| McIlroy | 9.1% | 5.5% | 6.5% | 6.8% |
| Rahm | 7.7% | 4.7% | 5.3% | 5.7% |
| Koepka | 5.3% | 3.2% | 3.5% | 3.8% |
| Schauffele | 4.8% | 2.9% | 3.1% | 3.4% |
| Spieth | 4.3% | 2.6% | 2.8% | 3.0% |

The range for Scheffler across methods is 12.2% to 15.9%. That spread is larger than most claimed edges. State it. A model that says 16% is "an edge" under proportional and "no edge" under power.

```python
import numpy as np

def devig_power(p, tol=1e-12):
    lo, hi = 1.0, 10.0
    for _ in range(200):
        k = (lo + hi) / 2
        if np.sum(p ** k) > 1: lo = k
        else: hi = k
    return p ** k, k

def devig_shin(p):
    S = p.sum()
    def total(z): return np.sum((np.sqrt(z*z + 4*(1-z)*p*p/S) - z) / (2*(1-z)))
    lo, hi = 0.0, 0.999
    for _ in range(200):
        z = (lo + hi) / 2
        if total(z) > 1: lo = z
        else: hi = z
    q = (np.sqrt(z*z + 4*(1-z)*p*p/S) - z) / (2*(1-z))
    return q, z
```

## Step 4: Use more than one book

A single book's margin is a property of that book. Devig each book separately, then average the devigged probabilities (not the American prices). A sharp book such as Pinnacle, when present, usually carries a smaller margin; check `books[]` rather than assuming. Report the spread of devigged values across books as the market's uncertainty.

## Step 5: Sanity checks before using the number

- Devigged probabilities must sum to 1 (to within 1e-6).
- Never report a probability below the smallest meaningful value for the field. A +500000 price is not a 0.02% belief; the tail is noise. Group golfers beyond rank 40 as "the field".
- A major winner prior: with 89 to 156 starters in recent majors the average golfer has roughly a 1% chance or less (1 in 89 to 1 in 156). A model giving 3% to a rank-60 player needs a reason.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Price one major at one book | 5 | `limit` 200 and `bookmaker` |
| Compare all books for the top 20 | 5 | default call |
| Devig math | 0 | local |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Treat `implied_prob` as the true probability | It includes margin | Devig the full field |
| Proportional-devig a 115-runner market and call it true | Over-credits longshots, under-credits favourites | Show power or Shin beside it |
| Devig only the top 20 golfers | The tail's mass is missing, so the sum is below the real total | Devig the whole priced field |
| Average American odds across books | Not linear | Average devigged probabilities |
| Quote one devigged number to two decimals | False precision | Quote a range across methods and books |
| Assume each-way terms | Not in the data | Ask the user |

## Output Format

```
2024 Masters, DraftKings latest pregame (115 golfers)
Raw implied sum: 1.642
Scottie Scheffler: +400 = 20.0% raw
  devigged: 12.2% proportional / 15.9% power / 15.3% Shin
  working range: 15% to 16% (power and Shin agree; proportional likely too low)
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| Need the prices first | Pull and compare books | `odds-explorer` |
| Model probabilities look off | Check calibration | `probability-calibration` |
odds-explorer7.52 KB

View saved version →

---
name: odds-explorer
description: "Compares the outright winner prices of the four men's majors (Masters, PGA Championship, U.S. Open, Open Championship) across sportsbooks, finds the best price on a golfer, and reads how a price moved from the opening snapshot to the latest pregame snapshot. Use when the user asks about majors odds, who is favoured, which book has the best price, how a golfer's price changed, or a golfer's prices across majors. Do not use for converting prices or devigging -- see odds-analysis. Do not use for non-major tournaments, round, matchup, top-10 or each-way markets -- they are not in the data."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Odds Explorer

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `get_winner_odds` (5 credits per call) is the only odds tool. One call can return a whole major's field, all four majors of a season, or one golfer across the majors.

You are an expert in golf outright markets. Your goal is to show the user what the books priced, where the best number is, and how the market moved, without implying the data is a live feed.

## What is and is not in the data

- **In:** pre-tournament winner prices for the Masters, PGA Championship, U.S. Open and Open Championship, by sportsbook. Weekly snapshots from 2021 (the first seasons have fewer), plus the 2020 U.S. Open from a single snapshot. Source: The Odds API.
- **Opening** is the first snapshot a book had for that golfer. **Latest** is the last price before the first round. For past majors the latest price can be up to a week before the first tee time, so it is not a closing line.
- **Not in:** any other tournament, the LPGA, LIV or DP World Tour, round or matchup markets, finishing-position markets (top 5, top 10, top 20), in-play prices, each-way terms.
- Books seen in the data include DraftKings, FanDuel, BetMGM, Bovada, BetOnline, ESPN BET, Pinnacle and MyBookie. The set varies by golfer and year.

## When to Use

- "Who was the favourite at the 2024 Masters and at what price?"
- "Which book had the best number on Scheffler?"
- "How did McIlroy's price move into the US Open?"
- One golfer's price across every major he was priced for

## When NOT to Use

- Implied probability, overround, devigging -- see `odds-analysis`
- Non-major events -- no prices exist; ask for the user's own lines

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_winner_odds` | Winner prices by golfer and book: opening and latest pregame price with implied probability | 5 |
| `list_tournaments` | Find a major's `tournament_id` (use `major: true`) | 1 |
| `search_players` | Find a golfer's `player_id` | 1 |

`get_winner_odds` takes `tournament_id`, or `season` (all four majors), or `player_id`, optionally `major` (`masters`, `pga_championship`, `us_open`, `the_open`), `bookmaker` (e.g. `draftkings`, `pinnacle`) and `limit` (golfers per tournament, favourites first, default 20, max 200). It has no `league` argument.

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_odds`, `get_live_odds` | `get_winner_odds` (majors, pre-tournament) |
| `get_line_movement` | `get_winner_odds` returns opening and latest per book; there is no full time series |
| `get_closing_line` | The latest pregame snapshot is the nearest thing; label it as such |
| `get_matchup_odds`, `get_top_10_odds`, `get_round_odds` | Not available |
| `get_best_book` | Compare the `books[]` array yourself |

## How It Works

**Step 1: Get the id.**

```
list_tournaments {"season": 2024, "major": true}
```

**Step 2: Pull the market.** For a whole field, favourites first:

```
get_winner_odds {"tournament_id": "401580344", "limit": 5}
```

Real response (trimmed): `snapshots 51`, `first_captured_at 2023-04-21`, `last_captured_at 2024-04-05`, `golfers_priced 121`. Each golfer has `median_implied_prob_opening`, `median_implied_prob_latest` and `books[]`. Each book has `opening` and `latest_pregame`, each with `price` (American, positive for longshots), `implied_prob` and `captured_at`, and `snapshots`.

For Scottie Scheffler at the 2024 Masters:

```
betmgm      open +900  (10.0%)  latest +400 (20.0%)
betonlineag open +1000 (9.1%)   latest +400 (20.0%)
draftkings  open +900  (10.0%)  latest +400 (20.0%)
fanduel     open +700  (12.5%)  latest +430 (18.9%)
pinnacle    open +1022 (8.9%)   latest +407 (19.7%)
```

**Step 3: One book, or one golfer.**

```
get_winner_odds {"tournament_id": "401580344", "limit": 1, "bookmaker": "pinnacle"}
get_winner_odds {"player_id": "9478"}
```

**Step 4: Find the best price.** For a golfer, the best number at a given snapshot is the highest American price (a +430 beats a +400). Compare the `latest_pregame.price` across `books[]`. Report the spread, not only the best: FanDuel +430 versus +400 elsewhere is a real difference (18.9% versus 20.0% implied).

**Step 5: Read movement.** Compare `opening` with `latest_pregame` per book.
- Shortening (+900 to +400) means the implied probability rose from 10.0% to 20.0%.
- Opening dates differ by book (April 2023 for most, October 2023 for Pinnacle in this example). Do not compare an opening price at one book with an opening at another as if they were the same date; use `captured_at`.
- A weekly snapshot cannot show intra-week or steam moves. Do not claim sharp action from it.

**Step 6: Count snapshots.** `snapshots` per book tells you how dense the series is. Fewer than ten means the movement read is coarse.

## Practical notes on outrights

- Long shots carry the most margin. The 2024 Masters DraftKings latest prices for 115 golfers summed to a raw implied probability of 1.64 (see `odds-analysis`). Do not read a +50000 price as a 0.2% belief.
- The `median_implied_prob_*` fields are the median across books. The median hides a book that is an outlier; check `books[]` when it matters.
- A golfer can be priced at a book and absent at another. Missing is not zero.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| One major, all golfers | 5 | `limit` does not change the cost |
| All four majors of a season | 5 | one call with `season` |
| One golfer across majors | 5 | `player_id` |
| Id lookups | 1 each | |

A full pre-tournament scan of one major is about 6 to 8 credits including lookups.

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Call the latest price the closing line | Up to a week before first tee for past majors | Say "latest pregame snapshot" |
| Say "the books have him at 20%" | 20.0% is the raw implied probability including margin | Devig first (`odds-analysis`) |
| Look for top-10 or matchup prices | Not held | Ask for the user's own lines |
| Infer sharp money from a weekly series | Weekly snapshots cannot show timing | Describe the move and the dates only |
| Average prices across books to get the "market" | Averaging American odds is wrong | Average implied probabilities, or use the median field |

## Output Format

```
2024 Masters, winner market, latest pregame snapshot (5 Apr 2024)
Scottie Scheffler: +400 at BetMGM, BetOnline, DraftKings, Bovada; +430 at FanDuel (best); +407 Pinnacle
Move since first snapshot: +900 to +400 at DraftKings (10.0% to 20.0% raw implied)
Snapshots: 48 (DraftKings). Raw implied probabilities include margin.
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| A price to convert or devig | True probabilities | `odds-analysis` |
| Want the field context | Form and course record | `tournament-preview` |
outright-model8.87 KB

View saved version →

---
name: outright-model
description: "Builds a win-probability model for a golf tournament from BirdieAPI ratings: rating as of a date, course fit, field list, Monte Carlo simulation of four rounds with a cut, and a comparison with the devigged market. Use when the user wants the chance a golfer wins a major or any stroke-play event, wants to simulate a tournament, or asks why a model disagrees with the odds. Do not use for validating the model's history -- see walk-forward-validation. Do not use for plain player lookups -- see player-scouting."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Outright Model

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `get_ratings` with `as_of` (2 credits) is the input. `get_course_history` (5) adds fit. `get_winner_odds` (5) gives the market to compare against. `get_leaderboard` (2) gives past fields and cut sizes.

You are an expert in simulation models for golf winners. Your goal is to produce honest win probabilities with uncertainty, show which assumptions move them most, and compare them with the market without implying a bet follows.

## When to Use

- "What is the chance Scheffler wins the PGA Championship?"
- "Simulate the Open with these ratings."
- "Why does my model give the favourite 7% when the books imply 15%?"

## When NOT to Use

- Calibrating the output -- see `probability-calibration`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_ratings` | Rating per player, `as_of` any date, `min_rounds`, `limit` up to 500 | 2 |
| `get_player_results` | Rounds with `adj_vs_field`, to estimate round-to-round spread | 5 |
| `get_course_history` | Course fit inputs (see `course-fit`) | 5 |
| `get_leaderboard` | Past fields and `cut_count` for the same event | 2 |
| `list_tournaments` | Tournament row with `cut_round`, `cut_score`, `cut_count`, `field_size` | 1 |
| `get_winner_odds` | The market's priced golfers (a field proxy) and prices | 5 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_win_probability`, `get_predictions` | Build it here; BirdieAPI does not serve model predictions |
| `get_field` for an upcoming event | Not available. Use the priced golfers from `get_winner_odds` as a proxy, or ask the user for the field |
| `get_tee_times`, `get_weather` | Not available; weather and wave draw are not modelled |
| `get_live_leaderboard` | Not available; no in-play updating |

## Model

For golfer `i` in a field of `n`, in round `r`:

```
strokes_vs_field[i, r] = skill[i] + fit[i] - field_mean + eps[i, r]
skill[i]  ~ Normal(rating[i], tau)        # rating uncertainty, drawn once per simulated tournament
eps[i, r] ~ Normal(0, sigma)               # round-to-round noise
```

Higher is better. Winner = highest total over four rounds (after the cut: 36 holes, then the cut line, then the weekend). Ties are rare in a continuous simulation; real ties go to a playoff, which is close to a coin toss among the tied.

Inputs:
- `rating[i]` from `get_ratings` with `as_of` = the day before the first round.
- `fit[i]` from `course-fit` (optional, shrunk).
- `sigma`: round-to-round standard deviation, estimated from `get_player_results` (`adj_vs_field` per round).
- `tau`: how uncertain the rating is. Larger for few `weighted_rounds`.
- The field: `n` golfers and their ratings. The field mean cancels in the winner comparison but matters for the cut.

## How It Works

**Step 1: Ratings before the event.**

```
get_ratings {"as_of": "2024-04-04", "limit": 500, "min_rounds": 20}
```

Real response: 313 rated players, best 2.88 (Scottie Scheffler), lowest -1.68. Ratings are within one tour, so for LIV golfers in a major, widen `tau` and say why; their rating is built against LIV fields.

**Step 2: The field.** The upcoming field is not in the data. Take the priced golfers as a proxy:

```
get_winner_odds {"tournament_id": "401580344", "limit": 200, "bookmaker": "draftkings"}
```

115 golfers were priced for the 2024 Masters; 97 of them had a rating. The unrated ones (old champions, amateurs, qualifiers) are mostly weak. Dropping them makes the field artificially strong and pushes every probability down. Give them a rating below the lowest rated player, or flag it in the output.

**Step 3: Estimate sigma.**

```
get_player_results {"player_id": "9478", "season": 2023, "limit": 50}
```

Real response: 96 rounds with `adj_vs_field`, mean 3.35, standard deviation 2.53. Pool several players rather than using one. Note this standard deviation also includes slow drift in form, so it slightly overstates pure round noise; that is a modelling choice to state, not hide.

**Step 4: Simulate.**

```python
import numpy as np

def simulate(rating, sigma, tau, cut_n, n_sims=20000, seed=7):
    rng = np.random.default_rng(seed)
    wins = np.zeros(len(rating))
    for _ in range(n_sims // 1000):
        skill = rating[None, :] + rng.normal(0, tau, (1000, len(rating)))
        tot = np.zeros((1000, len(rating)))
        for rd in range(4):
            tot += skill + rng.normal(0, sigma, (1000, len(rating)))
            if rd == 1:                                   # cut after round 2
                kth = np.sort(tot, axis=1)[:, -cut_n][:, None]
                tot = np.where(tot < kth, -1e9, tot)
        wins += np.bincount(np.argmax(tot, axis=1), minlength=len(rating))
    return wins / n_sims
```

`cut_n` comes from the tournament row (`cut_count` was 50 for the 2024 Masters). Ties at the cut line would let a few more through; ignore them for winner probabilities and say so.

**Step 5: Run the sensitivity, not one number.** First-pass illustration on the 97 rated, priced golfers of the 2024 Masters (ratings as of 2024-04-04, 20,000 simulations, seed 7, no course fit):

| Assumptions | Scheffler | Rahm | Schauffele | McIlroy |
|-------------|-----------|------|------------|---------|
| sigma 2.53, tau 1.0 | 6.7% | 5.2% | 4.2% | 3.7% |
| sigma 2.53, tau 0.5 | 8.7% | 6.3% | 5.0% | 4.4% |
| sigma 2.53, tau 0.0 | 9.7% | 6.8% | 5.6% | 4.8% |
| sigma 2.0, tau 0.0 | 14.2% | 8.8% | 7.2% | 6.1% |

For comparison, the DraftKings latest price devigged by the power or Shin method put Scheffler at 15% to 16% (see `odds-analysis`). The favourite's probability swings from 6.7% to 14.2% on two assumptions. That is the main lesson: the output of a rating-based simulation is governed by `sigma` and `tau`, which must be estimated and validated, not picked to match the market. Matching the market by tuning the inputs defeats the point of the comparison.

**Step 6: Add fit and the market.** Add `fit[i]` from `course-fit` to `skill`. Then compare to the devigged market: where the model disagrees by more than its own sensitivity range, ask why before concluding anything. Common reasons: ratings lag a form change; injury or withdrawal knowledge the data lacks; a field mismatch; a wrong `sigma`.

**Step 7: Blend, don't replace.** The market contains information a rating does not (injury news, equipment, preparation). A defensible final probability is a weighted average of model and devigged market in log-odds, with the weight set by out-of-sample log loss in `walk-forward-validation`.

## Limits to state every time

- Score-based ratings, no shot data, no weather, no tee times, no injury information.
- Four-round stroke play only. Match play and team formats are not modelled.
- The 2024 numbers above are a worked illustration of method, not a forecast or a recommendation.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Ratings as of a date | 2 | one call, up to 500 players |
| Market and field proxy | 5 | one call |
| Sigma from five players | 25 | pool across players |
| Course fit for top 20 | 100 | optional |
| Minimal build (ratings, market, one sigma pull) | 12 | |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Use today's rating for a past event | Leakage | `as_of` the day before |
| Tune `sigma` until the favourite matches the books | Circular; no information left to compare | Estimate it from data |
| Drop unrated golfers silently | Field too strong, probabilities too low | Include with a low rating, or disclose |
| Report 6.73% | False precision | Report a range from the sensitivity table |
| Ignore the cut | Players who miss it cannot win | Apply it after round 2 |

## Output Format

```
2024 Masters win probability, rating-based simulation (97 golfers, 20,000 runs)
Scottie Scheffler: 7% to 14% depending on sigma and tau (see table). Market, devigged: 15% to 16%.
Inputs: ratings as of 2024-04-04; sigma from data; no course fit; no weather or injury information.
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| A probability set | Check calibration on past majors | `probability-calibration` |
| A fit input | Build it properly | `course-fit` |
| Parameters to tune | Walk-forward | `walk-forward-validation` |
player-scouting7.25 KB

View saved version →

---
name: player-scouting
description: "Looks up and scouts golfers: finds a player id, reads career totals, season lines, recent results, round-by-round strokes versus the field, and compares two players head to head across the PGA Tour, LPGA, LIV, DP World Tour and Korn Ferry Tour. Use when the user names a golfer, asks how he or she has played, wants a career or season summary, or wants two players compared. Do not use for ranking the whole tour on form -- see strokes-vs-field. Do not use for a player's record at one course -- see course-fit. Do not use for tournament results -- see tournament-lookup."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Player Scouting

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `search_players` (1 credit) resolves a name. `get_player` (5), `get_player_results` (5), `get_player_stats` (5) and `get_head_to_head` (5) read the profile.

You are an expert golf scout. Your goal is to turn a name into a clear, sourced read on a player: how good, how recent, what kind of player, and how that compares with a rival.

## When to Use

- "How has Xander Schauffele played this year?"
- Career totals: starts, wins, top tens, cuts, money, scoring average, strokes versus the field
- A season line: scoring average, birdies per round, par 3/4/5 averages, driving and greens
- Recent form round by round, raw and adjusted for field strength
- Two players compared: who has finished ahead, round by round on the same course

## When NOT to Use

- Ranking everyone on one number -- see `strokes-vs-field`
- A player at one event or course across years -- see `course-fit`
- Who won a tournament -- see `tournament-lookup`
- A player's winner price -- see `odds-explorer`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `search_players` | Name to `player_id`, with country, tournaments played, wins, first and last season | 1 |
| `get_player` | Profile, career totals, our current rating, a line per season, last ten results | 5 |
| `get_player_results` | Tournament by tournament: finish, score, earnings, each round with field average, `vs_field` and `adj_vs_field`. Newest first | 5 |
| `get_player_stats` | One season: results, hole scoring, par-type splits, ball-striking lines where kept | 5 |
| `get_head_to_head` | Every tournament both played: who finished ahead and round-by-round strokes | 5 |
| `get_scorecard` | One tournament hole by hole (see `hole-scoring`) | 2 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_player_by_name` | `search_players` with `query` (2 characters minimum, accents optional) |
| `get_world_ranking`, `get_owgr` | Not available; use `get_ratings` |
| Season stats with no season | `season` is required on `get_player_stats`; use `get_player` for career totals |
| `get_strokes_gained_by_category` | Not available; no shot data. `sg_par3/4/5` are score-based splits by hole type |
| `get_player_odds` | `get_winner_odds` with `player_id` (majors only) |

## How It Works

**Step 1: Resolve the id.**

```
search_players {"query": "scheffler"}
```

Real response (trimmed): `player_id "9478"`, `name "Scottie Scheffler"`, `country "USA"`, `tournaments 167`, `wins 23`, `first_season 2014`, `last_season 2026`. If several players match, ask or use `first_season`/`last_season` to pick. Add `league` to search another tour; ESPN ids are shared across tours.

**Step 2: Pull the season.**

```
get_player_stats {"player_id": "9478", "season": 2025}
```

Real response (trimmed):

```
results:      starts 21, wins 6, top_tens 18, made_cut 21, average_finish 5.9,
              scoring_average 67.94, vs_field 2.78, adj_vs_field 3.86, earnings 27659552
hole_scoring: birdies_per_round 4.762, par3_average 2.983, par4_average 3.889, par5_average 4.428,
              sg_par3 0.576, sg_par4 2.32, sg_par5 0.957
ball_striking: driving_distance 308.3, driving_accuracy 65.8, greens_in_regulation 71.6, putts_per_gir 1.69
```

`vs_field` is strokes per round better than the field average; `adj_vs_field` also corrects for how strong the fields were. The `sg_par*` fields are strokes versus the field by hole type, score-based. They are not shot-level strokes gained.

**Step 3: Recent form.** For the last N tournaments with every round:

```
get_player_results {"player_id": "9478", "season": 2025, "limit": 10}
```

Each tournament has `rounds[]` with `strokes`, `field_avg`, `vs_field`, `adj_vs_field`. Average the `adj_vs_field` of the last 8 to 12 rounds for form. Weak-field events (a 20-man invitational) inflate raw `vs_field`; trust `adj_vs_field` for comparison.

**Step 4: Compare two players.**

```
get_head_to_head {"player_a": "9478", "player_b": "3470", "season": 2025}
```

Real summary: `tournaments 13, a_ahead 8, b_ahead 4, tied 1, rounds 52, a_better_rounds 31, b_better_rounds 18, a_minus_b_strokes_per_round -0.65`. Read the sign carefully: `a_minus_b_strokes_per_round` is strokes, so negative means player A scored fewer strokes per round (better). Making the cut beats missing it when deciding who finished ahead.

**Step 5: Put it in context.** Pair the profile with `get_ratings` (2 credits) to rank the player on the whole tour, and note the sample (rounds behind each number). A 13-event head-to-head is small; say so.

## What a Good Scouting Note Contains

1. Identity and tour (id, country, seasons played)
2. Level: current rating and rank, and career `vs_field`
3. Form: last 8 to 12 rounds on `adj_vs_field`, with the dates
4. Profile: par-type split, birdie rate, bogey avoidance, and driving and greens if kept (PGA Tour from 2011)
5. Caveats: sample size, missing seasons, tour changes

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Find an id | 1 | |
| Quick scout (id, season stats, last 10 results) | 11 | |
| Full scout plus rating and one rival | 23 | adds `get_player`, `get_ratings`, `get_head_to_head` |
| Scout a 10-player shortlist | 110 | tell the user first |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Guess an id from a name | Wrong player, silent error | `search_players` |
| Call `sg_par5` "putting" or "approach" | It is a hole-type split from scores | Name it "strokes versus the field on par 5s" |
| Compare a season of raw `vs_field` across tours | Each tour's fields differ | Use `adj_vs_field`, within one tour |
| Declare a head-to-head winner on 3 meetings | Tiny sample | Show the counts and the per-round gap |
| Quote a LIV player's rating beside a PGA Tour player's | Rated against different fields | Compare form on shared events (majors) with `get_head_to_head` |

## Output Format

```
Scottie Scheffler (USA, id 9478), 2025 PGA Tour
21 starts, 6 wins, 18 top tens, no missed cuts. Scoring average 67.94.
Strokes versus the field: +2.78 a round (+3.86 adjusted for field strength).
By hole type (score-based): par 3 +0.58, par 4 +2.32, par 5 +0.96 per round.
Sample: 84 rounds.
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| Strong rating | See where he ranks and why | `strokes-vs-field` |
| An event is coming | Course record | `course-fit` |
| Want a market view | Winner prices | `odds-explorer` |
| Want to model win chance | Build inputs | `outright-model` |
probability-calibration7.58 KB

View saved version →

---
name: probability-calibration
description: "Checks and repairs golf probabilities: reliability tables for made-cut, top-10 and winner probabilities, Brier score and log loss, pooled calibration across golfers, isotonic and Platt scaling, and why winner calibration needs outcomes beyond the majors. Use when the user asks whether their golf probabilities are calibrated, how to read a reliability diagram for longshots, how to calibrate a simulation's output, or why a model's 3% golfers win more or less than 3%. Do not use for building the model -- see outright-model. Do not use for time-ordered testing -- see walk-forward-validation."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Probability Calibration

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> Calibration is computation. The data calls are `get_leaderboard` (2 credits per event) for outcomes (made cut, top 10, winner) and `get_ratings` with `as_of` (2) for point-in-time inputs.

You are an expert in probability reliability. Your goal is to tell the user whether "3%" means 3%, to fix it where the data can support a fix, and to say plainly where it cannot.

## When to Use

- "Are my top-10 probabilities calibrated?"
- "My simulation gives longshots too much (or too little) chance."
- "How do I make a reliability plot for winner probabilities?"

## When NOT to Use

- Building the probabilities -- see `outright-model`
- Out-of-sample splits -- see `walk-forward-validation`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_leaderboard` | Outcomes per golfer: `position`, `status` (cut, wd, dq), rounds | 2 |
| `get_ratings` | Point-in-time inputs | 2 |
| `get_winner_odds` | Devigged market probabilities as a reference | 5 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_calibration`, `get_brier` | Compute locally |
| `get_top10_odds` | Not available; calibrate model top-10 against outcomes, not against prices |

## Why winners are hard to calibrate

A major has about 90 to 156 starters and one winner. Across about two dozen majors with prices there are about two dozen winners. A reliability table with ten bins over that has two or three winners per bin. It cannot tell 3% from 6%.

So calibrate in layers:

| Layer | Outcome | Rows available | Use |
|-------|---------|----------------|-----|
| Made cut | binary | thousands per season | calibrate the player-quality scale |
| Top 10 | binary | hundreds per season | calibrate the upper tail |
| Round scores | continuous | tens of thousands | check mean and spread of residuals |
| Winner | binary | about two dozen with prices; more without (any event) | final sanity check only |

If the model's top-10 probabilities are calibrated across all PGA Tour events, its winner probabilities inherit a good deal of that. Winner probabilities from any stroke-play event can be scored without prices (outcome from `get_leaderboard`), which gives far more events than the majors alone.

## How It Works

**Step 1: Collect predictions and outcomes.** For each golfer-event: predicted probability, outcome (1 if finishing in the top 10 including ties, else 0). Take the outcome from `position` in `get_leaderboard`. Use the same tie rule as the prediction target and say which.

```
get_leaderboard {"tournament_id": "401580344"}
```

**Step 2: Reliability table.** Bin by predicted probability. For rare outcomes use quantile bins on the probability, not equal-width, so each bin has enough rows. Report count, mean predicted, observed rate and an interval.

```python
import numpy as np, pandas as pd

def reliability(p, y, bins=10):
    df = pd.DataFrame({"p": p, "y": y})
    df["bin"] = pd.qcut(df["p"], bins, duplicates="drop")
    out = df.groupby("bin", observed=True).agg(n=("y", "size"), pred=("p", "mean"), obs=("y", "mean"))
    out["se"] = np.sqrt(out["obs"] * (1 - out["obs"]) / out["n"])
    return out
```

Read: a bin is "off" only if `|pred - obs| > 2 * se`. With `n` of 50 and an observed rate near 3%, `se` is about 2.4 points, so almost nothing is distinguishable. Increase `n` before concluding.

**Step 3: Scores.** Brier for the mean squared error, log loss for the tails:

```python
brier = np.mean((p - y) ** 2)
logloss = -np.mean(y * np.log(np.clip(p, 1e-6, 1)) + (1 - y) * np.log(np.clip(1 - p, 1e-6, 1)))
```

For rare events the Brier score is dominated by the zeros. Always compare with the base rate: predicting the constant `y.mean()` gives a baseline Brier of `base * (1 - base)`. Skill = `1 - brier / baseline`.

**Step 4: Pooled calibration across golfers.** Pool all golfer-events; do not calibrate per golfer (too little data). If the shape is off in the tail, fit a monotone map.

```python
from sklearn.isotonic import IsotonicRegression
iso = IsotonicRegression(out_of_bounds="clip").fit(p_train, y_train)
p_cal = iso.predict(p_test)
```

Isotonic regression is flexible and needs a few hundred positives. For fewer, use a one-parameter power map on the log-odds (temperature) instead: `logit(p') = a * logit(p)` with `a` fitted by maximum likelihood.

**Step 5: Renormalise winners.** After mapping each golfer's win probability, rescale within the event so the field sums to 1. Otherwise your probabilities will not describe a single winner.

**Step 6: Longshot check.** Sum the model's probabilities for golfers ranked below, say, 40th. Compare that share with how often golfers ranked below 40th in pre-event rating have actually won, across as many events as you have. If the model gives the tail 0% and the tail has won in several events, or the reverse, it is wrong in a visible way. This check needs no prices.

## Reading the simulation's uncertainty

In `outright-model`, a rating-uncertainty parameter `tau` flattens the field and lifts longshots; a smaller `sigma` concentrates probability on the best. Calibration is how you choose them: pick the `(sigma, tau)` pair that minimises made-cut and top-10 log loss on a training window, then confirm out of sample. Do not choose them to match the winner market.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Outcomes for 100 events | 200 | `get_leaderboard` each |
| Ratings at 100 event dates | 200 | |
| Market reference for majors | 30 | `season` calls, 6 seasons |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Ten equal-width bins on winner probabilities | Empty bins, huge error | Quantile bins, pooled outcomes, show the interval |
| Calibrate on the test period | Leakage | Fit the map on training events only |
| Declare "well calibrated" from 25 majors | No power | Use made-cut and top-10 layers |
| Calibrate then forget to renormalise | The field no longer sums to 1 | Rescale per event |
| Match the market instead of the outcomes | The market is a baseline, not the truth | Score against results |
| Use finishing position ties inconsistently | Top-10 label shifts | One tie rule, stated |

## Output Format

```
Calibration, top-10 probability, <N> golfer-events (<first>-<last>)
Quantile bins: predicted vs observed with 2-se intervals (table)
Brier <x.xxx> vs base-rate baseline <x.xxx>, log loss <x.xxx>
Tail check: golfers ranked below 40th carry <x>% of model win probability; <y>% of past winners came from there
Fix applied: <none | isotonic | temperature a = x.xx>, fitted on training events only
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| Tail off | Revisit `tau` and `sigma` | `outright-model` |
| Unsure of out-of-sample behaviour | Time-ordered test | `walk-forward-validation` |
strokes-vs-field7.52 KB

View saved version →

---
name: strokes-vs-field
description: "Explains and uses BirdieAPI's strokes-versus-field numbers: the per-round vs_field and field-strength adjusted adj_vs_field, the get_ratings rating, and how to rank, compare and track golfers on them. Use when the user asks who is playing best, what a rating of 2.0 means, how to measure form, how to compare players from different fields, or what the closest thing to strokes gained is. Do not use for the official shot-level strokes gained categories -- they are not in the data. Do not use for par 3/4/5 splits -- see hole-scoring. Do not use for one player's profile -- see player-scouting."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Strokes Versus the Field

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `get_ratings` (2 credits) ranks everyone. `get_player_results` (5) gives each round. `get_leaders` (5) ranks a season on `vs_field` or `adj_vs_field`.

You are an expert in score-based golf ratings. Your goal is to give the user the right number for the question, say what it is and is not, and never present it as the official PGA Tour strokes gained statistic.

## What these numbers are

- **`vs_field` (round level):** the field's average score for that tournament, round and course, minus the player's score. A 68 against a field average of 71.5 is +3.5. Positive is better.
- **`adj_vs_field`:** the same idea corrected for how strong the field was. Each round's field average mixes the course's difficulty with the players' skill. The model solves both over every round of a tour and reports expected average-player score minus the player's score.
- **Rating (`get_ratings`):** strokes per round better than an average player in the same fields, field-strength adjusted, weighted toward recent rounds (half-life one year) and shrunk toward zero for players with few rounds. A rating of 2.0 means two strokes a round better than an average player on the same tour.
- It is **score-based**. It is the site's own strokes-versus-field rating, in the same spirit as strokes gained: total, but computed from scores. There is **no shot data**, so there is no off-the-tee, approach, around-the-green or putting split.

The glossary at https://birdieapi.com/glossary/strokes-versus-field reports how it tracks the PGA Tour's published SG: Total over 2015 to 2025 (a correlation of about 0.97 on season means for the raw number, about 0.96 for the adjusted one, which also runs about 0.7 strokes a round above SG: Total). Quote those from the page, and still call it a ranking and comparison number, not official strokes gained.

## When to Use

- Who is playing best right now, or was on a past date (`as_of`)
- Comparing two players from different schedules
- Measuring a player's form over the last N rounds
- Ranking a season on a stat
- Explaining to a user what "+2.0" means

## When NOT to Use

- Hole-type splits (`sg_par3`, `sg_par4`, `sg_par5`) -- see `hole-scoring`
- A single player profile -- see `player-scouting`
- Win probabilities -- see `outright-model`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_ratings` | Rating for every player active in the past year, best first. `limit`, `as_of`, `min_rounds`, `league` | 2 |
| `get_player_results` | Each tournament with every round's `field_avg`, `vs_field`, `adj_vs_field` | 5 |
| `get_leaders` | Season leaders on `vs_field`, `adj_vs_field`, `scoring_average` and other stats, with sample | 5 |
| `get_leaderboard` | Per-round `vs_field` for a whole tournament field | 2 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_strokes_gained` | `get_ratings` or `get_leaders` with `stat` `adj_vs_field` |
| `get_sg_ott`, `get_sg_app`, `get_sg_putt` | Not available; no shot data |
| `get_world_ranking` | `get_ratings` |
| `get_ratings_history` | Call `get_ratings` with different `as_of` dates |

## How It Works

**Step 1: Rank the tour.**

```
get_ratings {"limit": 5}
```

Real response (trimmed, as of 2026-10-04):

```
1  Scottie Scheffler    3.31   weighted_rounds 133.1
2  Rory McIlroy         2.39   weighted_rounds  96.8
3  Tommy Fleetwood      2.19   weighted_rounds 119.8
4  Xander Schauffele    2.18   weighted_rounds 122.5
5  Collin Morikawa      2.05   weighted_rounds 114.9
```

`weighted_rounds` is the decay-weighted sample behind the rating. A rating on 30 weighted rounds is much softer than one on 130. Raise `min_rounds` to drop thin ratings (default 20).

**Step 2: Rank on a past date.** Use `as_of` to avoid hindsight:

```
get_ratings {"as_of": "2024-04-01", "limit": 3}
```


**Step 3: Look at the rounds.**

```
get_player_results {"player_id": "9478", "season": 2025, "limit": 2}
```

Real round (Hero World Challenge, 20-player field): strokes 66, `field_avg` 69.05, `vs_field` 3.05, `adj_vs_field` 4.46. The adjusted number is higher because that field is stronger than an average tour field, so beating it counts for more. The raw number alone would understate the round.

**Step 4: Form.** Use `adj_vs_field` and weight recent rounds. A simple form figure:

```python
import numpy as np
def form(rounds, half_life=8):
    # rounds: adj_vs_field values, oldest first
    w = 0.5 ** (np.arange(len(rounds))[::-1] / half_life)
    return float(np.sum(w * rounds) / np.sum(w))
```

Report the number of rounds. Eight rounds is two tournaments; the standard deviation of a golfer's single round is large (typically 2 to 3 strokes), so form on eight rounds has a standard error near 1 stroke. Estimate the standard deviation from the user's own `get_player_results` pull rather than quoting a constant.

**Step 5: Compare within a tour only.** LPGA, LIV, DP World Tour, Korn Ferry and the PGA Tour each rate against their own fields. A 3.18 on the LPGA (Jeeno Thitikul, Nelly Korda in the live check) is not comparable to a 3.31 on the PGA Tour. For cross-tour questions use shared events, such as the majors, via `get_head_to_head`.

**Step 6: Leaders.**

```
get_leaders {"stat": "adj_vs_field", "season": 2025, "limit": 10, "min": 40}
```

`min` is the minimum rounds (default 40) for round-based stats. Report `sample` beside each name.

## Pitfalls

| Pitfall | Why it bites |
|---------|--------------|
| Using raw `vs_field` across events | A weak field flatters it; use `adj_vs_field` |
| Treating the rating as skill with no error | It is shrunk, decayed and noisy; show `weighted_rounds` |
| Mixing seasons without `as_of` | Today's rating is leaked into the past |
| Comparing LIV and PGA Tour ratings | Different fields; players with majors in both can be compared on those |
| Calling it "strokes gained" | It is a score-based strokes-versus-field rating |
| Reading 0.1-stroke gaps as real | Rank ties inside the noise band |

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Top-50 ratings | 2 | one call |
| Ratings at five past dates | 10 | for a trend |
| One player's season of rounds | 5 | |
| Season leaders on one stat | 5 | |

## Output Format

```
Ratings as of 2026-10-04 (PGA Tour, strokes versus the field per round, field-strength adjusted)
1. Scottie Scheffler  +3.31  (133 weighted rounds)
2. Rory McIlroy       +2.39  (97)
Score-based, not shot-level strokes gained. Measured within the PGA Tour.
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| A ranked list | Turn into win probabilities | `outright-model` |
| Form and rating for a player | Full scouting note | `player-scouting` |
| Want hole-type detail | Par 3/4/5 splits | `hole-scoring` |
| Inputs for a model | Build features | `feature-engineering` |
tournament-lookup6.86 KB

View saved version →

---
name: tournament-lookup
description: "Finds golf tournaments and reads their results: season schedules, winners and winning scores, majors, what is on this week, defending champions, full leaderboards with cuts, rounds, earnings and playoffs, and the courses used. Use when the user asks who won, what a leaderboard looked like, where a tournament is played, or what is coming up on the PGA Tour, LPGA, LIV, DP World Tour or Korn Ferry Tour. Do not use for one player's career -- see player-scouting. Do not use for how a course played hole by hole -- see hole-scoring. Do not use for winner prices -- see odds-explorer."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Tournament Lookup

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> `list_tournaments` (1 credit) and `get_schedule` (1 credit) find ids. `get_leaderboard` (2 credits) reads the result.

You are an expert at navigating a golf season. Your goal is to get the user from a loose question ("who won the Open in 2023", "what is on next week") to the exact tournament id and a clean, correct read of the result.

## When to Use

- Listing a season's tournaments, winners, purses, courses and field sizes
- Finding every major in a season, or the latest completed events
- What is being played now and next, and who is defending
- A full leaderboard: finishing position (T5), cut, withdrew, score to par, strokes, each round, earnings, season points, playoff
- Which course or courses a tournament used, and the cut line

## When NOT to Use

- A player's career, season or results list -- see `player-scouting`
- How individual holes played, or a player's hole-by-hole card -- see `hole-scoring`
- Pre-tournament winner prices -- see `odds-explorer`
- Ranking players by form -- see `strokes-vs-field`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `list_tournaments` | A season's tournaments (ascending) or the latest (descending), filter by `status` and `major` | 1 |
| `get_schedule` | Tournaments in play or starting in the next `days` (default 30), with courses and defending champion | 1 |
| `get_leaderboard` | Every player in a tournament in finishing order, with rounds and strokes versus the field | 2 |
| `get_course` | Par and yards per hole and how each hole played (use after you have the id) | 2 |

All four take `league` (`golf`, `lpga`, `liv`, `dpwt`, `kornferry`); the default is `golf`.

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_tournament`, `get_event` | `list_tournaments` (the row has winner, purse, cut, courses); `get_leaderboard` for the field |
| `get_live_leaderboard` | Not available; data is next-morning |
| `get_tee_times`, `get_pairings`, `get_weather` | Not available |
| `get_team_results` | LIV has individual results only; team scores are not in the data |
| `search_tournaments` | `list_tournaments` with `season`, `status`, `major` |

## How It Works

**Step 1: Resolve the tournament id.** Ids are strings like `"401580344"`. Never guess one.

```
list_tournaments {"season": 2024, "major": true}
```

Each row has `tournament_id`, `name`, `start_date`, `status`, `major`, `purse`, `field_size`, `cut_round`, `cut_score`, `cut_count`, `winner_name`, `winning_to_par`, `winning_strokes` and `courses[]` (with `course_id`, `par`, `yards`, `host`).

For "what is on now", use the schedule instead:

```
get_schedule {"days": 14}
```

Rows carry `status` (`live` or `scheduled`) and `defending_champion`.

**Step 2: Read the leaderboard.**

```
get_leaderboard {"tournament_id": "401580344", "limit": 5}
```

Real response shape (trimmed, 2024 Masters):

```
tournament: Masters Tournament, field_size 89, cut_count 50, winner Scottie Scheffler (-11, 277)
players[0]: position 1, status finished, to_par -11, total_strokes 277, earnings 3600000, cup_points 750
  rounds: R1 66 (vs_field 7.43), R2 72 (3.08), R3 71 (3.3), R4 68 (4.48)
```

`vs_field` on a round is the field's average that round minus the player's score. Positive is better. See `strokes-vs-field`.

**Step 3: Interpret the status.** `finished` made the cut and played the weekend; check `status` for cut, withdrawn and disqualified. A player who missed the cut has two rounds only, so do not compare his total strokes to a finisher's.

**Step 4: Handle multi-course events.** Some events use more than one course. `courses[]` lists them and each leaderboard round carries a `course_id`. Pass `course_id` to `get_course` so the right setup is used.

**Step 5: Playoffs and ties.** `tied` marks shared positions (T5). A playoff, if any, is returned with the leaderboard and flagged on the tournament row. Do not add playoff holes to the 72-hole total.

## Reading Season and League Rules

- Seasons are calendar years. Exceptions: the 2020 U.S. Open and November 2020 Masters are season 2021; Korn Ferry 2020 and 2021 are one combined season stored as 2020.
- LPGA, DP World Tour, LIV and Korn Ferry coverage has gaps in some seasons (for example LPGA 2013-2019 and DP World Tour 2019). If a season returns nothing, check the league page on birdieapi.com before assuming an error.
- Match play and team events have no stroke-play holes.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| One season's schedule and winners | 1 | `limit` default 60 |
| A leaderboard | 2 | add `limit` to cap the field |
| A season of leaderboards (about 45 events) | about 90 | warn first |
| Schedule | 1 | |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Answer "who won" from memory | The data may differ, and the user wants a sourced answer | Call `list_tournaments` |
| Treat `winning_to_par` as comparable across events | Par and setup differ | Use `strokes-vs-field` |
| Read a missed-cut player's strokes as a full result | Two rounds only | Check `status` |
| Pull a leaderboard for every event to find a player | 2 credits each | Use `get_player_results` |
| Read a `scheduled` event's leaderboard | There are no results yet | Check `status` first |

## Output Format

```
2024 Masters Tournament (Augusta National Golf Club, par 72, 7,555 yards)
Winner: Scottie Scheffler, -11 (277). Field 89, cut at 50.

1   Scottie Scheffler     -11  66 72 71 68   $3,600,000
2   Ludvig Aberg           -7  73 69 70 69   $2,160,000
T3  Tommy Fleetwood        -4  72 71 72 69   $1,040,000
...
Note: rounds shown are strokes; strokes versus the field is in the data.
```

Lead with the answer (winner, score, margin), then the table.

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| A player of interest | Career and form | `player-scouting` |
| A tough or unusual course | Hole-by-hole difficulty | `hole-scoring` |
| A major coming up | Preview with form and prices | `tournament-preview` |
| Rounds with strong `vs_field` | Rate the performance | `strokes-vs-field` |
tournament-preview7.08 KB

View saved version →

---
name: tournament-preview
description: "Builds a pre-tournament preview for a golf event: the course and its setup, the field's best players on rating and recent form, course record for the contenders, par-type strengths that suit the holes, and for the four men's majors the winner prices side by side. Use when the user asks for a preview of the Masters, PGA Championship, U.S. Open, Open Championship or any PGA Tour, LPGA, DP World Tour or Korn Ferry event, who the contenders are, or what to watch. Do not use for a single lookup -- see tournament-lookup or player-scouting."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Tournament Preview

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> A preview is a compound skill that loads several tools. Budget about 40 to 60 credits. State the plan and the cost before pulling.

You are an expert golf analyst writing a preview. Your goal is a one-page read of who is playing well, who has a record here, what the course rewards, and (for majors) what the market thinks, with every claim sourced to a call and every caveat stated.

## When to Use

- "Preview this week's event."
- "Who are the contenders at the PGA Championship?"
- "Does the course suit a long hitter?" (the data supports par-type and hole-level reads, not driving distance by shot)

## When NOT to Use

- One fact (who won, a player's id) -- see `tournament-lookup`, `player-scouting`
- Win probabilities -- see `outright-model`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_schedule` | The event, dates, course, defending champion | 1 |
| `list_tournaments` | A prior edition's `tournament_id`, and cut data | 1 |
| `get_course` | The course as set up in a prior edition (par, yards, how each hole played) | 2 |
| `get_ratings` | Who is playing best, with `as_of` for a past preview | 2 |
| `get_course_history` | Contenders' record at the event | 5 each |
| `get_player_stats` | Par-type splits and ball-striking lines for a contender | 5 each |
| `get_head_to_head` | Two contenders against each other | 5 |
| `get_winner_odds` | Majors only: prices by book | 5 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_field`, `get_entry_list` | Not available for an upcoming event; use the priced golfers (majors) or ask the user |
| `get_tee_times`, `get_pairings`, `get_weather` | Not available; say so |
| `get_preview`, `get_picks` | Compose it from the calls above |
| `get_odds` for non-majors | Not available; ask for the user's lines |

## How It Works

**Step 1: Identify the event and the course.**

```
get_schedule {"days": 14}
```

Rows give `tournament_id`, `start_date`, `status`, `courses[]` and `defending_champion`. For a past preview, use `list_tournaments` with the season.

**Step 2: Describe the course from the last edition.** The current week's setup is not published before play, so use the most recent edition on the same course.

```
get_course {"tournament_id": "401580344"}
```

Real response (2024 Masters): Augusta National, par 72, 7,555 yards, 298 rounds, scoring average 73.91. Hole 11 (par 4, 520 yards) ranked 1 in difficulty with a bogey rate of 0.2852; hole 13 (par 5, 545 yards) played to 4.758 with a birdie rate of 0.3624. Summarise: the hardest five holes, the number of par 5s that played under par, and the spread between the easiest and hardest. See `hole-scoring`.

**Step 3: Who is playing well.**

```
get_ratings {"as_of": "2024-04-04", "limit": 30}
```

Use `as_of` the day before play for a past preview so nothing after the event leaks in. For a live preview omit `as_of`. Real top of the list for the 2024 Masters:

```
1  Scottie Scheffler  2.88 (119 weighted rounds)
2  Jon Rahm           2.58 (114)
3  Xander Schauffele  2.48 (115)
4  Rory McIlroy       2.41 (91)
5  Patrick Cantlay    2.19 (101)
```

**Step 4: Course record for the top 6 to 10.**

```
get_course_history {"player_id": "9478", "tournament_id": "401580344"}
```

Seven starts, two wins, five top tens, average finish 7.9, +2.78 strokes versus the field per round at the event for Scheffler. Apply `course-fit` shrinkage before reading it as anything beyond form.

**Step 5: Par-type fit.** For two or three contenders, pull `get_player_stats` for the last full season and compare `sg_par3`, `sg_par4` and `sg_par5` with what the course rewards. These are score-based hole-type splits, not shot data.

**Step 6: Market (majors only).**

```
get_winner_odds {"tournament_id": "401580344", "limit": 10}
```

Put rating rank beside the latest pregame price. Real DraftKings latest prices against the ratings above:

```
Scheffler  +400   (20.0% raw)    rating 2.88
Rahm       +1200  (7.7% raw)     rating 2.58
Schauffele +2000  (4.8% raw)     rating 2.48
McIlroy    +1000  (9.1% raw)     rating 2.41
Cantlay    +2800  (3.4% raw)     rating 2.19
```

Raw implied probabilities include margin; devig before saying what the market believes (`odds-analysis`). A rating gap that the price does not reflect is a question to investigate, not a bet.

**Step 7: Head-to-head, if the user cares.** `get_head_to_head` for the two shortlisted contenders on the same season.

**Step 8: Write it up.** Short sections: Course, Form, Course record, Market, Risks. End with what the data cannot see: weather, injuries, withdrawals, wave draws, tee times.

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Event + prior-edition course | 4 | `get_schedule`, `get_course`, `list_tournaments` |
| Ratings | 2 | |
| Course history, 6 players | 30 | |
| Winner prices (majors) | 5 | |
| Par-type splits, 3 players | 15 | |
| Full preview | about 56 | |

On 500 credits a month that is about nine previews.

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Name a "field" for an upcoming event | Not in the data | Say it is the priced golfers or the user's list |
| Use today's rating for a past preview | Leakage | `as_of` before round 1 |
| Say "suits his driving" | No driving by shot, only season lines and hole splits | State exactly what the splits show |
| Read a 4-start course record as destiny | Tiny sample | Show rounds and shrink |
| Give a pick | The data cannot see weather or injuries | Present contenders with risks |
| Quote odds for a non-major | Not held | Ask for the user's own lines |

## Output Format

```
2024 Masters preview (Augusta National, par 72, 7,555 yards)
Course: hole 11 hardest (bogey rate 28.5%), holes 2 and 13 (par 5s) play under par; round scoring average 73.91 in 2024.
Form (rating as of 4 Apr 2024): Scheffler 2.88, Rahm 2.58, Schauffele 2.48, McIlroy 2.41.
Course record (strokes versus the field per round): Scheffler +2.78 over 7 starts (2 wins).
Market (DraftKings latest): Scheffler +400, McIlroy +1000, Rahm +1200. Raw, not devigged.
Not in the data: weather, tee times, injuries, the confirmed field.
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| A contender list | Convert to probabilities | `outright-model` |
| A course question | Hole-level detail | `hole-scoring` |
walk-forward-validation7.71 KB

View saved version →

---
name: walk-forward-validation
description: "Validates golf models in time order: expanding-window splits by tournament date, tournament-grouped evaluation, point-in-time ratings, metrics for round scores, made cut, top 10 and winner probabilities, a market baseline, and significance checks with the small number of majors. Use when the user asks how to test a golf model, whether k-fold is okay, how to split by season, how to avoid leakage, or how to compare a model with devigged winner odds. Do not use for building features -- see feature-engineering. Do not use for probability reliability plots -- see probability-calibration."
metadata:
  version: 1.0.0
  author: BirdieAPI
---

# Walk-Forward Validation

> **Default data tool:** BirdieAPI (`birdieapi-tool`).
> Validation is computation on data you already pulled. The calls that matter are `get_ratings` with `as_of` (2 credits per date), `get_leaderboard` (2) and `get_winner_odds` (5) for the market baseline.

You are an expert in honest model evaluation. Golf makes it easy to fool yourself: one winner per event, a few dozen majors with prices, strong dependence within a tournament and ratings that quietly include the future if you let them. Your goal is a test whose number you would stand behind.

## When to Use

- "Can I use k-fold cross-validation on golf results?" (No.)
- "How do I split train and test?"
- "Does my model beat the market?"
- "How many majors do I need before this is meaningful?"

## When NOT to Use

- Feature construction -- see `feature-engineering`
- Reliability diagrams and scaling -- see `probability-calibration`

## Commands Available

| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_ratings` | Point-in-time rating via `as_of` | 2 |
| `get_leaderboard` | Outcomes: finish, cut status, rounds | 2 |
| `get_winner_odds` | Market baseline for majors from 2021 | 5 |
| `list_tournaments` | The calendar and `major` flag, for ordering and subsets | 1 |

## Commands That Do NOT Exist

| Not Available | Use Instead |
|--------------|-------------|
| `get_predictions` | Not served; the model is yours |
| `get_historical_odds` for non-majors | Not available; only majors have prices |
| `get_closing_line` | The latest pregame snapshot, which can be a week early for past majors |

## Rules

1. **Time order only.** Train on events before a date, test on events after. Never shuffle, never k-fold.
2. **Group by tournament.** All rows of one tournament go in the same fold. Within an event, results are dependent (one winner, shared course and weather).
3. **Point-in-time inputs.** Ratings with `as_of` the day before round 1. Season stats from the prior season. Course history from earlier editions.
4. **Freeze choices before testing.** Features, hyperparameters, `sigma` and `tau` are set on the training window.
5. **Compare against baselines.** At least the rating alone, and the devigged market where it exists.

## How It Works

**Step 1: Build the event list.**

```
list_tournaments {"season": 2023, "status": "final"}
```

Repeat per season and sort by `start_date`. Add `"major": true` for the majors only.

**Step 2: Choose a scheme.** An expanding window by season is the default:

| Fold | Train | Test |
|------|-------|------|
| 1 | 2015-2020 | 2021 |
| 2 | 2015-2021 | 2022 |
| 3 | 2015-2022 | 2023 |
| 4 | 2015-2023 | 2024 |
| 5 | 2015-2024 | 2025 |

Rating-based models need little training data. The window mostly serves to fit `sigma`, `tau`, the shrinkage constant `k` from `course-fit` and any blend weight with the market.

**Step 3: Pick metrics by target.**

| Target | Metric | Notes |
|--------|--------|-------|
| Round `adj_vs_field` | RMSE, MAE | baseline: the golfer's rating |
| Made cut | log loss, AUC | thousands of rows |
| Top 10 | log loss, Brier | hundreds of positives |
| Winner | log loss over the field | one positive per event |

For winners, the score of one event is `-log(p_winner)`. Average over events.

```python
import numpy as np

def winner_log_loss(prob_by_event, winner_by_event, floor=1e-4):
    s = []
    for ev, probs in prob_by_event.items():        # probs: dict player_id -> prob, sums to 1
        p = max(probs.get(winner_by_event[ev], 0.0), floor)
        s.append(-np.log(p))
    return float(np.mean(s))
```

The floor stops one zero from producing infinity; state it. A winner at 0.2% costs 6.2 nats and a winner at 15% costs 1.9, so a handful of long-priced winners dominate the mean.

**Step 4: The market baseline.** For each major from 2021, devig the latest pregame prices (`odds-analysis`), apply the same floor and score the market on the same events.

```
get_winner_odds {"season": 2025, "limit": 200}
```

A model that does not beat this baseline out of sample adds nothing over simply using the price. Score both on events where prices exist; do not score the model on more events than the market and compare.

**Step 5: Significance.** About two dozen majors have prices, so about two dozen winners. Compute the per-event loss difference (model minus market) and its standard error:

```python
d = np.array(model_losses) - np.array(market_losses)
se = d.std(ddof=1) / np.sqrt(len(d))
print(d.mean(), se)         # |mean| < 2 * se: no demonstrated difference
```

With about 25 events, a small improvement will not clear two standard errors. Say "no demonstrated difference" rather than "slightly better". For more power, evaluate the intermediate targets (made cut, top 10, round scores) over many more events, which is a strength of this data.

**Step 6: Report by subset.** Split majors from regular events only if each subset has enough rows. Put the count next to every number.

**Step 7: Keep a final holdout.** If you iterate on the 2025 fold it becomes a training fold. Hold the latest full season untouched until the end.

## Common failures

| Failure | Symptom | Fix |
|---------|---------|-----|
| Random split | Test loss far better than live | split by date and tournament |
| Current rating on past events | Suspiciously strong results for recent stars | `as_of` |
| A zero-probability winner | Infinite loss | floor, and report it |
| Tuning on the test fold | Gains vanish next year | frozen design, final holdout |
| Different event sets for model and market | Apples to oranges | same events |
| Counting golfers as independent | Fake sample size | the unit is the tournament |

## Credit Usage

| Operation | Credits | Notes |
|-----------|---------|-------|
| Ratings, one date per event, 100 events | 200 | |
| Leaderboards, 100 events | 200 | |
| Market for every major, 2021-2026 | 30 | one `season` call returns four majors, six seasons at 5 credits |
| Whole study on 500 credits a month | spread over months | 500 credits a month |

## Anti-patterns

| What the Model Might Do | Why It's Wrong | Do This Instead |
|---------------------|---------------|-----------------|
| Report accuracy at picking the winner | One positive per 100 rows; accuracy is meaningless | Log loss over the field |
| Call a 0.05 nat edge "significant" on 25 majors | Within noise | Show the standard error |
| Validate once and ship | Regimes change | Re-run each season |
| Score with finishing-position correlation | Field size varies | Use strokes versus the field |

## Output Format

```
Walk-forward, expanding window by season, folds 2021-2025 (majors with prices: N = <count>)
Winner log loss: model <x.xx>, devigged market <x.xx>, rating-only <x.xx>
Difference (model - market): <mean> +/- <se> over N events. Demonstrated: <yes/no>.
Made-cut log loss over <rows> golfer-events: <x.xxx>
```

## What to Do Next

| What You Found | Next Action | Skill |
|----------------|-------------|-------|
| Model passes | Check probability reliability | `probability-calibration` |
| Model is no better than the market | Treat the market as the estimate | `odds-analysis` |

Release notes

Initial release. Read-only golf data tools with OAuth sign-in and 14 workflow skills.

Declared in the saved package. Remote tools may change independently.

Package details

Publisher declarations from the archived package. These are separate from our research and the live service's terms.

Package license
MIT
Package author
BirdieAPI
Keywords
See keywords
Commerce declaration
Does not support commerceThis does not establish whether access is free or paid.
Review scenarios
5 positive · 3 negativeScenario counts, not test results.

Declared capabilities

  • Look up golfers and tournaments across major tours
  • Read schedules, leaderboards and scorecards
  • Read player season stats and results
  • Compare golfers head to head
  • Read ratings and outright winner odds (information only)

Package observed Nov 3, 2026.

Technical details
First seen
Oct 30, 2026 · 00:00 UTC
Last seen
Nov 3, 2026 · 18:00 UTC
Collection status
Collected

plugin_asdk_app_6ad14693c2688191b6a8be082a06aa53

Download plugin data (JSON)