Model documentation & methodology

How the model works

We compute probabilities using a Dixon-Coles bivariate Poisson model. We publish every expected-goals figure, source rate, and data quality check behind every evaluation.

01

Recency-weighted goal rates

Every match evaluation begins with team-level scoring and conceding rates (λ for home expected goals, μ for away expected goals).

Early in a season, a team may have played only one or two home matches. Rather than treating that small sample at face value, the solver applies Stein-type shrinkage: each team's attack and defence coefficients are pulled toward the league mean, with the pull scaled by matches played. A team that has played 1 home match is held 90% toward the mean; a team that has played 10 or more is used at full face value. This is a regularisation applied to the coefficients, not a blend of two scores.

When a team has fewer than 3 home or away matches in the current season, the pipeline falls back to prior-season statistics from the same league. That is a hard substitution, not a weighted blend — we use last season's numbers in full when this season's data is insufficient.

02

The Dixon-Coles Scoreline Engine

Standard independent Poisson models underestimate low-scoring outcomes (such as 0–0 and 1–0 draws/wins). The Dixon-Coles model introduces a low-score correlation adjustment parameters (τ) to solve this structural bias.

A single Dixon-Coles solver pass generates a 6×6 scoreline probability matrix for the fixture, covering scores 0–0 through 5–5. Index 5 is a tail bucket representing "5 or more" goals for that team. From this single matrix, all supported market probabilities are derived:

  • 1X2: Sum of Home Win, Draw, and Away Win cells.
  • Over/Under (1.5, 2.5, 3.5): Sum of scorelines where combined goals exceed or fall below the line.
  • Both Teams To Score (BTTS): Sum of scorelines where both home and away goals > 0.
  • Double Chance: Combined 1X, X2, 12 probabilities.
03

Data Quality Audit & Component Rules

The production pipeline gates every fixture on two hard thresholds before computing a probability:

1. Season Statistics
Home team ≥ 3 home matches played. Away team ≥ 3 away matches played. Below this threshold the fixture is suppressed — the solver cannot produce a meaningful lambda or mu from one or two matches.
2. League Average Denominators
A valid league average (home goal rate, away goal rate) must exist in the database. Without a denominator, the Dixon-Coles coefficients cannot be normalised and the fixture is suppressed.

Recent form and head-to-head records are fetched and displayed in the working section of each fixture card. They are not used as gates in the current probability pipeline — they feed into a separate v1 rules-based component score that was an earlier version of the model. That earlier score is displayed alongside the Dixon-Coles output but does not determine whether a signal is surfaced.

If a fixture fails either hard threshold, it is flagged as Not enough data and no probability or signal is produced.

04

Empirical Calibration & Per-Market Confidence Floors

We do not group all betting markets into single shared buckets. Because Over 1.5 Goals has a high natural occurrence (~77%) while Draw has a low natural occurrence (~25%), pooling markets distorts decile bands.

Below is our out-of-sample calibration audit evaluated strictly PER MARKET across 727 completed top-flight fixtures (2026 season). Any decile band with fewer than 50 fixtures is flagged as Insufficient sample (<50) rather than displaying a percentage a reader should not trust:

MarketPredicted BandSample (N)Model MeanObserved Hit RateDeltaHeadline Status
Over/Under 2.550–60%49853.7%50.8% (253/498)-2.9%Suppressed (<60%)
Over/Under 2.560–70%19263.5%66.7% (128/192)+3.2%Eligible (≥60% Floor)
Over/Under 2.570–80%3273.1%75.0% (24/32)+1.9%Small sample (N=32)
Both Teams To Score50–60%48054.4%52.9% (254/480)-1.4%Suppressed (<60%)
Both Teams To Score60–70%22463.7%62.9% (141/224)-0.8%Eligible (≥60% Floor)
Over/Under 1.560–70%4667.3%84.8% (39/46)+17.5%Suppressed (High Baseline Noise · N=46)
Over/Under 1.570–80%40875.3%77.2% (315/408)+1.9%Eligible (≥70% Floor)
1X2 (Home Win)50–60%9153.5%56.0% (51/91)+2.5%Suppressed (<60%)
1X2 (Home Win)60–70%2464.2%45.8% (11/24)-18.3%Display-Only Excluded (N=24)
Over/Under 3.560–70%23164.9%62.3% (144/231)-2.5%Display-Only Excluded
Over/Under 3.570–80%33273.3%66.9% (222/332)-6.5%Display-Only Excluded
Team Totals 1.560–70%54665.0%59.7% (326/546)-5.3%Display-Only Excluded
Team Totals 1.570–80%13473.7%67.9% (91/134)-5.8%Display-Only Excluded

Our Evidence-Based Selection Rules

  • 1. Small Sample Protection (N < 50 Threshold): Any decile band with fewer than 50 recorded match outcomes is marked as Insufficient sample (<50). We never present un-statistically significant small-sample frequencies (like 4/5 or 4/4) as evidence.
  • 2. 1X2 Moved to Display-Only: Top-flight match-winner predictions above 60% are rare (N=24 in 60-70%) and suffer from high draw/upset variance (-18.3% delta). 1X2 is strictly display-only on /fixture/[id] until sample volume builds.
  • 3. Over/Under 3.5 & Team Totals Exclusion: Over/Under 3.5 displays a -6.5% overconfidence bias in the 70–80% band, while Team Totals runs -5.3% to -5.8% overconfident and lost to closing lines in backtests. Both are reserved for display-only.
  • 4. Double Chance — Uncalibrated: Double Chance (1X, X2, 12) was never included in the 727-fixture calibration run. There are no decile bands, no delta figures, and no floor to clear. It is marked uncalibrated, not display-only — the distinction matters: display-only means we measured it and it failed; uncalibrated means we have not measured it at all.
  • 5. Core Primary Market Reliability: Core primary markets (Over/Under 2.5 and BTTS) calibrate closely within -0.8% to +3.2% of predicted probabilities in the 60–70% band with large sample volume (N=192 and N=224).
