Graded every week, misses included
NFL props: how our numbers have held up
Every prop on our props board gets checked against the box score after the game. We grade the last board we built before kickoff, on a rule we wrote down before grading a single prop. So far that is 4,351 props over 90 games, weeks 2 to 7.
The short version: the market has been better than us.
- On pass yards, rush yards, receiving yards and catches, the sportsbook line landed closer to what happened than our number did. On all of them the gap is bigger than chance would explain.
- Our side of the line landed 50.9% of the time. That is not clearly better than a coin flip, and it trails the 53.7% you would have hit by taking whichever side the books priced as more likely.
- Big disagreements did not help: when our number was 20% or more away from the line, our side landed 51.2% (585 of 1143).
- On anytime touchdowns the books' prices scored better than ours, and we rated long shots too low (more below).
We publish this because it is the honest state of the board. A props number is only useful if you know how far to trust it, and right now the answer is: as a second opinion next to the line, not as a replacement for it. This page updates automatically every week, whatever it shows.
How close each number landed
Average miss in the stat's own units, ours against the consensus sportsbook line, on the same props. "Closer" counts the props where ours was nearer the final number.
| Stat | Closer overall | Our avg miss | Line's avg miss | Ours closer |
|---|---|---|---|---|
| Pass yards | Market | 57.3 | 53.6 | 63 of 168 |
| Rush yards | Market | 19.5 | 17.8 | 207 of 484 |
| Receiving yards | Market | 22 | 21.1 | 457 of 994 |
| Catches | Market | 1.66 | 1.61 | 466 of 944 |
| Pass TDs | Too close to call | 0.91 | 0.93 | 67 of 111 |
"Better" means the difference holds up when we resample whole games (95% interval). Pass TDs use an expected count against a median-style line, so read that row loosely.
How often our side landed
Our lean is over when our number is above the line, under when it is below. Pushes count as neither.
| Stat | Landed | Rate | 20%+ gap only |
|---|---|---|---|
| Pass yards | 72 of 168 | 42.9% | 3 of 10 (30.0%) small |
| Rush yards | 234 of 485 | 48.2% | 112 of 233 (48.1%) |
| Receiving yards | 511 of 994 | 51.4% | 246 of 476 (51.7%) |
| Catches | 504 of 952 | 52.9% | 214 of 404 (53.0%) |
| Pass TDs | 61 of 116 | 52.6% | 10 of 20 (50.0%) small |
| All stats | 1382 of 2,715 | 50.9% | 585 of 1143 (51.2%) |
Week by week
| Week | Games | Leans landed | Rate | Line closer on |
|---|---|---|---|---|
| Week 2 | 16 | 222 of 461 | 48.2% | 3 of 5 |
| Week 3 | 16 | 224 of 473 | 47.4% | 4 of 5 |
| Week 4 | 16 | 273 of 486 | 56.2% | 5 of 5 |
| Week 5 | 15 | 234 of 455 | 51.4% | 3 of 5 |
| Week 6 | 14 | 223 of 438 | 50.9% | 4 of 5 |
| Week 7 | 13 | 206 of 402 | 51.2% | 4 of 4 |
Anytime touchdowns
For touchdowns we give a chance, not a line, so we grade the chance. Across 1,587 player-games the books' prices scored better than ours (Brier score 0.1407 against our 0.1454; lower is better). The table shows where: when we gave a player under a 10% chance, we said 3.8% on average and 7.0% of them scored. The books said 10.0%. We are too low on long shots.
| Our chance | Players | We said | Books said | Scored |
|---|---|---|---|---|
| under 10% | 639 | 3.8% | 10.0% | 7.0% |
| 10-20% | 372 | 14.7% | 20.2% | 21.0% |
| 20-35% | 317 | 26.5% | 28.7% | 27.8% |
| 35-50% | 181 | 41.6% | 39.4% | 33.1% |
| 50% and over | 78 | 60.1% | 55.5% | 56.4% |
The books' touchdown prices still include their margin, so their chances run a little high across the board.
Our biggest misses
- Sam Darnold SEA · Week 3MissPass yards: we said under (153.8 vs a line of 219.5). Actual: 379.
- Tyler Shough NO · Week 5MissPass yards: we said over (295.3 vs a line of 237.5). Actual: 96.
- Tetairoa McMillan CAR · Week 4MissReceiving yards: we said under (55.8 vs a line of 72.5). Actual: 192.
- Jaylin Noel HOU · Week 6MissReceiving yards: we said under (3.26 vs a line of 17.5). Actual: 107.
- Jaylen Waddle DEN · Week 2MissReceiving yards: we said under (41.7 vs a line of 53.5). Actual: 138.
- T.J. Hockenson MIN · Week 4MissReceiving yards: we said under (28.3 vs a line of 38.5). Actual: 119.
- Kyle Williams NE · Week 5MissReceiving yards: we said under (4.41 vs a line of 13.5). Actual: 91.
- Kenny Gainwell TB · Week 7MissRush yards: we said under (7.77 vs a line of 11.5). Actual: 88.
- Kenyon Sadiq NYJ · Week 3MissReceiving yards: we said under (21.2 vs a line of 30.5). Actual: 105.
- CeeDee Lamb DAL · Week 5MissReceiving yards: we said over (102.6 vs a line of 81.5). Actual: 9.
And our biggest hits
- Bijan Robinson ATL · Week 3HitRush yards: we said over (95.7 vs a line of 77.5). Actual: 194.
- Keon Coleman BUF · Week 4HitReceiving yards: we said over (29.2 vs a line of 12.5). Actual: 116.
- Cam Skattebo NYG · Week 7HitRush yards: we said over (50.5 vs a line of 35.5). Actual: 124.
- Matthew Stafford LA · Week 2HitPass yards: we said over (288.1 vs a line of 238.5). Actual: 327.
- Isaac TeSlaa DET · Week 4HitReceiving yards: we said over (27.4 vs a line of 17.5). Actual: 95.
- Jaylin Noel HOU · Week 4HitReceiving yards: we said over (22 vs a line of 11.5). Actual: 85.
- Zay Flowers BAL · Week 7HitReceiving yards: we said over (76.7 vs a line of 63.5). Actual: 137.
- Jayden Daniels WAS · Week 5HitRush yards: we said over (37.4 vs a line of 30.5). Actual: 103.
- Matthew Stafford LA · Week 4HitPass yards: we said over (307.5 vs a line of 249.5). Actual: 317.
- Jaylen Warren PIT · Week 5HitReceiving yards: we said over (28.1 vs a line of 20.5). Actual: 87.
The rule, written first
It is easy to grade yourself kindly once you have seen the results, so the rule was written and committed on 19 October 2026,
before the grader existed. It fixes which board counts (the last one built before kickoff), which props are left out
(inactive players, as sportsbooks void them), and every number above. Its fingerprint is
7dd3152732318965; any change to the rule would change it.
Coverage: week 1 boards carried no sportsbook lines, so grading starts in week 2. 369 props were void because the player did not record a stat. 2 finished games had no board before kickoff.
Read the full rule
# NFL props board: pre-registered grading rule
Written 2026-10-19, before the grader existed and before any NFL props row had been compared with a box score by this
tool. `tools/nfl_props_grade.py` prints this file's sha256 and the commit that added it into every report, so the rule a
report was graded under can be checked against what was written here first. Mirrors `docs/CFB_PROPS_PREREG.md`.
Unlike the CFB rule there is no pass/fail bar and no "not yet" verdict. The record is public proof of how the board has
held up, so it is published whatever it says, misses included. The sections below fix how it is computed.
## Question
How close are the ATO model's NFL stat projections (pass yards, pass TDs, rush yards, receiving yards, catches, anytime
TD) to what happened, compared with the market line the board showed next to them, and how often does the model's side of
the line (over or under) land?
## What is graded
- The board used for a game is the last archived build whose `generated` time is before that game's kickoff
(`~/archives/nfl-props-snapshots/<season>-weekNN/props_*.json`). Later builds never replace it. Kickoff comes from the
nflverse schedule (`gameday` and `gametime`, Eastern time, converted to UTC). A board belongs to a game when its
`season` and `week` equal the game's and the row's team and opponent are the game's two teams (opponent codes LAR, WSH
and JAC are mapped to nflverse's LA, WAS, JAX, the same mapping the board builder uses).
- One row per player, stat and game. A row counts only if all of these hold:
1. the player's `status` on that build is not OUT, DOUBTFUL, IR or D (the builder already drops these; the check is kept
so the rule does not depend on it);
2. it has the market in question (see Markets);
3. the game is final in nflverse (the schedule has a score and the weekly player stats hold rows for that `game_id`);
4. the player has a stats row for that game, matched on game, normalised name and team. Normalisation is
`tools.nfl_props_history.norm_name`, the rule the board builder uses (lower case, suffixes Jr/Sr/II/III/IV dropped,
letters only). A player with no row is a void prop (inactive, or no recorded stat in any category) and is left out of
the primary numbers, as sportsbooks void those props.
- Sensitivity, reported next to the primary and never instead of it: players with no stats row counted as 0.
- Rows are counted and reported at every drop: no board before kickoff, game not final, status, no market, void.
- The reported actuals are nflverse weekly player stats (`stats_player_week_<season>.parquet`, CC-BY-4.0): `passing_yards`,
`passing_tds`, `rushing_yards`, `receiving_yards`, `receptions`. Anytime TD is `rushing_tds + receiving_tds >= 1`, the
same quantity the model's probability is built from. Sensitivity: also counting `special_teams_tds`.
## Markets (kept separate, never pooled)
- **Books**: the consensus line `bk_line` (the line most books hang, at least 2 books, `bk_books >= 2`) and its de-vigged
over chance `bk_over`. Anytime TD: `bk_over` is the average Yes price across books converted to a chance, so it keeps the
books' margin; this is stated wherever it is shown.
- **Underdog**: `ud_line` and `ud_over` where the board carried them (they were removed from the page on 2026-10-15, so
the Underdog arm covers earlier weeks only).
- Kalshi and Novig fields are not graded: Kalshi's terms bar public display and Novig is display-only on the board.
A row can appear in both arms; each arm is analysed on its own rows.
## Comparisons (all with n)
1. **Accuracy, line stats** (pass yards, rush yards, receiving yards, catches, pass TDs). By stat: mean absolute error of
ours and of the market line, bias (prediction minus actual) for each, the paired mean of (|ours - actual| minus
|line - actual|) with a 95% bootstrap interval, and the share of rows where ours was closer. The bootstrap resamples
whole GAMES (players in one game share script and weather, so rows are not independent), 2,000 resamples, seed
20261019. `ours` is the board's `ours` field: the model's per-game expected stat line scaled to its adjusted projection;
for pass TDs it is an expected count, the market line is a median-style line, and the report says so.
2. **Lean.** Over when ours is above the line, Under when below; equal means no lean. Pass TDs: Over when the model's
chance of clearing the line (`ours_over`) is above the market's (`bk_over` or `ud_over`), the comparison the board
itself shows for that stat. A lean hits when the actual lands on that side. A push (actual equals the line) is reported
separately and is in neither hits nor misses. Reported overall and by stat for all leans, and for the large-gap bucket:
**gap percent >= 20 in absolute value**, where gap percent = (ours - line) / max(line, floor) with floors 50 pass yards,
15 rush or receiving yards, 2 catches, recomputed per market arm; for pass TDs the bucket is a gap of at least 20
percentage points between the two chances. Each rate carries its Wilson 95% interval and the exact one-sided binomial
p-value against 50%.
3. **Baselines for the lean.** The market's own lean (Over when its de-vigged over chance is above 50%) on the same rows
where that chance exists; always Over.
4. **Anytime TD** is a probability and is not graded by MAE. Brier score and log loss (probabilities clipped to
[0.001, 0.999]) of ours and of the market's implied chance, the paired mean of (Brier ours minus Brier market) with the
same game-cluster bootstrap, the Brier of a constant equal to the sample's scoring rate (context), and a calibration
table by ours-probability band (under 10%, 10-20%, 20-35%, 35-50%, 50% and over).
5. **By week and by stat** the same numbers are produced for each NFL week, so the page can show the record week by week.
6. **Largest misses and hits.** In the large-gap bucket on the Books arm: the ten leans that missed by the widest margin
and the ten that hit by the widest, margin = |actual - line| on the lean side. For anytime TD: the five rows where the
model's chance beat the market's by the most and the player did not score, and the five where it was lower by the most
and he scored.
X = 20 is fixed here and is not tuned after seeing results.
## Display rule
A rate or interval whose n is under 30 is not shown on the public page; it says "n under 30, not shown" and the count.
The JSON keeps n for every number and marks a rate with `small: true`.
## Known limits, stated in advance
- The board's lines are only as fresh as its last sportsbook pull before the build; the board can show a line that moved
later. That can only make the market look worse than its closing line.
- The model does not use the prop lines as an input. It is fitted on earlier games, so weeks 1-6 are out of sample for the
weekly projections, but the role adjustment on `ours` is the board's own and was changed on 2026-10-10 and 2026-10-17;
earlier boards were graded as they stood, not re-built.
- Weeks 1 and 2 have thinner coverage: the archive starts at 2026-09-13 15:48Z, so games before that have no board, and
early builds carried no sportsbook lines on some rows.
- Rows within a game are correlated; intervals resample games, but six weeks is still a small sample.
- Lines are mostly x.5, so pushes are rare; whole-number Underdog lines can push and are counted as such.
- A void prop (player did not play) is left out; a player who played and recorded nothing may also lack a stats row and
is treated as void in the primary numbers. The sensitivity run counts those as 0.
Lines and prices are what the books showed when the board was built, captured a median of 23 minutes before kickoff. Nothing here is betting advice.