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:

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:

  1. Take devigged_home, devigged_draw, devigged_away, devigged_over, devigged_under, and rho_identifier (the frozen correlation parameter for the fixture's league. Its numeric value is published in jointgrid/maineight_rho_frozen.json in the project's source, keyed by this identifier and by league).
  2. 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.
  3. 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.
  4. Read the probability of the row's own cell (its result and ou_side) off that adjusted grid, and take its reciprocal. That reciprocal is fair_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

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.