Where these numbers come from

These figures come from an earlier test against 727 finished matches. The records of that test were not kept, so we cannot reproduce it. We are re-running it in a way anyone can check, and will publish the result whether it is better or worse.

We are building a forward-going prediction store. Every prediction made before kickoff will be recorded, settled against the actual result, and published here when N ≥ 50 per decile band. When that table is large enough to stand on its own, we will replace this one.

Every row in the forward-going table is recorded as of the most recent automated sweep before kickoff. Fixtures already in play when we grade are never recorded. That guard is enforced in code, not by convention.

Corner and Card Markets — Pending Calibration

Our corner and card models are not yet calibrated. We are collecting predictions made before kickoff and will publish the results when we have enough data — including if they fail.

The threshold is N ≥ 50 observed outcomes per probability decile band. We are stating this now so it cannot be moved later. A market that fails to clear its floor at that sample size will be reported as failing, and the numbers will be published either way.

The card model uses team averages only. Referee strictness is not modelled — no public API provides per-referee booking rates at the data tier we use. We say this here and on every card that shows a card estimate.

Public Disclosure — Re-Run Error & Invalidation2026-09-23 UTC

"Our first re-run contained an error. Team statistics were fetched using the match date itself, and the provider includes that day's completed matches — so each prediction was made with partial knowledge of the result it was predicting. Every figure from that run has been discarded. We are re-running it correctly. This is why we published our method before running it."

Pre-Registered Audit Protocol (v2)Revised: 2026-09-22 13:35:04 UTC — Revised before any backtest API call was made

Pre-Registered Backtest Audit Terms (2025/26 Season) — Version 3 (Corners Addendum)

Before making a single API call for the 2025/26 corners backtest, the following binding terms are pre-registered:

  • •Pre-Registered Corner Markets: Over 8.5, Over 9.5, Over 10.5, Under 8.5, Under 9.5, and Under 10.5 Corners.
  • •Inherited 60% Publication Floor: The 60% floor is carried over from our goals markets, where it was measured. It has not been measured for corners. If the calibration shows a different floor is appropriate, we will pre-register that floor and test it in a separate run rather than changing it after seeing these results.
  • •Negative-Binomial Model, Poisson Fallback & Out-of-Sample Parameter Freeze: Total match corners are modeled via a Negative-Binomial distribution. The dispersion parameter r is fitted on 2024/25 completed fixtures via the method of moments: r = mean² / (variance − mean). Where the 2024/25 sample shows variance at or below the mean, we use Poisson for that division rather than a defaulted dispersion parameter. A Negative-Binomial at very large r is mathematically a Poisson; this rule formalises that equivalence rather than inserting an arbitrary constant. The model (fitted r or Poisson) is frozen per division before any 2025/26 evaluation begins and NEVER refitted on the test season. National League is excluded from corners evaluation: API-Football returns no statistics rows for that competition.
  • •Leak Guard & Evaluation Mechanics: Fitting dispersion r on 2024/25 uses completed fixtures. However, the 2025/26 evaluation must predict each fixture's corner total strictly from team averages BEFORE it, never from its own statistics row. /fixtures/statistics for a completed fixture cannot itself leak, since it returns that fixture's own statistics by definition. Corner predictions enforce playedTotal ≤ expPlayedAtKickoff as of matchKickoffUtcDate - 1 day.
  • •Numeric Pass Criterion: Bonferroni-corrected binomial confidence intervals centered on the model's mean prediction for every probability band at or above 60% with N ≥ 50.
  • •Protocol Freeze & Bug Rules: From the first API call, the protocol cannot change. A bug found mid-run means stop, disclose publicly, and re-run ALL divisions from scratch. No eligibility decisions on partial results.
  • •Display-Only Policy: Corners stay display-only regardless of outcome — users see the probability and calibration record. We publish no corner picks until results are fully reviewed and discussed.
Public Protocol Disclosure 1 (2026-09-26): "Our first corners run evaluated only 100 fixtures per division instead of the full season, so no market had enough data to be tested. Every figure from that run has been discarded and it is being re-run in full."
Public Protocol Disclosure 2 (2026-09-26): "The full-season re-run used an unregistered default of r = 20 for Premier League, Austrian Bundesliga, and Chance Liga (where the 2024/25 sample showed variance ≤ mean, making the method-of-moments estimate undefined), and r = 15 for National League (where API-Football returns no corner statistics at all). These defaults were not pre-registered. We are now pre-registering the correct rule — Poisson for any division where variance ≤ mean — and re-running those three divisions under it. National League is removed from corner evaluation entirely: no data means no model. All results from the affected divisions in the first re-run have been discarded."
Public Protocol Disclosure 3 (2026-10-09 UTC)V4 Team Corners Protocol Restart & Covered-Division Scope
Version 4 Protocol Restart & Local Box-Score Corner Backfill

Team corner prediction rows generated before October 9, 2026, were calculated with weight w = 0% (defaulting 100% to division half-means) due to missing team summary statistics. On October 9, 2026, we completed a 100% offline backfill of 6,497 completed fixtures from local box scores, enabling point-in-time team averages with shrinkage constant k = 5.8 and prior-season decay α = 0.3 (N_eff = N_2026 + 0.3 · N_2025).

COVERED-DIVISION SAMPLE CORRECTION: All 48 previously settled Total Corner prediction rows in the database belong to EFL League Two (a lower division excluded from publication). Covered top-flight corner markets stand at N = 0 across all bands. The pre-registered calibration sample for covered top-flight corner markets (Total Corners and Team Corners) officially begins on October 9, 2026 when the daily sweep first writes pre-kickoff corner prediction rows across all 16 covered top-flight divisions.

