The Kelly Criterion f* = (bp - q) / b tells you the exact fraction of bankroll to wager on each bet to maximize long-run growth. Full Kelly grows fastest but drawdowns exceed 60%. Use quarter-Kelly (f*/4) in production — it captures ~75% of the growth with ~25% of the variance.

Why This Matters for Agents

Bet sizing is the single highest-leverage decision in an autonomous agent’s pipeline. An agent that identifies +EV bets but sizes them wrong will either go broke (overbetting) or leave most of its edge on the table (underbetting). The Kelly Criterion solves this problem exactly: given an edge and odds, it returns the mathematically optimal fraction of bankroll to risk.

This is Layer 4 — Intelligence. The Kelly calculation sits at the output of the agent’s decision engine: after the model estimates true probabilities (via Bayesian updating, regression, or ensemble methods) and after expected value confirms positive edge, Kelly converts that edge into a position size. The resulting allocation then flows to Layer 2 — Wallet, where infrastructure like Coinbase Agentic Wallets or Safe smart contract wallets enforce the position limits. The Agent Wallet Comparison documents how each wallet handles programmatic allocation constraints.

The Math

Deriving Kelly from First Principles

Consider a bettor with bankroll W who wagers a fraction f on a bet with:

  • Win probability: p
  • Loss probability: q = 1 - p
  • Net odds: b (for every $1 wagered, you receive $b in profit if you win)

After one bet, the bankroll becomes:

Win:  W × (1 + f × b)     with probability p
Loss: W × (1 - f)          with probability q

The geometric growth rate G(f) determines long-run bankroll trajectory. Over N bets, the expected log-wealth is:

E[log(W_N)] = N × [p × log(1 + fb) + q × log(1 - f)]

The growth rate per bet is:

G(f) = p × log(1 + fb) + q × log(1 - f)

To maximize G(f), take the derivative and set it to zero:

dG/df = pb/(1 + fb) - q/(1 - f) = 0

pb(1 - f) = q(1 + fb)
pb - pbf = q + qfb
pb - q = pbf + qfb
pb - q = fb(p + q)
pb - q = fb        [since p + q = 1]

f* = (pb - q) / b
f* = (bp - q) / b

This is the Kelly Criterion. Equivalently:

f* = p - q/b = p - (1-p)/b

Variable definitions:

  • f* = optimal fraction of bankroll to wager
  • b = net decimal odds (decimal odds minus 1; e.g., decimal odds of 2.50 gives b = 1.50)
  • p = true probability of winning the bet
  • q = 1 - p = probability of losing

Why Kelly Maximizes Geometric Growth

The key insight: Kelly maximizes E[log(W)], not E[W]. Maximizing expected wealth (arithmetic mean) would tell you to bet your entire bankroll on any +EV bet. Maximizing expected log-wealth (geometric mean) accounts for the asymmetry of compounding — a 50% loss requires a 100% gain to recover.

Arithmetic view:  Bet everything on +EV → maximum E[W]
                  But one loss = bankruptcy

Geometric view:   Bet f* on +EV → maximum E[log(W)]
                  Bankroll grows at the fastest sustainable rate

Kelly is the only strategy that:

  1. Never risks ruin (f* < 1 for any legitimate edge)
  2. Maximizes long-run growth rate
  3. Outperforms any other fixed-fraction strategy given enough bets

The Kelly Formula for Common Odds Formats

Odds FormatExampleb ValueKelly Formula
Decimal odds2.501.50f* = (1.50p - q) / 1.50
American favorite-1500.667f* = (0.667p - q) / 0.667
American underdog+2002.00f* = (2.00p - q) / 2.00
Prediction market YES at $0.40Pays $1.001.50f* = (1.50p - q) / 1.50

For prediction markets: if you buy YES at price c, your net odds are b = (1 - c) / c. If you buy YES at $0.40 and win, you profit $0.60 on a $0.40 bet, so b = 0.60/0.40 = 1.50.

Fractional Kelly

Full Kelly maximizes growth but the ride is brutal. Over a 1,000-bet sequence with 5% edge, full Kelly routinely produces drawdowns exceeding 60% from peak bankroll. For agents managing real capital, this is unacceptable.

Fractional Kelly wagers a constant fraction of the full Kelly amount:

f_fractional = k × f*

where k is the Kelly fraction:
  k = 1.00  → Full Kelly     (max growth, max variance)
  k = 0.50  → Half Kelly     (75% of max growth rate, 50% of variance)
  k = 0.25  → Quarter Kelly  (50% of max growth rate, 25% of variance)

