The Poisson distribution models goal-scoring with P(k) = (λ^k × e^(-λ)) / k!, where λ is the expected goals per match. Build a scoreline probability matrix by computing P(Home=i) × P(Away=j) for every combination, then aggregate into match result, over/under, and correct score probabilities. Compare against sportsbook lines to find +EV bets.
Why This Matters for Agents
The Poisson distribution is the single most important mathematical model for an autonomous agent betting on soccer, hockey, or baseball. It converts team statistics into scoreline probabilities, which convert into fair odds for every market type — match result, over/under, correct score, Asian handicap, and both-teams-to-score.
This is Layer 4 — Intelligence. An agent’s decision pipeline works like this: pull live odds from The Odds API or offshore sportsbook APIs, estimate expected goals (λ) from team data, build the Poisson scoreline matrix, convert to fair odds, compare against market odds, and flag any bet where the agent’s edge exceeds a threshold. The Poisson model is the core of that pipeline for scoring-based sports. Without it, the agent has no probability estimate to feed into its expected value calculation or Kelly sizing.
The Math
The Poisson Distribution
The Poisson distribution models the number of events occurring in a fixed interval when three conditions hold:
- Independence: Each event occurs independently of all others.
- Constant rate: The average rate of events (λ) is constant across the interval.
- No simultaneity: Two events cannot occur at the exact same instant.
The probability mass function:
P(k) = (λ^k × e^(-λ)) / k!
where k is the number of events (goals) and λ is the expected number of events (expected goals per match).
Variable definitions: k = 0, 1, 2, 3, … (non-negative integers), λ > 0 (the rate parameter, also equal to both the mean and variance of the distribution), e ≈ 2.71828 (Euler’s number).
For λ = 1.5 (a typical away team’s expected goals in soccer):
P(0 goals) = (1.5^0 × e^(-1.5)) / 0! = 0.2231 (22.3%)
P(1 goal) = (1.5^1 × e^(-1.5)) / 1! = 0.3347 (33.5%)
P(2 goals) = (1.5^2 × e^(-1.5)) / 2! = 0.2510 (25.1%)
P(3 goals) = (1.5^3 × e^(-1.5)) / 3! = 0.1255 (12.6%)
P(4 goals) = (1.5^4 × e^(-1.5)) / 4! = 0.0471 (4.7%)
P(5+) = 1 - sum of above = 0.0186 (1.9%)
Why Soccer Goals Follow Poisson (Approximately)
Soccer goals satisfy the three Poisson conditions better than almost any other sporting event:
Independence: Goals in soccer are largely independent. Scoring a goal at minute 23 does not directly cause or prevent a goal at minute 67. The correlation is weak — there is some tactical shift after goals (trailing teams attack more), but the effect is small enough that the independence assumption holds at the match level.
Constant rate: Over 90 minutes, the average scoring rate is approximately constant. Yes, more goals are scored in the second half (fatigue, substitutions, tactical changes), but the deviation from uniformity is small enough that the Poisson approximation remains useful.
No simultaneity: Two goals cannot be scored at the exact same instant. The ball is in one place. One team scores at a time.
The fit is not perfect — and we will address the specific failure modes below — but it is good enough that every major sportsbook uses Poisson-derived models as a baseline.
Estimating λ: Attack and Defense Ratings
The key input to the Poisson model is λ — the expected goals for each team in a specific match. The standard estimation method uses attack strength and defense strength ratings relative to league averages.
For a match between Home Team and Away Team:
λ_home = AttackStrength_home × DefenseWeakness_away × LeagueAvg_home_goals
λ_away = AttackStrength_away × DefenseWeakness_home × LeagueAvg_away_goals
Where:
AttackStrength_home = (Home team's home goals scored / Home games played) / League avg home goals per game
DefenseWeakness_away = (Away team's away goals conceded / Away games played) / League avg away goals per game
And symmetrically for the away team’s λ.
Worked Derivation: Premier League Example
Using 2024-25 Premier League data (through matchweek 30):
League averages (per game):
Home goals scored: 1.55
Away goals scored: 1.20
Arsenal (home):
Home goals scored: 2.20 per game → AttackStrength = 2.20 / 1.55 = 1.419
Home goals conceded: 0.60 per game → DefenseStrength = 0.60 / 1.20 = 0.500
Wolves (away):
Away goals scored: 0.80 per game → AttackStrength = 0.80 / 1.20 = 0.667
Away goals conceded: 2.00 per game → DefenseWeakness = 2.00 / 1.55 = 1.290
Arsenal λ_home = 1.419 × 1.290 × 1.55 = 2.837
Wolves λ_away = 0.667 × 0.500 × 1.20 = 0.400
Arsenal’s expected goals: 2.84. Wolves’ expected goals: 0.40. This passes the smell test — Arsenal at home against a weak away side should produce a lopsided λ ratio.
The Scoreline Probability Matrix
With λ_home and λ_away, compute the joint probability of every scoreline. Under the independence assumption:
P(Home = i, Away = j) = Poisson(i; λ_home) × Poisson(j; λ_away)
Build a matrix from 0-0 through at least 6-6 (goals above 6 are negligible for soccer):
Arsenal (λ=2.84) vs Wolves (λ=0.40) — Scoreline Probabilities
Wolves 0 Wolves 1 Wolves 2 Wolves 3 Wolves 4
Ars 0 3.9% 1.6% 0.3% 0.0% 0.0%
Ars 1 11.1% 4.4% 0.9% 0.1% 0.0%
Ars 2 15.7% 6.3% 1.3% 0.2% 0.0%
Ars 3 14.9% 6.0% 1.2% 0.2% 0.0%
Ars 4 10.6% 4.2% 0.8% 0.1% 0.0%
Ars 5 6.0% 2.4% 0.5% 0.1% 0.0%
Ars 6 2.8% 1.1% 0.2% 0.0% 0.0%
Aggregating:
Arsenal Win: 82.4%
Draw: 10.5%
Wolves Win: 7.1%
Over/Under Markets
To price an over/under 2.5 goals market, sum all scoreline probabilities where total goals >= 3:
P(Over 2.5) = Σ P(i, j) for all i + j >= 3
P(Under 2.5) = Σ P(i, j) for all i + j <= 2
For Arsenal vs Wolves (λ_home=2.84, λ_away=0.40):
P(Under 2.5) = P(0,0) + P(1,0) + P(2,0) + P(0,1) + P(1,1) + P(0,2)
= 3.9% + 11.1% + 15.7% + 1.6% + 4.4% + 0.3%
= 37.0%
P(Over 2.5) = 1 - 37.0% = 63.0%
Fair decimal odds: Over 2.5 at 1.587, Under 2.5 at 2.703. If BetOnline offers Over 2.5 at 1.70 (-143 American), the agent has found a +EV line — the model says fair price is 1.587, and the book is offering more.
Asian Handicap Markets
Asian handicaps shift the scoreline. For Arsenal -1.5 Asian handicap, sum all scoreline probabilities where Arsenal wins by 2+ goals:
P(Arsenal -1.5) = Σ P(i, j) for all i - j >= 2
= 15.7% + 14.9% + 6.3% + 10.6% + 6.0% + 4.2% + 2.8% + ...
= 64.3%
Fair decimal odds: 1.555. If the market offers 1.65, there is edge.
The Dixon-Coles Correction
The basic Poisson model has a known flaw: it underestimates draws and low-scoring results in soccer. Dixon and Coles (1997) introduced a dependence parameter ρ (rho) that adjusts the joint probability of four specific scorelines:
P*(0,0) = P(0,0) × (1 + λ_home × λ_away × ρ)
P*(1,0) = P(1,0) × (1 - λ_away × ρ)
P*(0,1) = P(0,1) × (1 - λ_home × ρ)
P*(1,1) = P(1,1) × (1 + ρ)
Where P(i,j) is the original Poisson probability, P*(i,j) is the corrected probability, and ρ is estimated from historical data (typically ρ ≈ -0.03 to -0.10 for top European leagues).
A negative ρ increases the probability of 0-0 and 1-1 draws while decreasing 1-0 and 0-1 results. This matches the observed data: in real soccer, draws occur more often than the independent Poisson model predicts because teams adjust tactics when level (defending a draw, slowing pace).
All other scorelines (2-0, 2-1, 3-1, etc.) remain unchanged. The correction is small — typically shifting probabilities by 0.5-2 percentage points — but in betting markets where edges are 2-5%, this correction matters.
Worked Examples
Example 1: Premier League Match — Market Comparison
Match: Liverpool vs Newcastle, 2024-25 Premier League
Team stats (season averages through matchweek 28):
Liverpool (home): 2.35 goals scored, 0.71 conceded per home game
Newcastle (away): 1.13 goals scored, 1.47 conceded per away game
League avg: 1.55 home goals, 1.20 away goals
λ_Liverpool = (2.35/1.55) × (1.47/1.55) × 1.55 = 1.516 × 0.948 × 1.55 = 2.227
λ_Newcastle = (1.13/1.20) × (0.71/1.20) × 1.20 = 0.942 × 0.592 × 1.20 = 0.669
Model output:
Liverpool Win: 70.2% → Fair odds: 1.425
Draw: 17.8% → Fair odds: 5.618
Newcastle Win: 12.0% → Fair odds: 8.333
Over 2.5: 52.8% → Fair odds: 1.894
Under 2.5: 47.2% → Fair odds: 2.119
Market lines on Bovada (typical pricing):
Liverpool ML: -200 (implied 66.7%) → Model says 70.2% → +EV on Liverpool
Draw: +340 (implied 22.7%) → Model says 17.8% → No edge
Newcastle ML: +500 (implied 16.7%) → Model says 12.0% → No edge
Over 2.5: -125 (implied 55.6%) → Model says 52.8% → No edge
Under 2.5: +105 (implied 48.8%) → Model says 47.2% → No edge
The agent flags Liverpool ML as the only +EV bet. The model gives Liverpool 70.2% vs the market’s 66.7% — that’s a 3.5 percentage point edge, which translates to an EV of +$0.035 per dollar wagered.
Example 2: MLS Match — Over/Under Focus
Match: LAFC vs Inter Miami, MLS 2025
λ_LAFC = 1.85 (home expected goals)
λ_Miami = 1.40 (away expected goals)
Combined λ = 3.25
P(Over 2.5 goals) = 1 - P(0 goals) - P(1 goal) - P(2 goals)
where total goals ~ Poisson(3.25):
P(0) = 3.9%
P(1) = 12.6%
P(2) = 20.5%
P(Under 2.5) = 37.0%
P(Over 2.5) = 63.0%
Note: this shortcut using the total λ gives an approximation. The exact calculation from the scoreline matrix is slightly different because the sum of two independent Poissons is itself Poisson only when computing the total — but for over/under markets the shortcut is accurate to within ~1%.
If MyBookie offers Over 2.5 at -140 (implied 58.3%), the model sees 63.0% — a 4.7 percentage point edge.
Implementation
import numpy as np
from scipy.stats import poisson
from dataclasses import dataclass
@dataclass
class TeamStats:
"""Season statistics for a single team."""
name: str
home_goals_scored: float # per game average at home
home_goals_conceded: float # per game average at home
away_goals_scored: float # per game average away
away_goals_conceded: float # per game average away
@dataclass
class MatchOdds:
"""Poisson model output for a single match."""
home_win: float
draw: float
away_win: float
over_2_5: float
under_2_5: float
home_lambda: float
away_lambda: float
scoreline_matrix: np.ndarray
most_likely_score: tuple[int, int]
def estimate_lambda(
home_team: TeamStats,
away_team: TeamStats,
league_avg_home_goals: float = 1.55,
league_avg_away_goals: float = 1.20,
) -> tuple[float, float]:
"""
Estimate expected goals (lambda) for each team in a specific match.
Uses attack/defense strength ratings relative to league averages.
Args:
home_team: Season stats for the home team.
away_team: Season stats for the away team.
league_avg_home_goals: League-wide average home goals per game.
league_avg_away_goals: League-wide average away goals per game.
Returns:
Tuple of (home_lambda, away_lambda).
"""
home_attack = home_team.home_goals_scored / league_avg_home_goals
away_defense_weakness = away_team.away_goals_conceded / league_avg_home_goals
away_attack = away_team.away_goals_scored / league_avg_away_goals
home_defense_weakness = home_team.home_goals_conceded / league_avg_away_goals
lambda_home = home_attack * away_defense_weakness * league_avg_home_goals
lambda_away = away_attack * home_defense_weakness * league_avg_away_goals
return lambda_home, lambda_away
def build_scoreline_matrix(
lambda_home: float,
lambda_away: float,
max_goals: int = 7,
rho: float = 0.0,
) -> np.ndarray:
"""
Build the joint scoreline probability matrix.
Args:
lambda_home: Expected goals for home team.
lambda_away: Expected goals for away team.
max_goals: Maximum goals to consider per team (0 to max_goals-1).
rho: Dixon-Coles dependence parameter. 0 = no correction.
Typical values: -0.03 to -0.10.
Returns:
Matrix of shape (max_goals, max_goals) where entry [i, j]
is P(Home=i, Away=j).
"""
home_probs = poisson.pmf(np.arange(max_goals), lambda_home)
away_probs = poisson.pmf(np.arange(max_goals), lambda_away)
# Outer product gives independent joint probabilities
matrix = np.outer(home_probs, away_probs)
# Apply Dixon-Coles correction to low-scoring outcomes
if rho != 0.0:
matrix[0, 0] *= 1 + lambda_home * lambda_away * rho
matrix[1, 0] *= 1 - lambda_away * rho
matrix[0, 1] *= 1 - lambda_home * rho
matrix[1, 1] *= 1 + rho
# Renormalize so probabilities sum to 1
matrix /= matrix.sum()
return matrix
def match_probabilities(matrix: np.ndarray) -> dict[str, float]:
"""
Extract match result probabilities from a scoreline matrix.
Args:
matrix: Scoreline probability matrix of shape (N, N).
Returns:
Dict with home_win, draw, away_win, over_2_5, under_2_5 probabilities.
"""
max_goals = matrix.shape[0]
home_win = 0.0
draw = 0.0
away_win = 0.0
under_2_5 = 0.0
for i in range(max_goals):
for j in range(max_goals):
p = matrix[i, j]
if i > j:
home_win += p
elif i == j:
draw += p
else:
away_win += p
if i + j <= 2:
under_2_5 += p
over_2_5 = 1.0 - under_2_5
return {
"home_win": home_win,
"draw": draw,
"away_win": away_win,
"over_2_5": over_2_5,
"under_2_5": under_2_5,
}
def asian_handicap_prob(
matrix: np.ndarray,
line: float,
side: str = "home",
) -> float:
"""
Calculate Asian handicap probability from scoreline matrix.
Args:
matrix: Scoreline probability matrix.
line: Handicap line (e.g., -1.5 means home must win by 2+).
side: 'home' or 'away'.
Returns:
Probability of covering the handicap.
"""
max_goals = matrix.shape[0]
prob = 0.0
for i in range(max_goals):
for j in range(max_goals):
if side == "home":
margin = i - j
else:
margin = j - i
if margin + line > 0:
prob += matrix[i, j]
elif margin + line == 0:
prob += matrix[i, j] * 0.5 # push = half win
return prob
def prob_to_decimal_odds(prob: float) -> float:
"""Convert probability to fair decimal odds."""
if prob <= 0:
return float("inf")
return 1.0 / prob
def find_value_bets(
model_probs: dict[str, float],
market_odds: dict[str, float],
min_edge: float = 0.02,
) -> list[dict]:
"""
Compare model probabilities against market odds to find +EV bets.
Args:
model_probs: Dict of outcome -> model probability.
market_odds: Dict of outcome -> decimal odds offered by sportsbook.
min_edge: Minimum edge (as fraction) to flag a bet.
Returns:
List of value bet dicts with outcome, model_prob, market_prob, edge, ev.
"""
value_bets = []
for outcome, model_prob in model_probs.items():
if outcome not in market_odds:
continue
offered_odds = market_odds[outcome]
market_implied = 1.0 / offered_odds
edge = model_prob - market_implied
if edge >= min_edge:
ev_per_unit = model_prob * (offered_odds - 1) - (1 - model_prob)
value_bets.append({
"outcome": outcome,
"model_prob": model_prob,
"market_implied": market_implied,
"offered_odds": offered_odds,
"fair_odds": prob_to_decimal_odds(model_prob),
"edge": edge,
"ev_per_unit": ev_per_unit,
})
return sorted(value_bets, key=lambda x: x["edge"], reverse=True)
def predict_match(
home_team: TeamStats,
away_team: TeamStats,
league_avg_home: float = 1.55,
league_avg_away: float = 1.20,
rho: float = -0.05,
max_goals: int = 7,
) -> MatchOdds:
"""
Full match prediction pipeline.
Args:
home_team: Home team season stats.
away_team: Away team season stats.
league_avg_home: League average home goals per game.
league_avg_away: League average away goals per game.
rho: Dixon-Coles correction parameter.
max_goals: Max goals per team in the matrix.
Returns:
MatchOdds with all probabilities and the scoreline matrix.
"""
lambda_h, lambda_a = estimate_lambda(
home_team, away_team, league_avg_home, league_avg_away
)
matrix = build_scoreline_matrix(lambda_h, lambda_a, max_goals, rho)
probs = match_probabilities(matrix)
# Find most likely scoreline
idx = np.unravel_index(np.argmax(matrix), matrix.shape)
return MatchOdds(
home_win=probs["home_win"],
draw=probs["draw"],
away_win=probs["away_win"],
over_2_5=probs["over_2_5"],
under_2_5=probs["under_2_5"],
home_lambda=lambda_h,
away_lambda=lambda_a,
scoreline_matrix=matrix,
most_likely_score=(int(idx[0]), int(idx[1])),
)
# --- Full example: Arsenal vs Wolves ---
if __name__ == "__main__":
arsenal = TeamStats(
name="Arsenal",
home_goals_scored=2.20,
home_goals_conceded=0.60,
away_goals_scored=1.80,
away_goals_conceded=0.93,
)
wolves = TeamStats(
name="Wolves",
home_goals_scored=1.20,
home_goals_conceded=1.53,
away_goals_scored=0.80,
away_goals_conceded=2.00,
)
result = predict_match(arsenal, wolves, rho=-0.05)
print(f"Arsenal (home) vs Wolves (away)")
print(f"λ_Arsenal = {result.home_lambda:.3f}")
print(f"λ_Wolves = {result.away_lambda:.3f}")
print(f"Most likely score: {result.most_likely_score[0]}-{result.most_likely_score[1]}")
print()
print(f"Arsenal Win: {result.home_win:.1%} (fair odds: {prob_to_decimal_odds(result.home_win):.3f})")
print(f"Draw: {result.draw:.1%} (fair odds: {prob_to_decimal_odds(result.draw):.3f})")
print(f"Wolves Win: {result.away_win:.1%} (fair odds: {prob_to_decimal_odds(result.away_win):.3f})")
print(f"Over 2.5: {result.over_2_5:.1%} (fair odds: {prob_to_decimal_odds(result.over_2_5):.3f})")
print(f"Under 2.5: {result.under_2_5:.1%} (fair odds: {prob_to_decimal_odds(result.under_2_5):.3f})")
print()
# Compare against hypothetical BetOnline lines
market_odds = {
"home_win": 1.30, # -333 American
"draw": 5.50, # +450
"away_win": 10.00, # +900
"over_2_5": 1.70, # -143
"under_2_5": 2.20, # +120
}
model_probs = {
"home_win": result.home_win,
"draw": result.draw,
"away_win": result.away_win,
"over_2_5": result.over_2_5,
"under_2_5": result.under_2_5,
}
value_bets = find_value_bets(model_probs, market_odds, min_edge=0.02)
if value_bets:
print("Value bets found:")
for vb in value_bets:
print(f" {vb['outcome']}: model {vb['model_prob']:.1%} vs market {vb['market_implied']:.1%} "
f"| edge {vb['edge']:.1%} | EV ${vb['ev_per_unit']:.3f}/unit")
else:
print("No value bets found at 2% minimum edge threshold.")
Limitations and Edge Cases
Poisson underestimates draws. The independent Poisson model systematically underestimates the probability of draws in soccer by 1-3 percentage points. The Dixon-Coles correction partially fixes this, but even with the correction, the model slightly underweights draws in matches between evenly-matched defensive teams. If your agent bets draw markets, apply the Dixon-Coles correction at minimum — or use a bivariate Poisson model that introduces explicit correlation between the two teams’ goal counts.
Score-dependent tactics are invisible. When a team goes up 1-0 in the 70th minute, they often sit deeper and defend, reducing both teams’ scoring rates for the remainder of the match. The Poisson model assumes a constant λ across the full 90 minutes. For pre-match betting, this is acceptable — the λ represents the time-averaged rate. For live (in-play) betting, a state-dependent model is required. See the NBA win probability guide for state-dependent approaches that can be adapted to soccer.
High-scoring sports break the model. Basketball averaging 110+ points per team violates every Poisson assumption. Scoring events are not independent (possessions alternate), the rate is not constant (shot clock, end-of-quarter effects), and the scoring volume is too high for Poisson to be useful. The Poisson model works when λ is between 0.5 and 4.0 — soccer (league avg ~2.5 total goals), hockey (league avg ~3.0), and baseball (league avg ~4.5) are in the sweet spot. Football (NFL) is borderline with average total scores around 22-23 points per team, but touchdowns (~4.5 per game total) can be modeled with Poisson.
Small sample sizes distort λ estimates. If your agent estimates λ from the first 5 matches of a season, the attack/defense ratings are unstable. A team that scored 4 goals in its first home game looks elite — but it may have just played the worst defense in the league. Use at least 10-15 games for stable estimates, and weight recent matches more heavily (exponential decay weighting with half-life of ~10 matches works well).
Home advantage is not constant. The league-average home goal rate (1.55 in the Premier League) is just that — an average. Some teams have outsized home advantage (altitude in La Paz, hostile atmospheres at Anfield) and others have nearly none. If your agent uses a blanket league average for home advantage, it will systematically misprice matches at extreme home-advantage venues.
Promoted teams and roster turnover. At the start of a season, newly promoted teams have no top-flight data. Using their championship-level stats will overestimate their attack strength. Similarly, teams that lose key players in transfer windows have a structural break in their data. The agent should discount pre-transfer stats or impute from comparable squad values.
FAQ
How does the Poisson distribution model soccer scores?
The Poisson distribution models each team’s goal count independently using P(k) = (λ^k × e^(-λ)) / k!, where λ is the team’s expected goals per match. You estimate λ from team attack and defense ratings relative to league averages. The joint probability of any scoreline (e.g., 2-1) is P_home(2) × P_away(1), which assumes independence between the two teams’ scoring.
What is the Dixon-Coles correction in soccer betting models?
The Dixon-Coles correction fixes the Poisson model’s known problem of underestimating draws and low-scoring results. It introduces a dependence parameter ρ (rho) that adjusts the joint probability of scorelines 0-0, 1-0, 0-1, and 1-1. A negative ρ increases the probability of 0-0 and 1-1 results while decreasing 1-0 and 0-1, matching observed soccer data more accurately.
How do you calculate over/under probabilities from a Poisson model?
Sum the joint probabilities of all scorelines where the total goals exceed the line. For over 2.5 goals, add up P(Home=i, Away=j) for every combination where i + j >= 3. The under probability is 1 minus the over probability. This gives you a precise implied probability to compare against sportsbook lines for edge detection.
Why does the Poisson model fail for basketball and high-scoring sports?
Basketball scores violate the Poisson assumption of independent events with a constant rate. Scoring rates change with game state (trailing teams play faster), shot clock mechanics create clustering, and individual possessions are not independent. A single NBA quarter has 25+ scoring events with strong serial correlation. The Poisson model works best when λ is between 0.5 and 4, which covers soccer (2.5 avg), hockey (3.0 avg), and baseball (4.5 avg) but not basketball (110+ avg).
How can a betting agent use Poisson modeling to find +EV bets?
An agent estimates λ for each team from recent form, head-to-head data, and league averages, then builds a full scoreline probability matrix. It converts those probabilities to fair odds and compares against live sportsbook lines from The Odds API. Any market where the agent’s implied probability exceeds the sportsbook’s implied probability (after removing the vig) is a candidate +EV bet. See the expected value guide for the full EV calculation framework.
What’s Next
The Poisson model is the starting point for score-based sports modeling. From here, the math gets more sophisticated — and more profitable.
- Next in the series: Expected Goals (xG) Betting Model takes the Poisson framework and replaces league-average λ estimates with shot-level data, dramatically improving accuracy for soccer match prediction.
- Tournament modeling: World Cup 2026 Betting Math extends Poisson to group-stage simulations and knockout-round modeling, where you need 10,000+ Monte Carlo iterations through tournament brackets.
- Edge detection pipeline: The Odds API Edge Detection Pipeline shows how to pull live lines from 50+ sportsbooks and automatically compare against your Poisson model output.
- Full stack context: See the Agent Betting Stack for how Poisson modeling fits into the Layer 4 intelligence module alongside Elo ratings and regression models.
- Vig removal: The AgentBets Vig Index tracks sportsbook overrounds — essential for converting raw market odds into true implied probabilities before comparing against your Poisson output.
