Every morning of the Tour we published the model's prices next to the bookmaker's, then let the road settle it. This is the full accounting β where we lost, where we genuinely won, and the two defects that explain almost everything in between.
β System overview & season boards Β· Stage-by-stage diary Β· GC tracker
The verdict, in five sentences. Across all 20 previewed stages the bookmaker beat the model clearly on picking winners β that headline is real and we are not going to bury it. But the deficit lives almost entirely at the sharp end of the result: by ten riders deep the model matches the book, and by fifteen deep it is marginally ahead. Nearly all of the gap traces to two specific causes β five "hard-man" stages of a kind the model structurally cannot read (four of which it flagged in advance), and a habit of under-pricing its own favourites even when it has the right ones. On the thirteen days settled by the peloton or the GC group, the model actually beat the book at picking the winner. And in the GC race it named the right man every single day while refusing to believe him β the clearest single defect on the list.
This report is written to be readable without a betting or machine-learning background. Ten terms carry all of it; everything else is plain English. Skim these now or refer back as needed β technical terms below also get a short gloss the first time they matter.
The bookmaker's odds (here Unibet, via the Kambi feed), used as our benchmark "expert" forecast. Beating the book is a high bar: its prices aggregate the opinions of everyone willing to stake real money.
Raw bookmaker odds are deliberately inflated so the book profits whatever happens β the implied chances add up to ~140β150%, not 100%. "Devigging" strips that margin out so the prices become honest probabilities. Every book number on this page is devigged.
The standard score for a probability forecast: you are penalised by how surprised you were by what actually happened. Confidently right scores near 0; confidently wrong scores very badly. Lower is better. "Nats" is its unit β only differences matter. Feel: giving the winner 10% instead of 20% costs 0.69 nats; Β±0.1 per stage is small, Β±1.0 is a badly missed day.
Whether stated probabilities match reality in the long run: if you say 30% on a hundred occasions, about 30 should happen. Saying 37% when the true rate is 55% is under-confident β the ordering can be perfect while the numbers are wrong. That turns out to be our main fixable problem.
Our model prices each rider separately, so its raw numbers don't add to 100% (they summed to 137β180%). A real market must add up: exactly 1 winner, 3 podium places, 15 top-15 spots. Before any comparison here, both boards are rescaled to the same total. Skipping this step systematically flatters the model β it is the easiest way to fool yourself, so we never skip it.
A ranking check needing no probabilities: of the riders who actually finished in the top 10, how many were in your own top 10? Reported as percentage points of model minus book.
How closely a predicted running order matches the real finishing order across the whole field. 1.0 is perfect, 0 is random.
Before the event vs after it. A pattern you can only identify afterwards is interesting; one you flagged beforehand can be acted on. That distinction drives the most important section of this report.
Choosing not to bet β or not to publish confidently β on days your forecast is likely to be weak. Sitting out is a legitimate strategy: losses avoided count exactly as much as gains made.
Project terms. Heads are the model's separate components (winner, podium, top-5, top-15, DNF). Reads are the 3β4 alternative scenarios we published each morning about how the day might unfold. Hindsight runs re-ran the model afterwards knowing the day's actual shape β a control for "would course knowledge have saved us?" They were never published.
Where model and book disagree on who was priced, both are scored over the same riders. The book listed 92β142 of ~170 starters each day; unlisted riders are assigned the board's longest price, never zero. Stage 1 was a team time trial and was never previewed β the review covers stages 2β21.
The fairest single comparison is the winner market: average log loss on the stage winner, model vs book, over all 20 previewed stages. We score the model the way it was actually published β equal weight across that morning's reads, no hindsight in the average.
| Frame | Model | Book |
|---|---|---|
| Equal weight across published reads (honest, ex-ante) | 2.407 | 1.944 |
| Best read per stage, picked after the fact (upper bound) | 2.129 | 1.944 |
| LambdaRank head alone, best read | 2.154 | β |
Average winner-market log loss per stage, lower is better. Even our post-hoc-best read loses by 0.19 nats/stage; the number we could actually have published loses by 0.46. Counting scenario-level top picks: the book's favourite won on 34 of 57 reads, ours on 22.
But averages hide everything interesting. Here is the whole Tour, day by day β how far above or below the book the model priced each actual winner:
Per-stage winner-market delta in nats: how much more (green) or less (red) probability the model put on the actual winner than the book did, equal-weight across that day's published reads. Tags mark the two stage classes discussed below. Each stage links to its original preview page.
Two things jump out of that chart. First, the losses are not spread evenly β six days (4, 13, 16, 18, 20, 21) account for the overwhelming bulk of the deficit, and four of them carry the same tag. Second, the model's single best day was better than anything the book managed all Tour. Both threads get pulled below.
Stage 15, La PlagneβLe Grand-Bornand over the Col de la ColombiΓ¨re and up to Solaison: Remco Evenepoel, +2.77 nats. The market priced Evenepoel as a rouleur out of his depth on a 9% wall β the old "time-trial-shaped men can't win real mountains" archetype. The model's view, all Tour, was that class and form transfer: it had him far shorter than the book, and he rode away on the steepest finish of the race. One stage doesn't prove a model, but this is exactly the kind of disagreement we built it to have β a priced, public, pre-registered dissent from the market's typecasting, settled in our favour on the road.
The winner market is the harshest possible lens: it scores exactly one rider per day. Widen the lens and the picture changes completely. For each depth k, count how many of the actual top k finishers each board had in its own top k β a pure ranking check that no pricing margin or normalization quirk can distort:
| Depth | Model | Book | Delta |
|---|---|---|---|
| Top 3 | 25.2 / 60 (42.1%) | 31 / 60 (51.7%) | β9.6pp |
| Top 5 | 47.1 / 100 (47.1%) | 52 / 100 (52.0%) | β4.9pp |
| Top 10 | 102.6 / 200 (51.3%) | 103 / 200 (51.5%) | β0.2pp |
| Top 15 | 160.9 / 300 (53.6%) | 160 / 300 (53.3%) | +0.3pp |
Actual top-k riders captured inside each board's own top k, summed over 20 stages. Model = its published reads averaged per stage (the honest ex-ante number, not a post-hoc pick). Rank correlation over the whole field: model 0.507, book 0.532.
The deficit shrinks monotonically with depth and vanishes entirely by ten riders. The model identifies the right group of contenders as well as the aggregated opinion of the betting market. What it cannot yet do as well is resolve the very front of that group β which of the right fifteen actually stands on the podium β and, as the next section shows, put honest weight on its choice even when the choice is correct.
The same gap shows up in probability space on the podium market (the book priced one on 18 of 20 stages): mean per-rider log loss model 0.0521 vs book 0.0459. These podium numbers are averaged over all ~170 riders rather than scored on a single winner, so they live on a much smaller scale than the 2.4 / 1.9 win figures β compare them only with each other.
So the honest reframe of the headline is: we are not worse than the market at reading a bike race. We are worse at concentrating probability once the right riders have been found. That decomposes into exactly two failure modes, and they behave very differently.
On days the model reads correctly, it still shades its top price down. Its winner probabilities cap out around 0.35β0.40 where the book confidently goes 0.42β0.54 (stages 6, 8, 10, 12, 14, 16, 21 β mostly days the favourite duly won). This is not a winner-market quirk; it repeats at every depth. Each cell below reads probability we stated β rate it actually happened. If we were calibrated, the pairs would match:
| Head | Stated ~0.30β0.50 | Stated 0.50β0.75 | Stated 0.75+ |
|---|---|---|---|
| Winner | 0.285 β 0.391 (0.20β0.35 band) | 0.381 β 0.400 (0.35+ band) | β |
| Podium | 0.373 β 0.545 | 0.591 β 0.729 | β |
| Top 5 | 0.377 β 0.582 | 0.635 β 0.821 | β |
| Top 15 | 0.384 β 0.442 | 0.595 β 0.745 | 0.778 β 0.895 |
Calibration of the published boards across all visible reads. When we said 63.5% top-5, it happened 82.1% of the time. Every head is under-confident above ~0.30 and slightly over-confident in the long tail β one consistent, monotone miscalibration on a model whose ordering is fine.
This looks like the cheapest fix imaginable β one curve, no new features, no retraining. Whether it actually is turns out to be the most interesting finding of the whole review, and it depends entirely on failure mode two.
Then there are the days where no amount of calibration would help, because the model gave the winner 0.4β2.5% and the book, while also surprised, was far less so. These are not random misses. They are one recognisable kind of bike race: a shattered, non-mountain Grand Tour stage won by a punchy hard man from a break, a late move, or a reduced strongman sprint β Pedersen from a front split of ten (st 4), van der Poel over the final climbs (st 9), Schmid from the escape on a rolling day (st 13), Philipsen from a 26-rider mixed break (st 17), van der Poel again on the Montmartre circuits (st 21).
Two disciplines in how we drew this class, because carving out your bad days after the fact is the oldest self-deception in forecasting. First, membership is defined by day shape and winner type only β never by how we scored. The proof it isn't cherry-picked: stage 17 is in the class and was one of our better days (+0.41). Second, stage 2 is deliberately not in it: the day shattered, but the thirteen who contested the finish were GC men β del Toro from PogaΔar, Vingegaard and Evenepoel. A selective GC day is a different animal.
The critical evidence that this is structural: after the Tour we re-ran each of these days as a hindsight run, handing the model the stage's actual shape for free. It did no better. Course knowledge is not the constraint β the model has no machinery for "who gets in the break and who wins from it", and knowing the day's character doesn't substitute for it.
| Panel | n | Win LL model/book | Podium LL | Top 3 | Top 5 | Top 10 | Top 15 |
|---|---|---|---|---|---|---|---|
| All stages | 20 | 2.407 / 1.944 (β0.463) | β.0061 | β10 | β5 | β0 | +0 |
| Minus hard-man core {4, 9, 13, 17} | 16 | 2.090 / 1.720 (β0.370) | β.0046 | β6 | β1 | +3 | +3 |
| Minus hard-man wide {4, 9, 13, 17, 21} | 15 | 2.032 / 1.749 (β0.283) | β.0043 | β5 | β1 | +3 | +4 |
| Minus all break-won days {13, 17, 18, 20} | 16 | 2.029 / 1.793 (β0.237) | β.0026 | β6 | +2 | +2 | +2 |
| Peloton / GC-settled days only | 13 | 1.722 / 1.754 (+0.033) | β.0007 | β0 | +5 | +3 | +4 |
Every panel rerun with stage classes removed. Top-k columns are model-minus-book percentage points of actual top-k captured; podium column is the log-loss delta (+ = model better). LL = log loss, lower is better.
Three findings fall out of that table.
So the honest summary of the stage race is one sentence: we price a bike race at or above market level whenever it is decided by the group we can see β and we lose the Tour on the days a breakaway decides it.
Everything in the previous section shares a defect: you only know a day was "break-won" after the break has won it. The hard-man class is different, and this is the point of naming it. We flagged those days in the published previews, before they were raced. The morning reads for stages 4, 9, 17 and 21 carried an explicit hard-man / late-move branch, sitting in the config in machine-readable form. Nothing equivalent existed for the escape days β no read of stage 18 or 20 said "a break will take this" β but the hard-man days were named, in public, ahead of time.
That converts a post-hoc excuse into a testable policy: what if we had simply sat out the days we ourselves flagged?
| Ex-ante policy | n | Win LL model/book | Top 3 | Top 5 | Top 10 | Top 15 |
|---|---|---|---|---|---|---|
| Bet every stage | 20 | 2.407 / 1.944 (β0.463) | β10 | β5 | β0 | +0 |
| Skip the flagged days {4, 9, 17, 21} | 16 | 2.256 / 1.915 (β0.341) | β6 | β4 | +1 | +2 |
| Skip days the punchy-filter ran {9, 11, 12, 17, 21} | 15 | 2.294 / 1.837 (β0.456) | β11 | β3 | β2 | +1 |
| [Post-hoc core carve, for contrast] | 16 | 2.090 / 1.720 (β0.370) | β6 | β1 | +3 | +3 |
Scored as an abstention rule, the flag separates cleanly. Days it fired ran β0.951 nats against the book (three of four below the market); days it stayed silent ran β0.341. Sitting out those four mornings would have avoided 3.80 of the Tour's roughly 9.3 total deficit nats β more than the post-hoc carve recovers (3.33) β while touching only 20% of the stages. That is a real, tradeable separation, and it was available at preview time β a "stay away today" rule was sitting in the morning configs all Tour, waiting to be used.
The honest limits, stated plainly:
punchy_filter marker is strictly worse: it also fires on flat sprint days (11, 12) where we were fine.The shyness of section three has a textbook cheap fix: a one-parameter "temperature" transform that sharpens the top of the board and thins the tail, leaving the order untouched. No new features, no retraining β a fair floor for what calibration work is worth. We fitted it leave-one-stage-out: the parameter is learned on nineteen stages and applied to the held-out one, so no stage helps correct itself and the numbers below are honest out-of-sample results, not curve-fitting.
| Panel | Win LL: raw β calibrated | Gap to book: raw β calibrated | Fitted T |
|---|---|---|---|
| All 20 stages | 2.407 β 2.394 | β0.463 β β0.450 | 1.25 |
| Hard-man class removed | 2.032 β 1.954 | β0.283 β β0.205 | 1.40 |
| Hard-man stages alone | 3.532 β 3.643 (worse) | β1.002 β β1.113 | 1.00 |
T > 1 sharpens the board; T = 1 leaves it unchanged. On the same carve the podium-market gap closes from β.0033 to β.0005 β statistically level with the book. A monotone transform cannot reorder anyone, so every top-k number on this page is unchanged by this.
Read the fitted temperatures down the last column, because they contain the finding of the review. Ordinary days want T = 1.40 β sharpen hard. Hard days want T = 1.00 β don't touch it β and sharpening them makes things worse. The model's shyness is not uniformly a bug: it is correct self-doubt on the days it cannot read, and a costly false modesty everywhere else. Which is exactly why the naive version of the fix β one global correction β is worth almost nothing: β0.463 β β0.450 across all stages, a rounding error.
Could the ex-ante flag choose the temperature day by day? Not yet. Running that switch gets the days-we'd-bet number from β0.341 to β0.346 β no gain. The per-stage breakdown says precisely why: sharpening pays about +0.5 nats each on the ordinary days (3, 6, 10, 14, 19) and hands it all back on the surprise days the flag misses β stage 13 alone costs β1.20 when wrongly sharpened, stages 20, 18 and 11 another β0.6 to β0.5 each.
So the two failure modes are not separate work items. Knowing what kind of day it is unlocks the calibration; the calibration is nearly worthless without it. That inverts our first instinct β "calibration is the cheap fix, day-typing is the hard research" β into the true ordering: day-class detection is the gate, and it pays twice, once by abstaining where we're weak and once by licensing confidence everywhere else.
With both corrections applied on its own terms β hard days out, shyness fixed β the model's Tour reads: within 0.2 nats of the book on the winner, level on the podium, ahead by 3β4 points at top-10 and top-15. That is the fair statement of what we actually built: a genuinely competitive forecaster on every day decided by the group it can see.
The model had PogaΔar at the top of its GC board on all 18 days, and the final top three β PogaΔar, Evenepoel, del Toro β inside its top four all race. As a piece of race-reading, that is correct. As a forecast, it had one glaring defect, and the two charts below show it better than any table:
Rank correlation vs the final classification, after each stage
Devigged book price vs model board, after each stage
Left: the book's board converges on the truth as the race resolves (0.80 β 0.98); the model's is frozen at 0.77 β it ends the Tour knowing no more about the finishing order than it knew after stage 2. Right: the same freeze in probability. The model's PogaΔar price crept 0.36 β 0.47 while the book went 0.65 β 0.93; three weeks of yellow jersey moved our number eleven points. Average GC winner log loss over the 18 boards: model 0.969, book 0.205.
This is the clearest standalone defect the Tour surfaced. It isn't the top-end shyness of the stage model, and it isn't a hard-day blind spot β it's that whatever within-race state reaches the GC pricing blend, it barely moves the board. The book gets to update on every mountain; our blend effectively priced the route once and defended the opinion for three weeks.
The consolation is that the fix may already be in the building. Alongside the main blend we ran a separate state-aware GC outlook head β the component the site showed as a "position safety" caption. Scored properly against the final classification, it was the best GC podium estimator anyone brought to this race, market included:
| GC podium forecast (mean per-rider log loss, 18 boards) | Score |
|---|---|
| GC outlook head (renormalized) | 0.0202 |
| The book | 0.0299 |
| Model main blend | 0.0343 |
On the final board the outlook head had PogaΔar's win at 0.919 and the eventual top three at 0.97 / 0.93 / 0.93 to hold the top five β numbers that actually reflect a settled race. The instrument that responds to the road exists; it was captioning the page instead of pricing it.
Twenty-one settled stages with full prices, results, per-day gap curves and hindsight controls are now a labelled corpus. In priority order, with the priority argued by the numbers above:
The counterfactual showed calibration is worth about 0.08 nats/stage on correctly-identified ordinary days, harmful on hard days, and next to nothing globally. The binding constraint is recall on the hard/surprise class: stage 13 alone costs β1.20 when wrongly sharpened, and the climbers' escapes were never flagged at all. Detecting the day type pays twice β abstain where we're weak, commit where we're strong.
The miscalibration is one monotone curve, identical in shape across winner, podium, top-5 and top-15. Ordinary days want roughly T = 1.40. A corrector already exists in production but is win-only and was fitted on the general race corpus, not Grand Tour boards. Notably, ranking work is not indicated β at ten and fifteen deep we already match or beat the market.
Flat 0.77 across 18 boards while the book climbed to 0.98 is not a tuning issue; something structural about how within-race state reaches the GC blend needs to be found and fixed. First candidate: promote or blend in the GC outlook head, which beat both our main blend and the book on podium (0.0202 vs 0.0343 / 0.0299) and was treated as a caption.
The one carve available before the stage separated β0.951 from β0.341 nats and would have avoided 3.80 of roughly 9.3 deficit nats at 20% of stages. It is n = 4 and needs more Grand Tours before it is an edge rather than a hypothesis β but it costs nothing to emit, because the trigger already exists in the morning configs. The cheap first version is a published confidence tier on the day, not a model change.
The right boundary is "did an escape decide it", not the winner's rider type β the climbers' breaks cost as much as the hard-man days. Hindsight-is-no-better is the strongest evidence that this needs a structural component (break-membership modelling, per-rider group ladders), not better course descriptions. The Tour supplied the labelled instances to build it on.
We published 3β4 scenarios per day and never committed to a weighting. The gap between equal-weight (2.407) and best-read-in-hindsight (2.129) is the value a learned or judgment-weighted prior over reads could recover. The day-shape labels needed to fit one are now in the archive.
One meta-lesson to end on. The single best thing this exercise did was force every opinion into a number, in public, before the road answered β including the opinions that were wrong. The model's Tour was not the story we expected to tell (we assumed the mountains would be the hard part; they were nearly solved, and the transition days were the hole). We only know that because the whole record β every read, every board, every price β was written down first.