# Football Charts — odds archive, FREE SAMPLE

Per-bookmaker **opening and closing prices** for 1 football league(s), seasons
2024-25 to 2024-25, joined to full-time / half-time results and goal
minutes. Compiled by [football-charts.com](https://www.football-charts.com) from publicly displayed bookmaker prices
on odds-comparison pages. Not affiliated with any bookmaker or comparison site.

This is the free sample: one complete league-season in exactly the layout of the paid archive, so you can check the format before buying. Licence: evaluation and research only, no redistribution.

## What is inside

| file | one row per | rows |
|---|---|---|
| `matches.csv` | match (with prices) | 306 |
| `odds_1x2.csv` | match × scope (ft/ht) × bookmaker | 2439 |
| `odds_ah.csv` | match × Asian-handicap line × bookmaker | 11326 |
| `odds_ou.csv` | match × scope × over/under line × bookmaker | 17060 |
| `odds_bts.csv` | match × bookmaker (both teams to score) | 644 |
| `odds_cs.csv` | match × correct score × bookmaker | 12226 |
| `odds_htft.csv` | match × half-time/full-time combo × bookmaker | 13113 |
| `bookmakers.csv` | bookmaker id | — |
| `leagues.csv` | league | — |
| `coverage_league_season.csv` | league-season: what is priced, what is not | — |
| `excluded_matches.csv` | match WITHOUT prices, with the reason | 0 |
| `parquet/` | the same tables as Parquet (when present) | — |
| `COVERAGE.md`, `DATA_DICTIONARY.md`, `LICENSE.txt`, `CHANGELOG.md`, `MANIFEST.json` | | |

Every odds table starts with the same seven identity columns — `op_id, league, season,
match_date, home_team, away_team, ft_result` — so each file reads on its own. `op_id`
(an 8-character match id, also embedded in football-charts.com match records) is the key
to `matches.csv`, which adds kick-off time, half-time score and goal minutes. Rows are
sorted by match, then line, then bookmaker, so a ladder reads top to bottom.

## Read it honestly

- **Bookmaker set is thin.** Prices were captured through a small set of European/US-licensed
  bookmakers — on average 6.24 price the full-time 1X2 of a match in this
  bundle, fewer in small leagues and older seasons. There is no Pinnacle, no exchange, no in-play
  data. "Best of book" here means best of a few.
- **Not every market exists for every match.** 306 of 306 matches carry 1X2,
  306 carry an Asian-handicap ladder, 306 carry an over/under ladder and
  291 carry all eight markets. The per-league-season table says exactly which.
  Where a market is absent for a whole league-season, the source pass fetched only 1X2 and
  Asian handicap — it is a capture limit, not evidence the market did not exist.
- **Absence is never silent.** 0 matches in scope returned no prices (throttled by the
  source, never listed, or a fetch error). They are listed in `excluded_matches.csv` with the reason
  and carry no odds rows. Do not read them as "unpriced".
- **Opening = the first price listed; closing = the last pre-kick-off price.** Timestamps come
  twice: unix seconds (`open_ts`, `close_ts`) and ISO UTC (`open_at`, `close_at`). `move_*` is
  the direction of the source's last move (`up`, `down`) when it reported one — blank otherwise;
  compare `close_*` with `open_*` for the net change.
- **Asian handicap lines are the HOME line.** `line = -0.5` means home −0.5 / away +0.5;
  `close_home` is the price on the home side of that line.
- **Bookmaker names** come from `bookmakers.csv`; ids without a confirmed name are `bookmaker_<id>`
  (`known = false`). The id is the stable key, the name is a courtesy.
- Team names are Football Charts' canonical names (stable within a league); `op_match_slug` is
  the source slug when you need to recognise a match by its original spelling.

## Quick start

```python
import pandas as pd
ah = pd.read_csv("odds_ah.csv")             # readable on its own: every row names the match
one = ah[ah.op_id == ah.op_id.iloc[0]]      # one match's whole Asian-handicap ladder, per bookmaker
print(one[["home_team", "away_team", "bookmaker", "line", "open_home", "close_home", "close_at"]])
# join matches.csv only when you need kick-off time, half-time score or goal minutes
m = pd.read_csv("matches.csv")
x = ah.merge(m[["op_id", "kickoff_time", "ht_result", "first_goal_time"]], on="op_id")
```

Questions, corrections, licence requests: contact@football-charts.com.