The growth rate of fractional Kelly relative to full Kelly:

G(kf*) / G(f*) ≈ k(2 - k)

Half Kelly:    0.50 × (2 - 0.50) = 0.75  → 75% of max growth rate
Quarter Kelly: 0.25 × (2 - 0.25) = 0.4375 → ~44% of max growth rate

The variance reduction is approximately proportional to k^2, so quarter-Kelly cuts variance by ~94% relative to full Kelly while retaining ~44% of the growth rate. This is an excellent tradeoff for production systems.

Recommendation for autonomous agents: use quarter-Kelly (k = 0.25) as the default. The marginal growth rate lost is small compared to the drawdown reduction. An agent that survives is infinitely more valuable than one that grows 2x faster but goes broke.

Simultaneous Kelly for Multiple Concurrent Bets

Agents rarely bet on one event at a time. When placing N concurrent bets, the single-bet Kelly formula overallocates — it doesn’t account for the combined risk of multiple open positions.

For N independent, simultaneous bets, the objective is:

Maximize: Σ_i [p_i × log(1 + f_i × b_i) + q_i × log(1 - f_i)]

Subject to: Σ f_i ≤ 1  (total allocation cannot exceed bankroll)
            0 ≤ f_i ≤ f_max for all i  (position limits)

This has no closed-form solution for N > 1 independent bets. Use numerical optimization.

Kelly for Correlated Outcomes

When bet outcomes are correlated — for example, betting on multiple NFL games where weather affects all of them — the optimal allocation depends on the covariance structure. Positive correlation between bets reduces optimal allocation (the combined risk is higher than the sum of individual risks). Negative correlation allows slightly larger allocations.

The general approach: enumerate all 2^N outcome scenarios for N correlated binary bets, assign joint probabilities to each scenario, and maximize expected log-wealth over all scenarios. For N > 10, Monte Carlo approximation is necessary.

Worked Examples

Example 1: Single Bet — Polymarket Binary Contract

Market: “Will the Fed cut rates in June 2026?” on Polymarket. YES trading at $0.42. Your model estimates the true probability at 52%.

Price (c) = $0.42
Net odds: b = (1 - 0.42) / 0.42 = 1.381
True probability: p = 0.52
Loss probability: q = 0.48

f* = (bp - q) / b
f* = (1.381 × 0.52 - 0.48) / 1.381
f* = (0.718 - 0.48) / 1.381
f* = 0.238 / 1.381
f* = 0.1723  (17.23% of bankroll)

Quarter-Kelly: 0.1723 × 0.25 = 0.0431 (4.31% of bankroll)

On a $10,000 bankroll, quarter-Kelly says bet $431 on YES.

Example 2: Sports Bet — NFL Spread

Lakers -3.5 at -110 on BetOnline. American odds of -110 means you risk $110 to win $100.

American odds: -110
Decimal odds: 1 + 100/110 = 1.909
Net odds: b = 0.909
Implied probability (from odds): 110/210 = 52.38%
Your model's true probability: p = 0.56
q = 0.44

f* = (0.909 × 0.56 - 0.44) / 0.909
f* = (0.509 - 0.44) / 0.909
f* = 0.069 / 0.909
f* = 0.0759 (7.59%)

Quarter-Kelly: 0.0759 × 0.25 = 0.0190 (1.90%)

On a $5,000 bankroll: quarter-Kelly = $95 wagered. The edge here is 3.62 percentage points (56% true vs. 52.38% implied), which is a strong edge in sports betting. Full Kelly at 7.59% would be aggressive; quarter-Kelly at 1.90% is conservative but survivable.

Example 3: Multiple Simultaneous Bets

An agent has three concurrent +EV positions:

BetTrue Prob (p)Odds (b)Single Kelly (f*)
Polymarket: Fed rate cut YES at $0.420.521.38117.2%
Kalshi: CPI > 3.5% YES at 28 cents0.352.5714.9%
BetOnline: Chiefs -2.5 at -1080.570.9267.8%

Sum of individual Kelly fractions: 29.9%. Simultaneous Kelly (solved numerically) will allocate less to each bet because the combined risk of three positions is higher than any individual position’s risk. Typical result: ~60-80% of each individual Kelly, depending on correlation structure.

Implementation

import numpy as np
from scipy.optimize import minimize
from dataclasses import dataclass


