Methodology
How prices get from a sportsbook into a comparison, what has to be true before we call two prices the same market, and the checks that run before anything is shown. Including the ones we added because we got it wrong.
1. Collection
We read each sportsbook's own public price feed — the same data the site serves to its own front end — on a per-book interval. Books differ enormously in how fast they move and how tolerant they are of being read, so the interval is set per book rather than globally, and backs off automatically when a book starts failing.
Every book is read in English. This is not a presentation choice; it is load-bearing. The same fixture rendered in two languages produces two different team strings, and they will not match each other. A book that only offers a localised feed is read through its English locale or not at all.
We record a price with the time we saw it. Nothing is interpolated, and we do not carry a price forward when a book stops returning it — a stale price is removed rather than held.
2. Which markets we compare
Only markets whose results are mutually exclusive and exhaustive can be compared meaningfully. We currently handle three shapes:
| Shape | Meaning | Example |
|---|---|---|
| home–draw–away | three-way match result | 1X2 |
| home–away | two-way money line | ml |
| over–under | totals at a stated line | totals:2.5 |
A book's own label for a market is matched against a per-book rule set that returns accept, reject, or unknown. Two things about that gate matter:
-
It fails closed.
unknownis not treated as acceptable. A market label we have not seen before is excluded until it is classified, rather than guessed at. - The classification is checked against the outcome shape. A market claiming to be a two-way money line that arrives with three outcomes is rejected on the spot, regardless of what its label said.
Both of those exist because of a specific failure. A book's board that silently failed to switch sport served a three-way soccer market through a two-way money-line path, which turned the draw price into the away price and produced a convincing 34% price difference that was entirely fictional. The shape check is what now catches that class of bug, and the fail-closed default is what stops the next unseen label doing the same thing.
3. Matching the same fixture across books
This is the hard part, and it is where almost every false result comes from. Books name teams and players inconsistently, and they disagree about start times. A fixture is matched on normalised names plus a kickoff window, and both halves are sport-specific.
Kickoff windows
| Sport | Window | Why |
|---|---|---|
| Default | ±15 min | books agree closely on scheduled kickoffs |
| Tennis | ±6 hours | order-of-play times are estimates; we have seen the same match listed hours apart |
| Esports (slotted) | ±5 min | tournaments run many fixtures back to back; a wide window merges adjacent slots |
| Esports (real fixtures) | ±12 hours | listings for a single day's match can be spread very wide |
Source of truth: KICKOFF_WINDOW_MS and friends in
packages/shared/src/match.ts
The tennis window is the interesting one, because widening it is dangerous in a specific way. A six-hour window admits a player's doubles match, which happens later the same day and reuses the same surname. And a singles pair's two surnames are a strict subset of a doubles pair's four, so a naive token-set comparison scores that as a perfect match. A coin-flip doubles line against a lopsided singles line then looks like an enormous price difference.
So tennis names carry an explicit doubles discriminator: a side formatted as a pair is marked as such during normalisation and can never match a singles side, no matter how well the surnames score.
Name normalisation
Names are reduced before comparison — accents folded, punctuation and sponsor suffixes stripped, common abbreviations unified. Tennis takes a separate path: books render the same player as Surname F., F. Surname, Firstname Surname, or a surname with a country code appended, so a tennis side is reduced to surname tokens plus given-name initials, and doubles pairs are split on their separator.
Fuzzy scores above an accept threshold are taken; scores below a review threshold are rejected outright. The band between them is held for review rather than silently accepted or discarded.
4. Checks before anything is shown
A price difference is not published just because the arithmetic works. Each one is checked for:
- Same platform behind different brands. A large share of sportsbooks are white-labels of a handful of underlying platforms. Two brands on the same platform frequently serve identical prices, and occasionally serve differing prices that are an artefact of the platform rather than a real disagreement between two businesses. These are flagged.
- Consensus outliers. A price is compared against every other book pricing the same result. One book alone against the field is treated as suspect, because that is what a scraping bug looks like from the inside.
- Market status. Some books suspend a market at market level while every selection under it continues to report itself as open, at placeholder prices. Selection status alone is not trusted.
- Freshness. Stale prices are dropped rather than shown.
Flagged rows are kept, not deleted. That is what makes our published edge distribution possible — we can report how often the checks fire and at what sizes, rather than only reporting what survived them.
5. Known limitations
Stated because a methodology page that lists only strengths is marketing.
- Our checks are not ground truth. They are heuristics. Some flagged results are real; some unflagged results are still wrong.
- We do not place bets and cannot see limits. Whether a book will accept a given stake on a given market is invisible to us, and it is frequently the thing that matters most.
- A price is what a book published, not a promise. Books move prices, suspend markets, and void bets taken at obviously wrong prices. Every figure we show is a reading taken at a moment.
- Coverage is uneven. Not every book prices every fixture or every market, so the set of books being compared varies from row to row. The row itself shows which books it used.
- Tennis is our least reliable sport, for the naming reasons above. Measured rates are in the edge distribution.
6. Corrections
Bugs that produced wrong results, what caused them, and what changed:
| What went wrong | Fix |
|---|---|
| Quarter handicap read as a full-game money line | central market-type gate with an outcome-shape check; fails closed on unknown labels |
| Tennis doubles matched onto the singles match | explicit doubles discriminator during name normalisation |
| Suspended market whose selections still looked open | gate on market-level status, not selection status |
| Novelty markets ingested as fixtures | specials filter at ingest; affected rows purged |
| Board failed to switch sport, 3-way served as 2-way | board-identity verification plus the shape backstop; affected data purged |
Each of these produced plausible-looking results before it was caught. That is the honest reason this page exists: the failure mode of a price scanner is not an obvious error, it is a confident wrong answer.