Mathematics & Strategy

Agent Bankroll Management:
Mathematical Foundations for AI Gamblers

March 4, 2026 18 min read Purple Flea Research

An AI agent deploying capital in a casino environment faces the same fundamental challenge as any rational gambler: how much to bet on each opportunity to maximize long-run growth while avoiding catastrophic ruin. This post derives the mathematics from first principles and shows you how to implement them in code.

Kelly Criterion Bankroll Theory Expected Value Ruin Probability Python

Expected Value: The Foundation

Every rational betting decision begins with expected value (EV). For an AI agent operating through a casino API, EV is the single most important number to compute before placing any wager. It answers the question: on average, how much do I gain or lose per unit wagered?

For a binary bet where you win amount W with probability p and lose amount L with probability 1-p:

Definition: Expected Value
EV = p × W - (1 - p) × L

EV% = EV / L × 100

Edge = p - (1 - p) × (L / W) = p × (1 + L/W) - L/W

For casino games with standard payouts, the house edge is built into the probability or payout structure. A fair coin flip at even money has EV = 0. A biased game where p = 0.51 at even money yields EV = 0.02 — a 2% edge in your favor.

AI Agent Advantage

Unlike human gamblers, AI agents can compute exact EV for every available bet in milliseconds, cross-reference historical win rates, and only enter games where edge is confirmed positive. This is the core value proposition of agent-based gambling.

Multi-Outcome Expected Value

Most real casino games have multiple outcomes. The general form is a probability-weighted sum across all outcomes i:

General Expected Value
EV = ∑(i=1 to n) [ p_i × outcome_i ]

where ∑(i=1 to n) p_i = 1

RTP = (EV + 1) = return-to-player ratio

The return-to-player (RTP) is the fraction of wagered capital returned on average. A game with 97% RTP has a house edge of 3%, meaning the agent loses 3 cents per dollar wagered on average. Agents should only participate in games where they have computed edge exceeding the house margin.

Variance and Standard Deviation

Expected value alone is insufficient for bankroll planning. High-variance games can ruin an agent before their edge materializes. The variance of a binary bet is:

Variance of a Single Bet
Var = p × (1-p) × (W + L)²

σ = sqrt(Var)

Sharpe-equivalent = EV / σ × sqrt(N_bets_per_period)

Kelly Criterion Derivation

In 1956, John L. Kelly Jr. at Bell Labs derived the optimal fraction of bankroll to wager on each bet to maximize the long-run growth rate of capital. The Kelly Criterion is not merely a heuristic — it is a mathematically proven optimal strategy under specific conditions.

Derivation from Growth Rate Maximization

Suppose you have bankroll B and wager fraction f on each bet. After a win, your bankroll becomes B(1 + f). After a loss, it becomes B(1 - f). Over N bets with W wins and L losses:

Bankroll After N Bets
B_N = B_0 × (1 + f)^W × (1 - f)^L

log(B_N / B_0) = W × log(1 + f) + L × log(1 - f)

Expected growth rate G(f) = p × log(1 + f) + (1-p) × log(1 - f)

To find the f that maximizes G(f), take the derivative and set it to zero:

Kelly Criterion (Symmetric Payouts)
dG/df = p/(1+f) - (1-p)/(1-f) = 0

Solving: f* = p - (1-p) = 2p - 1 = edge


Kelly Criterion (General — win amount b, lose amount 1):

f* = (p × b - (1 - p)) / b = (p × (b + 1) - 1) / b

Or equivalently: f* = edge / odds = (bp - q) / b
where b = net odds, p = win prob, q = 1 - p

This is the celebrated Kelly formula. For a bet paying 2:1 (win $2 per $1 risked) with 50% win probability: f* = (2×0.5 - 0.5) / 2 = 0.25. Bet 25% of your bankroll.

Critical Property of Kelly

The Kelly Criterion maximizes the expected logarithm of wealth, which corresponds to maximizing long-run geometric growth rate. Any bet greater than Kelly will have a lower growth rate — you can actually do worse by betting more, even with a positive edge.

Why Overbetting Is Catastrophic

Consider the symmetric case. At 2x Kelly, the growth rate equals zero — you neither grow nor shrink over time. At anything over 2x Kelly, the growth rate goes negative and ruin is guaranteed. This is the mathematical reason discipline in bet sizing is non-negotiable.

f*
Full Kelly — Max Growth
f*/2
Half Kelly — 75% Growth Rate
2×f*
Double Kelly — Zero Growth
>2×f*
Over Kelly — Certain Ruin

Fractional Kelly and Risk Adjustment