Methodology & Validation: Prior-season weighting α = 0.3 was tuned on 481 mid-season Scandinavian/summer fixtures (mean N_2026 = 17.9) and validated on 482 held-out early-season European fixtures (mean N_2026 = 5.0 to 8.5). On the held-out test set (G = 482 fixture clusters, N = 964 team predictions), α = 0.3 yielded a Brier error reduction of +0.00323 on Over/Under 3.5 (CR1 Sandwich SE = 0.00137, t = 2.364, p = 0.021). The Over/Under 4.5 line corroborates this (+0.00372 reduction, CR1 SE = 0.00164, t = 2.264, p = 0.027). Within-fixture leg correlation is indistinguishable from zero (r = -0.054, 95% CI [-0.143, +0.035]), so cluster-robust SEs differ from naive SEs by under 3%.

Public Protocol Disclosure 4 (2026-10-09 UTC)Sample Correction & Top-Flight Sweep Launch
Top-Flight Sample Restart & Invalidation of Previous Projection Dates

CRITICAL CORRECTION: Crossing dates previously published in Disclosure 4 were computed from 1,392 lower-division and cup prediction rows that fall outside our pre-registered scope. Covered top-flight divisions stood at N = 0 across all markets because past prediction rows were written only when a user or admin browsed a fixture card pre-kickoff.

The official top-flight calibration sample officially begins on October 9, 2026 with the deployment of our daily pre-kickoff automated sweep (app/api/cron/record-predictions/route.ts), which records every NS fixture across all 16 covered top-flight divisions. The 1,392 lower-division rows remain in the database as an out-of-sample record for un-published divisions, but are 100% excluded from top-flight calibration counters.

Withholding Policy: No new crossing dates will be published until the automated sweep has run for at least 7 days to establish an observed top-flight fill rate. Projecting crossing dates from the fixture schedule alone without observed fill data is how void dates were published previously. New crossing dates will be calculated and published strictly from observed 7-day sweep fill rates.

  • •Point-in-Time Prediction Rule (Option A): Each recorded prediction is stored as of the most recent automated sweep before kickoff, ensuring the calibrated sample evaluates the point-in-time probabilities displayed to users on matchday.
  • •Double Chance 1X (Home or Draw) Floor (≥ 70%): Pre-registered publication floor at ≥ 70%. Crossing dates withheld pending 7 days of live sweep data.
  • •Double Chance X2 (Draw or Away) Floor (≥ 60%): Pre-registered publication floor at ≥ 60%. Crossing dates withheld pending 7 days of live sweep data.
  • •Omission of Double Chance 12 & Over/Under 0.5: Dropped from pre-registration tracking. DC 12 probability standard deviation is σ = 6.07pp (64.3% in one band), and O/U 0.5 is a single-band market (71.2% in 0.90-1.00). Neither outcome can populate the multi-band distribution required by V1.3 calibration testing.
Empirical finding: per-team corner dispersion holds out of sample

Per-team corner counts exhibit clear overdispersion (pooled Var/Mean ratio = 1.661 in the 2024/25 fitting sample). When evaluated on the un-fitted 2025/26 test season across 5,463 completed fixtures in 18 covered divisions, the property holds strictly out of sample at a pooled Var/Mean ratio of 1.633, with 14 of 18 divisions remaining fully stable.

Division24/25 Frozen r25/26 N25/26 Var/Mean25/26 Fit rVerdict
Premier League (England)5.83801.5329.4Var Decreased
Championship (England)8.15561.6048.5Stable
EFL League Two (England)9.15561.45910.7Stable
Ligue 1 (France)7.73071.7986.0Stable
Bundesliga (Germany)7.93071.6637.3Stable
Eredivisie (Netherlands)6.63081.6937.5Stable
Primeira Liga (Portugal)6.23081.8265.7Stable
Eliteserien (Norway)5.72402.1104.8Stable
Superliga (Denmark)7.41921.45210.7Var Decreased
Serie A (Italy)6.93461.7006.3Stable
La Liga (Spain)7.13801.6777.1Stable (Exact)
Jupiler Pro League (Belgium)11.33191.45611.1Stable
Premiership (Scotland)6.62301.48110.4Var Decreased
Süper Lig (Turkey)8.03061.6167.7Stable
Super League (Switzerland)8.22291.29617.2Var Decreased
Bundesliga (Austria)11.81951.5798.1Stable
Saudi Pro League8.23051.8615.9Stable
Chance Liga (Czech Republic)7.92751.5968.3Stable
Pooled 18-Division Average7.95,4631.6338.31.661 Fitted Ref

Note: All 4 non-stable divisions in 2025/26 show variance DECREASING (fitted r inflated). This means our frozen pre-registered r parameters understate dispersion there, making model probabilities slightly too wide — the conservative direction.

Public Protocol Disclosure 5 (2026-10-15 UTC)Bug Fix + Model Change v5 — Promoted-Team Prior Correction (All 15 Leagues)
Two Compounding Defects Corrected: Missing Lower-Division Map Entries + Fallback Short-Circuit Bug

Defect 1 — Missing lower-division map entries (10 leagues): When a promoted team has no prior top-flight record, our Stein-blend model uses a prior anchor to stabilise early-season rate estimates. The lower-division fallback — using the team's own second-tier record as the prior anchor — was only configured for 5 of our 15 calibrated top-flight leagues (Premier League, La Liga, Bundesliga, Serie A, Ligue 1). The remaining 10 calibrated leagues had no entry, so promoted teams in those leagues silently received the top-flight league average as their prior anchor — a rate they had never played at.

