NFL spread modeling starts with EPA/play and DVOA efficiency metrics. Predicted Spread = (Home EPA/play - Away EPA/play) x Plays + HFA. The NFL side market is brutally efficient — edges live in player props, teasers crossing key numbers (3 and 7), and early-week lines before sharp action arrives.
Why This Matters for Agents
The NFL generates more betting handle than any other sport in North America. An autonomous betting agent targeting NFL markets needs sport-specific models — generic approaches fail because NFL scoring is discrete (3s and 7s), the sample size is tiny (272 regular-season games per year), and the market is the sharpest in sports betting.
This is Layer 4 — Intelligence. The agent’s NFL module ingests team and player efficiency data, generates spread and total predictions, projects player props, and identifies correlation opportunities in same-game parlays. These predictions feed into the agent’s expected value framework and Kelly sizing module for position sizing. The efficiency data itself can be sourced via The Odds API for lines or scraped from public EPA/DVOA databases, then compared against live sportsbook prices tracked in the AgentBets Vig Index.
The Math
Team Efficiency Metrics
Three metrics form the foundation of any serious NFL model:
EPA/play (Expected Points Added per play): Measures the change in expected points on each play relative to the situation (down, distance, field position). A team averaging +0.15 EPA/play on offense adds 0.15 expected points per play above baseline. EPA captures both volume (a team that runs 65 plays sees more total EPA) and efficiency (a team that gains +0.20 EPA/play on fewer plays is more efficient per snap).
EPA(play) = EP_after - EP_before
where EP = expected points based on (down, distance, yard_line) lookup table
DVOA (Defense-adjusted Value Over Average): Football Outsiders’ metric comparing each play’s outcome to the league average for that situation, adjusted for opponent. Expressed as a percentage: +15% DVOA means a team is 15% better than league average. DVOA decomposes into offensive DVOA, defensive DVOA (lower is better for defenses), and special teams DVOA.
Success Rate: Binary metric — a play is “successful” if it gains at least 40% of needed yards on 1st down, 60% on 2nd down, or 100% on 3rd/4th down. Success rate correlates more strongly with future performance than yards per play because it is less influenced by explosive play variance.
Point Spread Modeling
The core spread prediction formula:
Predicted Margin = (Home_Off_EPA - Away_Def_EPA) x Expected_Plays/2
+ (Home_Def_EPA - Away_Off_EPA) x Expected_Plays/2
+ HFA
Simplified:
Predicted Margin = (Home_Net_EPA - Away_Net_EPA) x Expected_Plays/2 + HFA
where Net_EPA = Offensive EPA/play - Defensive EPA/play (from the team’s perspective, using opponent-adjusted values), Expected_Plays is the average total plays per game for the matchup (typically 120-140 combined), and HFA is home field advantage.
Home Field Advantage (HFA): The historical NFL HFA has declined from ~3 points pre-2020 to approximately 1.5-2.5 points in recent seasons. The COVID-era empty stadiums produced near-zero HFA, and the partial recovery suggests crowd noise accounts for roughly 1-1.5 points of the total effect. Altitude (Denver: +1.5 additional points), travel distance (cross-country: +0.5), and time zone disadvantage (+0.3 for West-to-East early kickoffs) create venue-specific adjustments.
HFA_adjusted = HFA_base + altitude_bonus + travel_penalty + timezone_penalty
NFL average HFA_base ≈ 2.0 points (2021-2025 seasons)
Denver altitude_bonus ≈ 1.5 points
Cross-country travel_penalty ≈ 0.5 points
Timezone disadvantage ≈ 0.3 points
Key Numbers
NFL scoring is discrete: touchdowns (6 + PAT = 7, or 6 + 2pt = 8), field goals (3), and safeties (2). This produces a non-uniform distribution of final margins. The key numbers, based on historical NFL data (2000-2025):
Margin Frequency Cumulative
3 15.6% —
7 9.4% —
6 5.5% —
10 5.4% —
4 4.8% —
14 4.6% —
1 4.3% —
0 1.0% (push)
Three and 7 dominate. An agent pricing NFL spreads must account for the probability mass concentrated at these margins. A spread of -2.5 versus -3 is not a half-point difference — it’s the difference between losing to every game that lands on exactly 3 (15.6% of games) versus pushing. This half-point is worth approximately 5-6 cents on a dollar bet.
Totals Modeling
Predicting the total combined score (Over/Under) uses pace and efficiency:
Predicted Total = Home_Off_Points_Per_Play x Expected_Plays/2
+ Away_Off_Points_Per_Play x Expected_Plays/2
+ Adjustments
A more precise formulation uses drive-level efficiency:
Predicted Total = (Home_Off_Drives x Home_Off_Pts_Per_Drive)
+ (Away_Off_Drives x Away_Off_Pts_Per_Drive)
where:
Drives ≈ Expected_Plays / Avg_Plays_Per_Drive
Pts_Per_Drive = f(EPA/play, RedZone_Efficiency, Turnover_Rate)
Game environment matters for totals: wind speed > 15 mph reduces passing efficiency by approximately 0.03 EPA/play; indoor games produce 1.5-2.0 more total points on average; games with temperatures below 32°F reduce totals by approximately 1.5 points.
Teaser Math
A teaser gives you additional points on the spread in exchange for parlaying multiple legs. The standard NFL teaser: 6 points, two teams, at -110 odds.
Break-even math for a two-team teaser at -110:
Payout on $110 bet = $210 ($110 stake + $100 profit)
Required probability = 110 / 210 = 52.38%
Since both legs must win: p_leg^2 = 0.5238
p_leg = sqrt(0.5238) = 0.7237
Each leg needs a 72.4% win rate to break even.
The Wong teaser strategy identifies the mathematically optimal teaser legs. A 6-point teaser is profitable when it crosses through the key numbers 3 and 7. The optimal windows:
Teaser Window Analysis (6-point teaser)
Original Spread → Teased Spread Key Numbers Crossed Historical Win %
-1.0 → +5.0 0, 3 ~79%
-1.5 → +4.5 0, 3 ~78%
-2.0 → +4.0 0, 3 ~77%
-2.5 → +3.5 0, 3 ~76%
+1.5 → +7.5 3, 7 ~80%
+2.0 → +8.0 3, 7 ~79%
+2.5 → +8.5 3, 7 ~78%
Break-even per leg: 72.4%
Wong windows exceed break-even by 4-8 percentage points.
The correlation between legs in a same-game scenario can make teasers even more attractive: if you tease the underdog and the under in the same game, these legs are positively correlated (low-scoring games favor underdogs), violating the independence assumption sportsbooks use when pricing teasers.
Player Prop Modeling
Player prop projections decompose into three components:
Projected Stat = Opportunity_Volume x Per_Opportunity_Efficiency x Opponent_Adjustment
Passing Yards:
Proj = Pass_Attempts x Yards_Per_Attempt x (Lg_Avg_DVOA / Opp_Pass_Def_DVOA)
Rushing Yards:
Proj = Rush_Attempts x Yards_Per_Carry x (Lg_Avg_DVOA / Opp_Rush_Def_DVOA)
Receiving Yards:
Proj = Targets x Yards_Per_Target x (Lg_Avg_DVOA / Opp_Pass_Def_DVOA)
where Opportunity_Volume is projected from the player’s snap share, team pace, and game script (a trailing team passes more, a leading team runs more), Per_Opportunity_Efficiency uses the player’s season-long or rolling average (regressed toward position mean early in season), and Opponent_Adjustment scales by the opponent’s defense DVOA for the relevant position group.
Game script adjustment is critical for props. The implied team total (derived from the spread and total) predicts game flow:
Home Implied Total = (Total + Spread) / 2 (using negative spread for favorites)
Away Implied Total = (Total - Spread) / 2
Example: Home -7, Total 48
Home Implied Total = (48 + 7) / 2 = 27.5
Away Implied Total = (48 - 7) / 2 = 20.5
A team with an implied total of 27.5 is expected to score ~4 touchdowns. Their QB will throw more, their RB may see game-script-adjusted volume, and their WRs benefit from positive game flow.
Same-Game Parlay Correlation
Sportsbooks price same-game parlay (SGP) legs as if they are independent. They are not. Key NFL correlations:
Correlation Matrix (approximate r values):
QB Pass Yds Team Points RB Rush Yds WR Rec Yds
Team Points +0.60 — +0.35 +0.45
QB Pass Yds — +0.60 -0.15 +0.70
QB Pass TDs +0.75 +0.70 -0.10 +0.40
RB Rush Yds -0.15 +0.35 — -0.10
Game Total +0.40 +0.75 +0.20 +0.35
Team Wins +0.20 +0.55 +0.30 +0.15
The exploitable combinations:
QB Passing Yards Over + Team to Win by 7+: r ≈ +0.40. Sportsbooks underestimate this correlation because blowouts often involve sustained passing (unlike the “run out the clock” narrative — teams actually pass more than expected in moderate blowout scenarios through 3 quarters).
WR Receiving Yards Over + QB Passing TDs Over: r ≈ +0.50. A QB having a high-TD game almost certainly throws for volume. The primary target benefits disproportionately.
RB Rush Yards Over + Team Win + Under: r ≈ +0.30. A low-scoring game the team wins implies ball control and rushing efficiency.
Worked Examples
Example 1: Week 8 Spread Prediction
Kansas City Chiefs at Buffalo Bills. Season EPA/play data (through Week 7):
Chiefs Offense EPA/play: +0.12
Chiefs Defense EPA/play: -0.08 (negative is good for defense)
Bills Offense EPA/play: +0.18
Bills Defense EPA/play: -0.05
Expected combined plays: 130
HFA for Buffalo: 2.0 (base) + 0.0 (no altitude/travel adjustment) = 2.0
The Bills are home. Predicted margin (from Buffalo’s perspective):
Bills_Net = Off(+0.18) - Def_opponent_faces(-0.08) → Bills off vs KC def
KC_Net = Off(+0.12) - Def_opponent_faces(-0.05) → KC off vs BUF def
Home margin = (Bills_Off_EPA - KC_Def_EPA) x 65 + (KC_Off_EPA - Bills_Def_EPA) x (-65) + HFA
= (0.18 - (-0.08)) x 65 - (0.12 - (-0.05)) x 65 + 2.0
= (0.26) x 65 - (0.17) x 65 + 2.0
= 16.9 - 11.05 + 2.0
= 7.85
Predicted line: Bills -7.85
If the market has Bills -6.5 at -110 on BetOnline, the model sees 1.35 points of value on the Bills. The agent checks if this exceeds the threshold for a positive EV bet given the market’s vig.
Example 2: Wong Teaser
The Odds API returns these lines:
Lions -2.5 (-110) at Bears
Packers +1.5 (-110) at Vikings
A 6-point teaser shifts these to:
Lions +3.5 (crosses through 0 and 3)
Packers +7.5 (crosses through 3 and 7)
Historical win rates for these windows: Lions leg ~76%, Packers leg ~80%.
Combined probability = 0.76 x 0.80 = 0.608
Break-even probability at -110 = 0.5238
Edge = 0.608 - 0.5238 = 0.0842 (8.4% edge)
EV per $110 bet = 0.608 x $100 - 0.392 x $110 = $60.80 - $43.12 = +$17.68
ROI = $17.68 / $110 = 16.1%
This is a strong Wong teaser. The agent at Bovada or BetOnline would flag this as a high-EV play.
Example 3: Player Prop — Patrick Mahomes Passing Yards
Mahomes season avg: 38.2 attempts/game, 7.4 yards/attempt
Opponent (Raiders) pass defense DVOA: +12.5% (25th ranked, bad)
League average pass defense DVOA: 0.0% (by definition)
Opponent adjustment = 1 + (Opp_DVOA / 100) = 1 + 0.125 = 1.125
Projected yards = 38.2 x 7.4 x 1.125 = 318.2 yards
Sportsbook line on BookMaker: 289.5 yards (-115 over / -105 under)
Model projection: 318.2 yards
Implied probability of over (assuming normal distribution, std dev ≈ 55 yards):
z = (289.5 - 318.2) / 55 = -0.522
P(over) = 1 - Φ(-0.522) = 0.699 (69.9%)
Break-even at -115: 115/215 = 53.5%
Edge: 69.9% - 53.5% = 16.4%
Implementation
import numpy as np
import pandas as pd
from scipy import stats
from dataclasses import dataclass
@dataclass
class TeamEfficiency:
"""Season-level team efficiency metrics."""
team: str
off_epa_per_play: float
def_epa_per_play: float # negative = good defense
off_success_rate: float
def_success_rate: float
plays_per_game: float
off_dvoa: float # percentage, e.g., 15.0 = +15%
def_dvoa: float # percentage, negative = good
pass_def_dvoa: float
rush_def_dvoa: float
@dataclass
class SpreadPrediction:
"""Output of the spread model."""
home_team: str
away_team: str
predicted_margin: float # positive = home favored
predicted_total: float
home_implied_total: float
away_implied_total: float
confidence_interval_90: tuple[float, float]
def predict_spread(
home: TeamEfficiency,
away: TeamEfficiency,
hfa: float = 2.0,
altitude_bonus: float = 0.0,
travel_penalty: float = 0.0,
model_std: float = 13.5
) -> SpreadPrediction:
"""
Predict NFL point spread from team efficiency metrics.
Args:
home: Home team efficiency data
away: Away team efficiency data
hfa: Base home field advantage in points
altitude_bonus: Additional points for altitude (e.g., Denver +1.5)
travel_penalty: Points for cross-country travel
model_std: Standard deviation of prediction error (NFL ≈ 13.5 pts)
Returns:
SpreadPrediction with margin, total, and confidence interval
"""
total_hfa = hfa + altitude_bonus + travel_penalty
avg_plays = (home.plays_per_game + away.plays_per_game) / 2
half_plays = avg_plays / 2
# Home offense vs away defense
home_off_component = (home.off_epa_per_play - away.def_epa_per_play) * half_plays
# Away offense vs home defense
away_off_component = (away.off_epa_per_play - home.def_epa_per_play) * half_plays
predicted_margin = home_off_component - away_off_component + total_hfa
# Total prediction using points per play
home_points = (home.off_epa_per_play - away.def_epa_per_play + 0.37) * half_plays
away_points = (away.off_epa_per_play - home.def_epa_per_play + 0.37) * half_plays
predicted_total = max(home_points + away_points, 30.0)
home_implied = (predicted_total + predicted_margin) / 2
away_implied = (predicted_total - predicted_margin) / 2
ci_90 = (
predicted_margin - 1.645 * model_std,
predicted_margin + 1.645 * model_std
)
return SpreadPrediction(
home_team=home.team,
away_team=away.team,
predicted_margin=round(predicted_margin, 1),
predicted_total=round(predicted_total, 1),
home_implied_total=round(home_implied, 1),
away_implied_total=round(away_implied, 1),
confidence_interval_90=(round(ci_90[0], 1), round(ci_90[1], 1))
)
def evaluate_teaser(
spreads: list[float],
teaser_points: float = 6.0,
odds: int = -110,
key_number_rates: dict[int, float] | None = None
) -> dict:
"""
Evaluate a multi-team NFL teaser for profitability.
Args:
spreads: Original spreads for each leg (negative = favorite)
teaser_points: Points added to each spread (standard: 6)
odds: American odds for the teaser (standard: -110)
key_number_rates: Historical frequency of margins at key numbers
Returns:
Dictionary with teaser analysis including EV and edge
"""
if key_number_rates is None:
key_number_rates = {
0: 0.010, 1: 0.043, 2: 0.035, 3: 0.156,
4: 0.048, 5: 0.030, 6: 0.055, 7: 0.094,
8: 0.028, 9: 0.018, 10: 0.054, 13: 0.032, 14: 0.046
}
# Break-even calculation
if odds < 0:
break_even_prob = abs(odds) / (abs(odds) + 100)
else:
break_even_prob = 100 / (odds + 100)
n_legs = len(spreads)
per_leg_break_even = break_even_prob ** (1 / n_legs)
leg_analysis = []
for spread in spreads:
teased = spread + teaser_points
numbers_crossed = []
for key in [0, 3, 7]:
if spread < key <= teased or teased <= key < spread:
numbers_crossed.append(key)
probability_gained = sum(
key_number_rates.get(abs(n), 0) for n in numbers_crossed
)
# Base win rate estimate: starts at 50% for pick'em, adjusts
base_win_rate = 0.50 + (teased * 0.03) # rough linear approx
base_win_rate = min(max(base_win_rate, 0.50), 0.90)
# Add probability mass from key numbers crossed
estimated_win_rate = min(base_win_rate + probability_gained * 0.5, 0.95)
leg_analysis.append({
"original_spread": spread,
"teased_spread": teased,
"key_numbers_crossed": numbers_crossed,
"estimated_win_rate": round(estimated_win_rate, 3),
"exceeds_breakeven": estimated_win_rate > per_leg_break_even
})
combined_prob = np.prod([leg["estimated_win_rate"] for leg in leg_analysis])
if odds < 0:
profit_on_win = 100
risk = abs(odds)
else:
profit_on_win = odds
risk = 100
ev = combined_prob * profit_on_win - (1 - combined_prob) * risk
roi = ev / risk
return {
"legs": leg_analysis,
"combined_probability": round(combined_prob, 4),
"break_even_probability": round(break_even_prob, 4),
"per_leg_break_even": round(per_leg_break_even, 4),
"edge": round(combined_prob - break_even_prob, 4),
"ev_per_100_risked": round(ev / risk * 100, 2),
"is_positive_ev": combined_prob > break_even_prob
}
def project_player_prop(
season_attempts: float,
yards_per_attempt: float,
opp_dvoa: float,
prop_line: float,
prop_odds: int = -115,
std_dev: float = 55.0,
stat_type: str = "passing_yards"
) -> dict:
"""
Project a player stat and evaluate the prop bet.
Args:
season_attempts: Player's average attempts per game
yards_per_attempt: Player's yards per attempt
opp_dvoa: Opponent defense DVOA for the relevant stat (positive = bad D)
prop_line: Sportsbook over/under line
prop_odds: American odds on the over
std_dev: Standard deviation of the stat distribution
stat_type: Type of stat for labeling
Returns:
Dictionary with projection, edge, and EV analysis
"""
# Opponent adjustment: DVOA is % above average, so bad defense = positive
opp_multiplier = 1 + (opp_dvoa / 100)
projection = season_attempts * yards_per_attempt * opp_multiplier
# Probability of going over the line (normal approximation)
z_score = (prop_line - projection) / std_dev
prob_over = 1 - stats.norm.cdf(z_score)
prob_under = stats.norm.cdf(z_score)
# Break-even probability
if prop_odds < 0:
break_even = abs(prop_odds) / (abs(prop_odds) + 100)
profit_on_win = 100
risk = abs(prop_odds)
else:
break_even = 100 / (prop_odds + 100)
profit_on_win = prop_odds
risk = 100
edge_over = prob_over - break_even
ev_over = prob_over * profit_on_win - (1 - prob_over) * risk
return {
"stat_type": stat_type,
"projection": round(projection, 1),
"prop_line": prop_line,
"prob_over": round(prob_over, 3),
"prob_under": round(prob_under, 3),
"break_even": round(break_even, 3),
"edge_over": round(edge_over, 3),
"ev_per_100_risked_over": round(ev_over / risk * 100, 2),
"recommendation": "OVER" if edge_over > 0.02 else ("UNDER" if edge_over < -0.05 else "PASS"),
"z_score": round(z_score, 3)
}
def sgp_correlation_ev(
leg_probs: list[float],
correlation_matrix: np.ndarray,
sgp_odds: float,
stake: float = 100.0
) -> dict:
"""
Estimate same-game parlay EV accounting for correlation.
Uses a Gaussian copula approximation to estimate the joint probability
of all legs winning, given pairwise correlations.
Args:
leg_probs: Marginal win probability for each leg
correlation_matrix: Pairwise correlation matrix (n x n)
sgp_odds: Decimal odds offered by sportsbook (e.g., 3.5)
stake: Bet amount
Returns:
Dictionary with independent vs correlated probability and EV
"""
n = len(leg_probs)
# Independent probability (what sportsbook assumes)
independent_prob = np.prod(leg_probs)
# Gaussian copula approximation for correlated joint probability
# Convert marginal probs to normal quantiles
normal_quantiles = [stats.norm.ppf(p) for p in leg_probs]
# Simulate from multivariate normal with correlation structure
np.random.seed(42)
n_sims = 100_000
samples = np.random.multivariate_normal(
mean=np.zeros(n),
cov=correlation_matrix,
size=n_sims
)
# Convert back to uniform, then check if each leg wins
uniform_samples = stats.norm.cdf(samples)
wins = np.all(uniform_samples < np.array(leg_probs), axis=1)
correlated_prob = np.mean(wins)
# EV calculation
payout = stake * sgp_odds
ev_independent = independent_prob * payout - stake
ev_correlated = correlated_prob * payout - stake
return {
"independent_probability": round(independent_prob, 4),
"correlated_probability": round(correlated_prob, 4),
"probability_uplift": round(correlated_prob - independent_prob, 4),
"sgp_odds_decimal": sgp_odds,
"implied_probability": round(1 / sgp_odds, 4),
"ev_if_independent": round(ev_independent, 2),
"ev_with_correlation": round(ev_correlated, 2),
"edge_from_correlation": round(
(correlated_prob - 1 / sgp_odds) * 100, 2
),
"is_positive_ev": correlated_prob > (1 / sgp_odds)
}
# --- Example usage ---
if __name__ == "__main__":
# Spread prediction
bills = TeamEfficiency(
team="BUF", off_epa_per_play=0.18, def_epa_per_play=-0.05,
off_success_rate=0.50, def_success_rate=0.42,
plays_per_game=66, off_dvoa=22.5, def_dvoa=-8.3,
pass_def_dvoa=-12.0, rush_def_dvoa=-4.5
)
chiefs = TeamEfficiency(
team="KC", off_epa_per_play=0.12, def_epa_per_play=-0.08,
off_success_rate=0.47, def_success_rate=0.40,
plays_per_game=64, off_dvoa=18.0, def_dvoa=-12.0,
pass_def_dvoa=-15.0, rush_def_dvoa=-9.0
)
pred = predict_spread(bills, chiefs, hfa=2.0)
print(f"=== {pred.away_team} at {pred.home_team} ===")
print(f"Predicted spread: {pred.home_team} {pred.predicted_margin:+.1f}")
print(f"Predicted total: {pred.predicted_total}")
print(f"90% CI: [{pred.confidence_interval_90[0]:+.1f}, {pred.confidence_interval_90[1]:+.1f}]")
print()
# Teaser evaluation
teaser = evaluate_teaser([-2.5, 1.5], teaser_points=6.0, odds=-110)
print("=== Wong Teaser ===")
for leg in teaser["legs"]:
print(f" {leg['original_spread']:+.1f} → {leg['teased_spread']:+.1f} "
f"Keys crossed: {leg['key_numbers_crossed']} "
f"Win rate: {leg['estimated_win_rate']:.1%}")
print(f" Combined: {teaser['combined_probability']:.1%} "
f"Edge: {teaser['edge']:+.1%} "
f"EV/100: ${teaser['ev_per_100_risked']:.2f}")
print()
# Player prop
prop = project_player_prop(
season_attempts=38.2, yards_per_attempt=7.4,
opp_dvoa=12.5, prop_line=289.5, prop_odds=-115,
std_dev=55.0, stat_type="passing_yards"
)
print(f"=== Player Prop: {prop['stat_type']} ===")
print(f" Projection: {prop['projection']} yds vs line {prop['prop_line']}")
print(f" P(over): {prop['prob_over']:.1%} Edge: {prop['edge_over']:+.1%}")
print(f" Recommendation: {prop['recommendation']}")
print()
# SGP correlation
corr_matrix = np.array([
[1.0, 0.6],
[0.6, 1.0]
])
sgp = sgp_correlation_ev(
leg_probs=[0.65, 0.60],
correlation_matrix=corr_matrix,
sgp_odds=2.80
)
print("=== Same-Game Parlay (2 legs, r=0.6) ===")
print(f" Independent prob: {sgp['independent_probability']:.1%}")
print(f" Correlated prob: {sgp['correlated_probability']:.1%}")
print(f" EV (correlated): ${sgp['ev_with_correlation']:.2f} per $100")
print(f" Positive EV: {sgp['is_positive_ev']}")
Limitations and Edge Cases
Small sample sizes destroy signal. The NFL regular season is 18 weeks. Through Week 4, EPA/play estimates have massive variance. An agent must regress team metrics toward the league mean early in the season. A reasonable schedule: 75% regression to mean in Weeks 1-3, 50% in Weeks 4-6, 25% in Weeks 7-10, 0% from Week 11 onward. Even at season’s end, 17 games is a tiny sample — one blowout can shift a team’s EPA/play by 0.02.
Injuries are the largest unmodeled variable. A starting QB injury changes a team’s EPA/play by 0.05-0.15 overnight. No efficiency model captures this without a real-time injury-adjusted layer. An agent must monitor injury reports (typically released Wednesday, Thursday, and Friday during the NFL week) and apply manual adjustments.
The side market is brutally efficient. NFL point spreads close within 1 point of the true probability over 97% of the time. The median sharp model generates 1-2% ROI on sides — barely above the vig. An agent targeting NFL sides needs sub-1% edge detection, low vig sportsbooks (check the Vig Index), and high volume to overcome variance.
Player props have wider edges but lower limits. Sportsbooks know their prop lines are softer, so they limit winning prop bettors aggressively. An agent running a profitable prop model on Bovada or MyBookie will face reduced limits within weeks. Diversifying across books (including sharp-friendly BookMaker) extends the window.
Teaser math assumes historical distributions hold. If the NFL’s scoring distribution shifts (rule changes, offensive evolution), the key number frequencies change. The 2-point conversion push in recent years slightly reduces the frequency of margin = 7. An agent should recalculate key number frequencies each season.
Correlation estimates are noisy. The r = 0.60 between QB passing yards and team points is a historical average. Individual QBs deviate: a run-heavy team’s QB might show r = 0.40, while a pass-first team’s QB shows r = 0.75. Use team-specific correlations when the sample size supports it (3+ seasons of the same QB-system combination).
FAQ
How do you build an NFL point spread model?
An NFL point spread model starts with team efficiency metrics — EPA/play, DVOA, and success rate — as inputs. The core formula is Predicted Spread = (Home Offensive EPA/play - Away Defensive EPA/play) x Expected Plays/2 - (Away Offensive EPA/play - Home Defensive EPA/play) x Expected Plays/2 + Home Field Advantage. HFA in the modern NFL is approximately 1.5-2.5 points. Regress efficiency inputs toward the mean early in the season when sample sizes are small.
What are the key numbers in NFL point spreads?
The key numbers are 3 and 7 because NFL games are decided by field goals (3 points) and touchdowns with extra points (7 points). Approximately 15.6% of NFL games land on a margin of exactly 3, and 9.4% land on exactly 7. These numbers make teasing through them mathematically valuable — a half-point around 3 is worth approximately 5-6 cents on the dollar.
How does teaser math work in NFL betting?
A standard 6-point NFL teaser shifts your spread by 6 points and requires all legs to win. A two-team teaser at -110 requires each leg to win at approximately 72.4% to break even (since both must hit: 0.7237^2 = 0.5238). The Wong teaser strategy targets spreads from -1 to -2.5 and +1.5 to +2.5, which when teased cross through both 3 and 7, capturing roughly 25% additional probability mass.
How do you model NFL player props mathematically?
Player prop modeling decomposes into three factors: opportunity volume (target share, snap percentage, rush attempts), per-opportunity efficiency (yards per target, yards per carry), and opponent adjustment (defense DVOA by position group). The projection formula is Projected Stat = Opportunity Volume x Efficiency x Opponent Multiplier. For passing yards: Projected = Attempts x Yards/Attempt x (1 + Opponent Pass DVOA / 100).
How does the Elo rating system connect to NFL modeling?
Elo ratings provide a power ranking foundation that captures long-term team strength. An Elo difference of 25 points corresponds to roughly 1 point of spread. Elo can supplement EPA-based models by providing stable baselines early in the season when efficiency sample sizes are small. See the Elo Ratings and Power Rankings guide for the full mathematical derivation and implementation.
What’s Next
This guide covers NFL-specific modeling. The techniques here connect to several other guides in the series:
- Power rankings as model input: The Elo Ratings and Power Rankings guide provides the rating system that feeds into spread predictions as a complementary signal to EPA.
- Regression techniques for NFL features: Regression Models for Sports Betting covers the linear, logistic, and ridge regression methods used to weight EPA, DVOA, and situational factors.
- Soccer equivalent: The Expected Goals (xG) Betting Model applies analogous efficiency decomposition to football/soccer, using shot-level data instead of play-level EPA.
- Sizing your NFL bets: Run your spread edge through the Kelly Criterion to determine optimal position sizes given the high variance of NFL outcomes.
- Full agent pipeline: The Agent Betting Stack shows how the NFL intelligence module connects to wallet management, data ingestion, and execution layers.
- Finding the best lines: Compare vig across sportsbooks for NFL markets at the AgentBets Vig Index — even 0.5% vig reduction compounds significantly over a full season.
