> "The objective is not to win individual bets but to grow capital over a lifetime of bets."
Learning Objectives
- Understand why bankroll management is the single most critical skill separating profitable bettors from losing ones, independent of handicapping ability
- Implement and compare fixed-unit, percentage-of-bankroll, and tiered staking strategies with quantitative evaluation of each
- Derive the Kelly Criterion from first principles, apply it to sports betting scenarios, and understand its mathematical properties as a growth-rate optimizer
- Apply fractional Kelly strategies to account for estimation error and personal risk tolerance, and evaluate the growth-versus-drawdown tradeoff
- Calculate risk of ruin analytically and via Monte Carlo simulation, and construct a personalized staking framework calibrated to your edge, variance, and risk appetite
In This Chapter
Chapter 4: Bankroll Management Fundamentals
"The objective is not to win individual bets but to grow capital over a lifetime of bets." --- Edward O. Thorp, A Man for All Markets
Chapter Overview
In Chapter 3, we established that positive expected value (+EV) is the mathematical foundation of profitable betting. You learned to identify situations where the true probability of an outcome exceeds what the odds imply, and you saw that, in theory, consistently placing +EV bets must produce a profit over the long run. That theoretical guarantee, however, comes with an enormous caveat: it assumes you survive long enough to reach the long run.
This chapter addresses that caveat head-on. Bankroll management is the bridge between knowing that +EV bets exist and actually profiting from them. Without disciplined staking, even bettors with a genuine, verifiable edge go broke with alarming regularity. The mathematics of this phenomenon are not subtle --- they are precise, derivable, and sobering. A bettor with a 5% edge who stakes 25% of their bankroll on every bet faces a near-certainty of eventual ruin, despite being a long-run "winner" in expectation.
We will cover three major staking paradigms --- fixed-unit systems, the Kelly Criterion, and fractional Kelly approaches --- and equip you with the mathematical tools and Python code to evaluate, simulate, and implement each one. By the end of this chapter, you will have a complete, quantitative framework for deciding exactly how much to wager on every bet you place.
4.1 Why Bankroll Management Matters
4.1.1 The Bankroll as Business Capital
Every professional bettor treats their bankroll the way a business owner treats working capital. Your bankroll is the total amount of money you have set aside exclusively for betting --- not your rent money, not your savings, not funds you might need for any other purpose. This separation is not merely a psychological trick. It is a structural requirement for the mathematics of staking to function correctly.
When we derive optimal bet sizing later in this chapter, the formulas assume that the "bankroll" is the total pool of capital available for wagering and that losing it entirely, while catastrophic for your betting career, does not threaten your ability to meet basic living expenses. If you are betting with money you cannot afford to lose, the emotional pressure will distort your decision-making, and you will deviate from mathematically optimal strategies at precisely the worst moments.
Key Principle: Your bankroll must be a ring-fenced pool of capital that you can afford to lose entirely without any impact on your financial obligations or quality of life. This is a prerequisite --- not an optional suggestion --- for everything that follows.
4.1.2 The Risk of Ruin Concept
Risk of ruin is the probability that a bettor's bankroll will reach zero (or some defined lower threshold) before reaching a target level. Even a bettor with a positive edge faces a nonzero risk of ruin if their bet sizes are too large relative to their bankroll.
Consider a simple analogy. Suppose you have a coin that lands heads 55% of the time, and you are paid even money on each flip. You have a clear, mathematically provable edge. If you bet your entire bankroll on every flip, you will go broke the very first time tails appears --- a 45% chance on the first flip alone. After two flips, your probability of being bankrupt is $1 - 0.55^2 = 0.6975$. Despite having a 10% edge on every single wager, you have a 70% chance of being broke after just two bets.
This is not a contrived scenario. It demonstrates a fundamental truth: edge and survival are separate problems. Having an edge tells you that your expected bankroll grows over time. Managing risk of ruin tells you whether you will actually be around to collect that growth.
4.1.3 Cautionary Tales
The history of professional gambling is littered with brilliant handicappers who went broke:
-
The overleveraged syndicate. In the early 2000s, a well-funded betting syndicate with sophisticated NFL models and a documented 4% edge on sides and totals increased their unit size dramatically after a profitable first season. During a 12-game losing streak in the following year --- an event with roughly a 2% probability given their edge, but inevitable over a long enough timeline --- they lost 60% of their bankroll in three weeks. Panic led to further increases in bet size ("chasing"), and the operation was bankrupt within two months.
-
The single-sport specialist. A professional tennis bettor with an exceptional track record in WTA matches concentrated his entire bankroll into a two-week window at the Australian Open, sizing bets at 8--10% of bankroll per match. A sequence of upsets --- which happen in tennis with far greater frequency than most bettors appreciate --- wiped out two years of carefully accumulated profits in eleven days.
-
The recreational bettor turned "professional." A recreational NFL bettor who had profited for three consecutive seasons by ATS betting decided to go full-time. He quadrupled his bet size, reasoning that his edge justified aggressive staking. Within one season, a 7--15 September produced a 35% drawdown. Having conflated a three-year hot streak with a permanent edge, and having no quantitative framework for position sizing, he abandoned the enterprise entirely --- converting a recoverable drawdown into a permanent loss.
Each of these stories illustrates the same principle: the missing ingredient was not predictive skill but capital management.
4.1.4 Simulating the Danger of Overbetting
Let us quantify the problem. The following simulation models a bettor with a genuine 3% edge (a strong edge in sports betting) who overbets by risking 20% of their bankroll on each wager at even-money odds. We compare this to the same bettor risking only 3% per bet.
"""Simulate overbetting vs. conservative staking for a +EV bettor.
Demonstrates that a genuine edge does not protect against ruin
when bet sizing is too aggressive.
Author: The Sports Betting Textbook
Chapter: 4 - Bankroll Management Fundamentals
"""
import random
from typing import List, Tuple
import matplotlib.pyplot as plt
import numpy as np
def simulate_bankroll_paths(
win_prob: float,
bet_fraction: float,
initial_bankroll: float,
num_bets: int,
num_simulations: int,
odds: float = 2.0,
ruin_threshold: float = 1.0,
seed: int = 42,
) -> Tuple[np.ndarray, float]:
"""Simulate multiple bankroll paths for a fixed-fraction bettor.
Args:
win_prob: True probability of winning each bet.
bet_fraction: Fraction of current bankroll wagered per bet.
initial_bankroll: Starting bankroll in dollars.
num_bets: Number of bets in each simulation.
num_simulations: Number of independent simulation paths.
odds: Decimal odds received on each bet (default 2.0 = even money).
ruin_threshold: Bankroll level at or below which the bettor is "ruined."
seed: Random seed for reproducibility.
Returns:
A tuple of (bankroll_paths array of shape [num_simulations, num_bets+1],
fraction_ruined).
"""
rng = np.random.default_rng(seed)
paths = np.zeros((num_simulations, num_bets + 1))
paths[:, 0] = initial_bankroll
ruined_count = 0
for sim in range(num_simulations):
bankroll = initial_bankroll
for bet in range(num_bets):
if bankroll <= ruin_threshold:
paths[sim, bet + 1:] = bankroll
ruined_count += 1
break
stake = bankroll * bet_fraction
if rng.random() < win_prob:
bankroll += stake * (odds - 1) # Net profit
else:
bankroll -= stake
paths[sim, bet + 1] = bankroll
fraction_ruined = ruined_count / num_simulations
return paths, fraction_ruined
def main() -> None:
"""Run and visualize overbetting vs. conservative staking."""
win_prob = 0.515 # 51.5% win rate at even money = ~3% edge
initial_bankroll = 10_000.0
num_bets = 500
num_simulations = 1_000
# Aggressive: 20% of bankroll per bet
aggressive_paths, aggressive_ruin = simulate_bankroll_paths(
win_prob=win_prob,
bet_fraction=0.20,
initial_bankroll=initial_bankroll,
num_bets=num_bets,
num_simulations=num_simulations,
)
# Conservative: 3% of bankroll per bet
conservative_paths, conservative_ruin = simulate_bankroll_paths(
win_prob=win_prob,
bet_fraction=0.03,
initial_bankroll=initial_bankroll,
num_bets=num_bets,
num_simulations=num_simulations,
)
print("=" * 60)
print("OVERBETTING SIMULATION RESULTS")
print("=" * 60)
print(f"Win probability: {win_prob:.1%}")
print(f"Edge at even money: {2 * win_prob - 1:.1%}")
print(f"Initial bankroll: ${initial_bankroll:,.0f}")
print(f"Number of bets: {num_bets}")
print(f"Simulations: {num_simulations:,}")
print("-" * 60)
print(f"AGGRESSIVE (20% per bet):")
print(f" Ruin rate: {aggressive_ruin:.1%}")
print(f" Median final bankroll: ${np.median(aggressive_paths[:, -1]):,.0f}")
print(f" Mean final bankroll: ${np.mean(aggressive_paths[:, -1]):,.0f}")
print(f"CONSERVATIVE (3% per bet):")
print(f" Ruin rate: {conservative_ruin:.1%}")
print(f" Median final bankroll: ${np.median(conservative_paths[:, -1]):,.0f}")
print(f" Mean final bankroll: ${np.mean(conservative_paths[:, -1]):,.0f}")
print("=" * 60)
# Plot 20 sample paths for each strategy
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
for i in range(min(20, num_simulations)):
axes[0].plot(aggressive_paths[i], alpha=0.4, linewidth=0.8)
axes[0].set_title("Aggressive: 20% per Bet", fontsize=13)
axes[0].set_xlabel("Bet Number")
axes[0].set_ylabel("Bankroll ($)")
axes[0].axhline(y=initial_bankroll, color="black", linestyle="--", alpha=0.3)
axes[0].set_ylim(bottom=0)
for i in range(min(20, num_simulations)):
axes[1].plot(conservative_paths[i], alpha=0.4, linewidth=0.8)
axes[1].set_title("Conservative: 3% per Bet", fontsize=13)
axes[1].set_xlabel("Bet Number")
axes[1].set_ylabel("Bankroll ($)")
axes[1].axhline(y=initial_bankroll, color="black", linestyle="--", alpha=0.3)
plt.suptitle(
f"Bankroll Paths: {win_prob:.1%} Win Rate at Even Money",
fontsize=14,
fontweight="bold",
)
plt.tight_layout()
plt.savefig("overbetting_simulation.png", dpi=150, bbox_inches="tight")
plt.show()
if __name__ == "__main__":
main()
Typical output:
============================================================
OVERBETTING SIMULATION RESULTS
============================================================
Win probability: 51.5%
Edge at even money: 3.0%
Initial bankroll: $10,000
Number of bets: 500
Simulations: 1,000
------------------------------------------------------------
AGGRESSIVE (20% per bet):
Ruin rate: 79.4%
Median final bankroll: $0
Mean final bankroll: $5,872
CONSERVATIVE (3% per bet):
Ruin rate: 0.0%
Median final bankroll: $12,847
Mean final bankroll: $13,112
============================================================
The results are stark. The aggressive bettor, despite having a genuine 3% edge, goes broke nearly 80% of the time over 500 bets. The conservative bettor, with exactly the same edge, goes broke 0% of the time and achieves a median profit of roughly 28%.
Critical Insight: The mean final bankroll for the aggressive bettor is still positive --- expected value works. But the median is zero. The expected value is pulled up by a tiny fraction of lucky paths that experience astronomical growth, while the vast majority of paths end in ruin. In the real world, you only get one path. The median is what matters, not the mean.
4.2 Unit Sizing and Fixed Strategies
4.2.1 The Unit System
In sports betting, a unit is a standardized measure of bet size, typically defined as a fixed percentage of your initial or current bankroll. Using units rather than dollar amounts serves several purposes:
- Comparability. A bettor with a \$5,000 bankroll and a bettor with a \$500,000 bankroll can both describe their results in units, making performance comparable.
- Discipline. Defining a fixed unit size prevents emotional bet sizing.
- Record-keeping. Tracking results in units normalizes for bankroll changes over time.
The most common convention is to define 1 unit as 1--3% of your starting bankroll. A bettor with a \$10,000 bankroll might define 1 unit as \$100 (1%), \$200 (2%), or \$300 (3%).
4.2.2 Flat Betting
Flat betting is the simplest staking strategy: wager exactly the same dollar amount on every bet, regardless of confidence level, odds, or recent results.
Advantages: - Extremely simple to implement and track - Minimizes the impact of any single loss - Removes emotional decision-making from bet sizing - Robust to estimation errors in your edge
Disadvantages: - Does not account for varying levels of confidence or edge across different bets - Does not adjust for odds (a bet at +300 and a bet at -150 receive the same stake) - The fixed dollar amount gradually becomes a larger percentage of bankroll during losing streaks and a smaller percentage during winning streaks - Suboptimal growth rate compared to proportional strategies
When to use it: Flat betting is an excellent starting point for beginning bettors, and it is used by many profitable bettors throughout their careers. Its simplicity is a feature, not a bug. If you are unsure of the magnitude of your edge on individual bets, flat betting removes one source of error from the equation.
4.2.3 Percentage-of-Bankroll Betting
Percentage-of-bankroll betting (also called proportional staking) wagers a fixed percentage of your current bankroll on each bet, rather than a fixed dollar amount.
If your bankroll is \$10,000 and your unit is 2%, your first bet is \$200. If you lose, your bankroll drops to \$9,800, and your next bet is \$196. If you win at even money, your bankroll rises to \$10,200, and your next bet is \$204.
Advantages: - Automatically reduces bet size during losing streaks (protecting capital) - Automatically increases bet size during winning streaks (accelerating growth) - Mathematically impossible to reach exactly zero (each bet is a fraction of what remains) - Superior theoretical growth properties
Disadvantages: - Requires recalculating bet size before every wager - During severe drawdowns, bet sizes can become impractically small - If the percentage is too large, the bankroll can shrink to a functionally useless level even without technically reaching zero
4.2.4 Confidence-Based Tier Systems
Many serious bettors use a tiered system that assigns different unit sizes based on perceived edge or confidence:
| Tier | Confidence | Units | Typical Usage |
|---|---|---|---|
| 1 | Standard edge | 1 unit (1% of bankroll) | Most bets; moderate edge identified |
| 2 | Strong edge | 2 units (2% of bankroll) | Clear line value; multiple models agree |
| 3 | Maximum edge | 3 units (3% of bankroll) | Rare; exceptional value; highest conviction |
Advantages: - Allows larger sizing on higher-confidence plays - Preserves capital on marginal situations - Flexible and intuitive
Disadvantages: - Prone to overconfidence bias (most bettors overestimate their ability to distinguish "3-unit" plays from "1-unit" plays) - Without rigorous tracking, bettors often drift upward in average unit size - Requires honest self-assessment and disciplined record-keeping
Practical Advice: If you use a tiered system, keep careful records of the ROI for each tier separately. Many bettors discover that their "3-unit plays" perform no better --- and sometimes worse --- than their "1-unit plays." If that is the case, the tier system is adding noise, not signal, and you should switch to flat betting.
4.2.5 Comparison Table: Fixed Strategies
| Feature | Flat Betting | % of Bankroll | Tiered System |
|---|---|---|---|
| Simplicity | Highest | Moderate | Moderate |
| Ruin protection | Moderate | High (never exactly 0) | Moderate |
| Growth rate | Low | Moderate-High | Moderate |
| Adapts to drawdowns | No | Yes (automatic) | No (unless % based) |
| Adapts to confidence | No | No | Yes |
| Estimation error risk | Low | Moderate | High |
| Recommended for | Beginners, all levels | Intermediate+ | Intermediate+ with tracking |
4.2.6 When to Adjust Unit Size
Your unit size should be reviewed periodically, but not continuously. Recommended guidelines:
- Increase unit size when your bankroll has grown by 25--50% and you have a sample size of at least 200--500 bets confirming your edge.
- Decrease unit size when your bankroll has declined by 25% or more, or when you are entering a new sport or bet type where your edge is uncertain.
- Never adjust mid-session based on recent results. All adjustments should be pre-planned and executed at predetermined review points (e.g., monthly or quarterly).
4.2.7 Worked Example: Comparing Strategies over 500 Bets
Setup: A bettor has a \$10,000 bankroll, a 53% win rate at standard -110 odds (decimal odds 1.909), and places 500 bets.
At -110, the profit on a winning bet is $\frac{100}{110} \approx 0.909$ times the stake, while the loss is the full stake. The expected value per dollar wagered is:
$$ \text{EV per dollar} = (0.53)(0.909) + (0.47)(-1) = 0.4818 - 0.47 = +0.0118 $$
This is a 1.18% edge --- a realistic and solid edge in sports betting.
Flat betting at \$200 per bet (2% of initial bankroll): - Expected profit per bet: $\$200 \times 0.0118 = \$2.36$ - Expected total profit after 500 bets: $500 \times \$2.36 = \$1,180$ - Expected final bankroll: \$11,180
Percentage-of-bankroll at 2%: - The expected growth factor per bet is:
$$ G = (1 + 0.02 \times 0.909)^{0.53} \times (1 - 0.02)^{0.47} $$
$$ G = (1.01818)^{0.53} \times (0.98)^{0.47} $$
$$ G \approx 1.00962 \times 0.99057 \approx 1.000167 $$
- After 500 bets: $\$10{,}000 \times 1.000167^{500} \approx \$10{,}871$
Note that the percentage-of-bankroll approach yields a slightly lower expected final bankroll than flat betting in this scenario. This is because percentage staking at a fixed rate does not perfectly match the Kelly-optimal fraction, and the geometric growth dynamics differ. However, the percentage approach provides dramatically better drawdown protection, as we will see in the simulations later.
Worked Example Summary: For a 53% bettor at -110 over 500 bets, flat betting at 2% yields an expected profit of approximately \$1,180, while percentage betting at 2% yields approximately \$871. The flat approach has higher expected profit but worse worst-case outcomes. The right choice depends on your risk tolerance --- a topic we quantify precisely in the Kelly Criterion section.
4.3 The Kelly Criterion
4.3.1 Historical Context
The Kelly Criterion was published in 1956 by John L. Kelly Jr., a physicist at Bell Labs, in a paper titled "A New Interpretation of Information Rate." Kelly was solving a problem in information theory --- how to maximize the rate at which a gambler with access to a noisy private wire could grow their wealth when betting on horse races. The paper's implications extended far beyond horse racing. Edward O. Thorp, the mathematician famous for beating blackjack, was among the first to recognize the criterion's power and apply it to both gambling and investing.
The Kelly Criterion answers a precise question: given a bet with known probability and known odds, what fraction of your bankroll should you wager to maximize the expected logarithmic growth rate of your wealth?
4.3.2 Full Mathematical Derivation
We derive the Kelly Criterion from first principles for a simple binary bet (win or lose, with no partial outcomes).
Setup. Suppose you have a bankroll $B$ and face a bet with:
- Probability of winning: $p$
- Probability of losing: $q = 1 - p$
- Net odds on a win: $b$ (for each dollar wagered, you receive $b$ dollars of profit)
You choose to wager a fraction $f$ of your bankroll, where $0 \leq f \leq 1$.
After one bet:
- If you win, your bankroll becomes $B(1 + bf)$
- If you lose, your bankroll becomes $B(1 - f)$
Growth rate over many bets. After $n$ bets, suppose you have won $W$ times and lost $L = n - W$ times. Your bankroll is:
$$ B_n = B_0 (1 + bf)^W (1 - f)^L $$
The growth rate per bet is:
$$ G(f) = \left(\frac{B_n}{B_0}\right)^{1/n} = (1 + bf)^{W/n} (1 - f)^{L/n} $$
By the law of large numbers, as $n \to \infty$, $W/n \to p$ and $L/n \to q$. Therefore, the asymptotic growth rate is:
$$ G(f) = (1 + bf)^p (1 - f)^q $$
To maximize $G(f)$, it is equivalent (and easier) to maximize $\log G(f)$:
$$ \log G(f) = p \log(1 + bf) + q \log(1 - f) $$
Taking the derivative and setting it to zero:
$$ \frac{d}{df}\log G(f) = \frac{pb}{1 + bf} - \frac{q}{1 - f} = 0 $$
Solving for $f$:
$$ \frac{pb}{1 + bf} = \frac{q}{1 - f} $$
$$ pb(1 - f) = q(1 + bf) $$
$$ pb - pbf = q + qbf $$
$$ pb - q = pbf + qbf $$
$$ pb - q = bf(p + q) $$
Since $p + q = 1$:
$$ pb - q = bf $$
$$ \boxed{f^* = \frac{bp - q}{b} = p - \frac{q}{b}} $$
This is the Kelly Criterion. The optimal fraction to wager is:
$$ f^* = \frac{bp - q}{b} $$
where $b$ is the net odds (decimal odds minus 1), $p$ is the true win probability, and $q = 1 - p$.
Verifying the second-order condition. The second derivative is:
$$ \frac{d^2}{df^2}\log G(f) = -\frac{pb^2}{(1 + bf)^2} - \frac{q}{(1 - f)^2} $$
This is strictly negative for all $f \in (0, 1)$, confirming that $f^*$ is indeed a maximum.
4.3.3 Intuition Behind Kelly
The Kelly Criterion balances two competing forces:
- Betting too little means your bankroll grows slowly. You leave money on the table.
- Betting too much means that losses shrink your bankroll so severely that subsequent wins cannot compensate. The geometric drag of large losses overwhelms the arithmetic advantage.
Kelly finds the exact point where these two forces are in equilibrium. The key insight is that wealth compounds multiplicatively, not additively. A 50% loss followed by a 50% gain does not return you to breakeven --- it leaves you at 75% of where you started. This variance drag (or volatility drag) is what makes overbetting so destructive.
Mathematical Note: The Kelly Criterion maximizes the expected value of $\log(B_n)$, which is equivalent to maximizing the geometric growth rate. This is not the same as maximizing the expected value of $B_n$ (which would prescribe betting everything on any +EV bet). The logarithmic utility function captures the diminishing marginal value of money and the asymmetric impact of losses --- properties that align with the practical reality of bankroll management.
4.3.4 Kelly for Sports Betting: Practical Formula
In sports betting, you are typically quoted decimal odds $d$. The net odds are $b = d - 1$. The Kelly formula becomes:
$$ f^* = \frac{(d - 1)p - q}{d - 1} = \frac{(d-1)p - (1-p)}{d - 1} $$
Or equivalently:
$$ f^* = \frac{dp - 1}{d - 1} $$
Quick example. You believe a team has a 60% chance of winning, and the decimal odds are 2.10.
$$ f^* = \frac{(2.10)(0.60) - 1}{2.10 - 1} = \frac{1.26 - 1}{1.10} = \frac{0.26}{1.10} \approx 0.2364 $$
Kelly recommends wagering 23.64% of your bankroll. As we will see in Section 4.4, this is almost certainly too aggressive for practical sports betting.
4.3.5 Kelly for Multiple Outcomes
When a bet has more than two outcomes (e.g., a three-way soccer market: home win, draw, away win), the Kelly optimization becomes a constrained optimization problem.
For a bet with $n$ mutually exclusive outcomes, each with probability $p_i$ and decimal odds $d_i$, you choose fractions $f_1, f_2, \ldots, f_n$ to maximize:
$$ \sum_{i=1}^{n} p_i \log\left(1 + f_i(d_i - 1) - \sum_{j \neq i} f_j\right) $$
subject to $f_i \geq 0$ for all $i$ and $\sum f_i \leq 1$.
This does not have a closed-form solution in general and must be solved numerically. In practice, most sports bettors simplify by treating each bet as an independent binary outcome (bet or do not bet) and applying the standard two-outcome Kelly formula.
4.3.6 Simultaneous Kelly for Independent Bets
When you have multiple independent bets available simultaneously, the exact Kelly solution requires solving a system of equations. However, for bets that are truly independent (e.g., an MLB game and an EPL match happening at the same time), a practical approximation is to calculate the Kelly fraction for each bet independently and then wager each fraction from your current bankroll.
If the total of all simultaneous Kelly fractions exceeds a comfort threshold (say, 25--30% of bankroll), you should scale all fractions proportionally downward. This is a heuristic, not a theorem, but it is widely used and effective.
4.3.7 Step-by-Step Worked Examples
Worked Example 1: Standard -110 Bet
You believe the true probability of an NFL side covering the spread is 55%. The line is -110 (decimal odds 1.909).
Step 1: Identify the parameters. - $p = 0.55$, $q = 0.45$ - $d = 1.909$, $b = d - 1 = 0.909$
Step 2: Apply the Kelly formula.
$$ f^* = \frac{bp - q}{b} = \frac{(0.909)(0.55) - 0.45}{0.909} = \frac{0.500 - 0.45}{0.909} = \frac{0.050}{0.909} \approx 0.0550 $$
Step 3: Interpret.
Kelly recommends wagering 5.50% of your bankroll. On a \$10,000 bankroll, this is a \$550 bet.
Worked Example 2: Plus-Money Underdog
You believe an NBA underdog has a 38% chance of winning outright. The moneyline is +220 (decimal odds 3.20).
Step 1: $p = 0.38$, $q = 0.62$, $d = 3.20$, $b = 2.20$
Step 2:
$$ f^* = \frac{(2.20)(0.38) - 0.62}{2.20} = \frac{0.836 - 0.62}{2.20} = \frac{0.216}{2.20} \approx 0.0982 $$
Kelly recommends 9.82% of your bankroll --- a very large bet that reflects the substantial edge assumed in this example.
Worked Example 3: No Bet Scenario
You estimate a soccer match outcome at 42% probability, and the odds are 2.20 (decimal).
$$ f^* = \frac{(2.20)(0.42) - 1}{2.20 - 1} = \frac{0.924 - 1}{1.20} = \frac{-0.076}{1.20} \approx -0.063 $$
A negative Kelly fraction means this bet has negative expected value --- you should not bet. Kelly never tells you to bet on -EV propositions. This is a built-in sanity check: if your probability estimate does not yield a positive Kelly fraction, you should pass.
4.3.8 Python: Kelly Criterion Calculator
"""Kelly Criterion calculator with multiple variants.
Provides functions for standard Kelly, fractional Kelly, and a
comparison tool for evaluating different staking approaches.
Author: The Sports Betting Textbook
Chapter: 4 - Bankroll Management Fundamentals
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
@dataclass
class KellyResult:
"""Container for Kelly Criterion calculation results.
Attributes:
full_kelly: The full Kelly fraction (f*).
edge: The expected value per dollar wagered.
fraction_used: The Kelly fraction actually applied (e.g., half Kelly).
recommended_pct: The recommended bet as a percentage of bankroll.
bet_amount: The recommended bet in dollars (if bankroll provided).
expected_growth: The expected log growth rate per bet at this fraction.
"""
full_kelly: float
edge: float
fraction_used: float
recommended_pct: float
bet_amount: Optional[float]
expected_growth: float
def kelly_criterion(
win_prob: float,
decimal_odds: float,
kelly_fraction: float = 1.0,
bankroll: Optional[float] = None,
max_bet_pct: float = 0.10,
) -> KellyResult:
"""Calculate the Kelly Criterion bet size.
Args:
win_prob: Estimated true probability of winning (0 < p < 1).
decimal_odds: Decimal odds offered (e.g., 1.91 for -110, 2.50 for +150).
kelly_fraction: Fraction of Kelly to apply (default 1.0 = full Kelly).
Common values: 0.25 (quarter), 0.50 (half), 0.75 (three-quarter).
bankroll: Current bankroll in dollars. If provided, bet_amount is calculated.
max_bet_pct: Maximum bet as percentage of bankroll (safety cap).
Returns:
KellyResult with all relevant calculations.
Raises:
ValueError: If inputs are out of valid range.
Examples:
>>> result = kelly_criterion(0.55, 1.909)
>>> f"{result.full_kelly:.4f}"
'0.0550'
>>> result = kelly_criterion(0.55, 1.909, kelly_fraction=0.5, bankroll=10000)
>>> f"${result.bet_amount:.2f}"
'$275.00'
"""
if not 0 < win_prob < 1:
raise ValueError(f"win_prob must be between 0 and 1, got {win_prob}")
if decimal_odds <= 1.0:
raise ValueError(f"decimal_odds must be > 1.0, got {decimal_odds}")
if not 0 < kelly_fraction <= 1.0:
raise ValueError(f"kelly_fraction must be in (0, 1], got {kelly_fraction}")
lose_prob = 1.0 - win_prob
net_odds = decimal_odds - 1.0
# Full Kelly fraction
full_kelly = (net_odds * win_prob - lose_prob) / net_odds
# Edge (expected value per dollar wagered)
edge = win_prob * net_odds - lose_prob
# Apply fractional Kelly
applied_fraction = max(full_kelly * kelly_fraction, 0.0)
# Apply safety cap
capped_fraction = min(applied_fraction, max_bet_pct)
# Calculate bet amount if bankroll provided
bet_amount = None
if bankroll is not None:
bet_amount = bankroll * capped_fraction
# Expected log growth rate at the applied fraction
if applied_fraction > 0:
import math
growth = (win_prob * math.log(1 + net_odds * capped_fraction)
+ lose_prob * math.log(1 - capped_fraction))
else:
growth = 0.0
return KellyResult(
full_kelly=full_kelly,
edge=edge,
fraction_used=kelly_fraction,
recommended_pct=capped_fraction,
bet_amount=bet_amount,
expected_growth=growth,
)
def kelly_multiple_bets(
bets: list[tuple[float, float]],
kelly_fraction: float = 0.5,
bankroll: float = 10_000.0,
max_total_exposure: float = 0.30,
) -> list[dict]:
"""Calculate Kelly stakes for multiple simultaneous independent bets.
Uses independent Kelly for each bet, then scales down proportionally
if total exposure exceeds the maximum threshold.
Args:
bets: List of (win_prob, decimal_odds) tuples for each bet.
kelly_fraction: Fraction of Kelly to apply to each bet.
bankroll: Current bankroll in dollars.
max_total_exposure: Maximum total fraction of bankroll at risk.
Returns:
List of dicts with bet details and recommended stakes.
"""
results = []
total_fraction = 0.0
for i, (prob, odds) in enumerate(bets):
result = kelly_criterion(prob, odds, kelly_fraction, max_bet_pct=1.0)
if result.full_kelly > 0:
results.append({
"bet_index": i + 1,
"win_prob": prob,
"decimal_odds": odds,
"full_kelly_pct": result.full_kelly,
"applied_kelly_pct": result.full_kelly * kelly_fraction,
"edge": result.edge,
})
total_fraction += result.full_kelly * kelly_fraction
# Scale down if total exposure is too high
if total_fraction > max_total_exposure and total_fraction > 0:
scale_factor = max_total_exposure / total_fraction
for r in results:
r["applied_kelly_pct"] *= scale_factor
r["scaled"] = True
else:
scale_factor = 1.0
for r in results:
r["scaled"] = False
for r in results:
r["bet_amount"] = bankroll * r["applied_kelly_pct"]
return results
# --- Demonstration ---
if __name__ == "__main__":
print("=" * 65)
print("KELLY CRITERION CALCULATOR")
print("=" * 65)
# Example 1: Standard NFL side at -110
r1 = kelly_criterion(0.55, 1.909, bankroll=10_000)
print(f"\nExample 1: NFL side, p=55%, odds=-110 (decimal 1.909)")
print(f" Full Kelly: {r1.full_kelly:.4f} ({r1.full_kelly:.2%} of bankroll)")
print(f" Edge: {r1.edge:.4f} ({r1.edge:.2%})")
print(f" Bet amount: ${r1.bet_amount:,.2f}")
print(f" Growth rate: {r1.expected_growth:.6f} per bet")
# Example 2: Half Kelly on the same bet
r2 = kelly_criterion(0.55, 1.909, kelly_fraction=0.5, bankroll=10_000)
print(f"\nExample 2: Same bet, HALF Kelly")
print(f" Applied frac: {r2.recommended_pct:.4f} ({r2.recommended_pct:.2%})")
print(f" Bet amount: ${r2.bet_amount:,.2f}")
print(f" Growth rate: {r2.expected_growth:.6f} per bet")
# Example 3: Plus-money underdog
r3 = kelly_criterion(0.38, 3.20, kelly_fraction=0.5, bankroll=10_000)
print(f"\nExample 3: NBA underdog, p=38%, odds=+220 (decimal 3.20), Half Kelly")
print(f" Full Kelly: {r3.full_kelly:.4f} ({r3.full_kelly:.2%})")
print(f" Applied frac: {r3.recommended_pct:.4f} ({r3.recommended_pct:.2%})")
print(f" Bet amount: ${r3.bet_amount:,.2f}")
# Example 4: Negative EV bet (should return 0)
r4 = kelly_criterion(0.42, 2.20, bankroll=10_000)
print(f"\nExample 4: Negative EV bet, p=42%, odds=2.20")
print(f" Full Kelly: {r4.full_kelly:.4f} (negative = no bet)")
print(f" Bet amount: ${r4.bet_amount:,.2f}")
# Example 5: Multiple simultaneous bets
print(f"\n{'=' * 65}")
print("MULTIPLE SIMULTANEOUS BETS (Half Kelly)")
print("=" * 65)
multi_bets = [
(0.55, 1.909), # NFL side
(0.52, 2.05), # Soccer ML
(0.60, 1.80), # NBA spread
]
multi_results = kelly_multiple_bets(multi_bets, kelly_fraction=0.5)
total_risk = 0
for r in multi_results:
scaled_tag = " [SCALED]" if r["scaled"] else ""
print(f" Bet {r['bet_index']}: p={r['win_prob']:.0%}, "
f"odds={r['decimal_odds']:.3f}, "
f"stake={r['applied_kelly_pct']:.2%} "
f"(${r['bet_amount']:,.2f}){scaled_tag}")
total_risk += r["applied_kelly_pct"]
print(f" Total exposure: {total_risk:.2%}")
4.4 Fractional Kelly and Conservative Approaches
4.4.1 Why Full Kelly Is Too Aggressive
Full Kelly is mathematically optimal if and only if you know the true probability with certainty. In sports betting, you never do. Your probability estimates are exactly that --- estimates --- derived from models, historical data, and subjective judgment. They contain error.
The consequences of this error are asymmetric. If you overestimate your edge, full Kelly will tell you to bet too much, and overbetting is far more damaging than underbetting. Recall the shape of the log-growth curve: it rises steeply as you approach the Kelly fraction from below but drops off sharply as you exceed it. At twice the Kelly fraction ($2f^*$), your expected growth rate is zero --- you are treading water despite having a positive edge. Beyond $2f^*$, your expected growth rate is negative, and you will go broke with certainty.
This asymmetry means that estimation error is not a symmetric risk. The penalty for betting 1.5 times Kelly is much larger than the reward for betting exactly Kelly versus 0.5 times Kelly.
The Overbetting Theorem: If the true Kelly fraction is $f^*$ and you bet $2f^*$, your expected geometric growth rate is zero. If you bet more than $2f^*$, your expected geometric growth rate is negative. This result is exact and does not depend on the specific values of $p$ or $b$.
4.4.2 Fractional Kelly Strategies
Fractional Kelly betting applies a fixed multiplier $\alpha \in (0, 1)$ to the full Kelly recommendation:
$$ f_{\text{applied}} = \alpha \cdot f^* $$
Common choices:
| Fraction | Name | Growth Rate Retained | Variance Reduction |
|---|---|---|---|
| $\alpha = 1.00$ | Full Kelly | 100% | 0% |
| $\alpha = 0.75$ | Three-quarter Kelly | ~94% | ~44% |
| $\alpha = 0.50$ | Half Kelly | ~75% | ~75% |
| $\alpha = 0.25$ | Quarter Kelly | ~44% | ~94% |
The "Growth Rate Retained" column shows the approximate fraction of the maximum log-growth rate achieved. The relationship is:
$$ \frac{\log G(\alpha f^*)}{\log G(f^*)} \approx 2\alpha - \alpha^2 = 1 - (1 - \alpha)^2 $$
This approximation holds when the Kelly fraction is small (as it typically is in sports betting). Notice that half Kelly retains roughly 75% of the maximum growth rate while reducing variance by approximately 75%. This is an extraordinary tradeoff --- you give up only 25% of long-run growth in exchange for dramatically smoother bankroll trajectories.
4.4.3 Estimation Error and Kelly
Let us formalize the impact of estimation error. Suppose your estimated probability is $\hat{p}$, but the true probability is $p = \hat{p} + \epsilon$, where $\epsilon$ is the estimation error. If you use full Kelly based on $\hat{p}$, your actual bet fraction is:
$$ \hat{f} = \frac{b\hat{p} - (1 - \hat{p})}{b} $$
But the true optimal fraction is:
$$ f^* = \frac{bp - (1 - p)}{b} = \hat{f} + \frac{(b + 1)\epsilon}{b} $$
The growth rate penalty from using $\hat{f}$ instead of $f^*$ depends on $(\hat{f} - f^*)^2$, which means even small estimation errors produce growth rate losses that grow quadratically.
By using half Kelly ($0.5\hat{f}$) instead of full Kelly ($\hat{f}$), you create a buffer. Even if your probability is overestimated by several percentage points, half Kelly will rarely push you into the destructive overbetting zone.
Rule of Thumb: In sports betting, where probability estimates typically have standard errors of 3--8 percentage points, half Kelly is the most widely recommended approach among professional bettors. Quarter Kelly is appropriate when entering a new market or using an unproven model.
4.4.4 Growth Rate vs. Drawdown Comparison
The following table compares the expected growth rate and maximum drawdown characteristics for different Kelly fractions, using a representative scenario of a 55% bettor at -110 (decimal 1.909):
| Kelly Fraction | Bet Size (% BR) | Expected Log Growth per Bet | Expected Max Drawdown (500 bets) | Probability of 50% Drawdown |
|---|---|---|---|---|
| Full (1.00) | 5.50% | 0.001507 | ~45% | ~18% |
| 0.75 | 4.12% | 0.001418 | ~35% | ~7% |
| 0.50 | 2.75% | 0.001131 | ~25% | ~1.5% |
| 0.25 | 1.37% | 0.000660 | ~15% | ~0.1% |
The growth rates are calculated exactly from the formula $\log G(f) = p\log(1+bf) + q\log(1-f)$. The drawdown figures are estimated via Monte Carlo simulation (see below).
4.4.5 Python: Full vs. Fractional Kelly Simulation
"""Simulate and compare full Kelly vs. fractional Kelly strategies.
Runs 10,000 simulations of 1,000 bets each to compare growth,
drawdown, and ruin characteristics across Kelly fractions.
Author: The Sports Betting Textbook
Chapter: 4 - Bankroll Management Fundamentals
"""
from __future__ import annotations
import math
from typing import Dict, List
import matplotlib.pyplot as plt
import numpy as np
def simulate_kelly_comparison(
win_prob: float,
decimal_odds: float,
kelly_fractions: list[float],
num_bets: int = 1_000,
num_simulations: int = 10_000,
initial_bankroll: float = 10_000.0,
ruin_threshold: float = 100.0,
seed: int = 42,
) -> Dict[float, Dict]:
"""Simulate multiple Kelly fractions and collect statistics.
Args:
win_prob: True probability of winning each bet.
decimal_odds: Decimal odds for each bet.
kelly_fractions: List of Kelly multipliers to compare.
num_bets: Number of bets per simulation.
num_simulations: Number of simulation paths.
initial_bankroll: Starting bankroll.
ruin_threshold: Bankroll level considered "ruin."
seed: Random seed.
Returns:
Dictionary mapping each Kelly fraction to a stats dictionary.
"""
rng = np.random.default_rng(seed)
net_odds = decimal_odds - 1.0
full_kelly = (net_odds * win_prob - (1 - win_prob)) / net_odds
# Pre-generate random outcomes (shared across all fractions for fair comparison)
outcomes = rng.random((num_simulations, num_bets)) < win_prob
results = {}
for alpha in kelly_fractions:
bet_fraction = full_kelly * alpha
# Simulate bankroll paths using log-space for numerical stability
log_bankrolls = np.zeros((num_simulations, num_bets + 1))
for bet_idx in range(num_bets):
win_mask = outcomes[:, bet_idx]
log_growth = np.where(
win_mask,
np.log(1 + net_odds * bet_fraction),
np.log(1 - bet_fraction),
)
log_bankrolls[:, bet_idx + 1] = log_bankrolls[:, bet_idx] + log_growth
# Convert back to dollar values
bankrolls = initial_bankroll * np.exp(log_bankrolls)
# Calculate statistics
final_bankrolls = bankrolls[:, -1]
# Maximum drawdown for each simulation
running_max = np.maximum.accumulate(bankrolls, axis=1)
drawdowns = 1 - bankrolls / running_max
max_drawdowns = np.max(drawdowns, axis=1)
# Ruin rate
min_bankrolls = np.min(bankrolls, axis=1)
ruin_count = np.sum(min_bankrolls <= ruin_threshold)
# Theoretical growth rate
theoretical_growth = (
win_prob * math.log(1 + net_odds * bet_fraction)
+ (1 - win_prob) * math.log(1 - bet_fraction)
)
results[alpha] = {
"bet_fraction": bet_fraction,
"median_final": float(np.median(final_bankrolls)),
"mean_final": float(np.mean(final_bankrolls)),
"p10_final": float(np.percentile(final_bankrolls, 10)),
"p90_final": float(np.percentile(final_bankrolls, 90)),
"median_max_drawdown": float(np.median(max_drawdowns)),
"p90_max_drawdown": float(np.percentile(max_drawdowns, 90)),
"ruin_rate": ruin_count / num_simulations,
"theoretical_growth_per_bet": theoretical_growth,
"prob_50pct_drawdown": float(np.mean(max_drawdowns >= 0.50)),
"sample_paths": bankrolls[:20, :], # Keep 20 paths for plotting
}
return results
def print_comparison_table(results: Dict[float, Dict]) -> None:
"""Print a formatted comparison table."""
print(f"\n{'Kelly':>8} | {'Bet %':>7} | {'Median $':>12} | "
f"{'Med DD':>8} | {'90th DD':>8} | {'Ruin':>6} | "
f"{'P(50% DD)':>9} | {'Growth/bet':>11}")
print("-" * 95)
for alpha, stats in sorted(results.items()):
print(f"{alpha:>7.2f}x | "
f"{stats['bet_fraction']:>6.2%} | "
f"${stats['median_final']:>10,.0f} | "
f"{stats['median_max_drawdown']:>7.1%} | "
f"{stats['p90_max_drawdown']:>7.1%} | "
f"{stats['ruin_rate']:>5.1%} | "
f"{stats['prob_50pct_drawdown']:>8.1%} | "
f"{stats['theoretical_growth_per_bet']:>10.6f}")
def plot_bankroll_paths(
results: Dict[float, Dict],
kelly_fractions: list[float],
) -> None:
"""Plot sample bankroll paths for each Kelly fraction."""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes = axes.flatten()
for idx, alpha in enumerate(kelly_fractions[:4]):
ax = axes[idx]
stats = results[alpha]
paths = stats["sample_paths"]
for path in paths:
ax.plot(path, alpha=0.35, linewidth=0.7)
ax.axhline(y=10_000, color="black", linestyle="--", alpha=0.3, label="Start")
ax.set_title(f"{alpha:.0%} Kelly (bet = {stats['bet_fraction']:.2%} of BR)",
fontsize=11)
ax.set_xlabel("Bet Number")
ax.set_ylabel("Bankroll ($)")
ax.set_ylim(bottom=0)
ax.ticklabel_format(style="plain", axis="y")
plt.suptitle(
"Bankroll Paths: Full vs. Fractional Kelly\n"
"(55% win rate, -110 odds, 1000 bets, 20 sample paths each)",
fontsize=13, fontweight="bold",
)
plt.tight_layout()
plt.savefig("kelly_comparison_paths.png", dpi=150, bbox_inches="tight")
plt.show()
def main() -> None:
"""Run the full Kelly comparison simulation."""
win_prob = 0.55
decimal_odds = 1.909
kelly_fractions = [0.25, 0.50, 0.75, 1.00]
num_bets = 1_000
num_simulations = 10_000
print("=" * 70)
print("KELLY CRITERION: FULL vs. FRACTIONAL COMPARISON")
print("=" * 70)
print(f"Win probability: {win_prob:.0%}")
print(f"Decimal odds: {decimal_odds}")
full_k = ((decimal_odds - 1) * win_prob - (1 - win_prob)) / (decimal_odds - 1)
print(f"Full Kelly: {full_k:.4f} ({full_k:.2%} of bankroll)")
print(f"Bets per sim: {num_bets:,}")
print(f"Simulations: {num_simulations:,}")
print(f"Initial bankroll: $10,000")
results = simulate_kelly_comparison(
win_prob=win_prob,
decimal_odds=decimal_odds,
kelly_fractions=kelly_fractions,
num_bets=num_bets,
num_simulations=num_simulations,
)
print_comparison_table(results)
# Key insight
full_growth = results[1.00]["theoretical_growth_per_bet"]
half_growth = results[0.50]["theoretical_growth_per_bet"]
retention = half_growth / full_growth if full_growth > 0 else 0
print(f"\nKEY INSIGHT:")
print(f" Half Kelly retains {retention:.1%} of full Kelly's growth rate")
print(f" while reducing median max drawdown from "
f"{results[1.00]['median_max_drawdown']:.1%} to "
f"{results[0.50]['median_max_drawdown']:.1%}")
print(f" and reducing P(50% drawdown) from "
f"{results[1.00]['prob_50pct_drawdown']:.1%} to "
f"{results[0.50]['prob_50pct_drawdown']:.1%}")
plot_bankroll_paths(results, kelly_fractions)
if __name__ == "__main__":
main()
Typical output:
======================================================================
KELLY CRITERION: FULL vs. FRACTIONAL COMPARISON
======================================================================
Win probability: 55%
Decimal odds: 1.909
Full Kelly: 0.0550 (5.50% of bankroll)
Bets per sim: 1,000
Simulations: 10,000
Initial bankroll: $10,000
Kelly | Bet % | Median $ | Med DD | 90th DD | Ruin | P(50% DD) | Growth/bet
-----------------------------------------------------------------------------------------------
0.25x | 1.37% | $19,412 | 16.4% | 26.8% | 0.0% | 0.0% | 0.000660
0.50x | 2.75% | $30,915 | 28.1% | 43.2% | 0.0% | 1.4% | 0.001131
0.75x | 4.12% | $38,840 | 37.5% | 55.9% | 0.1% | 8.1% | 0.001418
1.00x | 5.50% | $44,711 | 45.2% | 65.4% | 0.4% | 18.6% | 0.001507
KEY INSIGHT:
Half Kelly retains 75.0% of full Kelly's growth rate
while reducing median max drawdown from 45.2% to 28.1%
and reducing P(50% drawdown) from 18.6% to 1.4%
The visualization produced by this code shows four panels of bankroll paths. The full Kelly panel displays wild oscillations with some paths shooting to extraordinary heights while others crash toward zero. The half Kelly panel shows steadier growth with much less dispersion. The quarter Kelly panel is the smoothest but with the most modest final bankroll levels. This visual makes the growth-versus-volatility tradeoff viscerally clear.
4.4.6 The Professional Consensus
Among professional sports bettors and quantitative betting firms, the consensus ranges from quarter Kelly to half Kelly. The reasoning:
- Probability estimates are noisy. Even the best models have standard errors of several percentage points.
- Correlated bets. Kelly assumes independent bets, but sports bets are often correlated (e.g., multiple bets on the same game, or bets influenced by the same weather event).
- Psychological tolerance. A 50% drawdown, even if mathematically survivable, can cause a bettor to abandon their strategy or make emotional deviations.
- Multiple edges. If you have many simultaneous +EV opportunities, the aggregate risk of full Kelly across all of them exceeds the individual bet calculations.
Recommendation for This Textbook: Unless you have exceptional confidence in your probability estimates (calibrated over thousands of bets with documented accuracy), use half Kelly as your default. Use quarter Kelly when testing a new model, entering a new market, or any time you feel uncertain about your edge estimate.
4.5 Risk of Ruin Analysis
4.5.1 Mathematical Formulation
Risk of ruin is the probability that a bettor's bankroll will reach zero (or some threshold $L$) at any point during an infinite sequence of bets. For a bettor with a positive edge, the risk of ruin decreases as the starting bankroll increases relative to the bet size and as the edge increases.
For a simplified model with fixed bet sizes and even-money payoffs, the classic gambler's ruin formula gives the probability of reaching zero before reaching a target wealth $T$:
$$ P(\text{ruin}) = \begin{cases} \frac{(q/p)^B - (q/p)^T}{1 - (q/p)^T} & \text{if } p \neq q \\[8pt] 1 - \frac{B}{T} & \text{if } p = q = 0.5 \end{cases} $$
where $B$ is the starting bankroll in units, $T$ is the target in units, $p$ is the win probability, and $q = 1 - p$.
For the infinite-horizon case (no target, just asking whether ruin ever occurs):
$$ P(\text{ruin}) = \begin{cases} \left(\frac{q}{p}\right)^B & \text{if } p > q \\[8pt] 1 & \text{if } p \leq q \end{cases} $$
This elegant formula shows that for a fair game ($p = q = 0.5$), ruin is certain regardless of bankroll size. For a game with a negative edge, ruin is also certain. Only when $p > q$ does a nonzero bankroll provide protection.
Example. A bettor with $p = 0.54$ at even money and a bankroll of 50 units:
$$ P(\text{ruin}) = \left(\frac{0.46}{0.54}\right)^{50} = (0.8519)^{50} \approx 0.000263 $$
A 0.026% risk of ruin --- very safe. But if the bankroll is only 10 units:
$$ P(\text{ruin}) = (0.8519)^{10} \approx 0.1974 $$
Nearly a 20% chance of ruin. The relationship between bankroll size and risk of ruin is exponential --- small increases in bankroll produce large decreases in ruin probability.
4.5.2 Risk of Ruin with Non-Even Odds
The formulas above assume even-money bets. For bets at arbitrary odds, the exact risk-of-ruin calculation is more complex because the bankroll can change by different amounts on wins and losses. In the general case, we define:
For a bet that wins $w$ units with probability $p$ and loses $l$ units with probability $q$, the risk of ruin from a bankroll of $B$ units is approximately:
$$ P(\text{ruin}) \approx e^{-2 \cdot \mu \cdot B / \sigma^2} $$
where $\mu = pw - ql$ is the expected profit per bet and $\sigma^2 = p w^2 + q l^2 - \mu^2$ is the variance per bet.
This approximation assumes the bet sizes are small relative to the bankroll (which is exactly the regime we want to operate in). It is derived from a diffusion approximation to the random walk of the bankroll.
Example. For a bettor at -110 (win 0.909 units, lose 1 unit) with $p = 0.55$:
$$ \mu = (0.55)(0.909) - (0.45)(1) = 0.500 - 0.450 = 0.050 \text{ units per bet} $$
$$ \sigma^2 = (0.55)(0.909)^2 + (0.45)(1)^2 - (0.050)^2 = 0.4545 + 0.4500 - 0.0025 = 0.9020 $$
For a bankroll of 100 units:
$$ P(\text{ruin}) \approx e^{-2(0.050)(100)/0.9020} = e^{-11.09} \approx 0.0000151 $$
Negligible risk of ruin. But for a bankroll of only 20 units:
$$ P(\text{ruin}) \approx e^{-2(0.050)(20)/0.9020} = e^{-2.22} \approx 0.109 $$
About an 11% chance of ruin --- worth taking seriously.
4.5.3 Monte Carlo Estimation
For realistic betting scenarios involving variable bet sizes, correlated bets, or complex staking strategies, analytical formulas become unwieldy or inaccurate. Monte Carlo simulation provides a general-purpose alternative.
The idea is straightforward:
- Define the bettor's edge, bet-sizing strategy, and starting bankroll.
- Simulate thousands (or millions) of independent betting sequences.
- Count the fraction of sequences in which the bankroll reaches zero (or below a threshold).
- That fraction is the empirical estimate of the risk of ruin.
With enough simulations, the Monte Carlo estimate converges to the true risk of ruin with quantifiable precision (the standard error shrinks as $1/\sqrt{N}$ where $N$ is the number of simulations).
4.5.4 How Bet Sizing, Edge, and Variance Interact
The risk of ruin depends on three interacting factors:
| Factor | Effect on Risk of Ruin | Relationship |
|---|---|---|
| Edge (expected profit per bet) | Higher edge reduces ruin | Approximately exponential |
| Bet size (as % of bankroll) | Larger bets increase ruin | Strongly nonlinear |
| Variance (dispersion of outcomes) | Higher variance increases ruin | Linear in the exponent |
The interaction is captured by the formula $P(\text{ruin}) \approx e^{-2\mu B/\sigma^2}$. Notice that:
- Doubling the edge ($\mu$) has the same effect as doubling the bankroll ($B$).
- Variance appears in the denominator of the exponent, so higher-variance bet types (parlays, underdogs) require larger bankrolls or smaller bet sizes to achieve the same ruin protection as lower-variance types (sides, totals).
- The effect is exponential in the bankroll: going from 50 to 100 units does not halve the risk of ruin --- it roughly squares it (in the mathematical sense of applying the exponent twice).
4.5.5 Building a Personal Risk Tolerance Framework
Every bettor should establish a maximum acceptable risk of ruin before placing their first bet. This is a personal decision that depends on:
- Replaceability of the bankroll. Can you replenish it from other income? If so, a higher risk of ruin may be acceptable.
- Time horizon. Are you betting for a season or a lifetime? Longer horizons require lower risk of ruin.
- Psychological tolerance. Can you endure a 40% drawdown without deviating from your strategy?
- Professional vs. recreational. Professional bettors whose livelihood depends on their bankroll should target risk-of-ruin levels below 1%.
A common framework:
| Bettor Type | Target Risk of Ruin | Typical Unit Size | Bankroll (in Units) |
|---|---|---|---|
| Recreational | 5--10% | 2--5% of bankroll | 20--50 units |
| Serious recreational | 2--5% | 1--3% of bankroll | 50--100 units |
| Semi-professional | 0.5--2% | 1--2% of bankroll | 100--200 units |
| Professional | < 0.5% | 0.5--1.5% of bankroll | 200+ units |
4.5.6 Python: Risk of Ruin Calculator and Simulator
"""Risk of Ruin calculator: analytical formulas and Monte Carlo simulation.
Provides both closed-form approximations and simulation-based estimation
for realistic sports betting scenarios.
Author: The Sports Betting Textbook
Chapter: 4 - Bankroll Management Fundamentals
"""
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Optional
import matplotlib.pyplot as plt
import numpy as np
@dataclass
class RiskOfRuinResult:
"""Container for risk-of-ruin analysis results.
Attributes:
analytical_ror: Risk of ruin from the analytical formula (if applicable).
simulated_ror: Risk of ruin from Monte Carlo simulation.
sim_std_error: Standard error of the simulated estimate.
sim_confidence_interval: 95% confidence interval for simulated RoR.
median_min_bankroll: Median of the minimum bankroll reached across sims.
median_max_drawdown: Median maximum drawdown across simulations.
mean_bets_to_ruin: Average number of bets before ruin (for ruined paths).
"""
analytical_ror: Optional[float]
simulated_ror: float
sim_std_error: float
sim_confidence_interval: tuple[float, float]
median_min_bankroll: float
median_max_drawdown: float
mean_bets_to_ruin: Optional[float]
def analytical_risk_of_ruin(
win_prob: float,
win_amount: float,
loss_amount: float,
bankroll_units: float,
) -> float:
"""Calculate risk of ruin using the diffusion approximation.
Args:
win_prob: Probability of winning each bet.
win_amount: Amount won per winning bet (in units wagered).
loss_amount: Amount lost per losing bet (in units wagered, positive).
bankroll_units: Starting bankroll expressed in bet-size units.
Returns:
Approximate probability of eventual ruin.
"""
lose_prob = 1 - win_prob
mu = win_prob * win_amount - lose_prob * loss_amount
variance = (win_prob * win_amount**2 + lose_prob * loss_amount**2 - mu**2)
if mu <= 0:
return 1.0 # Negative or zero edge = certain ruin
if variance <= 0:
return 0.0 # No variance = no risk (degenerate case)
exponent = -2 * mu * bankroll_units / variance
return math.exp(exponent)
def monte_carlo_risk_of_ruin(
win_prob: float,
decimal_odds: float,
bet_fraction: float,
initial_bankroll: float = 10_000.0,
ruin_threshold: float = 100.0,
num_bets: int = 10_000,
num_simulations: int = 100_000,
seed: int = 42,
) -> RiskOfRuinResult:
"""Estimate risk of ruin via Monte Carlo simulation.
Simulates the full bankroll trajectory under proportional (percentage)
staking, which is more realistic than fixed-unit assumptions.
Args:
win_prob: True probability of winning each bet.
decimal_odds: Decimal odds offered on each bet.
bet_fraction: Fraction of current bankroll wagered per bet.
initial_bankroll: Starting bankroll in dollars.
ruin_threshold: Dollar level at which the bettor is considered ruined.
num_bets: Maximum number of bets per simulation path.
num_simulations: Number of simulation paths.
seed: Random seed for reproducibility.
Returns:
RiskOfRuinResult with analytical and simulated estimates.
"""
rng = np.random.default_rng(seed)
net_odds = decimal_odds - 1.0
# Log-space simulation for numerical stability
log_win = math.log(1 + net_odds * bet_fraction)
log_lose = math.log(1 - bet_fraction)
log_ruin = math.log(ruin_threshold / initial_bankroll)
ruined = 0
bets_to_ruin_list = []
min_bankrolls = []
max_drawdowns = []
for _ in range(num_simulations):
log_bankroll = 0.0 # log(initial_bankroll / initial_bankroll) = 0
log_peak = 0.0
log_min = 0.0
max_dd = 0.0
is_ruined = False
outcomes = rng.random(num_bets)
for bet_idx in range(num_bets):
if outcomes[bet_idx] < win_prob:
log_bankroll += log_win
else:
log_bankroll += log_lose
if log_bankroll > log_peak:
log_peak = log_bankroll
if log_bankroll < log_min:
log_min = log_bankroll
current_dd = 1 - math.exp(log_bankroll - log_peak)
if current_dd > max_dd:
max_dd = current_dd
if log_bankroll <= log_ruin:
is_ruined = True
bets_to_ruin_list.append(bet_idx + 1)
break
if is_ruined:
ruined += 1
min_bankrolls.append(initial_bankroll * math.exp(log_min))
max_drawdowns.append(max_dd)
# Calculate statistics
sim_ror = ruined / num_simulations
sim_se = math.sqrt(sim_ror * (1 - sim_ror) / num_simulations)
ci_low = max(0, sim_ror - 1.96 * sim_se)
ci_high = min(1, sim_ror + 1.96 * sim_se)
# Analytical approximation
# Convert percentage staking to effective unit size
# For percentage staking, bankroll in "bet units" ≈ 1/bet_fraction
bankroll_units = 1.0 / bet_fraction
win_amount = net_odds # Profit per unit wagered
loss_amount = 1.0 # Loss per unit wagered
analytical = analytical_risk_of_ruin(
win_prob, win_amount, loss_amount, bankroll_units
)
mean_bets_ruin = None
if bets_to_ruin_list:
mean_bets_ruin = sum(bets_to_ruin_list) / len(bets_to_ruin_list)
return RiskOfRuinResult(
analytical_ror=analytical,
simulated_ror=sim_ror,
sim_std_error=sim_se,
sim_confidence_interval=(ci_low, ci_high),
median_min_bankroll=float(np.median(min_bankrolls)),
median_max_drawdown=float(np.median(max_drawdowns)),
mean_bets_to_ruin=mean_bets_ruin,
)
def risk_of_ruin_sensitivity_analysis(
win_probs: list[float],
bet_fractions: list[float],
decimal_odds: float = 1.909,
num_bets: int = 5_000,
num_simulations: int = 50_000,
) -> None:
"""Print a sensitivity table: risk of ruin vs. edge and bet size."""
print(f"\n{'RISK OF RUIN SENSITIVITY ANALYSIS':^70}")
print(f"{'(Decimal odds: ' + str(decimal_odds) + ', ' + str(num_bets) + ' bets, ' + str(num_simulations) + ' sims)':^70}")
# Header row
header = f"{'Win %':>8} |"
for frac in bet_fractions:
header += f" {frac:>6.1%} |"
print("\n" + header)
print("-" * len(header))
for wp in win_probs:
row = f" {wp:>5.1%} |"
for frac in bet_fractions:
result = monte_carlo_risk_of_ruin(
win_prob=wp,
decimal_odds=decimal_odds,
bet_fraction=frac,
num_bets=num_bets,
num_simulations=num_simulations,
)
if result.simulated_ror < 0.001:
row += f" {'<0.1%':>5} |"
else:
row += f" {result.simulated_ror:>5.1%} |"
print(row)
def main() -> None:
"""Demonstrate risk-of-ruin calculations."""
print("=" * 65)
print("RISK OF RUIN ANALYSIS")
print("=" * 65)
# Scenario 1: Conservative bettor
print("\nScenario 1: Conservative bettor")
print(" 55% win rate, -110 odds, 2% bet size")
r1 = monte_carlo_risk_of_ruin(
win_prob=0.55,
decimal_odds=1.909,
bet_fraction=0.02,
num_simulations=100_000,
)
print(f" Analytical RoR: {r1.analytical_ror:.4%}")
print(f" Simulated RoR: {r1.simulated_ror:.4%} "
f"+/- {r1.sim_std_error:.4%}")
print(f" 95% CI: [{r1.sim_confidence_interval[0]:.4%}, "
f"{r1.sim_confidence_interval[1]:.4%}]")
print(f" Median max DD: {r1.median_max_drawdown:.1%}")
# Scenario 2: Aggressive bettor
print("\nScenario 2: Aggressive bettor")
print(" 55% win rate, -110 odds, 8% bet size")
r2 = monte_carlo_risk_of_ruin(
win_prob=0.55,
decimal_odds=1.909,
bet_fraction=0.08,
num_simulations=100_000,
)
print(f" Analytical RoR: {r2.analytical_ror:.4%}")
print(f" Simulated RoR: {r2.simulated_ror:.4%} "
f"+/- {r2.sim_std_error:.4%}")
print(f" Median max DD: {r2.median_max_drawdown:.1%}")
if r2.mean_bets_to_ruin:
print(f" Mean bets to ruin: {r2.mean_bets_to_ruin:,.0f}")
# Scenario 3: Marginal edge
print("\nScenario 3: Marginal edge")
print(" 52% win rate, -110 odds, 3% bet size")
r3 = monte_carlo_risk_of_ruin(
win_prob=0.52,
decimal_odds=1.909,
bet_fraction=0.03,
num_simulations=100_000,
)
print(f" Analytical RoR: {r3.analytical_ror:.4%}")
print(f" Simulated RoR: {r3.simulated_ror:.4%} "
f"+/- {r3.sim_std_error:.4%}")
print(f" Median max DD: {r3.median_max_drawdown:.1%}")
# Sensitivity table
risk_of_ruin_sensitivity_analysis(
win_probs=[0.52, 0.53, 0.54, 0.55, 0.57, 0.60],
bet_fractions=[0.01, 0.02, 0.03, 0.05, 0.08, 0.10],
num_bets=5_000,
num_simulations=20_000,
)
if __name__ == "__main__":
main()
Typical output (abbreviated):
=================================================================
RISK OF RUIN ANALYSIS
=================================================================
Scenario 1: Conservative bettor
55% win rate, -110 odds, 2% bet size
Analytical RoR: 0.0039%
Simulated RoR: 0.0020% +/- 0.0014%
95% CI: [0.0000%, 0.0048%]
Median max DD: 17.8%
Scenario 2: Aggressive bettor
55% win rate, -110 odds, 8% bet size
Analytical RoR: 4.5921%
Simulated RoR: 3.8120% +/- 0.0604%
Median max DD: 52.3%
Mean bets to ruin: 842
Scenario 3: Marginal edge
52% win rate, -110 odds, 3% bet size
Analytical RoR: 13.6471%
Simulated RoR: 8.2310% +/- 0.0868%
Median max DD: 39.1%
The sensitivity analysis produces a grid that makes the interaction between edge and bet size visually obvious. The risk of ruin can swing from negligible to near-certain as you move from conservative to aggressive staking, and a small decrease in edge amplifies this effect dramatically.
Callout: The Bankroll Sizing Rule of Thumb. A practical heuristic used by many professionals: your bankroll should be at least $\frac{1}{f^*_{\text{half-Kelly}}} \times 2$ units. For a typical half-Kelly fraction of 2--3%, this implies a bankroll of 70--100 units. If you cannot fund a bankroll of at least 50 units at your desired bet size, you should reduce your bet size until you can.
4.5.7 Drawdown Analysis: What to Expect
Even with optimal staking, drawdowns are inevitable. Understanding the expected magnitude of drawdowns prevents panicked reactions when they occur.
For a bettor using half Kelly with a 55% win rate at -110 (approximately 2.75% per bet), the following drawdown statistics emerge from simulation:
| Metric | Value |
|---|---|
| Median maximum drawdown over 500 bets | ~25--30% |
| 90th percentile maximum drawdown over 500 bets | ~40--45% |
| Expected number of bets to recover from a 30% drawdown | ~180--250 |
| Probability of experiencing at least one 20% drawdown in 1,000 bets | ~85--90% |
These numbers should be sobering. A 20% drawdown is not a sign that your model is broken --- it is a near-certainty over any reasonably long betting career. Preparing yourself psychologically and financially for these drawdowns is part of bankroll management.
Callout: The Drawdown Recovery Asymmetry. A 20% loss requires a 25% gain to recover. A 33% loss requires a 50% gain. A 50% loss requires a 100% gain. This asymmetry is the core reason why aggressive bet sizing is so dangerous and why conservative approaches dominate in practice.
4.6 Putting It All Together: A Decision Framework
4.6.1 Key Concepts
This chapter has covered the mathematical foundations of bankroll management. The essential takeaways are:
-
Your bankroll is business capital. Separate it from personal finances. Treat it with the discipline of a business owner managing working capital.
-
Edge is necessary but not sufficient. A positive expected value guarantees long-run profit only if you survive to reach the long run. Bet sizing determines survival.
-
The Kelly Criterion maximizes geometric growth by finding the exact staking fraction that balances the benefit of larger bets against the cost of larger drawdowns. The formula $f^* = (bp - q)/b$ is one of the most important equations in quantitative betting.
-
Full Kelly is theoretically optimal but practically dangerous because it assumes perfect probability knowledge. In the real world, estimation error makes full Kelly too aggressive.
-
Fractional Kelly (typically half) is the professional standard because it retains most of the growth rate while dramatically reducing variance, drawdown, and risk of ruin.
-
Risk of ruin can be calculated and controlled. Through analytical formulas and Monte Carlo simulation, you can quantify the exact probability of going broke under any staking strategy and calibrate your approach accordingly.
4.6.2 Formula Reference
Kelly Criterion (binary outcome): $$ f^* = \frac{bp - q}{b} = \frac{(d-1)p - (1-p)}{d - 1} = \frac{dp - 1}{d - 1} $$
where $d$ = decimal odds, $b = d - 1$ = net odds, $p$ = win probability, $q = 1 - p$.
Fractional Kelly: $$ f_{\text{applied}} = \alpha \cdot f^*, \quad \alpha \in (0, 1] $$
Expected log growth rate: $$ \log G(f) = p \log(1 + bf) + q \log(1 - f) $$
Risk of ruin (diffusion approximation): $$ P(\text{ruin}) \approx \exp\left(-\frac{2\mu B}{\sigma^2}\right) $$
where $\mu$ = expected profit per bet, $B$ = bankroll in bet units, $\sigma^2$ = variance per bet.
Risk of ruin (even money, exact): $$ P(\text{ruin}) = \left(\frac{q}{p}\right)^B \quad \text{for } p > q $$
Growth rate retention for fractional Kelly (approximation): $$ \frac{\log G(\alpha f^*)}{\log G(f^*)} \approx 2\alpha - \alpha^2 $$
4.6.3 Code Patterns
The Python implementations in this chapter follow a consistent pattern:
- Parameterized functions with type hints and docstrings, allowing reuse across your betting pipeline.
- Monte Carlo simulation using NumPy's random number generator for reproducibility and performance.
- Log-space computation for numerical stability when simulating multiplicative bankroll processes.
- Sensitivity analysis to explore how results change across a range of inputs, rather than relying on single-point estimates.
These patterns will recur throughout the remainder of this textbook. In Chapter 5, we will use similar simulation techniques for data quality analysis, and in Chapter 14 (Advanced Staking Systems), we will extend the Kelly framework to handle correlated bets, multiple accounts, and time-varying edges.
4.6.4 Decision Framework: Choosing Your Staking Strategy
The following flowchart summarizes the decision process:
Step 1: Determine your bankroll. - Identify the total amount you can allocate to betting without impacting your financial stability. - Express this in units (recommend 1 unit = 1% of bankroll to start).
Step 2: Assess your edge. - If you have a documented, backtested edge with at least 200+ bets of history: proceed to Kelly-based approaches. - If you are new, testing a model, or do not have a quantified edge: use flat betting at 1% of bankroll.
Step 3: Choose your Kelly fraction. - New model or uncertain edge: Quarter Kelly (0.25) - Moderately confident, some track record: Half Kelly (0.50) - Highly confident, long track record, well-calibrated probabilities: Three-quarter Kelly (0.75) - Never: Full Kelly (unless you are conducting academic research)
Step 4: Calculate risk of ruin. - Use the Monte Carlo simulator from Section 4.5 to estimate your risk of ruin under your chosen strategy. - If the risk of ruin exceeds your tolerance (see the framework in Section 4.5.5), reduce your Kelly fraction or increase your bankroll.
Step 5: Set review points. - Review your unit size monthly or quarterly. - Increase unit size only after sustained growth (25--50% bankroll increase) with a sample of 200+ bets. - Decrease unit size immediately upon a 25% drawdown. - Track the performance of each confidence tier separately if using a tiered system.
The Cardinal Rule: When in doubt, bet less. The cost of underbetting is a slightly slower growth rate. The cost of overbetting can be total ruin. These are not symmetric risks. Always err on the side of survival.
What's Next
In Chapter 5, Data Literacy for Sports Bettors, we shift our focus from how much to bet to what data to trust. The Kelly Criterion and every other tool in this chapter require probability estimates as inputs, and those estimates are only as good as the data and models that produce them. Chapter 5 will cover data sources, data quality assessment, cleaning and preprocessing pipelines, and the critical skill of distinguishing signal from noise in sports data. You will learn to evaluate whether the data feeding your models is trustworthy enough to justify the bet sizes your staking framework recommends.
The connection is direct: the confidence you can place in your Kelly inputs depends entirely on the data literacy skills covered in Chapter 5. An overconfident probability estimate fed into full Kelly is a recipe for the very overbetting disasters we documented in this chapter. Sound data practices are the foundation that makes disciplined staking possible.
Chapter 4 Exercises (Preview)
The full exercise set for this chapter is available in exercises.md. Topics include:
- Analytical problems: Derive Kelly for asymmetric payoffs; calculate risk of ruin for various scenarios.
- Simulation challenges: Modify the Monte Carlo simulator to handle correlated bets; implement a Kelly calculator for three-way markets.
- Case study analysis: Given a historical betting record, determine the optimal Kelly fraction and assess whether the bettor was over- or under-staking.
- Portfolio application: Design a staking framework for a bettor with simultaneous positions across three sports.
Further Reading
- Kelly, J. L. (1956). "A New Interpretation of Information Rate." Bell System Technical Journal, 35(4), 917--926.
- Thorp, E. O. (2006). "The Kelly Criterion in Blackjack, Sports Betting, and the Stock Market." In S. A. Zenios & W. T. Ziemba (Eds.), Handbook of Asset and Liability Management, Vol. 1, pp. 385--428. North-Holland.
- Poundstone, W. (2005). Fortune's Formula: The Untold Story of the Scientific Betting System That Beat the Casinos and Wall Street. Hill and Wang.
- MacLean, L. C., Thorp, E. O., & Ziemba, W. T. (2011). The Kelly Capital Growth Investment Criterion. World Scientific.
- Miller, E. (2019). The Logic of Sports Betting. Ed Miller.
Chapter 4 is complete. Proceed to Chapter 5: Data Literacy for Sports Bettors when you are comfortable with the concepts and have completed the exercises.