Defect 2 — Fallback short-circuit bug (all 15 leagues): Even for the 5 leagues that did have a lower-division map entry, the fallback did not always fire. The API-Football /teams/statistics endpoint returns a non-null object with fixtures.played = 0 when a team has no games in a given league-season. The prior selection code used the JavaScript ?? (nullish coalescing) operator: priorStats = priorSameLeague ?? lowerDivStats. Because ?? only falls through on null or undefined — not on a non-null zero-game object — the lower-division fallback was silently bypassed for any promoted team whose zero-game same-division response was cached. The team then received the league average as its prior, regardless of whether a second-tier entry existed. This bug affected all 15 mapped leagues.

What was fixed (pipeline v5, commit c63d2ff, 2026-10-15):

  • 1.LOWER_DIVISION_MAP extended from 5 to 15 leagues: added Eredivisie → Eerste Divisie; Süper Lig → TFF 1. Lig; Eliteserien → 1. divisjon; Primeira Liga → Liga Portugal 2; Jupiler Pro League → Challenger Pro League; Czech Liga → FNL; Superliga (DEN) → 1. division; Swiss Super League → Swiss Challenge League; Austrian Bundesliga → 2. Liga; Premiership (SCO) → Championship (SCO).
  • 2.fetchTeamStatsWithFallback now treats a zero-game same-division prior as absent: priorSameLeagueHasGames = (played.home + played.away) > 0. The lower-division entry is used whenever the same-division prior has zero games, regardless of whether the API returned a non-null object.

Measured impact on 2 pending fixtures (verified via live API):

  • •Çorum FK v Rizespor (Süper Lig, fid 1584432): Çorum FK had 3 home games (w = 0.30; prior carries 70%). Old: 34.3% / 31.0% / 34.7%. New (TFF 1. Lig prior, H21/A21, 1.714 GF/g): 47.3% / 30.0% / 22.6%. Delta: home +13.0pp, away −12.1pp. Voided and rewritten under v5.
  • •AC Horsens v Randers FC (Superliga DEN, fid 1549035): AC Horsens had 5 home games (w = 0.50; prior carries 50%). Old: 33.5% / 29.1% / 37.4%. New (1. division prior, H16/A16, 1.625 GF/g — near league avg): 41.1% / 29.8% / 29.1%. Delta: home +7.6pp, away −8.3pp. Voided and rewritten under v5.

Backtest divergence — full scope (all 15 leagues, including original 5): The published eligible-markets table was validated against a backtest that ran entirely under the buggy pipeline. Any promoted team across all 15 mapped leagues that had a cached zero-game same-division response received a league-average prior instead of its second-tier prior. The complete scope — fixtures, prediction rows, published rows, and per-band breakdown — has been audited and is reported below. The eligibility table will be re-run under v5 if the affected share of any passing band is material.

Pipeline version bumped v4 → v5. Git commits: c63d2ff (fix), 08a25d0 (disclosure correction). All board caches from v4 are stale and re-grade on next read under v5.

Measured backtest divergence (all 15 leagues, audited 2026-10-15):

  • •32 of 293 sample fixtures affected (10.9%) — 14 in the original 5 leagues, 18 in the new 10
  • •1,020 of 7,477 prediction rows affected (13.6%)
  • •50 of 165 published eligible rows affected (30.3%)
  • •Band concentration: 65–70% band 41% affected; 75–80% band 41% affected; 90%+ band 48% affected

Eligibility table status: With 30.3% of published rows and 41–48% of the high-confidence bands coming from fixtures priced under the defective model, the published eligible-markets table cannot be relied on as-is. The backtest is being re-run under pipeline v5. No new eligible markets will be published from this table until the rerun is complete and its results committed.

Public Protocol Disclosure 6 (2026-10-19 UTC)Incomplete Backtest Coverage — Three Divisions
Three divisions were tested on only part of their season; two of them were never properly tested

A defect in our backtest script caught division-level errors, carried on, and still wrote a results file that looked complete. In the 25–26 September run it left three divisions truncated:

  • •Jupiler Pro League: 8 of 321 fixtures evaluated (2.5%)
  • •Danish Superliga: 50 of 193 fixtures evaluated (25.9%)
  • •Saudi Pro League: 165 of 306 fixtures evaluated (53.9%)
  • •The other 16 divisions were 99.7–100% covered.

What those files show: for Jupiler Pro League and the Danish Superliga, no market had a probability band with N ≥ 50, so under our own rule every market in those two divisions is "insufficient N". For the Saudi Pro League, one market (Over 1.5 at ≥70%) cleared a single band on 165 fixtures; the Saudi Pro League is display-only and publishes nothing.

What this meant for published picks: Jupiler Pro League and the Danish Superliga were flagged as calibrated in our code from 21 September, before any result of this backtest existed. The market-eligibility rules applied to them came from the pooled 727-fixture table, not from a test of those divisions. Published prediction rows exist for both (Jupiler Pro League: 10 rows on 9 October; Danish Superliga: 7 rows between 9 and 18 October), and an AI Approved Pick appeared on a Danish Superliga fixture on 18 October. On 19 October both divisions were marked uncalibrated (display-only), that pick was withdrawn, and one settled accumulator that included a Jupiler Pro League leg was removed from the track record with a published correction. Treat any earlier pick in these two divisions as untested.

Also found: the point-in-time leak check in the backtest did not run for uncalibrated divisions (Saudi Pro League, Championship, League Two, National League). It does now.

Fix: any error in any division now aborts the whole day and writes no results file, and every division is checked against a committed expected fixture count before anything is written. The full rerun is being run under these rules. No eligibility decision will be taken from partial results.

Public Protocol Disclosure 7 (2026-10-19 UTC)Publication Suspended in Every Division
We are not publishing any pick in any division until our backtest has been rerun and has given each division and market its own verdict

