Eleven distributions cover every modeling need a betting agent encounters. Poisson for scoring rates (PMF = e^(-λ)λ^k/k!), Beta for Bayesian probability estimation (posterior = Beta(α+w, β+l)), Normal for point spreads, and Log-normal for bankroll growth. This page is your bookmark-and-reference companion for model building.
Why This Matters for Agents
An autonomous betting agent’s Layer 4 intelligence module is fundamentally a probability machine. Every decision — whether to buy a Polymarket contract, size a Kelly bet, or flag an arbitrage opportunity — starts with a distributional assumption. Pick the wrong distribution and the model outputs garbage. Pick the right one and the agent captures edge that cruder models miss.
This guide is the reference layer beneath every other page in the Math Behind Betting series. When the Poisson guide derives goal-scoring models, it assumes you know what a Poisson distribution is. When the Bayesian updating guide builds a probability estimator, it assumes you know why Beta is the conjugate prior for Bernoulli trials. When the Kelly guide talks about geometric growth, it assumes you understand log-normal processes. This page fills those gaps. Bookmark it. You will come back to it during model building, and your agent’s feature engineering pipeline will reference these distributions directly.
The Math
Distribution Selection Flowchart
Before diving into individual distributions, here’s the decision tree an agent follows:
What type of data are you modeling?
│
├── Binary outcome (yes/no, win/lose)
│ ├── Single trial → BERNOULLI
│ └── n independent trials → BINOMIAL
│
├── Count data (goals, runs, events)
│ ├── Variance ≈ Mean → POISSON
│ └── Variance > Mean → NEGATIVE BINOMIAL
│
├── Continuous measurement (spread, margin, return)
│ ├── Symmetric, no hard bounds → NORMAL
│ ├── Right-skewed, multiplicative → LOG-NORMAL
│ └── Small sample (n < 30) → STUDENT'S t
│
├── Probability estimation (0 to 1)
│ ├── Single probability → BETA
│ └── Probability vector (sums to 1) → DIRICHLET
│
├── Time between events → EXPONENTIAL
│
└── No prior information → UNIFORM
1. Bernoulli Distribution
The atomic unit of betting. One trial, two outcomes.
PMF: P(X = k) = p^k * (1 - p)^(1-k), where k in {0, 1}
- p = probability of success (YES outcome, win)
- X = 1 if success, 0 if failure
- Mean = p
- Variance = p(1 - p)
Betting use: Models a single bet outcome or a single prediction market contract resolution. A Polymarket YES contract at $0.63 is a Bernoulli trial with p = 0.63.
Parameter estimation: Maximum likelihood estimate is p_hat = number of successes / number of trials. For a single observation, p_hat = x (the observation itself).
2. Binomial Distribution
The Bernoulli extended to n independent trials.
PMF: P(X = k) = C(n, k) * p^k * (1 - p)^(n-k)
- n = number of trials (bets placed)
- k = number of successes (wins)
- p = probability of success per trial
- C(n, k) = n! / (k!(n-k)!)
- Mean = np
- Variance = np(1 - p)
Betting use: “If I place 100 bets each with 55% win probability, what’s the chance I win 60 or more?” That’s P(X >= 60) with X ~ Binomial(100, 0.55). Also used to assess whether a bettor’s track record is skill or luck — if 58 wins in 100 bets exceeds what Binomial(100, 0.50) would produce at the 95th percentile, there’s evidence of edge.
Parameter estimation: Given observed k wins in n trials, p_hat = k/n. Confidence interval via Wilson score interval (preferred over Wald for small n or extreme p).
3. Poisson Distribution
The workhorse for scoring events in sports.
PMF: P(X = k) = e^(-λ) * λ^k / k!
- λ (lambda) = expected rate of events per interval (e.g., goals per match)
- k = number of events observed
- Mean = λ
- Variance = λ (mean equals variance — the Poisson fingerprint)
Betting use: Models goals in soccer (λ ≈ 1.2-1.5 per team per match), runs in baseball, touchdowns in NFL. Given home team λ_h = 1.6 and away λ_a = 1.1, the probability of a 2-1 scoreline is P(H=2) * P(A=1) = 0.258 * 0.332 = 0.0857. This is the foundation of the Poisson sports modeling guide and feeds directly into expected goals (xG) models.
Parameter estimation: MLE for λ is the sample mean of observed counts. For soccer, compute from historical match data over the last 2-3 seasons, adjusted for home/away and opponent strength.
4. Normal (Gaussian) Distribution
The bell curve. Central to point spread modeling and CLV analysis.
PDF: f(x) = (1 / (σ√(2π))) * exp(-(x - μ)^2 / (2σ^2))
- μ (mu) = mean (expected value)
- σ (sigma) = standard deviation
- Mean = μ
- Variance = σ^2
Betting use: Point spread distributions — NFL game margins are approximately Normal with σ ≈ 13.5 points. If a spread is -3.5 and your model predicts a mean margin of -6.0, the probability of covering is P(X < -3.5) where X ~ Normal(-6.0, 13.5), which equals Φ((−3.5 − (−6.0)) / 13.5) = Φ(0.185) = 0.573. Also models closing line value (CLV) residuals and the distribution of model prediction errors.
Parameter estimation: MLE gives μ_hat = sample mean, σ_hat = sample standard deviation. For point spreads, use a rolling window of 3-5 seasons.
5. Log-Normal Distribution
Models multiplicative processes — bankroll growth under Kelly sizing.
PDF: f(x) = (1 / (xσ√(2π))) * exp(-(ln(x) - μ)^2 / (2σ^2)), for x > 0
- μ = mean of ln(X)
- σ = standard deviation of ln(X)
- Mean = exp(μ + σ^2/2)
- Variance = (exp(σ^2) - 1) * exp(2μ + σ^2)
Betting use: If each bet multiplies the bankroll by a random factor (1 + f*outcome), the bankroll after n bets is approximately log-normally distributed by the Central Limit Theorem applied to the log. This is why Kelly criterion maximizes E[ln(bankroll)] — it targets the geometric growth rate μ - σ^2/2. The bankroll growth guide and drawdown analysis both depend on this distribution.
Parameter estimation: Take the log of observed bankroll multipliers. Compute mean and standard deviation of the logged values. Those are μ and σ.
6. Beta Distribution
The Bayesian probability estimator. Conjugate prior for Bernoulli/Binomial data.
PDF: f(x; α, β) = (x^(α-1) * (1-x)^(β-1)) / B(α, β), for x in [0, 1]
- α (alpha) = shape parameter (pseudo-count of successes + 1)
- β (beta) = shape parameter (pseudo-count of failures + 1)
- B(α, β) = Beta function (normalizing constant)
- Mean = α / (α + β)
- Variance = αβ / ((α + β)^2 * (α + β + 1))
Betting use: The core of Bayesian probability estimation for betting agents. Start with a prior Beta(α_0, β_0). After observing w wins and l losses, the posterior is Beta(α_0 + w, β_0 + l). The mean estimate is (α_0 + w) / (α_0 + w + β_0 + l). Polyseer uses Beta-distributed beliefs in its multi-agent Bayesian architecture to aggregate probability estimates across sources.
Common priors:
- Beta(1, 1) = Uniform — no prior information
- Beta(0.5, 0.5) = Jeffreys prior — minimally informative
- Beta(10, 10) = centered at 50% with moderate certainty (calibrated for coin-flip events)
Parameter estimation: Method of moments: α_hat = x_bar * ((x_bar*(1-x_bar)/s^2) - 1), β_hat = (1 - x_bar) * ((x_bar*(1-x_bar)/s^2) - 1), where x_bar is the sample mean and s^2 is the sample variance of observed proportions.
7. Exponential Distribution
Models time between events. The continuous analog of the geometric distribution.
PDF: f(x; λ) = λ * exp(-λx), for x >= 0
- λ = rate parameter (events per unit time)
- Mean = 1/λ
- Variance = 1/λ^2
Betting use: Models time between goals in soccer (if goals arrive as a Poisson process, inter-goal times are Exponential), time between scoring plays in any sport, and time-to-resolution in prediction markets. An agent doing live betting on NBA win probability uses Exponential arrival rates to model scoring bursts. Also models time-to-first-event props: “Will there be a goal in the first 15 minutes?” with λ = 2.7/90 goals per minute.
Parameter estimation: MLE gives λ_hat = 1 / x_bar (inverse of sample mean of inter-event times).
8. Negative Binomial Distribution
The overdispersed Poisson. Handles count data where variance exceeds the mean.
PMF: P(X = k) = C(k + r - 1, k) * p^r * (1 - p)^k
Alternative parameterization (mean-dispersion): P(X = k; μ, α) where μ = mean, α = dispersion parameter
- When α → 0, Negative Binomial → Poisson
- Variance = μ + αμ^2 (always >= μ)
Betting use: NFL total points, cricket run rates, and any sport where scoring is clumpy rather than independent. Poisson assumes each goal/score is independent, but in reality, a team that scores once often has momentum — the scoring rate increases. Negative Binomial captures this extra variance. If your Poisson model underestimates the probability of extreme scores (0-0 draws and 5-4 thrillers simultaneously), switch to Negative Binomial.
Parameter estimation: MLE via iterative methods (scipy.optimize). Quick check: if sample variance / sample mean > 1.5, Negative Binomial is preferred over Poisson.
9. Student’s t Distribution
The honest Normal for small samples.
PDF: f(x; ν) = (Γ((ν+1)/2) / (√(νπ) * Γ(ν/2))) * (1 + x^2/ν)^(-(ν+1)/2)
- ν (nu) = degrees of freedom (= n - 1 for sample mean)
- Heavier tails than Normal — accounts for uncertainty in σ estimate
- As ν → ∞, Student’s t → Normal
Betting use: Early-season team power ratings, new player prop lines, and any model parameter estimated from fewer than 30 datapoints. If you estimate a team’s scoring rate from 5 games, the confidence interval from a Normal assumption is too narrow — Student’s t gives wider intervals that reflect the genuine uncertainty. Critical for the Elo ratings and power rankings guide when rating new teams and in regression models with limited training data.
Parameter estimation: ν = n - 1 where n is sample size. Location and scale estimated from sample mean and standard deviation.
10. Dirichlet Distribution
The Beta distribution generalized to probability vectors. Essential for multi-outcome markets.
PDF: f(x_1, …, x_k; α_1, …, α_k) = (1/B(α)) * Π(x_i^(α_i - 1))
- x_1, …, x_k = probability vector (sums to 1)
- α_1, …, α_k = concentration parameters
- B(α) = multivariate Beta function
- E[x_i] = α_i / Σα_j
- When all α_i = 1, Dirichlet = Uniform over the simplex
Betting use: Multi-candidate election markets on Polymarket and Kalshi. If “Who will win the 2028 presidential election?” has 6 candidates, the probability vector lives on a 6-simplex. Dirichlet(α_1, …, α_6) represents the agent’s uncertainty over all possible probability assignments. Update by adding observed poll counts or market signals to the α parameters. This is covered in depth in the multi-outcome markets guide and the political prediction market modeling guide.
Parameter estimation: MLE via fixed-point iteration or Newton’s method. For Bayesian updating, simply add observed category counts to the prior α vector.
11. Uniform Distribution
The “I know nothing” prior. Maximum entropy for bounded continuous data.
PDF: f(x; a, b) = 1 / (b - a), for x in [a, b]
- a = minimum
- b = maximum
- Mean = (a + b) / 2
- Variance = (b - a)^2 / 12
Betting use: The non-informative prior when an agent has zero information about a probability. Uniform(0, 1) = Beta(1, 1) serves as the starting point for Bayesian estimation before any data arrives. Also used in Monte Carlo simulation for generating random scenarios and in multi-armed bandit algorithms for initializing arm priors.
Distribution Conversion Table
Several distributions are related. Knowing the connections lets an agent switch models when assumptions change:
From To When / How
─────────────────────────────────────────────────────────────────
Bernoulli(p) → Binomial(1, p) Single trial is Binomial with n=1
Binomial(n, p) → Normal(np, np(1-p)) When n > 30 and p not extreme
Binomial(n, p) → Poisson(np) When n large, p small, np moderate
Poisson(λ) → Normal(λ, λ) When λ > 30
Poisson(λ) → Neg Binomial When variance > mean (add dispersion)
Exponential(λ) → Poisson process Inter-arrival times of Poisson events
Beta(α, β) → Dirichlet(α, β) Beta is Dirichlet with k=2
Normal → Student's t(ν) When σ estimated from small sample
Normal(μ, σ²) → Log-normal Y = exp(X) where X ~ Normal
Uniform(0,1) → Beta(1, 1) Identical distributions
Worked Examples
Example 1: Poisson Match Scoreline Probabilities
A Premier League match between Arsenal (home, xG λ_h = 1.8) and Newcastle (away, xG λ_a = 1.1). An agent building an expected goals model needs exact scoreline probabilities to compare against BetOnline’s scoreline props:
Scoreline P(Arsenal=i) × P(Newcastle=j) Joint Prob
0-0 0.1653 × 0.3329 0.0550
1-0 0.2975 × 0.3329 0.0990
1-1 0.2975 × 0.3662 0.1089
2-1 0.2678 × 0.3662 0.0980
2-0 0.2678 × 0.3329 0.0891
0-1 0.1653 × 0.3662 0.0605
If BetOnline prices the 1-1 draw at +800 (implied 11.1%), the agent sees edge: model says 10.89%, market says 11.1%. No bet — the market price is fair. But if the 2-1 line is priced at +1200 (implied 7.7%), the model at 9.8% sees a 2.1 percentage point edge.
Example 2: Beta-Bayesian Probability Estimation on Kalshi
A Kalshi market asks “Will US GDP growth exceed 3% in Q2 2026?” The agent has no strong prior, so it starts with Beta(2, 3) — a mild skeptical prior leaning toward NO (mean = 0.40). Economic indicators arrive:
- 3 data points suggest YES (strong jobs reports, retail sales)
- 1 data point suggests NO (manufacturing contraction)
Posterior: Beta(2 + 3, 3 + 1) = Beta(5, 4). Mean = 5/9 = 0.556. The 95% credible interval is [0.234, 0.849]. The agent’s point estimate is 55.6%. If Kalshi prices YES at 48 cents, the agent sees 7.6 cents of expected edge.
Example 3: Normal Distribution for NFL Spread Evaluation
The NFL mathematical modeling guide estimates the point spread distribution. Chiefs -3.5 vs. Ravens on BetOnline at -110. The agent’s model predicts Chiefs win by 6.2 points with σ = 13.5.
P(Chiefs cover -3.5) = P(X > 3.5) where X ~ Normal(6.2, 13.5^2) = P(Z > (3.5 - 6.2) / 13.5) = P(Z > -0.200) = 0.579
At -110, the breakeven probability is 52.4%. The model gives 57.9%. That’s 5.5 points of edge. Kelly fraction: f* = (0.579 * 1.909 - 0.421) / 1.909 = 0.358, meaning the agent allocates ~9% of bankroll at quarter-Kelly.
Implementation
"""
BettingDistributions: Complete reference implementation for all distributions
used in sports betting and prediction market modeling.
pip install numpy scipy
"""
import numpy as np
from scipy import stats
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass
@dataclass
class DistributionResult:
"""Standardized output for distribution calculations."""
distribution: str
parameters: Dict[str, float]
mean: float
variance: float
probabilities: Optional[Dict[str, float]] = None
class BettingDistributions:
"""All probability distributions a betting agent needs, in one class."""
# ── Bernoulli ──────────────────────────────────────────────────
@staticmethod
def bernoulli_ev(p: float, payout_win: float, cost: float) -> float:
"""
Expected value of a single Bernoulli bet.
Args:
p: true probability of winning
payout_win: payout if bet wins (e.g., 1.0 for prediction market)
cost: cost to enter the bet
Returns:
Expected value in dollars
"""
return p * payout_win - cost
# ── Binomial ───────────────────────────────────────────────────
@staticmethod
def binomial_win_probability(
n_bets: int,
win_prob: float,
min_wins: int
) -> float:
"""
P(wins >= min_wins) over n_bets independent bets.
Args:
n_bets: number of bets
win_prob: probability of winning each bet
min_wins: minimum wins to calculate P(X >= min_wins)
Returns:
Probability of achieving at least min_wins
"""
return 1 - stats.binom.cdf(min_wins - 1, n_bets, win_prob)
@staticmethod
def binomial_skill_test(
wins: int,
total: int,
null_prob: float = 0.50
) -> Dict[str, float]:
"""
Test whether a betting record shows skill vs. luck.
Args:
wins: observed wins
total: total bets
null_prob: assumed win rate under no skill (default: coin flip)
Returns:
Dict with p_value, observed_rate, and required_wins for 95% significance
"""
p_value = 1 - stats.binom.cdf(wins - 1, total, null_prob)
required_95 = stats.binom.ppf(0.95, total, null_prob) + 1
return {
"observed_rate": wins / total,
"p_value": p_value,
"significant_at_05": p_value < 0.05,
"wins_needed_for_significance": int(required_95),
}
# ── Poisson ────────────────────────────────────────────────────
@staticmethod
def poisson_scoreline_matrix(
lambda_home: float,
lambda_away: float,
max_goals: int = 7
) -> np.ndarray:
"""
Joint probability matrix for match scorelines.
Assumes independence between home and away scoring.
Args:
lambda_home: expected goals for home team
lambda_away: expected goals for away team
max_goals: maximum goals per team to compute
Returns:
(max_goals+1) x (max_goals+1) matrix of joint probabilities
"""
home_probs = stats.poisson.pmf(range(max_goals + 1), lambda_home)
away_probs = stats.poisson.pmf(range(max_goals + 1), lambda_away)
return np.outer(home_probs, away_probs)
@staticmethod
def poisson_match_outcomes(
lambda_home: float,
lambda_away: float,
max_goals: int = 7
) -> Dict[str, float]:
"""
Home win, draw, away win probabilities from Poisson model.
Args:
lambda_home: expected goals for home team
lambda_away: expected goals for away team
max_goals: max goals per side
Returns:
Dict with home_win, draw, away_win probabilities
"""
matrix = BettingDistributions.poisson_scoreline_matrix(
lambda_home, lambda_away, max_goals
)
home_win = sum(
matrix[i][j]
for i in range(max_goals + 1)
for j in range(max_goals + 1)
if i > j
)
draw = sum(matrix[i][i] for i in range(max_goals + 1))
away_win = sum(
matrix[i][j]
for i in range(max_goals + 1)
for j in range(max_goals + 1)
if i < j
)
return {"home_win": home_win, "draw": draw, "away_win": away_win}
# ── Normal ─────────────────────────────────────────────────────
@staticmethod
def normal_spread_probability(
predicted_margin: float,
spread: float,
sigma: float = 13.5
) -> float:
"""
Probability of covering a point spread.
Args:
predicted_margin: model's predicted margin (positive = favorite wins)
spread: the spread to cover (e.g., -3.5 means favorite must win by 4+)
sigma: standard deviation of margin (NFL ≈ 13.5, NBA ≈ 12.0)
Returns:
Probability of the favorite covering the spread
"""
z = (predicted_margin - abs(spread)) / sigma
return float(stats.norm.cdf(z))
@staticmethod
def normal_total_probability(
predicted_total: float,
line: float,
sigma: float
) -> Dict[str, float]:
"""
Over/under probabilities for a total line.
Args:
predicted_total: model's predicted total points
line: the over/under line (e.g., 48.5)
sigma: standard deviation of total
Returns:
Dict with over and under probabilities
"""
z = (line - predicted_total) / sigma
under_prob = float(stats.norm.cdf(z))
return {"over": 1 - under_prob, "under": under_prob}
# ── Log-normal ─────────────────────────────────────────────────
@staticmethod
def lognormal_bankroll_projection(
initial_bankroll: float,
mean_log_return: float,
std_log_return: float,
n_bets: int,
percentiles: List[float] = [0.05, 0.25, 0.50, 0.75, 0.95]
) -> Dict[str, float]:
"""
Project bankroll distribution after n bets under log-normal growth.
Args:
initial_bankroll: starting bankroll
mean_log_return: mean of log(1 + return) per bet
std_log_return: std of log(1 + return) per bet
n_bets: number of bets
percentiles: which percentiles to report
Returns:
Dict mapping percentile labels to projected bankroll values
"""
mu_n = n_bets * mean_log_return
sigma_n = std_log_return * np.sqrt(n_bets)
result = {}
for p in percentiles:
log_value = stats.norm.ppf(p, loc=mu_n, scale=sigma_n)
result[f"p{int(p*100)}"] = initial_bankroll * np.exp(log_value)
result["expected"] = initial_bankroll * np.exp(mu_n + sigma_n**2 / 2)
result["geometric_growth_rate"] = mean_log_return - std_log_return**2 / 2
return result
# ── Beta ───────────────────────────────────────────────────────
@staticmethod
def beta_posterior(
prior_alpha: float,
prior_beta: float,
wins: int,
losses: int
) -> Dict[str, float]:
"""
Bayesian update for probability estimation.
Args:
prior_alpha: prior alpha (pseudo-successes + 1)
prior_beta: prior beta (pseudo-failures + 1)
wins: observed successes
losses: observed failures
Returns:
Posterior statistics including mean, mode, and 95% credible interval
"""
post_alpha = prior_alpha + wins
post_beta = prior_beta + losses
dist = stats.beta(post_alpha, post_beta)
ci_low, ci_high = dist.ppf(0.025), dist.ppf(0.975)
mode = (post_alpha - 1) / (post_alpha + post_beta - 2) if (
post_alpha > 1 and post_beta > 1
) else dist.mean()
return {
"alpha": post_alpha,
"beta": post_beta,
"mean": dist.mean(),
"mode": mode,
"std": dist.std(),
"ci_95_low": ci_low,
"ci_95_high": ci_high,
}
# ── Exponential ────────────────────────────────────────────────
@staticmethod
def exponential_first_event(
rate: float,
time_window: float
) -> float:
"""
P(at least one event occurs within time_window).
Args:
rate: events per unit time (e.g., goals per minute)
time_window: time period to check (e.g., 15 minutes)
Returns:
Probability of at least one event
"""
return 1 - np.exp(-rate * time_window)
# ── Negative Binomial ──────────────────────────────────────────
@staticmethod
def negbin_vs_poisson_test(
observed_counts: List[int]
) -> Dict[str, float]:
"""
Test whether Negative Binomial fits better than Poisson.
Compares sample variance to sample mean (dispersion test).
Args:
observed_counts: list of observed count values
Returns:
Dict with mean, variance, dispersion ratio, and recommendation
"""
data = np.array(observed_counts)
mean = data.mean()
var = data.var(ddof=1)
dispersion = var / mean if mean > 0 else float("inf")
return {
"mean": float(mean),
"variance": float(var),
"dispersion_ratio": float(dispersion),
"recommendation": (
"negative_binomial" if dispersion > 1.5
else "poisson" if dispersion > 0.7
else "check_underdispersion"
),
}
# ── Student's t ────────────────────────────────────────────────
@staticmethod
def t_confidence_interval(
sample_mean: float,
sample_std: float,
n: int,
confidence: float = 0.95
) -> Tuple[float, float]:
"""
Confidence interval for a mean using Student's t.
Args:
sample_mean: observed mean
sample_std: observed standard deviation
n: sample size
confidence: confidence level (default 0.95)
Returns:
(lower_bound, upper_bound) of the confidence interval
"""
df = n - 1
t_crit = stats.t.ppf((1 + confidence) / 2, df)
margin = t_crit * sample_std / np.sqrt(n)
return (sample_mean - margin, sample_mean + margin)
# ── Dirichlet ──────────────────────────────────────────────────
@staticmethod
def dirichlet_posterior(
prior_alphas: List[float],
observed_counts: List[int]
) -> Dict[str, object]:
"""
Bayesian update for multi-outcome probability estimation.
Args:
prior_alphas: prior concentration parameters (one per outcome)
observed_counts: observed counts per outcome
Returns:
Posterior means, standard deviations, and alpha parameters
"""
post_alphas = [a + c for a, c in zip(prior_alphas, observed_counts)]
total = sum(post_alphas)
means = [a / total for a in post_alphas]
stds = [
np.sqrt(a * (total - a) / (total**2 * (total + 1)))
for a in post_alphas
]
return {
"posterior_alphas": post_alphas,
"means": means,
"stds": stds,
"total_concentration": total,
}
# ── Usage demonstration ────────────────────────────────────────────
if __name__ == "__main__":
bd = BettingDistributions()
# Poisson: Arsenal 1.8 xG vs Newcastle 1.1 xG
outcomes = bd.poisson_match_outcomes(1.8, 1.1)
print("Arsenal vs Newcastle (Poisson):")
print(f" Home win: {outcomes['home_win']:.1%}")
print(f" Draw: {outcomes['draw']:.1%}")
print(f" Away win: {outcomes['away_win']:.1%}")
# Normal: Chiefs -3.5 spread, model says -6.2, sigma 13.5
cover_prob = bd.normal_spread_probability(6.2, -3.5, 13.5)
print(f"\nChiefs cover -3.5: {cover_prob:.1%}")
# Beta: Bayesian estimation starting from Beta(2,3), observe 3 wins 1 loss
posterior = bd.beta_posterior(2, 3, 3, 1)
print(f"\nBeta posterior: mean={posterior['mean']:.3f}, "
f"95% CI=[{posterior['ci_95_low']:.3f}, {posterior['ci_95_high']:.3f}]")
# Binomial: is 58/100 at 50% line significant?
skill = bd.binomial_skill_test(58, 100, 0.50)
print(f"\n58/100 wins: p-value={skill['p_value']:.4f}, "
f"significant={skill['significant_at_05']}")
# Log-normal bankroll projection: 1000 bets, 1% edge, $10k start
projection = bd.lognormal_bankroll_projection(
initial_bankroll=10000,
mean_log_return=0.005,
std_log_return=0.05,
n_bets=1000
)
print(f"\nBankroll after 1000 bets ($10k start):")
print(f" 5th percentile: ${projection['p5']:,.0f}")
print(f" Median: ${projection['p50']:,.0f}")
print(f" 95th percentile: ${projection['p95']:,.0f}")
print(f" Geometric growth: {projection['geometric_growth_rate']:.4f}/bet")
# Dirichlet: 4-candidate election market
post = bd.dirichlet_posterior(
prior_alphas=[1, 1, 1, 1],
observed_counts=[45, 30, 15, 10]
)
candidates = ["Candidate A", "Candidate B", "Candidate C", "Candidate D"]
print(f"\nDirichlet posterior (election market):")
for name, mean, std in zip(candidates, post['means'], post['stds']):
print(f" {name}: {mean:.1%} +/- {std:.1%}")
# Exponential: P(goal in first 15 min) with 2.7 goals/match rate
p_goal = bd.exponential_first_event(rate=2.7 / 90, time_window=15)
print(f"\nP(goal in first 15 min): {p_goal:.1%}")
# Negative Binomial test
nfl_scores = [17, 24, 31, 10, 42, 7, 28, 35, 14, 21, 38, 3, 27, 20, 34]
nb_test = bd.negbin_vs_poisson_test(nfl_scores)
print(f"\nNFL scores dispersion: {nb_test['dispersion_ratio']:.2f} "
f"→ {nb_test['recommendation']}")
Limitations and Edge Cases
Independence assumption violations. Poisson and Binomial assume independent events. In reality, scoring in sports exhibits momentum effects — a goal at minute 70 increases the probability of another goal before full time (the trailing team pushes forward). The Negative Binomial partially addresses this, but correlated event models (Hawkes processes) are more accurate for live betting. The correlation and portfolio theory guide covers correlated outcome modeling.
Parameter instability. All distribution parameters are estimated from historical data. A team’s Poisson λ from last season may not reflect this season’s reality after roster changes. An agent must track parameter drift and re-estimate frequently. The feature engineering guide covers rolling window estimation and regime detection.
Tail behavior matters. The Normal distribution underestimates extreme outcomes. NFL margins have fatter tails than Normal predicts — blowouts (30+ point margins) happen more often than a Gaussian would suggest. For tail-sensitive applications (drawdown modeling, extreme loss estimation), use Student’s t with low degrees of freedom or fit an empirical distribution.
Poisson-Normal approximation breaks below λ = 5. The Poisson distribution is skewed for small λ (typical in soccer: λ ≈ 1.2-1.8). Do not use the Normal approximation for goal-scoring models — use exact Poisson PMFs.
Beta concentration trap. A Beta(100, 100) prior is extremely concentrated around 0.50. It takes hundreds of observations to move it significantly. If your agent’s probability estimates seem stuck, the prior is too strong. Start with Beta(1, 1) or Beta(2, 2) unless you have genuine prior information.
Dirichlet with many outcomes. For markets with 20+ outcomes (e.g., “Which country wins the World Cup?”), Dirichlet estimation becomes numerically sensitive. Use log-space computations and consider a sparse Dirichlet where most α values are small (< 1).
FAQ
What probability distribution should I use for modeling goals scored in soccer?
Use the Poisson distribution with λ equal to the team’s expected goals (xG) rate. For a team averaging 1.4 goals per match, P(exactly 2 goals) = e^(-1.4) * 1.4^2 / 2! = 0.242. If you observe overdispersion (variance exceeds mean across a large sample of matches), switch to Negative Binomial. The Poisson distribution sports modeling guide derives the full match scoreline model.
How do I estimate the probability of a prediction market outcome using Bayesian updating?
Start with a Beta(α, β) prior — Beta(1,1) for uniform ignorance, or shape it from historical base rates. After observing w wins and l losses of similar events, the posterior is Beta(α + w, β + l). The mean estimate is (α + w) / (α + w + β + l). The Beta distribution is the conjugate prior for Bernoulli trials, making updates exact and computationally free. See the Bayesian updating guide for the full framework.
What distribution models bankroll growth over time for a betting agent?
Bankroll growth under Kelly sizing follows a log-normal distribution. If each bet multiplies the bankroll by a random factor, the log of bankroll after n bets is approximately Normal by the Central Limit Theorem. The geometric growth rate is μ - σ^2/2, which Kelly maximizes. The Kelly Criterion guide and bankroll growth guide provide full implementations.
When should I use Student’s t distribution instead of Normal in sports betting models?
Use Student’s t when estimating parameters from small samples — early-season team ratings, new player performance, or markets with fewer than 30 historical datapoints. The t-distribution has heavier tails than Normal, producing wider confidence intervals that reflect small-sample uncertainty. As sample size exceeds 30, t converges to Normal and the distinction disappears.
How do I model multi-outcome prediction markets with more than two outcomes?
Use the Dirichlet distribution, which generalizes the Beta to probability vectors that sum to 1. For a market with k outcomes, Dirichlet(α_1, …, α_k) represents uncertainty over the probability simplex. Update by adding observed outcome counts to the α parameters. The multi-outcome markets guide covers the combinatorial math, and the political prediction market modeling guide applies Dirichlet to election forecasting.
What’s Next
This reference page connects to every quantitative guide in the series. Here are the most direct next steps:
- Deep dive on any distribution: The Poisson sports modeling guide and Bayesian updating guide give full treatments of the two most important distributions for betting agents.
- Put distributions to work: The odds API edge detection pipeline shows how to build a complete system that feeds distribution outputs into live trading decisions.
- The implementation toolkit: The Python libraries for quantitative betting guide covers scipy.stats, numpy, and every other library referenced on this page.
- Where it all fits: The Agent Betting Stack maps these Layer 4 intelligence tools to the full agent architecture, from data ingestion through wallet infrastructure to trade execution on offshore sportsbooks and prediction markets.
