One sentence: it turns each matchup into four gap numbers — line play, skill play, quarterback form, and whether either team's usual quarterback is missing — multiplies each by a learned weight, adds them up, and squashes the total into a probability. This page teaches every step of that sentence, with one real game as the running example, so you can change the model yourself.
Verdict first, as always: over 469 out-of-sample games the market still beats even the QB-aware model — Brier 0.2003 vs 0.2128, t = 2.29. But the QB features are real signal: they beat the QB-blind baseline (t = -2.46) and closed roughly a third of the gap to the market (the baseline loses at t = 3.57). Both facts matter: availability is information, and the market prices it before we do.
A model here is just a formula with adjustable knobs. Each featureis one pre-game fact expressed as a number — “how much better is the home team's line play, in standard deviations?”. Each feature gets a weight — a knob saying how much that fact should matter. Multiply each feature by its weight, add everything up (plus one constant for home-field advantage), and you have a single number: positive leans home, negative leans away.
Trainingis nothing more mysterious than knob turning: show the formula five seasons of past games, and after each pass nudge every weight slightly in the direction that would have made its predictions less wrong. Repeat a few thousand times until the nudges stop helping. That's “gradient descent” — the entire mechanism, no magic.
The last step turns the sum into a probability. The sigmoid(“squash”) curve maps any sum to a value between 0 and 1: a sum of 0 means 50/50, big positive sums approach (but never reach) 100%:
ESPN box scores, 2019–2025 → the warehouse
Every game's team and player box score — passing, rushing, sacks, tackles for loss, QB hits, QB rating — scraped keylessly into DuckDB (arena:nfl-scrape, arena:nfl-players-scrape).
Four features per game, all strictly pre-game
LINE and SKILL: each team's average rating over PRIOR games this season (minimum 3 — earlier games are skipped, not guessed). QB FORM: the starting quarterback's own last-3-starts composite. QB CHANGE: is today's starter NOT the QB who started most of the team's prior games? No feature ever sees the game it predicts — that's the anti-lookahead rule that makes every number on this page honest.
Standardize, weight, sum, squash
Each feature becomes a z-score ('how unusual, in standard deviations?') so yards and ratings share one scale. Training fits the four weights plus the home-field intercept on 2019–23; 2024–25 stays untouched for the test.
Score against reality, then against the market
Brier score (average squared miss) on the held-out seasons, then the same games against Polymarket's price an hour before kickoff. The market is the bar that matters — and it still wins.
MIA @ NE, 2026-01-04 — chosen because MIA started a non-usual QB, so the availability feature actually fires. Read each row left to right: the raw gap, its z-score, the learned weight, and what it adds to the sum. This table plus the curve below it IS the model — there is nothing else hiding.
| feature | raw gap | z-score | × weight | = adds |
|---|---|---|---|---|
| lineΔ | -0.197 | -0.431 | +0.262 | -0.113 |
| skillΔ | +0.931 | +1.590 | +0.320 | +0.509 |
| qbFormΔ | +0.553 | +0.751 | +0.121 | +0.091 |
| qbChangeΔ | +1.000 | +1.817 | +0.284 | +0.517 |
| home-field advantage (the intercept — added to every game) | +0.171 | |||
| sum → through the squash curve → | +1.175 | P(home) 76.4% | ||
The sum lands on the squash curve here — and the model called this one home, which was right (NE won). One game proves nothing either way — the 447-game Brier above is the real scoreboard. That instinct is most of statistics.
Each feature's solo out-of-sample AUC — “if you knew ONLY this number, how well would you sort winners above losers?” (0.5 = coin flip):
AUC 0.658 alone. Passing efficiency and QB play carry the most signal.
AUC 0.631 alone — a single yes/no fact about availability rivals whole-team ratings. This is the injury channel, measured.
AUC 0.621 alone. Weaker than availability, and remember MLB form died entirely under the same test — form claims always deserve suspicion.
AUC 0.618 alone. The trenches matter — measurably less than folklore says.
The QB features were added the way every feature must be: pre-registered (prediction written down first: availability should help, form probably not), tested out-of-sample against the old model on identical games, and held to a bar — |t| ≥ 2 and weights that tell the same story when the training years are split in half.
Result: the QB model beat the QB-blind baseline (Brier 0.2131 vs 0.2205, t = -2.46), with stable weights, on a test set where a non-usual starter appeared in 191 of 447 games. Against the market it still loses (t = 2.29) — the market knew about every one of those backups before we did. And one honest footnote: building this test exposed a bug in the old model's QB-rating code (a misspelled stat key), now fixed and disclosed in RESULTS.md — stricter tests find bugs, which is another reason to run them.
A probability is only honest if it means what it says: of the games the model calls “65% home”, about 65% should actually go home. That property is calibration:
predicted 28.5% · actually won 20.8%
predicted 42.7% · actually won 43.0%
predicted 57.3% · actually won 56.6%
predicted 74.2% · actually won 78.0%
○ predicted · ● actual, on a 0–100% axis. Perfect calibration puts every pair on top of each other.
scripts/research/nfl-qb-backtest.ts — compute the home−away gap, push it into f, add its name to FEATURES. The z-scoring, fitting, and scoring need no changes.This site writes probabilities as cents per $1: a 60c price means the market thinks something is 60% likely, and a correct $0.60 bet pays $1. If your true chance is better than the price, the bet is good; if worse, it's bad. That one sentence is most of sports betting.
A bet on which team wins the game, nothing else. American odds like -150 mean 'bet $150 to win $100' (a favorite); +130 means 'bet $100 to win $130' (an underdog). Both convert to an implied probability.
A sportsbook's two sides always add to MORE than 100% — the extra is the vig, the book's built-in fee. De-vigging scales both sides down so they add to exactly 100%; the result is the book's honest opinion, called the fair or no-vig price.
A feature is one pre-game fact turned into a number (a rating gap, a yes/no flag). A weight is the knob saying how much that fact matters, learned from past games. The model is nothing but features × weights, summed, squashed. Adding a feature = adding a column; training finds its knob.
The simplest honest prediction machine: multiply each input by a learned weight, add them up, and squash the total through an S-shaped curve so the answer is always between 0 and 1 — a probability. The weights are learned from past games by nudging them until predictions fit outcomes.
The S-shaped curve that turns any sum into a probability. Sum 0 → 50%. Sum +2 → about 88%. It never quite reaches 0% or 100% — the model is structurally incapable of certainty, which is a feature.
How the knobs get set: predict every past game, measure how wrong you were, nudge every weight a tiny step in the direction that reduces the wrongness, repeat thousands of times. No insight, no magic — just accumulated nudging. Everything interesting lives in the features you feed it.
Do the percentages mean what they say? Collect every game the model called ~65% and check that ~65% actually went that way. A model can be calibrated and still lose to the market — calibration is honesty, not edge.
Writing down what you expect BEFORE running the test. Try enough features and something will look great by pure luck; the registered prediction plus a fixed bar (|t| ≥ 2, stable weights) is the defense. This project's ledger: two nulls (MLB form, MLB head-to-head) and one partial pass (NFL QB availability).
Before the weights see an input, it is rescaled to 'how unusual is this, in standard deviations from average?'. That puts ERA (runs) and WHIP (runners) on the same footing so their weights are comparable.
The honesty meter for probability forecasts: the average of (prediction − outcome)², where the outcome is 1 or 0. Lower is better. Always guessing 50% scores 0.25 — a model has to beat that to know anything.
A 0.5-to-1 score for 'does this single number sort winners above losers?'. 0.5 = coin flip, 1.0 = perfect. A feature with AUC 0.66 alone is genuinely informative; 0.52 is barely a whisper.
How many standard errors an effect is away from zero. Roughly: |t| ≥ 2 means the effect is probably real; below that it is indistinguishable from luck. This site refuses to believe anything under t = 2, and distrusts single t ≥ 2 results too (see the arena page).
Did the price move toward your side after you bet? The closing price is the market's most informed opinion, so consistently beating it is the fastest reliable evidence of skill — much faster than waiting for wins and losses to pile up.
The stake-sizing formula that maximizes long-run growth: bet a fraction of bankroll proportional to your edge. Everyone who uses it bets a QUARTER of what it says, because overestimating your edge is easy and overbetting is ruinous.
Tested on games the model never saw while learning. A model graded on its own training data is a student grading their own exam — every honest number on this site is out-of-sample, and it's why our numbers look worse (and truer) than most.