@dataclass
class BetOpportunity:
    """A single betting opportunity with estimated edge."""
    name: str
    true_prob: float      # Agent's estimated probability of winning
    decimal_odds: float   # Decimal odds (e.g., 2.50)

    @property
    def net_odds(self) -> float:
        """Net odds: profit per $1 wagered if win."""
        return self.decimal_odds - 1.0

    @property
    def implied_prob(self) -> float:
        """Market's implied probability from the odds."""
        return 1.0 / self.decimal_odds

    @property
    def edge(self) -> float:
        """Edge = true_prob - implied_prob."""
        return self.true_prob - self.implied_prob


def kelly_single(true_prob: float, net_odds: float) -> float:
    """
    Compute single-bet Kelly fraction.

    f* = (bp - q) / b

    Args:
        true_prob: Estimated true probability of winning (0 < p < 1).
        net_odds: Net decimal odds, i.e., profit per $1 wagered (b > 0).

    Returns:
        Optimal fraction of bankroll to wager. Returns 0 if no edge.
    """
    q = 1.0 - true_prob
    f_star = (net_odds * true_prob - q) / net_odds
    return max(f_star, 0.0)


def kelly_fractional(
    true_prob: float,
    net_odds: float,
    fraction: float = 0.25
) -> float:
    """
    Compute fractional Kelly bet size.

    Args:
        true_prob: Estimated true probability of winning.
        net_odds: Net decimal odds (profit per $1 wagered).
        fraction: Kelly fraction (0.25 = quarter-Kelly, 0.50 = half-Kelly).

    Returns:
        Fractional Kelly bet size as fraction of bankroll.
    """
    return kelly_single(true_prob, net_odds) * fraction


def kelly_simultaneous(
    bets: list[BetOpportunity],
    kelly_fraction: float = 0.25,
    max_per_bet: float = 0.10,
    max_total: float = 0.30
) -> dict[str, float]:
    """
    Compute simultaneous Kelly allocations for multiple independent bets.

    Uses scipy.optimize to maximize expected log-wealth across all positions,
    subject to per-bet and total allocation constraints.

    Args:
        bets: List of BetOpportunity objects.
        kelly_fraction: Fractional Kelly multiplier (default 0.25).
        max_per_bet: Maximum allocation per individual bet.
        max_total: Maximum total allocation across all bets.

    Returns:
        Dict mapping bet name to optimal allocation fraction.
    """
    n = len(bets)
    if n == 0:
        return {}

    def neg_expected_log_growth(fractions: np.ndarray) -> float:
        """Negative expected log-growth (we minimize this)."""
        total = 0.0
        for i, bet in enumerate(bets):
            f = fractions[i]
            p = bet.true_prob
            q = 1.0 - p
            b = bet.net_odds

            if f <= 0:
                continue

            log_win = np.log(1.0 + f * b) if (1.0 + f * b) > 0 else -1e10
            log_loss = np.log(1.0 - f) if (1.0 - f) > 0 else -1e10

            total += p * log_win + q * log_loss

        return -total

    bounds = [(0.0, max_per_bet) for _ in range(n)]

    constraints = [{
        "type": "ineq",
        "fun": lambda x: max_total - np.sum(x)
    }]

    # Start from individual fractional Kelly values (clamped)
    x0 = np.array([
        min(kelly_fractional(b.true_prob, b.net_odds, kelly_fraction), max_per_bet)
        for b in bets
    ])

    result = minimize(
        neg_expected_log_growth,
        x0,
        method="SLSQP",
        bounds=bounds,
        constraints=constraints
    )

    allocations = {}
    for i, bet in enumerate(bets):
        alloc = max(result.x[i], 0.0)
        allocations[bet.name] = round(alloc, 6)

    return allocations


