Data archive: how to read it
Every file linked from the archive is the exact CSV and
summary a main-seven serving run wrote, unmodified. This page explains the
columns and the arithmetic behind fair_price, so the numbers can be
checked without asking us anything.
What "fair price" and "builder price" mean
For a fixture, the pipeline reads the closing Pinnacle 1-X-2 odds and a
2.5-goals over/under pair (Pinnacle where posted, otherwise the next book
on a fixed priority list, see tier below), removes the bookmaker margin
from each (proportional devigging: invert each price, divide by the sum of
the inverses), and fits a Dixon-Coles scoreline grid to those two sets of
probabilities.
For each of six cells ({home, draw, away} times {over 2.5, under 2.5}), the record carries two prices for the same cell:
builder_price: 1 divided by the product of the devigged result probability and the devigged over/under probability, as if the two legs were independent. This is what multiplying two markets together, the way a bet-builder tool does, would price the cell at.fair_price: 1 divided by the cell's probability read off the fitted grid, which accounts for the fact that a result and a goals total are not independent. Some scorelines are consistent with both, some aren't.
Both are computed from the same devigged marginals. The only difference is whether the calculation treats the two legs as independent or reads them off one internally consistent grid. Neither number is a claim about either price relative to the market on level, since both are derived entirely from market prices themselves.
Reproducing fair_price from the recorded columns
Given one row of served.csv:
- Take
devigged_home,devigged_draw,devigged_away,devigged_over,devigged_under, andrho_identifier(the frozen correlation parameter for the fixture's league. Its numeric value is published injointgrid/maineight_rho_frozen.jsonin the project's source, keyed by this identifier and by league). - Fit
(lambda_home, lambda_away)by minimising the KL divergence between a Dixon-Coles Poisson grid at the frozen correlation parameter and the four devigged targets[home, draw, away, over]. Two free parameters, four targets, so exact recovery isn't expected or required at this stage. - Rake the grid's six-cell {result} x {over/under 2.5} table to the exact devigged marginals with iterative proportional fitting (alternating row and column rescaling until both sets of marginals match to a tight tolerance), then apply the raked region totals back onto the scoreline grid as multiplicative weights.
- Read the probability of the row's own cell (its
resultandou_side) off that adjusted grid, and take its reciprocal. That reciprocal isfair_price.
stage1_max_abs_error is the largest absolute gap between step 2's
grid-implied marginals and the devigged targets, before the raking in step
3. A diagnostic of how well a single scoreline grid can express the
market's prices on that fixture, not a quality signal about the final
fair_price figure, which matches the market marginals exactly by
construction once raking is applied.
Columns not covered above
snapshot_used/snapshot_capture_ts: which capture (CLOSEorT6) the row's prices came from, and when it was captured.bookmaker_1x2: always Pinnacle, because the 1-X-2 leg is Pinnacle-only.totals_bookmaker/tier: which book supplied the 2.5 over/under leg. Tier A means Pinnacle supplied both legs; Tier B means a fallback book supplied the totals leg (labelled wherever this figure appears).overround_1x2/overround_totals: the raw bookmaker margin on each leg before devigging, for reference.generated_before_t4h/hours_to_kickoff_at_generation: whether the row was generated at least four hours before kickoff, and how many hours that was.
All figures are recorded at 10 decimal places, which is enough precision
for step 2's optimiser to reproduce fair_price to within its own
numerical tolerance (roughly 2e-7), not a display choice.