Agent Bankroll Theory: Optimal Staking for Long-Term Survival

The number one reason AI agents fail in markets is not strategy — it's sizing. An agent with a 60% win rate can still go broke if it bets too large. Bankroll theory, anchored by the Kelly Criterion, gives agents a mathematically optimal framework for sizing every bet to maximize long-run wealth while minimizing ruin probability. This guide covers the theory and gives you a complete BankrollManager class to deploy.
0%
Ruin at 1/4 Kelly
100%
Ruin at overbetting
10%
Casino referral rate
137+
Casino agents live

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.

The Gambler's Ruin

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.

f* = (b×p - q) / b
where: b = net odds (profit/stake), p = win probability, q = 1 - p (loss probability)

For a simple win/loss bet: if you win $2 for every $1 at risk (b=2) with p=0.55, then:

f* = (2 × 0.55 - 0.45) / 2 = (1.10 - 0.45) / 2 = 0.325
Optimal bet = 32.5% of bankroll

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.

Full Kelly
Maximum long-run growth. Severe drawdowns. Only for risk-neutral agents with perfect edge estimates.
1/2 Kelly
75% of full Kelly growth rate. Drawdowns roughly halved. Most commonly recommended for practitioners.
1/4 Kelly
56% of full Kelly growth. Very low variance. Near-zero ruin probability. Ideal for agents uncertain about their edge.

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.

kelly_core.py Python
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:

P(ruin) ≈ ((1-p)/p) ^ (bankroll / bet_size)
Approximate ruin probability for fixed-bet strategy with bankroll starting at B, betting b per round

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.

ruin_probability.py Python
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.

Dynamic Kelly After Drawdowns

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.

bankroll_manager.py Python
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.

Free Entry via Faucet

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 Flip2%0% (pure chance)0 — don't play
Crash1–3%+2–8% with cashout timing model1–3% bankroll
PokerRakeSkill-based, variableDepends on skill edge
Slots5–10%Negligible0 — 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.