def simulate_kelly_strategies(
    true_prob: float,
    net_odds: float,
    n_bets: int = 1000,
    n_simulations: int = 10000,
    initial_bankroll: float = 10000.0
) -> dict[str, dict]:
    """
    Monte Carlo simulation comparing Kelly vs flat vs proportional betting.

    Args:
        true_prob: True probability of winning each bet.
        net_odds: Net decimal odds per bet.
        n_bets: Number of sequential bets per simulation.
        n_simulations: Number of simulation runs.
        initial_bankroll: Starting bankroll.

    Returns:
        Dict with strategy names mapping to performance metrics.
    """
    rng = np.random.default_rng(42)

    full_kelly_f = kelly_single(true_prob, net_odds)
    strategies = {
        "full_kelly": full_kelly_f,
        "half_kelly": full_kelly_f * 0.50,
        "quarter_kelly": full_kelly_f * 0.25,
        "flat_1pct": 0.01,
    }

    results = {}

    for name, frac in strategies.items():
        final_bankrolls = np.zeros(n_simulations)
        max_drawdowns = np.zeros(n_simulations)

        for sim in range(n_simulations):
            bankroll = initial_bankroll
            peak = initial_bankroll

            outcomes = rng.random(n_bets) < true_prob

            for win in outcomes:
                wager = bankroll * frac
                if win:
                    bankroll += wager * net_odds
                else:
                    bankroll -= wager

                if bankroll <= 0:
                    bankroll = 0.0
                    break

                peak = max(peak, bankroll)
                drawdown = (peak - bankroll) / peak
                max_drawdowns[sim] = max(max_drawdowns[sim], drawdown)

            final_bankrolls[sim] = bankroll

        geometric_mean = np.exp(np.mean(np.log(np.maximum(final_bankrolls, 1e-10))))

        results[name] = {
            "fraction": frac,
            "median_final": float(np.median(final_bankrolls)),
            "geometric_mean_final": float(geometric_mean),
            "mean_max_drawdown": float(np.mean(max_drawdowns)),
            "p95_max_drawdown": float(np.percentile(max_drawdowns, 95)),
            "ruin_rate": float(np.mean(final_bankrolls < 1.0)),
        }

    return results


def american_to_decimal(odds: int) -> float:
    """Convert American odds to decimal odds."""
    if odds < 0:
        return 1.0 + 100.0 / abs(odds)
    else:
        return 1.0 + odds / 100.0


def prediction_market_to_decimal(price: float) -> float:
    """Convert prediction market YES price to decimal odds."""
    if price <= 0 or price >= 1:
        raise ValueError(f"Price must be between 0 and 1, got {price}")
    return 1.0 / price


# --- Example usage ---
if __name__ == "__main__":
    # Single bet: Polymarket YES at $0.42, true prob 52%
    pm_price = 0.42
    pm_decimal = prediction_market_to_decimal(pm_price)
    pm_net = pm_decimal - 1.0
    f_full = kelly_single(0.52, pm_net)
    f_quarter = kelly_fractional(0.52, pm_net, 0.25)

    print("=== Single Bet Kelly ===")
    print(f"Polymarket YES at ${pm_price}, true prob = 52%")
    print(f"Decimal odds: {pm_decimal:.3f}, net odds: {pm_net:.3f}")
    print(f"Full Kelly:    {f_full:.4f} ({f_full*100:.2f}%)")
    print(f"Quarter Kelly: {f_quarter:.4f} ({f_quarter*100:.2f}%)")
    print()

    # Simultaneous Kelly: three concurrent bets
    bets = [
        BetOpportunity("Fed rate cut YES", 0.52, prediction_market_to_decimal(0.42)),
        BetOpportunity("CPI > 3.5% YES", 0.35, prediction_market_to_decimal(0.28)),
        BetOpportunity("Chiefs -2.5", 0.57, american_to_decimal(-108)),
    ]

    allocations = kelly_simultaneous(bets, kelly_fraction=0.25)
    print("=== Simultaneous Kelly (Quarter) ===")
    for name, alloc in allocations.items():
        print(f"  {name}: {alloc:.4f} ({alloc*100:.2f}%)")
    print(f"  Total allocation: {sum(allocations.values()):.4f}")
    print()

    # Simulation comparison
    print("=== Strategy Simulation (p=0.55, b=0.909, 1000 bets) ===")
    sim_results = simulate_kelly_strategies(
        true_prob=0.55, net_odds=0.909,
        n_bets=1000, n_simulations=10000
    )
    print(f"{'Strategy':<16} {'Frac':>6} {'Median Final':>14} "
          f"{'Geo Mean':>12} {'Avg MaxDD':>10} {'Ruin%':>7}")
    print("-" * 70)
    for name, metrics in sim_results.items():
        print(f"{name:<16} {metrics['fraction']:>5.3f} "
              f"${metrics['median_final']:>12,.0f} "
              f"${metrics['geometric_mean_final']:>10,.0f} "
              f"{metrics['mean_max_drawdown']:>9.1%} "
              f"{metrics['ruin_rate']:>6.2%}")

Limitations and Edge Cases

Kelly > 1.0 means your model is wrong. If the Kelly formula returns f* > 1.0 (bet more than your entire bankroll), your probability estimate is almost certainly too high. No legitimate sports bet or prediction market position should produce f* > 1.0. Recalibrate your model before doing anything else.