What changed today. From 19 October 2026, no AI Approved Pick, platform slip or track-record entry can be created in any division. A single switch in our code controls this, and it is switched off for every division. Match probabilities are still shown, marked display-only. Six pick entries on four live fixtures were withdrawn on 19 October. Picks on matches already played stay on past boards as the record of what we showed.

Why. In auditing our own record we found three problems. Together they mean we cannot show that any division's picks were properly validated.

  1. 1.The table that decided which markets we could publish cannot be traced. Our code cited a pooled calibration table of 727 fixtures as the basis for which markets were eligible in every division. We cannot find the script or the stored output that produced it, and we cannot establish which divisions or fixtures it contained. A rule we cannot reproduce is not one we can defend.
  2. 2.Every top-flight division was marked calibrated at once, on no division-level evidence. On 21 September a single change set the calibration flag to true for all 15 top-flight divisions, before any result from our reproducible backtest existed. Two of them, the Jupiler Pro League and the Danish Superliga, never cleared a single probability band of 50 fixtures; they were switched off earlier today (Disclosure 6).
  3. 3.Where divisions can be tested one at a time, the pooled table is not supported. Our pre-registered protocol decides eligibility per division and per market, never pooled. We first ran that test on our 25–26 September backtest data, which was provisional: the run was incomplete in three divisions and used the model before the v5 correction. The September figures in the next three bullets are superseded by the complete rerun reported in the update below. They showed:
  • •Only 36 of 128 division-and-market pairs had enough data to be tested at all; 92 did not. Among the 13 complete top-flight divisions, 24 of 104 pairs were testable. (The protocol requires at least two probability bands of 50 or more fixtures.)
  • •Over 1.5 goals failed in five divisions we were publishing: Serie A, Ligue 1, the Bundesliga, the Primeira Liga and the Austrian Bundesliga. In Serie A, the band our model rated 80–90% hit 70.6% over 68 fixtures, against 83.6% predicted. The pooled table had listed Over 1.5 as eligible.
  • •Double Chance 1X failed in the Premier League, Ligue 1, the Bundesliga and the Süper Lig, and Double Chance X2 failed in Serie A. (Double Chance floors were pre-registered after that backtest ran.)

Update, 20 October 2026: the complete rerun supersedes the September figures above. We reran the full pre-registered backtest on every completed 2025/26 fixture: 6,347 fixtures in all 19 divisions, using the current (v5) model. The three divisions that were incomplete in September are now complete.

  • •In the 15 top-flight divisions, 29 of 120 division-and-market pairs were testable and 91 were not. Of the 29, 18 passed and 11 failed. (September: 24 of 104 in the 13 divisions that were complete then.)
  • •Over 1.5 goals failed again in the same five divisions: Serie A, Ligue 1, the Bundesliga, the Primeira Liga and the Austrian Bundesliga.
  • •Corrected Double Chance failures. Double Chance 1X failed in the Premier League, the Bundesliga, the Eredivisie and the Jupiler Pro League. Double Chance X2 failed in Serie A and the Jupiler Pro League. These replace the September Double Chance failures; several of those did not repeat (Ligue 1 and the Süper Lig passed Double Chance 1X in the rerun), so Double Chance verdicts have shifted between runs and should be treated as weak.

These figures are not final verdicts. They are enough to show the pooled table cannot be relied on.

Update, 27 October 2026 (see Disclosure 11). The rerun verdicts for the Championship, League Two, National League and Saudi Pro League were computed with a blend defect, so we re-ran those four divisions. Eliteserien and the Danish Superliga, which the defect did not touch, reproduced exactly. 31 of the 32 verdicts are unchanged. One moved: League Two Double Chance X2, FAIL to PASS. That is not evidence of calibration. Its failing band (80–90%: observed 69.1% against a model mean of 84.9%) now holds 49 fixtures, one short of the 50 needed to test it, so the rule no longer tests it. In the National League the Under 2.5 60–70% band now falls outside its interval (observed 50.6% against 64.4%), and that market remains not testable. The corrected verdicts are in docs/calibration/rerun-per-division-verdicts-v6-2026-10-27; the frozen September figures remain beside them.

A caveat for anyone comparing the two tables. Our rule tests a probability band only if it holds at least 50 fixtures. Any change to the model moves fixtures between bands, so a band that was failing can drop below 50 and stop being tested. Under the protocol that reads as a pass, or as not testable, even though nothing about calibration improved. This is a property of the rule, not a defect in it, but it means a FAIL turning into a PASS between two runs needs the band sizes checked before it is read as progress. The League Two case above is one: that band lost 6 fixtures (55 to 49). The band-by-band sizes for every changed verdict are in the comparison file named above.

What we are doing.

  • •Rerunning the full pre-registered backtest: every completed 2025/26 fixture in every division, with the run aborting on any failure and every division checked against a committed fixture count before anything is written.
  • •Publishing a table for every division and market, with bands, sample sizes and pass, fail or not testable, in full and whatever the result.
  • •Switching a division back on only for markets that pass in that division, one at a time. Most division-and-market pairs cannot be tested on one season of backtest data alone and will stay off until the live record is large enough. That may take months.
  • •Reviewing every settled entry in the track record against these findings. Any removal will be published as a correction.

Amended later on 19 October 2026: point 3 stated the untestable count (92) and the top-flight breakdown (24 of 104). Amended again on 20 October 2026: those September figures, and the September Double Chance failures, are superseded by the complete rerun in the update inside point 3 (29 of 120; corrected Double Chance failures). The September figures are left in place and labelled so the change is visible.

Update, 23 October 2026. The rerun this disclosure waited for was completed on 20 October. Two disclosures have followed. Disclosure 9 (20 October) found the model overconfident at high stated probabilities. Disclosure 10 (23 October) tested our match-result probabilities against Pinnacle's closing price and found they did not beat it. Publication remains suspended in every division. Where this disclosure says picks would resume once the rerun finished or a division passed, that no longer holds: current evidence does not support resumption, and no resumption date or route is planned.