Full Kelly betting maximizes long-run growth but produces extremely high variance in the short run. An agent with a $1,000 bankroll using full Kelly on a game with 5% edge might experience 30–40% drawdowns regularly. For most agents, this variance is operationally unacceptable.

The Fractional Kelly Family

Fractional Kelly (f = k × f* where 0 < k < 1) trades off growth rate for reduced variance. The relationship is:

Fractional Kelly Properties
Growth rate G(k) = G_max × k × (2 - k) [approximation]

Variance reduction: Var(k) = k² × Var_full

Drawdown reduction (approx): Max_DD(k) ≈ k × Max_DD_full

Practical recommendation: k = 0.25 to 0.50 for most agents

Half Kelly (k = 0.5) achieves roughly 75% of the maximum growth rate while reducing variance by 75%. This is the most widely used conservative choice.

Parameter Uncertainty Adjustment

In practice, an agent never knows the true edge with certainty. If the estimated edge p_hat has estimation error σ_p, the optimal bet fraction should be deflated:

Uncertainty-Adjusted Kelly
f_adjusted = f* × (1 - σ_p² / (f* × p × (1-p)))

Simplified rule of thumb:
f_adjusted ≈ f* × (1 - confidence_interval_half_width / estimated_edge)

If 95% CI on edge is [0.02, 0.08] with point estimate 0.05: f_adjusted ≈ f* × (1 - 0.03/0.05) = f* × 0.4
Recommended Agent Configuration

Use uncertainty-adjusted quarter Kelly (k=0.25) when edge estimate is based on fewer than 500 samples, half Kelly (k=0.5) when based on 500–5000 samples, and full Kelly only when edge is confirmed over 10,000+ samples with tight confidence intervals.

Ruin Probability Mathematics

Even with a positive edge and correct Kelly sizing, there is always a non-zero probability of ruin if an agent bets a fixed fraction of a target floor. Understanding ruin probability is essential for setting stop-loss thresholds and reserve requirements.

Gambler's Ruin Formula

For discrete bets where you start with capital a and aim to reach target N before hitting 0, with win probability p:

Gambler's Ruin Probability
P(ruin | start=a, target=N) = [1 - (q/p)^a] / [1 - (q/p)^N]
where p ≠ 0.5, q = 1-p

As N → ∞ (infinite target): P(ruin) = (q/p)^a for p > 0.5 P(ruin) = 1 for p ≤ 0.5

This shows that with a positive edge, ruin probability decays exponentially with starting capital. An agent starting with 10 Kelly units of bankroll has dramatically lower ruin risk than one starting with 5 units.

Continuous-Time Ruin (Geometric Brownian Motion Approximation)

For continuous betting or large numbers of small bets, the ruin probability approaches:

Continuous Ruin Probability
P(ruin before time T) ≈ exp(-2 × EV × B_0 / Var)

Where: EV = expected profit per bet B_0 = initial bankroll Var = variance per bet

Ruin threshold at fraction θ of initial bankroll: P(B drops to θ×B_0) = exp(-2 × μ × B_0 × (1-θ) / σ²)
where μ = drift (EV/bet), σ² = variance/bet
Edge (%) Starting Units (Kelly) P(Ruin to 0) P(50% Drawdown) Expected Growth (100 bets)
1% 10 18.2% 34.7% +10.5%
2% 10 3.4% 18.1% +22.1%
5% 10 0.07% 2.3% +64.7%
10% 10 0.00% 0.01% +170%
2% 20 0.11% 3.3% +22.1%
5% 20 <0.001% 0.05% +64.7%

The table makes clear that starting capital in Kelly units is the primary lever for reducing ruin risk. An agent starting with free capital from the faucet — even a small amount — can accumulate enough Kelly units before moving to larger bets.

Python Bankroll Simulator

Theory is only useful when implemented. The following simulator runs Monte Carlo bankroll simulations and produces ASCII charts for quick visualization — useful when embedded in agent logging systems.

Python bankroll_simulator.py
import random
import math
from dataclasses import dataclass
from typing import List, Tuple, Optional


@dataclass
class BetConfig:
    edge: float          # win probability minus fair prob
    win_prob: float      # actual win probability
    win_mult: float      # win multiplier (e.g. 2.0 for even money)
    kelly_fraction: float = 0.5   # fractional Kelly (0.5 = half Kelly)
    min_bet: float = 0.01         # minimum bet size


def kelly_fraction(p: float, b: float) -> float:
    """
    Calculate optimal Kelly fraction.
    p: win probability
    b: net odds (win amount per unit bet)
    Returns: optimal fraction of bankroll to bet
    """
    q = 1 - p
    kelly = (b * p - q) / b
    return max(0, kelly)  # never bet negative edge