Kelly assumes exact probability knowledge. The formula takes true probability as a known input. In reality, your probability estimate has uncertainty. If your model says p = 0.55 but the true value is anywhere in [0.50, 0.60], full Kelly sized on p = 0.55 will overbet roughly half the time. This is the strongest argument for fractional Kelly — it implicitly accounts for estimation error.

Kelly ignores opportunity cost. The formula doesn’t consider that capital allocated to one bet is unavailable for a potentially better bet arriving in five minutes. Agents with high-frequency opportunity flow should use lower Kelly fractions or implement a capital reservation system.

Correlated bets violate independence. The single-bet Kelly formula assumes each bet is independent. Betting on three NFL games on the same Sunday introduces correlation through weather, referee assignments, and schedule effects. Using individual Kelly fractions for correlated bets overestimates optimal allocation. The simultaneous Kelly optimizer partially addresses this, but only if you model the correlation structure correctly.

Bankroll definition matters. Kelly says “fraction of bankroll” — but what is the bankroll? Is it the agent’s total capital? Just the amount deposited on a single platform? Capital across all platforms? If the agent has $50,000 total but only $10,000 deposited on Polymarket, Kelly should size relative to total deployable capital, not the platform balance. The Agent Wallet Comparison discusses unified bankroll tracking across platforms.

Transaction costs and slippage erode edge. Kelly assumes you can execute at the quoted odds. On thin Polymarket orderbooks, a large order moves the price. On sportsbooks, sharp action gets limited. The effective odds are worse than the quoted odds, which means the true Kelly fraction is smaller than the formula suggests. Always compute Kelly on executed odds, not displayed odds.

FAQ

What is the Kelly Criterion formula for sports betting?

The Kelly Criterion formula is f* = (bp - q) / b, where b is the decimal odds minus 1, p is your estimated true probability of winning, and q = 1 - p. It returns the fraction of your bankroll to wager on a single bet to maximize long-run geometric growth rate of capital.

Why do professional bettors use fractional Kelly instead of full Kelly?

Full Kelly maximizes geometric growth rate but produces extreme variance — simulations show maximum drawdowns exceeding 60% over typical 1,000-bet sequences even with a 5% edge. Professional bettors and autonomous agents use quarter-Kelly (f*/4) or half-Kelly (f*/2) to reduce variance dramatically while sacrificing only a small fraction of long-run growth. Quarter-Kelly reduces variance by approximately 75%.

How does Kelly Criterion connect to expected value?

Kelly sizing requires a positive expected value bet as input — you need edge before Kelly has anything to optimize. EV tells you whether to bet; Kelly tells you how much. A bet with negative EV always produces a negative Kelly fraction, meaning the formula correctly says “don’t bet.” See the expected value guide for the EV calculation framework.

How do you calculate Kelly Criterion for multiple simultaneous bets?

Simultaneous Kelly uses constrained optimization to maximize the sum of expected log-wealth across all concurrent positions. The key constraint is that total allocation cannot exceed your bankroll. Use scipy.optimize.minimize with the negative sum of E[log(W)] as the objective function and position bounds between 0 and a maximum fraction per bet.

What does it mean when Kelly Criterion recommends betting more than 100% of your bankroll?

A Kelly fraction exceeding 1.0 means your edge estimate is almost certainly wrong. No legitimate sports bet or prediction market position should produce f* > 1.0 under realistic conditions. If your model outputs Kelly > 100%, recalibrate your probability estimates. The math is correct — your inputs are not.

What’s Next

Kelly tells you how much to bet. The next question is: how do you survive the inevitable losing streaks while Kelly-sizing your way to long-run profit?

  • Next in the series: Drawdown Math: Understanding and Surviving Variance covers maximum drawdown distributions, ruin probability, and how fractional Kelly interacts with drawdown depth.
  • Bankroll compounding: The Mathematics of Bankroll Growth extends Kelly into compound return territory — how fast your bankroll actually grows under fractional Kelly across different edge profiles.
  • Where Kelly gets its inputs: Expected Value for Prediction Market Agents covers the EV calculation that determines whether a bet has edge in the first place.
  • Full agent architecture: The Agent Betting Stack shows how Kelly sizing fits into Layer 4 Intelligence and interfaces with Layer 2 Wallet infrastructure.
  • Sharp betting context: Sharp Betting explains why sportsbooks limit Kelly-optimal bettors and how agents adapt.