Public Protocol Disclosure 8 (2026-10-19 UTC)How Long Until a Division Can Publish Again
On current numbers, a division's live record will take many months to years to become testable, not weeks

Disclosure 4 said we would publish projected dates for when our live record could test each market once the daily sweep had run for 7 days. That time has passed and we have not published dates. This is why: the progress report behind those projections counted all divisions together. Our pre-registered protocol decides eligibility per division and per market, so pooled counts were the wrong unit. They made the sample look far closer to ready than it is: they suggested probability bands would reach 50 fixtures within days to weeks. That understated the wait badly. The report now counts per division.

The corrected position, as of 19 October 2026 (about ten days of sweep data, 4,059 resolved prediction rows across 16 divisions):

  • •The largest probability band in any single division holds 5 of the 50 fixtures required.
  • •No division has a testable market yet. Testing needs at least two bands of 50 or more.
  • •Projected from current fill rates, the earliest division-and-market pair becomes testable around February 2027, and the median around March 2028. For 56 of the 128 pairs we have no fill data yet to project from at all.
  • •These are rough straight-line projections from ten days of data and will move.

What this means. Picks resume, division by division, when a division and market pass the pre-registered test. The backtest rerun can give some pairs a verdict sooner: on the complete rerun of one season of backtest data, 29 of 120 pairs in the 15 top-flight divisions were testable (the September figure of 24 of 104 is superseded). For most pairs the live record is the only route, and on current numbers that is measured in many months to years. The protocol does not allow shortening it by pooling divisions or lowering the 50-fixture requirement.

Amended 20 October 2026: passing the test is not enough to resume publication. See Disclosure 9.

Update, 23 October 2026. The timeline above is no longer a forecast of when picks could resume. It estimated how long the live record would take to become testable. Since 19 October the rerun has finished (20 October), the model has been found overconfident (Disclosure 9), and its match-result probabilities have been tested against the closing market price and did not beat it (Disclosure 10). Passing the per-division test is not sufficient, and current evidence does not support resumption. The figures above are left in place as a record of what we said on 19 October.

Public Protocol Disclosure 9 (2026-10-20 UTC)The Model Is Overconfident — Passing The Test Is Not Enough
Our probabilities are systematically too confident, and the error grows with stated confidence

Across the 6,307 fixtures of our complete 2025/26 backtest (19 divisions, current v5 model), the probabilities we state are higher than the frequencies that followed. The gap is small at moderate confidence and large at high confidence. Each cell shows stated probability → how often it happened(number of fixtures). A calibrated model would show the same number on both sides.

MarketRated 60–70%Rated 70–80%Rated 80–90%Slope*
Over 1.5 goals65.6 → 74.1 (1,165)75.2 → 77.8 (1,924)84.6 → 79.4 (1,873)0.24
Double Chance 1X65.2 → 64.5 (1,098)75.1 → 71.7 (1,380)84.9 → 77.8 (1,250)0.65
Over 2.5 goals64.7 → 56.2 (1,062)74.3 → 61.5 (533)83.8 → 66.7 (189)0.27
Under 2.5 goals64.5 → 51.8 (985)74.4 → 49.9 (521)83.6 → 53.8 (160)0.27
BTTS Yes64.2 → 59.2 (1,069)73.9 → 61.1 (321)83.1 → 64.9 (37)0.22
BTTS No64.4 → 49.2 (935)73.7 → 48.1 (343)83.2 → 49.2 (63)0.22

*Fitted slope of outcome against stated probability across every fixture. 1.0 would be perfectly calibrated. Standard errors are 0.03–0.05.

  • •BTTS No rated 83% happened 49% of the time. Under 2.5 rated 84% happened 54% of the time. Those markets add very little beyond the base rate: Under 2.5 happened 47.7% of the time when rated 50–60% and 53.8% when rated 80–90%, against a base rate of 45.9%.
  • •Over 1.5 has a fitted slope of about 0.24 against our stated probability. It is too low where we are not confident (rated 50–60%, it happened 72.9% of the time) and too high where we are (rated 90%+, 86.8%).
  • •The 11 failures in the division-by-division test are one model-wide defect, not five unlucky divisions. In the 80–90% band, Over 1.5 is below its stated rate in 17 of 19 divisions and Double Chance 1X in all 18 that have enough data. The 18 passes survived because a band of about 100 fixtures tolerates a gap of roughly 8 points before it counts as a failure, so a consistent 5-point overconfidence passes the test.

What this means for picks. Our rules approve a pick when the model's probability, multiplied by the offered odds, is above 1. When the probability is too high, that margin is partly the model's own overconfidence. We checked it on the live market: for the coming weekend (23–26 October), 21 fixtures in the nine divisions where Over 1.5 passed would meet our selection rules, and 15 of them had a verified bookmaker price (median 1.18). Using the model's probabilities, those picks show an average edge of +5.0%. Using the frequencies the backtest actually recorded for the same probability bands, the average edge is −0.1%, and 8 of the 15 are positive. That is one weekend and 15 priced picks, so it is an indication, not a measurement, but it points one way: on the model's real hit rates, the picks our rules would approve have an expected edge of about zero. At those prices a single loss erases about six wins.

Publication stays suspended. Passing the pre-registered division-by-division test is necessary but not sufficient. We are not re-enabling any division while the model is known to be miscalibrated in this direction. Fixing it is a model change: it will need its own pre-registration and a fresh backtest on data it was not fitted to before anything is published. Earlier disclosures that say picks resume when a division passes should be read with this one.

Public Protocol Disclosure 10 (2026-10-23 UTC)Pre-Registration — Does The Match-Result Model Beat The Market?
The test is fixed before any result is compared with any price

