StableBet
Professor Furlong and Pascal at the AI Lab
THE AI LAB
LAB NOTES · LAB NOTES

How We Measure a Festival

Every festival raises the same five questions, and until now we answered them once, in prose, and then lost the answers.

What did the market charge to bet in? How did the AI tipsters do, and where? How would the betting systems we test all year have fared over those specific races? What would the week have felt like to someone betting through it? And who rode and trained the winners?

Answering those in an article is fine for the article. It is useless a year later, when the obvious question is whether this year's meeting was dearer than last year's, or whether the models really do fare worse in handicaps or just did once.

So from the Ebor Festival 2026 onwards, each meeting gets a record instead. One file, one schema, written once and then frozen. The articles read from it rather than restating it, which means a figure quoted in an article and a figure shown in a table are the same figure by construction, not by somebody remembering to update both.

This page is the record's documentation: what is in it, the conventions it pins down, what it deliberately refuses to claim, and the two mistakes we made building the first one.

What a festival record contains

Six blocks, for every meeting.

The races. Every race with its grade, distance, going, field size, non-runners, the winner with starting price, jockey and trainer, and the margin built into its book.

The market. The same margin aggregated across the meeting and broken down by grade, by field size and by official race class, plus the cheapest and dearest race to bet in.

The league. Every Silicon Tipster League entrant's record over that meeting alone: bets, winners, strike rate, profit and loss, return, and how often it sided with the favourite.

The systems. The betting systems we track all year, replayed over just those races, with each one's all-time figure alongside so a good week cannot be mistaken for a good system.

The simulation. What the meeting would have done to somebody betting through it under a few simple strategies, with the assumptions stored in the data rather than described loosely in prose.

The people. Jockey and trainer tallies for the meeting.

The record is immutable once published. Re-running the builder for a meeting that already has a file needs an explicit override, and the file carries no build timestamp, so regenerating it produces an identical file. That is not fussiness. An article captioned "the 2026 meeting" must not have its numbers move underneath it six months later, and the only reliable way to guarantee that is to make the data incapable of moving.

The conventions, pinned

Most disagreements about racing numbers are not disagreements about the racing. They are two people using the same word for different quantities. The record pins the following, and stores the definitions inside the file so a figure can never travel without its convention.

Overround against takeout. Overround is the amount by which the book total exceeds a fair book. Takeout is the share of stake the book keeps. At Ebor Festival they read 28.1% and 21.9%. Both are correct, they differ by about a third, and there is no field called simply "margin" anywhere in the schema so that nothing downstream has to guess.

How aggregates are formed. A meeting-level margin is computed from the average book across its races, not by averaging the per-race margins. Those two give different answers and the difference is large enough to look like an error when two pages disagree.

Settled prices only. Everything comes from the published settled results, never the morning card. The two disagree on individual races by several percentage points because prices move and horses come out.

Price ties split the stake. Backing "the favourite" in a race with two joint favourites means half a unit on each, not a unit on whichever the alphabet picks first.

Each-way at quarter odds. Real place terms vary by race. We use one convention throughout so that a festival figure is comparable with the season-long figure it is printed next to, and we say so rather than implying these were the terms on offer.

Races are matched by their runners, never by race identifier. Our own feeds disagree about race identifiers for three separate reasons, so anything that needs to line up two sources lines them up on the horses.

What the record refuses to claim

A dataset is defined as much by what it declines to hold as by what it holds.

We do not claim what proportion of real punters made money. It is the most-asked question and we cannot answer it. We hold no record of what any member of the public staked, on what, at what price. What we can do is simulate: run the meeting many times under a stated strategy and report the distribution. Those figures describe the simulation and we label them that way every time. A model dressed up as a survey is worse than no number at all.

We do not claim a system works because it won over a meeting. Twenty-eight races is a handful. Several systems finish a festival in profit purely on variance, which is why every festival figure in the record carries the all-time figure beside it. Our season-long board refuses outright to publish any system showing a gain, and correctly so. This dataset is deliberately kept out of that board precisely so a short-window positive can exist here without ever implying a winning strategy there.

We do not compare against a long run we have not measured. Our race-by-race results reach back to June, so "was this meeting dearer than usual?" is currently unanswerable. It stays unanswered rather than being estimated.

We do not turn margin into bookmaker revenue. Margin is a rate. Revenue needs turnover, and we have no turnover figures for anybody. Multiplying a rate we know by a volume we invented would produce a headline number and no information.

Two mistakes, kept on the record

Both of these produced confident, plausible, completely wrong numbers, and both were caught by cross-checking rather than by reading the code.

Every loser was being paid as though it had placed. The each-way calculation asks whether a horse's finishing position is inside the places. The code that fed it had collapsed each result into a simple won-or-not flag, so every non-winner arrived carrying position two. In a big field the place terms go to fourth, and two is inside four, so every beaten horse was settled as a placed horse. Each-way backing of outsiders came out at more than twelve times the stake for the meeting. A number that absurd is easy to catch. The same bug in a quieter place would have shifted a figure by a few points and shipped.

One dataset answered the same question twice, differently. The systems block reported backing the favourite at one figure while the simulation reported it at another, because one split the stake across joint favourites and the other backed whichever came first. Both were defensible in isolation. Published side by side they would have read as a contradiction, and a reader would have been right to lose confidence in the whole page. They now settle through identical code.

There is a third thing worth naming, which was not a bug but a trap. The Silicon Tipster League has an entrant called "the favourite" and the systems board has a strategy called "the favourite", and they are different bets: one backs the horse heading the market when the card is published, the other backs the horse that starts favourite. Over this meeting they picked different horses in seven races out of twenty-seven and returned figures separated by more than the meeting's entire margin. Both definitions now travel inside the data, next to the numbers they describe.

Every figure here is pulled live from our data and nothing beats the bookmaker's margin. For whether anyone holds a real edge, see our track record. 18+, please bet responsibly.

More from Lab Notes