College Football Model Methodology
Possession-based ratings, calibrated game distributions, and an in-game win probability model anchored on the published pregame line
This model rates every Division 1 college football team on one scale: expected scoring margin against an average FBS opponent on a neutral field. From those ratings it projects a spread and a total for every game with a probability distribution around each, and an in-game layer turns any game state into a home win probability. The published line, total, projected scores and team ratings are those numbers moved toward the sportsbook market; the model's own numbers stay on the site wherever they are labeled Pure.
The pregame model does not beat the closing spread. Across 3,853 backtested games from 2021 through 2025 its average margin error is 13.40 points against the closing line's 12.14, and the market wins every season. So the published line, total and scores are blended halfway to the market, picks are priced from those market-informed numbers, and the in-game model anchors on the published line instead of the pure model's number.
What the model offers is coverage and calibration. It rates all 266 D1 teams, including FCS programs the market barely prices, its 50/80/95% intervals were checked on three holdout seasons, and every prediction is frozen before kickoff and graded in public on the Performance page.
Prediction target
Home margin and game total, joint distribution
Rating unit
Points per possession, offense and defense per team
Rating engine
Bayesian linear model blended with a points-only rating
Training data
2019–2025 play-by-play; holdout 2023–2025
Home field
Refit weekly from a 2.5 ± 1.5 point prior
In-game model
3-parameter Gaussian on the final margin
Kickoff anchor
Published market-informed line, sd 15.35 points
Picks
Moneyline, spread and total at a flat unit (cfb-picks-v7)
East Carolina @ UABTotal
Thu, Oct 15, 4:30 PM PT · cfb-picks-v11
Pick: Over 47.5 at -110 (DraftKings), one unit. Settled loss, final East Carolina 17 at UAB 30 (-1.00u).
■ wins: 48 or more total points ■ loses
Model cover
57.6%
half-point line, no push at 47.5
Break-even at -110
52.4%
implied by the price, vig included
Edge
5.2%
57.6% no-push cover minus break-even; the gate is 2.0 points of line
EV per unit
+0.099
stake 1u, flat
Every number above is read from the frozen recommendation row and the projection it was priced from; the bars are recomputed from the stored mean, standard deviation and degrees of freedom with the same arithmetic the policy uses. The pick clears when the priced line sits at least 2.0 points beyond the price's break-even line and EV is positive; when too few picks clear in a week, the highest-edge positive-EV offers fill the slate. Spread picks are priced from the published line, totals from the published total, and moneylines from the market margin moved 0.2 of the way toward the pure model.
A rating engine learns team strength from possession-level data, a projection layer turns ratings into calibrated game distributions, and an in-game layer consumes a pregame anchor plus a play feed. Each layer sees only what it would have known at the time.
Inputs
CFBD play-by-play
Every D1 game, 2019+
CFBD API
Games, lines, talent, portal, returning
The Odds API
Priced moneylines, spreads and totals
Previous season fit
Seeds the preseason prior
Rating Engine
Possession construction
Scrimmage runs, garbage-time aware
Ridge over off/def PPP
Two numbers per team + fitted HFA
EPA process blend
Scoreboard and process, GLS-combined
Preseason prior
Talent, portal, returning production
Projections
Margin and total
Bivariate Student-t per game
Calibrated intervals
50/80/95% checked on holdout
Picks
One side per game per market, flat unit
Supabase publish
The only interface to this site
In-Game
Play-boundary states
Strictly pre-snap information
3-parameter WP model
Gaussian on the final margin
Market anchor
Published pregame line as the kickoff prior
Live publishing
Scoreboard states during games
Everything runs as batch CLI commands over parquet files. Supabase pg_cron dispatches GitHub Actions: a Monday update that refits, projects and publishes the next slate and grades the finished week, and a kickoff-capture window that restores that frozen forecast and records the final pregame market without refitting. Odds capture is append-only. During games a live worker reads the CFBD scoreboard and publishes win probabilities anchored on the published pregame line.
Ingest
Schedules, lines, talent, portal, play-by-play into raw parquet
Possessions
Classify plays, build possessions, aggregate team-game features
Fit
Ridge over offense/defense PPP plus home field, or the preseason prior
Project
Margin and total distributions for every upcoming game
Anchor
Published pregame lines anchor live win probability
Publish
Serving tables written to the Supabase cfb schema
Data sources
The engine works on possessions rebuilt from CFBD play-by-play, with garbage time excluded: a play stops counting once the margin exceeds 43 points in the 1st quarter, 38 in the 2nd, 28 in the 3rd, or 22 late. The target is points per possession, and the model is one offense and one defense number per team, a shift per conference (FCS is one pool), and a shared home-field term.
E[ppp_away] = base + off_away − def_home − 0.5 · hfa
// hfa applies only off neutral sites; prior 2.5 ± 1.5 points, refit weekly
It is solved as a ridge regression in closed form, which is a Bayesian update with Gaussian priors, so the posterior covariance comes out of the same solve and feeds projection uncertainty. Offense and defense priors are correlated at 0.5, which shrinks scoring environment harder than net strength and keeps totals calibrated. Scoreboard points are noisy, so the fit runs twice: against a 50/50 blend of points and an EPA-derived process score, then against their precision-weighted combination. Pace comes from a separate small ridge, and strength scales with a game's expected possessions.
That blend discounts blowout scoring, which under-projects games between distant pools, so a crossover gain adds a share of the pool-level gap to the stronger side. On the 2020–2025 walk-forward it cut the FBS over FCS bias from +6.1 to +1.3 points. Zero always means an average FBS team.
The model's own (pure) margin is half this fit and half a points-only ridge rating over every completed game with a final score, including FCS games with no play-by-play, carried across seasons with a 9-point prior. On the 2023–2025 holdout the blend cut margin error by 0.19 points on Division I games with a closing line, and the score-only games cut another 0.06. Two frozen corrections come last, fitted on 2020–2022 and checked on 2023–2025: a small margin adjustment from 14 process features of each team's earlier games, and a 0.8-point reduction to the total.
Hyperparameters were chosen on development seasons only, and the search preferred no recency decay: college seasons are short, and throwing away September to sharpen November costs more than it buys.
The Ratings page also publishes rush offense, pass offense, rush defense, pass defense, pass blocking, and run blocking. Each is an opponent-adjusted ridge over games strictly before the forecast week, centered so zero is an average FBS unit and positive is better. They are descriptive: they never feed the scoring engine and do not add up to the headline ratings.
Rush and pass units use competitive-play PPA. Pass blocking is sack PPA relative to the expected sack cost for the team's dropbacks, and run blocking is adjusted line yards on a PPA-equivalent scale; both reflect the whole offense, not film grades of the line. Preseason forecasts carry last season's final unit ratings. Special teams is omitted because CFBD has no reliable PPA for routine kicks.
Week 1 arrives with no current-season games, so the preseason rating starts from last season's final fit and adds offseason signals. Each input is a z-score across D1 multiplied by a weight in points of rating. The weights were fit by least squares on 364 FBS games from weeks 1 and 2 of 2022 through 2025 against the closing margin, and validated leaving one season out. Recruiting points and QB continuity fit to zero once talent and returning production were in:
| Input | Points per SD | Note |
|---|---|---|
| Previous season ratingMultiplier on last season's final rating, in points | 0.87 | Multiplier on last season's final rating, in points |
| Talent composite (prior season)CFBD roster talent | 3.70 | CFBD roster talent |
| Talent composite (current season)Zero until CFBD publishes it | 3.30 | Zero until CFBD publishes it |
| Returning productionPercent of team PPA returning | 1.70 | Percent of team PPA returning |
| Transfer portal quality balanceRated arrivals minus departures | 1.00 | Rated arrivals minus departures |
| Transfer portal count balanceHeadcount in minus out | 0.35 | Headcount in minus out |
| Coach continuitySame head coach as last season, or not | ±0.35 | Same head coach as last season, or not |
| QB continuityFeeds the scoring environment only | 0.00 | Feeds the scoring environment only |
| Recruiting class pointsFit to zero; kept for the uncertainty budget | 0.00 | Fit to zero; kept for the uncertainty budget |
Service academies do not sign rated recruiting classes, so their talent is treated as missing, not as a near-zero roster. A parallel equation projects each team's scoring environment from QB continuity, QB transfers and returning receiving production, which lets two teams with the same power rating project different totals.
Uncertainty is an explicit budget. Every rating starts at 6.05 points of standard deviation and grows in quadrature for each missing input: 3.0 with no previous rating, 3.0 for FCS teams, 1.5 for missing returning production, down to 0.75 for recruiting. Injury availability has no feed, so its term is always on. This is the SD column on the Ratings page, and it becomes each team's prior width when the in-season engine takes over after week 1.
Power rating is offense plus defense and scoring environment is offense minus defense, so the model's own (pure) projection is short:
pure_margin = 0.5 · joint_margin + 0.5 · points_only_margin + process_correction
pure_total = league_base + environment_home + environment_away − 0.8
// league_base = league scoring rate × the teams' blended pace
A team rated +10 is roughly a 10-point favorite over an average FBS team on a neutral field. A spread is the negated margin, following the sportsbook sign convention. These are the pure numbers, shown on the site only where they are labeled Pure.
What is published is market-informed. The pure margin is first mixed with a rating fitted to the closing lines of earlier games (weight 0.55 through week 3, 0.35 after), then moved halfway toward the game's own sportsbook line; the weight on the game's line is capped at 0.50. The published total is the pure total moved halfway toward the sportsbook total posted when the forecast was made, and the two published scores are the pair whose difference is the published line and whose sum is the published total.
published_margin = 0.5 · informed_margin + 0.5 · market_margin
published_total = 0.5 · pure_total + 0.5 · market_total
// w = 0.55 through week 3, 0.35 after
Published team ratings are the fitted ratings shifted so that, for every game a team plays that week, the rating difference plus home field equals the published line exactly. Each game's gap is split evenly between its two teams, offense and defense each take half of a team's shift, and the size of the shift is stored per team. A team with no game that week moves part of the way toward its market-history rating, by the same share the line uses (0.55 through week 3, 0.35 after). The underlying fit is unchanged and uses no market input: market information enters only at the output layer.
The Compare tab on the Ratings page follows from this. When the selected away team, home team and venue match a published game, it shows that game's published forecast: the line, the market line at forecast time and the projected score. For any other pairing or venue it shows a ratings-based line: away rating minus home rating minus home-field advantage, which is zero on a neutral field.
Around the pure point sits a bivariate Student-t distribution over margin and total, built from the fit's residual covariance plus parameter uncertainty, including both teams' rating SDs. The margin SD is scaled by 0.915, which brought 80% interval coverage from 0.85 back to 0.81, and lands near 16 points for a typical game. College football is that noisy.
The market comparison view flags any offer where the published line or total sees at least 4 points of edge with positive EV. Those flags are review diagnostics, not picks: the biggest raw edges usually involve FCS opponents with thin data.
Picks come from a separate policy, currently cfb-picks-v7, that selects at most one side per game and market, and every edge is measured after shrinking toward the market being bet into. Spreads are priced from a market-informed margin: the pure margin mixed with a rating fitted to the closing lines of earlier games (weight 0.55 through week 3, 0.35 after), then averaged with the consensus spread. Totals use the published total, the model total averaged with the sportsbook total. Moneylines keep only 0.2 of the model's disagreement with the market. Probabilities use the measured dispersion of results around these lines (15.35 points for margin, 15.93 for total), not the pure model's wider spread.
A pick needs the priced line at least 2 points beyond the price's break-even line, plus positive EV, from a forecast no more than a week old with no missing input beyond the always-on injury flag, a paired two-sided quote and a price under an hour old. If fewer than 15 picks clear the gate in a week, the highest-edge positive-EV offers fill the gap and are labeled as volume-floor picks, which is a product choice and not a calibration result. Anything else is a No Play.
Every pick records the exact line, price, book and decision time, then settles at that frozen contract at a flat one unit, along with the CFBD median closing line and the points by which the pick beat it. If a newer model version no longer makes an open pick, the fresh decision replaces it before kickoff; line movement alone never does. The half-market blend improved holdout margin error from 13.28 to 12.31 points while the closing line stayed better at 11.99, so it is not independent skill, and the gates have not been tuned on live outcomes. Results are on the Performance page.
Every backtested prediction was made walking forward through each season using only games from strictly earlier weeks, then frozen. The table is mean absolute error of the projected home margin, model versus closing spread, on the same games:
| Season | Model MAE | Market MAE |
|---|---|---|
| 2021 | 13.60 | 12.49 |
| 2022 | 13.86 | 12.28 |
| 2023 | 13.26 | 12.00 |
| 2024 | 13.46 | 12.02 |
| 2025 | 12.84 | 11.93 |
| Pooled | 13.40 | 12.14 |
The market wins every season, as expected: the closing line aggregates injury news, weather and sharper private models. The table is the frozen walk-forward of the base engine; the later points-only blend trimmed about a quarter point of holdout error and the closing line is still ahead. What the model can claim is calibration: on the untouched 2023–2025 holdout (2,258 games) the 50/80/95% margin intervals covered 51.6%, 80.1% and 94.2% of outcomes.
A backtest describes seasons that already happened, and the portal, NIL, realignment and playoff expansion keep changing the sport, so these error levels should be expected to drift. The 2026 season is graded live against the projection published before kickoff and the CFBD closing line, and that record supersedes this table as it accumulates.
The weak segment is early season: games where either team has fewer than two prior games grade around 15 points of margin MAE. Season-by-season detail is on the Performance page.
At every play boundary the in-game model asks: given the score, the clock, who has the ball and where, and what was believed before kickoff, how likely is the home team to win? The state for play N uses only plays 1 through N−1 plus the pre-snap situation. The form is a Gaussian over the final margin whose mean decays from the pregame expectation toward the live score as the clock runs out:
μ = home_margin + f · pregame_margin + possession_value
σ² = f · pregame_margin_sd² + 3.355²
P(home win) = Φ(μ / σ)
// f = fraction of regulation remaining; 0 in overtime
The three parameters were fit on play-level log loss over the 2019–2022 development seasons: possession is worth 0.306 points at a team's own 25, field position adds 0.0565 points per yard (first and goal at the 5 is worth about +4.3), and a 3.35-point floor keeps the distribution from collapsing as time expires.
On the 2023–2025 holdout (400,878 play states across 2,258 games) the model scores 0.4073 log loss and 0.1328 Brier and passes calibration checks in every quarter and score-margin bucket. There are no momentum or streak features; this is the reference any such idea has to beat.
Serving reads only a four-column anchor (game_id, model_week, home_margin, margin_sd), the three parameters and the play feed, so no outcome can leak in. Replaying all 759 games of 2025 as a simulated live feed, 135,662 served events matched the stored batch predictions exactly, at a median of 8.9 ms per event against a 1-second budget.
In production a live worker reads the CFBD scoreboard during games and publishes a win probability for each new state. It uses the score, the clock and possession; the scoreboard feed carries no field position, so that term is left out. The pregame expectation is the published market-informed line, with the 15.35-point dispersion picks are priced with.
If the market is the better pregame forecast, the in-game model should start from it. This was tested offline with the closing spread, which is a research evaluation and not the live anchor. One closing spread per game, the median across priced providers, enters the equation as pregame_margin = −closing_spread. The market prices no uncertainty, so margin_sdis a frozen constant, 15.445 points: the standard deviation of actual margins around the closing spread over 3,794 development-season games. Rescoring the frozen baseline on identical play boundaries with only the anchor swapped, on the 2023–2025 holdout:
| Metric | Model anchor | Market anchor | Δ |
|---|---|---|---|
| Log loss | 0.40730 | 0.38954 | −0.01775 |
| Brier score | 0.13280 | 0.12576 | −0.00704 |
The anchor's weight decays with the clock, so the log loss gain is largest early and vanishes late:
1st quarter
−0.0406
2nd quarter
−0.0231
3rd quarter
−0.0083
4th quarter
+0.0002
Overtime
0.0000
The pregame anchor is the binding constraint on in-game accuracy; this does not show an independent model beating the market. The live publisher does not wait for the closing line: it anchors on the published market-informed pregame line, which is already blended halfway toward the market. At kickoff the served win probability is essentially that published line; by the fourth quarter it is almost entirely the scoreboard. A forecast stored without a market-informed line falls back to the model's own projection.
The project is named after momentum, so this section is a little painful to write. The hypothesis was that process evidence carries signal the scoreboard has not absorbed yet. Eight evidence families (sustained stops, drive efficiency, turnovers, field position, fourth-down outcomes, missed kicks, success rate, tempo) were allowed to shift the expected final margin on top of the frozen baseline.
Version one used cumulative totals and improved the development seasons by 0.0030 of log loss; on holdout it was worse by 0.0004. Version two weighted recent plays with a 120-play half-life and was worse on holdout by 0.0006. My read is that whatever is real in a hot streak reaches the scoreboard quickly, and the scoreboard is already in the model. Momentum work is paused until I have a structurally different formulation.
The Heisman page runs on a separate player layer that never feeds team ratings. Every credited play is measured against what an average player produces against that opponent unit, using rush and pass unit ratings fit strictly before the game's week. Early in the season those ratings start from a frozen previous-season prior, so opening opponents are not treated as average. How a play's EPA is split among credited players is a fixed, documented table, because there is no ground truth for responsibility that could fit it. Defensive credit covers only tagged disruption plays (sacks, interceptions, breakups, fumbles) plus box-score tackles for loss and passes defended. Rates shrink toward the position mean with a four-game prior, FCS opponents count half, and a 30th-percentile replacement baseline is subtracted.
The Heisman board is a conditional logit trained on 2010 through 2025 ballots: predicted vote share is a softmax across each season's candidate pool, which sums to one the way a ballot does. Pools are chosen from statistics available that week, and evaluation uses later seasons only, counting winners missing from the pool as misses. The shares are conditional on the pool, not calibrated win probabilities.
Injuries are never inferred from play-by-play or line movement. The one availability input is a hand-entered, sourced and timestamped report that a starting quarterback is out or doubtful, which lowers that team's expected points by a fixed 1.5 (4.0 in the postseason). Everything else stays flagged as missing and widens uncertainty.
Estimated EV does not establish a real betting advantage, and nothing on this site is betting advice.
Live win probability reads a scoreboard feed, so it sees the score, clock and possession but not field position, live odds or injuries. FCS teams with sparse data carry the widest uncertainty and the largest model-market gaps.
Modeling
Data
Database
Frontend
Orchestration