Agent Bankroll Management:
Mathematical Foundations for AI Gamblers
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.
Table of Contents
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:
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.
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:
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:
σ = 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:
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:
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.
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.
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:
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:
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
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:
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:
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.
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:
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 units | 36.8% | 13.5% | 0.67% | 0.004% | Very High |
| 10 Kelly units | 13.5% | 1.8% | 0.004% | <0.001% | High |
| 20 Kelly units | 1.8% | 0.03% | <0.001% | <0.001% | Moderate |
| 50 Kelly units | 0.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.
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.