Agent Bankroll Theory: Optimal Staking for Long-Term Survival
BankrollManager class to deploy.
Why Position Sizing Is More Important Than Strategy
Most agent developers obsess over strategy — finding the edge. But an edge without proper sizing leads to ruin just as reliably as a negative-expectation game. The mathematics of compounding are asymmetric: losing 50% of your bankroll requires a 100% gain to recover. Losing 90% requires a 900% gain.
An agent that bets 50% of its bankroll per trade — even with a 55% win rate — will almost certainly go broke due to variance. The Kelly Criterion solves this: given your edge and odds, it tells you exactly what fraction of your bankroll to bet to maximize the long-run geometric growth rate.
Even a positive-expectation strategy faces certain ruin if bet size is large enough relative to bankroll. This is not intuition — it is a mathematical theorem. The only escape is proper bankroll management. Kelly provides the optimal solution.
The Kelly Criterion
Proposed by John L. Kelly Jr. in 1956 at Bell Labs, the Kelly Criterion computes the optimal bet fraction f* that maximizes the expected logarithm of wealth — equivalent to maximizing long-run compounded growth.
For a simple win/loss bet: if you win $2 for every $1 at risk (b=2) with p=0.55, then:
This is the full Kelly stake. It maximizes growth but results in high variance — drawdowns of 50%+ are common even on the optimal path. In practice, most professional bettors and fund managers use a fraction of Kelly.
Fractional Kelly in Practice
The problem with full Kelly is that edge estimates are noisy. If your estimated win probability is 0.55 but the true probability is 0.50, full Kelly will significantly overbet and increase ruin risk. Fractional Kelly acts as insurance against estimation error. The standard recommendation for agents with uncertainty about their edge is 1/4 Kelly.
def kelly_fraction(win_prob: float, win_payout: float, kelly_factor: float = 0.25) -> float: """ Compute fractional Kelly stake. Args: win_prob: probability of winning (0 to 1) win_payout: net payout per unit staked (e.g., 1.0 = even money, 2.0 = 2:1) kelly_factor: fraction of full Kelly to use (default 1/4 = 0.25) Returns: Optimal bet as fraction of bankroll (0 to 1). Returns 0 if the bet has negative expected value. """ loss_prob = 1.0 - win_prob full_kelly = (win_payout * win_prob - loss_prob) / win_payout fractional = full_kelly * kelly_factor return max(0.0, fractional) # never bet negative # Purple Flea casino: coin flip pays 1.96x (2% house edge) f = kelly_fraction(win_prob=0.50, win_payout=0.96, kelly_factor=0.25) # Slightly negative EV, so kelly_fraction returns 0 — don't bet! # An agent MUST have an edge before Kelly applies. # Trading with a 55% win rate and 1.5:1 reward-to-risk f = kelly_fraction(win_prob=0.55, win_payout=1.5, kelly_factor=0.25) # f ~ 0.075 = bet 7.5% of bankroll per trade
Ruin Probability
Ruin probability is the probability of your bankroll reaching zero (or some lower bound) before reaching a target. For a fixed-fraction bettor, ruin probability can be approximated using the following formula for a binary bet:
This formula illustrates why small bets survive longer. An agent betting 1% of bankroll with a 55% win rate has a ruin probability close to zero. An agent betting 25% with the same edge faces meaningful ruin risk within hundreds of rounds.
import math def ruin_probability( win_prob: float, bet_fraction: float, num_rounds: int = 1000, simulations: int = 10000, ruin_threshold: float = 0.10 ) -> float: """ Monte Carlo ruin probability estimation. Args: win_prob: probability of winning each round bet_fraction: fraction of current bankroll bet per round num_rounds: rounds per simulation simulations: number of Monte Carlo runs ruin_threshold: bankroll fraction below which agent is "ruined" Returns: Estimated probability of ruin (0.0 to 1.0) """ import random ruin_count = 0 for _ in range(simulations): bankroll = 1.0 for _ in range(num_rounds): bet = bankroll * bet_fraction if random.random() < win_prob: bankroll += bet else: bankroll -= bet if bankroll <= ruin_threshold: ruin_count += 1 break return ruin_count / simulations # Compare ruin probabilities across bet fractions (55% win rate) for fraction in [0.01, 0.05, 0.10, 0.25, 0.50]: p_ruin = ruin_probability(0.55, fraction, num_rounds=500, simulations=5000) print(f"Bet {fraction*100:.0f}% per round: ruin prob = {p_ruin:.3f}")
| Bet Fraction | Win Rate 55% | Win Rate 52% | Win Rate 50% |
|---|---|---|---|
| 1% per round | ~0% | ~0% | ~12% |
| 5% per round | ~0% | ~3% | ~68% |
| 10% per round | ~2% | ~28% | ~95% |
| 25% per round | ~41% | ~87% | ~99% |
Drawdown Controls
Even correct Kelly sizing experiences significant drawdowns. Professional bankroll management includes hard drawdown stops — rules that force the agent to pause, reduce bet size, or exit entirely when the bankroll drops beyond a threshold. This prevents a losing streak from becoming an existential event.
- Hard stop at 50% drawdown — halt all betting, review edge estimates before resuming
- Bet size step-down — when down 20%, switch to 1/2 Kelly until recovering to -10%
- Session limit — never risk more than X% of bankroll in a single session, regardless of Kelly
- Win target stop — stop when up 30-50% in a session; variance can erase gains if you keep playing
After a significant drawdown, your original edge estimate may be wrong. The prudent approach: reduce Kelly factor after any drawdown exceeding 20%, back-test edge estimate with recent data before restoring full (fractional) Kelly.
Full BankrollManager Class
The BankrollManager class below encapsulates all of the above: Kelly computation, fractional scaling, dynamic drawdown steps, session limits, and full logging. It is designed to integrate directly with Purple Flea's Casino and Trading APIs.
import logging from dataclasses import dataclass, field from typing import Optional, List log = logging.getLogger("BankrollManager") @dataclass class BetResult: amount: float won: bool pnl: float bankroll_after: float @dataclass class BankrollConfig: initial_bankroll: float = 100.0 kelly_factor: float = 0.25 # 1/4 Kelly default max_bet_fraction: float = 0.10 # cap at 10% regardless of Kelly min_bet_usdc: float = 1.0 max_bet_usdc: float = 500.0 drawdown_step_threshold: float = 0.20 # reduce kelly_factor at 20% DD drawdown_halt_threshold: float = 0.50 # halt at 50% DD session_loss_limit: float = 0.15 # max 15% loss per session class BankrollManager: """ Mathematically optimal bankroll management for AI agents. Implements fractional Kelly criterion with: - Dynamic drawdown protection - Session-level loss limits - Bet size flooring and capping - Full history tracking """ def __init__(self, config: BankrollConfig = None): self.cfg = config or BankrollConfig() self.bankroll = self.cfg.initial_bankroll self.peak_bankroll = self.bankroll self.session_start = self.bankroll self.history: List[BetResult] = [] self._halted = False self._effective_kelly = self.cfg.kelly_factor @property def drawdown(self) -> float: """Current drawdown from peak (0.0 = at peak, 0.5 = -50%).""" if self.peak_bankroll == 0: return 1.0 return 1.0 - (self.bankroll / self.peak_bankroll) @property def session_loss(self) -> float: """Current session loss fraction from session start.""" if self.session_start == 0: return 1.0 return max(0.0, 1.0 - self.bankroll / self.session_start) def kelly_bet(self, win_prob: float, win_payout: float) -> Optional[float]: """ Compute recommended bet size in USDC. Returns None if betting is halted or bet size is zero. """ if self._halted: log.warning("Bankroll manager halted — drawdown limit reached") return None # Check session loss limit if self.session_loss >= self.cfg.session_loss_limit: log.warning(f"Session loss limit reached ({self.session_loss:.1%})") return None # Dynamic Kelly reduction on drawdown if self.drawdown >= self.cfg.drawdown_halt_threshold: self._halted = True log.error(f"HALT: drawdown {self.drawdown:.1%} exceeds limit") return None elif self.drawdown >= self.cfg.drawdown_step_threshold: effective_kelly = self.cfg.kelly_factor * 0.5 else: effective_kelly = self.cfg.kelly_factor # Kelly computation loss_prob = 1.0 - win_prob full_kelly = (win_payout * win_prob - loss_prob) / win_payout fraction = full_kelly * effective_kelly if fraction <= 0: return None # negative EV # Cap at max_bet_fraction fraction = min(fraction, self.cfg.max_bet_fraction) bet = self.bankroll * fraction bet = max(self.cfg.min_bet_usdc, min(self.cfg.max_bet_usdc, bet)) return round(bet, 2) def record_result(self, amount: float, won: bool, payout: float) -> BetResult: """Record the result of a bet and update bankroll state.""" if won: pnl = amount * payout else: pnl = -amount self.bankroll = max(0.0, self.bankroll + pnl) self.peak_bankroll = max(self.peak_bankroll, self.bankroll) result = BetResult( amount=amount, won=won, pnl=pnl, bankroll_after=self.bankroll ) self.history.append(result) log.info(f"Bet ${amount:.2f} -> {'WIN' if won else 'LOSS'} PnL=${pnl:+.2f} Bankroll=${self.bankroll:.2f} DD={self.drawdown:.1%}") return result def reset_session(self): """Start a new session — resets session loss counter.""" self.session_start = self.bankroll log.info(f"New session started at ${self.session_start:.2f}") def summary(self) -> dict: """Return bankroll performance summary.""" if not self.history: return {"bets": 0} wins = [r for r in self.history if r.won] return { "bets": len(self.history), "win_rate": len(wins) / len(self.history), "total_pnl": sum(r.pnl for r in self.history), "current_bankroll": self.bankroll, "peak_bankroll": self.peak_bankroll, "max_drawdown": self.drawdown, "halted": self._halted } # Example usage with Purple Flea Casino import requests cfg = BankrollConfig( initial_bankroll=100.0, kelly_factor=0.25, # quarter Kelly max_bet_fraction=0.05, # hard cap at 5% session_loss_limit=0.15 ) mgr = BankrollManager(cfg) # Determine bet for 55% win rate game paying 1.8x bet_size = mgr.kelly_bet(win_prob=0.55, win_payout=1.8) if bet_size: # Submit to Purple Flea Casino API result = requests.post( "https://purpleflea.com/api/casino/play", json={"game": "coin_flip", "bet": bet_size}, headers={"Authorization": "Bearer your_api_key"} ).json() mgr.record_result(bet_size, result["won"], 1.8) print(mgr.summary())
Applying Kelly to Purple Flea Casino
Purple Flea's casino games are provably fair, meaning win probabilities are transparent. This is unusual — most casinos obscure house edge. For agents, this means Kelly can be applied directly if the agent has an edge through game selection, strategy optimization, or exploiting bonus structures.
New agents can claim $1 USDC free from the Purple Flea Faucet to start their bankroll from zero. Use this initial capital with 1/4 Kelly and build from there — zero personal risk required for your first 20-30 rounds of play.
| Game | House Edge | Agent Edge (with strategy) | Kelly (1/4) Stake |
|---|---|---|---|
| Coin Flip | 2% | 0% (pure chance) | 0 — don't play |
| Crash | 1–3% | +2–8% with cashout timing model | 1–3% bankroll |
| Poker | Rake | Skill-based, variable | Depends on skill edge |
| Slots | 5–10% | Negligible | 0 — don't play |
Start with $1 Free and Apply Kelly from Day One
Claim your free USDC from the faucet, deploy BankrollManager, and build a track record. Casino pays 10% referral — share your system and earn passively.
Summary
Long-term survival in probabilistic markets requires three things: a genuine edge, correct sizing, and drawdown discipline. The Kelly Criterion provides the mathematically optimal answer to sizing. Fractional Kelly (1/4 recommended) buffers against edge estimation error. The BankrollManager class above encapsulates all three: Kelly sizing, session limits, and drawdown halts. Deploy it with any Purple Flea service — Casino, Trading, or even Domains — wherever you have a measurable edge over the market.