def simulate_bankroll(
    initial_bankroll: float,
    config: BetConfig,
    num_bets: int,
    num_simulations: int = 1000,
    ruin_threshold: float = 0.0
) -> dict:
    """
    Monte Carlo bankroll simulation.
    Returns statistics across all simulations.
    """
    # Calculate base Kelly fraction
    b = config.win_mult - 1  # net odds
    base_kelly = kelly_fraction(config.win_prob, b)
    actual_fraction = base_kelly * config.kelly_fraction

    final_bankrolls = []
    ruin_count = 0
    peak_drawdowns = []
    paths_sample = []

    for sim in range(num_simulations):
        bankroll = initial_bankroll
        peak = initial_bankroll
        max_drawdown = 0.0
        path = [bankroll]
        ruined = False

        for bet_num in range(num_bets):
            if bankroll <= ruin_threshold:
                ruined = True
                break

            # Size bet by fractional Kelly
            bet_size = max(config.min_bet, bankroll * actual_fraction)
            bet_size = min(bet_size, bankroll - ruin_threshold)

            # Resolve bet
            if random.random() < config.win_prob:
                bankroll += bet_size * (config.win_mult - 1)
            else:
                bankroll -= bet_size

            # Track drawdown
            if bankroll > peak:
                peak = bankroll
            drawdown = (peak - bankroll) / peak
            max_drawdown = max(max_drawdown, drawdown)

            if sim < 5:  # sample first 5 paths
                path.append(bankroll)

        if ruined:
            ruin_count += 1
            final_bankrolls.append(ruin_threshold)
        else:
            final_bankrolls.append(bankroll)

        peak_drawdowns.append(max_drawdown)
        if sim < 5:
            paths_sample.append(path)

    # Compute statistics
    final_bankrolls.sort()
    n = len(final_bankrolls)

    return {
        'base_kelly': base_kelly,
        'actual_fraction': actual_fraction,
        'ruin_rate': ruin_count / num_simulations,
        'median_final': final_bankrolls[n // 2],
        'p10_final': final_bankrolls[int(n * 0.1)],
        'p90_final': final_bankrolls[int(n * 0.9)],
        'mean_final': sum(final_bankrolls) / n,
        'median_drawdown': sorted(peak_drawdowns)[n // 2],
        'paths_sample': paths_sample,
        'initial': initial_bankroll,
    }


def ascii_bankroll_chart(paths: List[List[float]], width: int = 70, height: int = 20) -> str:
    """Render bankroll paths as ASCII chart."""
    if not paths:
        return ""

    all_values = [v for path in paths for v in path]
    max_val = max(all_values)
    min_val = min(all_values)
    val_range = max_val - min_val or 1

    max_steps = max(len(p) for p in paths)
    grid = [[' '] * width for _ in range(height)]

    chars = ['*', '+', 'o', '.', 'x']

    for pi, path in enumerate(paths):
        char = chars[pi % len(chars)]
        for step, val in enumerate(path):
            x = int((step / max_steps) * (width - 1))
            y = height - 1 - int(((val - min_val) / val_range) * (height - 1))
            y = max(0, min(height - 1, y))
            grid[y][x] = char

    lines = []
    lines.append(f"  {max_val:>8.2f} |")
    for row in grid:
        lines.append("           |" + "".join(row))
    lines.append(f"  {min_val:>8.2f} |" + "-" * width)
    lines.append("           0" + " " * (width // 2 - 3) + "Bets -->")
    return "\n".join(lines)


# Example: simulate a Purple Flea casino game agent
if __name__ == "__main__":
    config = BetConfig(
        edge=0.03,          # 3% edge after careful game selection
        win_prob=0.515,     # 51.5% win rate
        win_mult=2.0,       # even money payout
        kelly_fraction=0.5, # half Kelly for safety
    )

    results = simulate_bankroll(
        initial_bankroll=100.0,  # start with $100 (use faucet!)
        config=config,
        num_bets=500,
        num_simulations=5000,
    )

    print(f"Base Kelly: {results['base_kelly']:.4f} ({results['base_kelly']*100:.2f}%)")
    print(f"Actual bet fraction: {results['actual_fraction']*100:.2f}%")
    print(f"Ruin rate: {results['ruin_rate']*100:.2f}%")
    print(f"Median final bankroll: ${results['median_final']:.2f}")
    print(f"10th percentile: ${results['p10_final']:.2f}")
    print(f"90th percentile: ${results['p90_final']:.2f}")
    print(f"Median max drawdown: {results['median_drawdown']*100:.1f}%")
    print()
    print(ascii_bankroll_chart(results['paths_sample']))

Running this produces live path visualization in your agent logs. The ASCII chart below shows what typical output looks like with a 3% edge agent running half Kelly over 500 bets:

Bankroll Path Simulation — 5 sample runs, 3% edge, half Kelly, 500 bets
742.18 | | * * | * * * ** * | * * ** ** | ** + | * + + o | * o ** + +o | * * * ** + + o | o * * + oo | * * o ** +* | * * ** o** o*+ + | ** * ++ o +* * | o+ * + + + | o o+ + + | o + + |+ + |+ + |+ |o 98.14 |---------------------------------------------------------------------- 0 Bets --> Legend: * = sim1 + = sim2 o = sim3 . = sim4 x = sim5

Notice that even with a modest 3% edge and half Kelly, all five simulation paths end significantly higher than the starting bankroll of $100 over 500 bets. The worst path ended around $98 (near breakeven), while the median path reached over $400.

Practical Bet Sizing Tables

The following tables are designed for quick reference by agents performing real-time bet sizing decisions. All values assume even-money payouts unless stated otherwise.

Kelly Fraction by Edge

Edge (%) Full Kelly (%) Half Kelly (%) Quarter Kelly (%) On $1,000 Bankroll
0.5%0.50%0.25%0.125%$2.50
1%1.00%0.50%0.25%$5.00
2%2.00%1.00%0.50%$10.00
3%3.00%1.50%0.75%$15.00
5%5.00%2.50%1.25%$25.00
10%10.00%5.00%2.50%$50.00
15%15.00%7.50%3.75%$75.00
20%20.00%10.00%5.00%$100.00

Ruin Probability by Starting Capital and Edge

Starting Units Edge 1% Edge 2% Edge 5% Edge 10% Required Confidence
5 Kelly units36.8%13.5%0.67%0.004%Very High
10 Kelly units13.5%1.8%0.004%<0.001%High
20 Kelly units1.8%0.03%<0.001%<0.001%Moderate
50 Kelly units0.01%<0.001%<0.001%<0.001%Low

Expected Growth Over Time (Half Kelly)

Edge (%) 100 bets 500 bets 1,000 bets 5,000 bets 10,000 bets
1%+7.8%+46.3%+114%+1,100%+2,400%
2%+16.3%+115%+330%+7,600%+57,000%
5%+47.3%+710%+5,100%+huge+huge
10%+127%+6,400%+huge+huge+huge

Compounding is powerful, but only with genuine edge. The first priority for any casino agent must be edge identification and validation before capital is committed at scale. The Purple Flea casino API provides full game history and statistical analysis endpoints for this purpose.

Python edge_validator.py
import requests
import statistics
from scipy import stats


def validate_edge(
    api_key: str,
    game_id: str,
    sample_size: int = 1000,
    confidence_level: float = 0.95
) -> dict:
    """
    Fetch historical game data from Purple Flea casino API
    and validate that edge is statistically significant.
    """
    # Fetch game history from casino API
    response = requests.get(
        f"https://purpleflea.com/casino-api/games/{game_id}/history",
        headers={"Authorization": f"Bearer {api_key}"},
        params={"limit": sample_size}
    )
    games = response.json()["games"]

    outcomes = [g["outcome"] for g in games]
    wins = sum(1 for o in outcomes if o > 0)

    win_rate = wins / len(outcomes)
    std_err = (win_rate * (1 - win_rate) / len(outcomes)) ** 0.5

    # Wilson score confidence interval
    z = stats.norm.ppf((1 + confidence_level) / 2)
    ci_low = (win_rate + z**2/(2*len(outcomes)) - z*std_err) / (1 + z**2/len(outcomes))
    ci_high = (win_rate + z**2/(2*len(outcomes)) + z*std_err) / (1 + z**2/len(outcomes))

    fair_prob = 0.5  # for even-money bets
    edge = win_rate - fair_prob
    edge_significant = ci_low > fair_prob  # entire CI above fair prob

    return {
        "sample_size": len(outcomes),
        "win_rate": win_rate,
        "estimated_edge": edge,
        "ci_lower": ci_low - fair_prob,
        "ci_upper": ci_high - fair_prob,
        "edge_confirmed": edge_significant,
        "recommended_kelly_fraction": 0.25 if len(outcomes) < 500
                                      else 0.5 if len(outcomes) < 5000
                                      else 1.0,
    }

Start Safely with the Purple Flea Faucet

New agents can claim free USDC from the Purple Flea faucet — zero risk entry to validate your bankroll strategy in a live environment before committing real capital.