A calibrated engine, measured against itself.
The market is efficient on price — we proved it by rejecting our own pregame signals across three sports. So the honest question is not “can we beat the close” but “does the machinery sharpen the forecast.” It does, in one measured place: mid-game. Every number below wears its receipt, and no dollar edge is claimed.
Trained only on the past, scored on the future.
An expanding-window backtest: every fold asserts max_train_date < min_test_date or fails — no K-fold on time-ordered games. The NBA win-probability ensemble across 3 folds and 2023-24 + 2024-25 seasons; the widening train column is the walk forward itself.
| Train frac | Train n | Val n | Acc | Brier |
|---|---|---|---|---|
| 0.4 | 982 | 491 | 0.737 | 0.186 |
| 0.6 | 1473 | 491 | 0.676 | 0.205 |
| 0.8 | 1964 | 491 | 0.715 | 0.188 |
results/winprob_walk_forward_results.json · 2026-07-20 · edge_claimed: false
In-game conditioning sharpens the forecast.
Fusing the pregame rating prior with the realized mid-game state improves win-probability calibration on a real out-of-sample corpus. A live book also sees the score — so this is calibration, not a claim of beating anyone.
Read this as calibration, not a win: most of the drop is the scoreboard state itself, free to anyone watching — the model’s own prior adds only the last sliver, decomposed below.
But how much of that is skill? A rating-blind third arm — conditioning on the score alone, no model prior — splits the lift. Most of it is the scoreboard itself, free to anyone watching. The model’s own contribution is the last column.
| Sport | static (prior only) | score-only | combined | mechanical share | model-prior share |
|---|---|---|---|---|---|
| NBA | 0.209 | 0.172 | 0.159 | ~73% | -0.014 (~27%) |
| MLB | 0.241 | 0.128 | 0.126 | ~99% | -0.001 (~1%) |
docs/INGAME_PROOF.md Sec. 2 + 2a · real-corpus OOS · each arm rounded independently to 3 dp; the share column is derived from full precision, so it will not reconcile to the 3-dp cells · source doc committed in this repo; live re-run prints VALIDATION_PENDING without the private corpus and falls back to this recorded table · edge_claimed: false
Its own eyes: calibration by game state.
Every graded in-game MLB prediction, bucketed by the model’s probability band and the inning. Each cell is the calibration error — how far the stated probability sits from what actually happened. Darker is a bigger gap: the improvement backlog, in the model’s own view.
Model vs market, every checkpoint, reported as-is.
| Sport | Market @ checkpoint | n | model vs market (paired) | 95% CI | verdict |
|---|---|---|---|---|---|
| NBA | winprob (Brier) @ end_q1 | 1592 | -0.0084 | [-0.016, -0.001] | market sharper provisional |
| NBA | winprob (Brier) @ halftime | 1593 | -0.0040 | [-0.010, 0.002] | underpowered |
| NBA | winprob (Brier) @ end_q3 | 1593 | +0.0011 | [-0.003, 0.005] | underpowered |
| NBA | winprob (Brier) @ q4_under5 | 1593 | +0.0019 | [-0.001, 0.005] | underpowered |
| MLB (in-game) | CRPS @ home_margin|end_inning_3 | 20 | +0.4499 | [-0.021, 1.069] | underpowered |
| MLB (in-game) | CRPS @ home_margin|end_inning_5 | 26 | +0.8555 | [0.326, 1.489] | underpowered |
| MLB (in-game) | CRPS @ home_margin|end_inning_6 | 22 | +0.9583 | [0.241, 1.805] | underpowered |
| MLB (in-game) | CRPS @ home_margin|end_inning_7 | 21 | +1.0660 | [0.266, 1.960] | underpowered |
| MLB (in-game) | CRPS @ home_margin|end_inning_8 | 21 | +1.7940 | [0.872, 3.059] | underpowered |
| MLB (in-game) | CRPS @ total_runs|end_inning_3 | 49 | +0.0891 | [-0.303, 0.506] | underpowered |
| MLB (in-game) | CRPS @ total_runs|end_inning_5 | 54 | +0.2604 | [-0.053, 0.575] | underpowered |
| MLB (in-game) | CRPS @ total_runs|end_inning_6 | 54 | +0.6392 | [0.317, 0.963] | model sharper provisional |
| MLB (in-game) | CRPS @ total_runs|end_inning_7 | 55 | +0.7327 | [0.413, 1.081] | model sharper provisional |
| MLB (in-game) | CRPS @ total_runs|end_inning_8 | 49 | +1.4201 | [0.966, 1.898] | model sharper provisional |
| MLB (pregame) | CRPS (total_runs) @ pregame | 300 | -0.0523 | [-0.166, 0.048] | underpowered |
| Soccer | winprob (Brier) @ home_win_prob|minute_60 | 22 | -0.0931 | [-0.157, -0.034] | underpowered |
| Soccer | winprob (Brier) @ home_win_prob|minute_75 | 17 | -0.1075 | [-0.189, -0.032] | underpowered |
Model beats market only where verdict starts MODEL_SHARPER (still PROVISIONAL pending more data). Most rows are UNDERPOWERED (CI spans zero) or MARKET_SHARPER -- reported as-is.
forecaster/cross_sport_scoreboard.json · positive Δ = model sharper (paired) · a sharper verdict needs the CI clear of 0 AND enough n · edge_claimed: false
The aggregate transition explorer.
No committed per-game trajectory exists to replay a single match honestly, so this is the aggregate: the calibrated win-probability swing carried by each adjacent-state transition, from the state grid above. Any in-game move decomposes into the transition it crossed.
Filtered: 13 of 20 candidate transitions are excluded because one end sits in a degenerate bucket — a realized rate of exactly 0.000 or 1.000, where every game in the bucket resolved the same way. Those buckets have no measurable swing to report; their apparent delta is thin one-sided support, not a move. Rows are also de-duplicated by destination state so one to-state cannot render as twin bars. What remains is what the data actually supports, which is mostly MLB and soccer.
Replay: watch the model think. Step through one real game’s states with the win probability and its attribution at every tick, receipts attached. This needs committed per-game trajectory data, which we haven’t published yet — so it is marked pending rather than mocked up. We never fabricate a trajectory.