We have never compared our outcomes with bookmaker prices. Before we do, here is exactly what will be run and what counts as passing. The full specification is in the repository at docs/decisions/2026-10-23-disclosure-10-market-test-preregistration.md; the commit that adds it is the timestamp.

  • •Data. Our stored v5 match-result probabilities for the 2025/26 backtest, in 14 top-flight divisions, joined to football-data.co.uk closing prices. The primary price is Pinnacle's closing 1X2, de-vigged; fixtures without one are excluded from the primary test. Team-name matching is fixed from names alone before any result is loaded, and unmatched fixtures stay out.
  • •Test. A three-outcome logit of the result on the log market probability (free slope, so our over-confidence is not mistaken for information) and the log gap between model and market. The parameter θ asks whether the model's disagreement with the market points the right way. Errors are clustered by team.
  • •Threshold, stated in advance. Pass only if the one-sided team-clustered p-value for θ > 0 is below 0.05 and θ̂ ≥ 0.10. Otherwise fail. It is run once; any later correction is reported next to the first result, not instead of it.
  • •Power. About 840 fixtures detect θ = 0.25 reliably, about 5,200 would be needed for θ = 0.1. A failure therefore would not rule out a small real effect.

A pass is not a green light. It would be retrospective evidence on a season that has already been mined (our model's constants and audits used it), and it is measured against Pinnacle's closing price, not the price you can take at SportyBet. It would earn only the right to a forward validation on predictions written before kickoff against prices captured before kickoff. Publication stays suspended whatever the result.

Public Protocol Disclosure 10 — Result (2026-10-23 UTC)Run once. Failed.
Against the closing line, on a full season, our match-result probabilities do not beat the market

We ran the test exactly as registered above, once. It covers 1,888 fixtures across 14 top-flight divisions in 2025/26, measured against Pinnacle's closing price.

  • •Estimate. θ̂ = −0.076 (90% interval −0.177 to +0.025; one-sided p = 0.89). θ asks whether our disagreement with the market points the right way; zero or below means it adds nothing.
  • •Threshold. The pre-registered pass rule was θ̂ ≥ 0.10 with p < 0.05. It failed.
  • •Fit. Our model's average log-likelihood per match is −1.048; the market's is −0.963. On its own, the model predicted results worse than the closing price.
  • •Checks. Both sensitivity checks agree. With a different de-vigging method (Shin), θ̂ = −0.078 (p 0.90). Using the market-average close on all 4,059 matched fixtures, θ̂ = −0.086 (p 0.97).
  • •What this rules out. The sample rules out an effect of the size we set out to detect. It cannot rule out an effect smaller than about 0.03.
  • •Per-division numbers are descriptive only. Nothing was selected or tested by division. Premier League (+0.18) and La Liga (+0.35) are positive and every other division is negative, but each division's uncertainty (standard errors 0.11 to 0.24) spans zero. Those two numbers are not findings and must not be read as evidence that the model works in any league.
  • •A data fault, caught before any result was scored. Four of the football-data files (Austria, Switzerland, Denmark, Norway) name the result column differently, which silently dropped them in a test run on simulated outcomes (1,479 fixtures instead of 1,888). We found and fixed it before any real outcome was scored.
  • •Limits. The primary sample is the 46% of fixtures with a Pinnacle close; the all-fixtures check agrees. This is one season, and the model was tuned on it.
  • •Order of events (git history). Pre-registration: commit a463247, 2026-10-23 08:08:16 UTC. Alias map and test script: commit cd58113, 08:13:17 UTC. Single run and result: commit 4970540, 08:14:03 UTC. The specification, the join and the code were all committed before the result.

What this means. Our 1X2 probabilities do not beat the closing line. Combined with Disclosure 9, where our goal and corner markets were shown to be overconfident, we have no demonstrated edge in any market we have been able to test. Publication stays suspended.

Public Protocol Disclosure 11 (2026-10-27 UTC)Defect found, fixed, disclosed.
A fallback we wrote on 20 September was never connected, so some teams were rated on a handful of matches

Each team's scoring and conceding rate is a blend of this season (weighted by matches played, up to 10) and a prior: last season in the same division, or in the division below, or, when a team has neither, the league average. The league-average fallback was in the original code and was never wired in. A team with matches this season but no prior-season data was therefore rated on its raw current rate at full weight, however few matches that was. This was live from 20 September to 27 October.

  • •Evidence. Ross County at home: 5 matches, 4.40 goals scored per game, expectation 2.47 (1.65 once fixed). AFC Hornchurch at home: 6 matches, 0.33 conceded per game, total expected goals 1.53 (2.06 fixed). Abha at home: 3 matches, 0.67 scored per game, expectation 0.98 (1.51 fixed).
  • •Backtest. The 2025/26 backtest used the same code. 802 of its 6,307 rows had a side with no prior-season data, all in the Championship, League Two, National League and the Saudi Pro League. The expectation only changes where a team had played fewer than 10 matches at that venue: 338 rows changed, the largest by 2.5 expected goals (Swindon v Shrewsbury, home 4.84 became 2.29). None of the 4,090 rows in the 14 divisions of the Disclosure 10 market test had such a side, so that result is unaffected. We re-ran those four divisions on 27 October from the same stored snapshots (12 fixture-list calls, no live fetches), with Eliteserien and the Danish Superliga as a control. The control reproduced bit for bit (every probability and every band identical), so nothing besides the blend changed. Of the 32 division-and-market verdicts, 31 are unchanged and one moved: see the 27 October update under Disclosure 7.
  • •Forward sample. Of 456 fixtures with prediction rows since 9 October, 38 fixtures (913 rows) had such a side. The only covered top-flight division affected is the Saudi Pro League (11 fixtures, 363 rows). Rows are never rewritten or deleted; the affected fixtures are listed in the repository and should be excluded from any forward test.
  • •Rows before and after come from different formulas. The fix is pipeline version 6. The same release refreshed all league averages, which were five to six weeks old, so version 6 differs from version 5 in two ways, not one. Rows are written once and never rewritten, so a fixture already recorded keeps its version 5 row even if it kicks off after the release. As of the regrade at 15:09 UTC on 27 October no version 6 row had been written yet. Rows must not be pooled across the boundary without saying so.
  • •Also fixed. The assistant kept its own copy of this blend, so the same match could show different numbers on the board and in the assistant. It now takes its numbers from the board's calculation: seven fixtures compared, identical to the decimal. For cup ties the assistant also rated only the home team on its domestic league; both teams now use it. Separately, the league average used as each team's baseline is now chosen by a fixed rule (the team's own domestic division first) instead of whichever row the database returned first.
  • •A coverage gap found while checking. Our filter that keeps youth and reserve sides off the board treated "Willem II" (an Eredivisie club) as a reserve team, so Willem II fixtures were never graded or recorded. Two covered fixtures since 9 October have no prediction rows because of it (Sparta Rotterdam v Willem II, 17 October; Willem II v Cambuur, 24 October); they are not in the forward sample. A prediction cannot be recorded after kickoff, so they cannot be recovered. The filter was corrected on 27 October.
  • •Also on the cards. The label under each match claimed a prior-season share for these teams even though none was used. It now states what was used: same-division prior, lower-division prior, or league average, and says home or away matches.

Publication stays suspended. Nothing in this disclosure changes the Disclosure 10 result.

Pre-Registered Backtest Audit Terms (2025/26 Season) — Version 2

We are conducting a fully reproducible sequential point-in-time backtest across all 2025/26 completed season matches in our watched divisions. Before making a single API call for this run, the following binding protocols are pre-registered:

  • •Per-Division Eligibility: Eligibility is decided separately per division (Premier League, La Liga, Championship, Saudi Pro League, League Two, National League, etc.), not pooled across divisions.
  • •Numeric Pass Criterion (Bonferroni Corrected): A market passes in a division if, for every probability band at or above its floor with N ≥ 50, the observed hit rate lies within the (1 − 0.05/k) binomial confidence interval around the model's mean prediction for that band (where k is the number of qualifying bands for that market in that division). For k = 3, each band is evaluated at a 98.3% confidence interval (z ≈ 2.39). A market with no band at or above its floor reaching N ≥ 50 is uncalibrated in that division.
  • •Expected False-Failure Disclosure: Even with Bonferroni correction, the family-wise error rate is 5%, meaning roughly 1 in 20 (5%) of perfectly calibrated market-divisions will fail by pure chance alone. Across ~100 market-division tests in this run, we expect approximately 5 false-failures purely due to statistical sample noise.
  • •Market Probability Floors: 60% for Over/Under 2.5 and Both Teams to Score (BTTS); 70% for Over/Under 1.5.
  • •Outcome Independence: Each outcome is tested separately — Under 2.5 and BTTS No are not assumed to share Over 2.5's and BTTS Yes's result.
  • •All-Prediction Inclusion: Calibration uses every prediction in the band, not only fixtures that would have become published picks.
  • •Pick Performance Transparency: Performance of fixtures that WOULD have been picks under the +8pp (+0.08 deviation) rule will be reported separately alongside for full transparency, but does not decide market eligibility.
  • •Protocol Freeze: From the first backtest API call, the protocol is frozen. No criteria, floors, or band definitions may be altered after data exists.
  • •Bug Protocol: If a code or logic bug is discovered mid-run: stop immediately, fix the bug, disclose it publicly, and re-run ALL divisions from scratch — not only affected ones.
  • •Atomic Settlement: No eligibility changes or pick rule updates will occur until the entire 7-day run completes and full JSON outputs are committed to git. Partial results are not acted on.
  • •Mandatory Full Disclosure: The complete script, raw JSON outputs, and calibration results will be committed to the repository and published here in full, whatever the outcome.
View Original Pre-Registration (v1 — Published 2026-09-22 13:22:00 UTC)

v1 Protocol: Evaluated bands at unadjusted 95% binomial CI ($z \approx 1.96$). Revised to v2 (Bonferroni correction) prior to making any backtest API calls to prevent compounding false-failure error rates across multi-band markets.

Top-Flight Domestic League Pick Policy

We publish picks only on top-flight domestic league fixtures — the type intended for calibration testing. European and cup fixtures, lower divisions, and non-calibrated leagues are shown with full working but are not picked until they have their own reproducible evidence sample.

European competitions (UEFA Champions League, Europa League, Conference League), domestic cups (FA Cup, EFL Cup, DFB Pokal, etc.), lower divisions (such as the EFL Championship), and non-calibrated top-flight leagues (such as the Saudi Pro League) are shown with full working but are not picked until they have their own evidence.

Threshold: N ≥ 50 observed outcomes per probability decile band per category. European competitions, domestic cups, lower divisions, and non-calibrated leagues remain tagged as Uncalibrated Division — Display Only until their specific decile bands clear this threshold and pass our out-of-sample calibration audit.

05

What we don’t claim

No market in our model has passed Gate 2 calibration. We do not beat the bookmaker’s closing line on any market at any confidence level we can demonstrate.

Grade labels (Strong signal, No strong view, Not enough data) reflect statistical consistency across inputs — not an assertion of financial edge or guaranteed winning picks.

Every published selection is logged to our immutable public ledger before kickoff and settled automatically when results complete. Our published record contains single picks only. A single measures whether our probability model was right. An accumulator measures our probability plus compounded bookmaker margin, which tells us nothing about the model. (Users can still build custom accumulators using the Pick Assistant, where combined margin warnings fire before generating a code).