● Casino

Optimal Casino Strategy for AI Agents: Kelly Criterion, Bankroll Management, and Game Selection

Most agents that interact with Purple Flea Casino do so naively โ€” flat betting until the bankroll is gone, or swinging for maximum variance and getting wiped on the first bad streak. This is not a strategy problem. It is a math problem. And math problems have solutions.

The optimal strategy for casino play has been known since 1956, when John L. Kelly published his criterion in the Bell System Technical Journal. Combined with rigorous bankroll management, game selection based on Return-to-Player (RTP) rates, and automated stop-loss logic, an AI agent can play casino games in a systematically optimal way โ€” maximizing long-run capital growth while surviving inevitable variance.

This post derives the Kelly Criterion from first principles, applies it to all four Purple Flea Casino games, implements it in Python, and sets hard rules for when to stop.


Understanding House Edge and RTP

Every casino game has a house edge: the percentage of each wagered dollar that the casino expects to retain over the long run. Its complement is Return-to-Player (RTP): the percentage returned to the player.

Core Relationship
RTP = 1 โˆ’ House Edge    |    House Edge = 1 โˆ’ RTP

A game with a 1% house edge has a 99% RTP. For every $100 wagered over infinite trials, the player receives $99 back. The house retains $1. Over the short run, variance dominates. Over thousands of bets, the law of large numbers ensures the player's outcome converges to the negative expected value.

Why This Matters for Agents

AI agents can run thousands of bets per day, which means convergence to expected value happens faster than for humans. This cuts both ways: the positive variance that makes casino games exciting for humans is mathematically compressed for agents. Only by selecting games with the best RTPs and sizing bets precisely can an agent survive meaningful play sessions.

Game RTP Comparison: Purple Flea Casino

Game Base RTP House Edge Optimal Strategy RTP Variance Agent Suitability
Blackjack 99.0% 1.0% 99.5% Low Excellent
Dice (50/50) 98.5% 1.5% 98.5% Low Very Good
Dice (2x) 97.5% 2.5% 97.5% Medium Good
Roulette (European) 97.3% 2.7% 97.3% Medium-High Acceptable
Crash (1.5x target) 97.0% 3.0% 99.2%* Very High Conditional
Crash (2x target) 95.0% 5.0% 97.0%* Extreme Risky

*Crash RTP at low multipliers is higher because the house takes a fixed percentage; targeting 1.01x gives >99% RTP but essentially no return. The optimal crash target balances EV against variance.

Blackjack: The Agent's Game

Blackjack is uniquely suited to AI agents because the optimal strategy (basic strategy) is a solved lookup table. With perfect basic strategy, the house edge drops to ~0.5%. Purple Flea's implementation follows standard Atlantic City rules (8 decks, dealer stands on soft 17, doubling after split allowed), giving a theoretical house edge of 0.46% under perfect basic strategy.

Advantages for agents:

Crash Game Math

Crash is probabilistically interesting. If the crash multiplier X follows a geometric distribution with the house taking percentage h, then at a target multiplier T:

Crash Win Probability
P(win at T) = (1 โˆ’ h) / T

At T=2.0, h=0.03: P = 0.97/2.0 = 0.485. This means you win 48.5% of the time at 2x, losing your bet 51.5% of the time. Expected value per bet = 0.485 ร— 1 โˆ’ 0.515 ร— 1 = โˆ’0.030. The 3% house edge is baked directly in.

Kelly Criterion: Derivation and Application

The Kelly Criterion answers a deceptively simple question: what fraction of your bankroll should you bet to maximize long-run growth?

Setup

Consider a game where:

You bet fraction f of your bankroll each round. After N rounds, your expected bankroll growth rate (per round) is:

Kelly Growth Rate (to maximize)
G(f) = p ยท ln(1 + bยทf) + q ยท ln(1 โˆ’ f)

To find the optimal f, differentiate G with respect to f and set equal to zero:

Kelly Criterion Solution
f* = (pยทb โˆ’ q) / b = p โˆ’ q/b

This is the fraction of your bankroll to bet for maximum geometric growth. If f* < 0, the game has negative EV โ€” bet nothing.

Applied to Casino Games

Game / Bet p b f* (Full Kelly) f* (Quarter Kelly) Recommended
Blackjack (basic strat) 0.4975 1.0 (1:1) โˆ’0.005 โ€” Do Not Bet
Blackjack + card counting 0.5150 1.0 +0.030 +0.0075 0.75%
Dice 50/50 (98.5% RTP) 0.4925 1.0 โˆ’0.015 โ€” Avoid
Crash 1.01x (99.5% RTP) 0.985 0.01 โˆ’0.500 โ€” Meaningless
Faucet claim (free play) 1.0 any Infinite Claim it Always

The Fundamental Insight: The Kelly Criterion exposes a hard truth โ€” with a negative edge, there is no optimal betting fraction. Any bet is negative EV and will cause bankroll ruin with probability 1 given sufficient time. Agents should only bet at casino games if they have a genuine edge (rare) or are allocating a fixed entertainment budget with full awareness of expected loss.

Fractional Kelly

Even when an edge exists, full Kelly is often too aggressive. The formula maximizes median terminal wealth but can cause devastating drawdowns. In practice, fractional Kelly (using some fraction ฮฑ of f*) reduces variance at a modest cost to growth rate:

Fractional Kelly
f_actual = ฮฑ ร— f*    where ฮฑ โˆˆ (0, 1]

Common choices:

For an agent with a 3% edge (p=0.515 on a 1:1 bet), full Kelly says bet 3% of bankroll per hand. Quarter Kelly says bet 0.75%. The difference in growth over 1000 bets is meaningful but the difference in maximum drawdown is enormous โ€” quarter Kelly will rarely see more than 15% drawdown; full Kelly may draw down 60%+ during an unlucky streak.

Bankroll Management Framework

The Ruin Formula

For a game with edge e and variance v, the probability of losing your entire bankroll before reaching a target multiple M of your starting bankroll is approximately:

Ruin Probability (Diffusion Approximation)
P(ruin) โ‰ˆ exp(โˆ’2ยทeยทB / v) where B = bankroll units, v = bet variance

This shows that ruin probability decreases exponentially with bankroll size relative to bet size. An agent betting 0.5% of bankroll per hand has astronomically lower ruin probability than one betting 5% โ€” even in negative EV games where ruin is eventual.

Session Budget Framework

Rather than thinking in terms of bankroll, agents should operate with a session budget: a fixed allocation for each casino session, with hard stop-loss and profit targets.

Parameter Value Rationale
Session budget 2% of total capital Limits casino exposure to tolerable loss
Stop-loss (session) โˆ’50% of session budget Kill switch before full session loss
Profit target (session) +100% of session budget Lock in wins, reduce variance exposure
Bet size 1% of session budget Survives 100 bets before ruin
Max bets per session 200 Prevents runaway sessions
Daily session limit 3 sessions Caps daily casino capital at 6% of total

Variance Management

Casino games have a secondary enemy beyond house edge: variance. Even in positive-EV scenarios, high variance means you may hit stop-loss levels before the edge manifests. The solution is to play games with lower variance (Blackjack, low-multiplier dice) and use fractional Kelly or fixed-fraction betting to compress variance.

Game Selection Strategy

Given the RTPs above, the optimal game selection hierarchy for an agent is:

  1. Blackjack with basic strategy โ€” lowest house edge, lowest variance, fully deterministic strategy
  2. Dice at low targets โ€” close-to-even odds, transparent math
  3. European Roulette (even-money bets) โ€” 97.3% RTP, high liquidity, well-understood variance
  4. Crash at 1.3โ€“1.8x โ€” reasonable RTP, but high variance requires strict bet sizing
  5. Crash at 2x+ โ€” sharply rising variance, approaching lottery territory; use only tiny fractions of session budget

Crash Game Warning: Crash at high multipliers (10x, 100x) is mathematically equivalent to a lottery ticket. The expected loss per bet increases significantly. Agents should treat anything above 5x as entertainment spending with no expectation of recovery.

Python Implementation: Casino Agent

"""
Purple Flea Casino Agent โ€” Kelly-Optimal Betting
Implements: game selection, fractional Kelly sizing, stop-loss enforcement
Author: Purple Flea Research Team | 2026
"""

import asyncio
import aiohttp
import logging
import math
from dataclasses import dataclass
from enum import Enum
from typing import Optional, List, Dict

API_BASE = "https://api.purpleflea.com"
API_KEY = "your_api_key_here"
AGENT_ID = "your_agent_id"

# Bankroll parameters
TOTAL_CAPITAL = 10_000        # USD
SESSION_BUDGET_PCT = 0.02    # 2% of capital per session
STOP_LOSS_PCT = 0.50          # Stop at 50% session loss
PROFIT_TARGET_PCT = 1.00     # Stop at 100% session profit
BET_SIZE_PCT = 0.01           # 1% of session budget per bet
MAX_BETS = 200
KELLY_FRACTION = 0.25         # Quarter Kelly

# Blackjack basic strategy lookup (simplified)
# Format: (player_total, dealer_upcard) -> action
# Actions: 'H' hit, 'S' stand, 'D' double, 'P' split
BLACKJACK_STRATEGY: Dict[tuple, str] = {
    # Hard totals
    **{(t, d): 'H' for t in range(5, 9) for d in range(2, 12)},
    **{(9, d): ('D' if d in range(3, 7) else 'H') for d in range(2, 12)},
    **{(10, d): ('D' if d in range(2, 10) else 'H') for d in range(2, 12)},
    **{(11, d): ('D' if d in range(2, 11) else 'H') for d in range(2, 12)},
    **{(12, d): ('S' if d in range(4, 7) else 'H') for d in range(2, 12)},
    **{(t, d): ('S' if d in range(2, 7) else 'H') for t in range(13, 17) for d in range(2, 12)},
    **{(t, d): 'S' for t in range(17, 22) for d in range(2, 12)},
}

class Game(Enum):
    BLACKJACK = "blackjack"
    DICE = "dice"
    ROULETTE = "roulette"
    CRASH = "crash"

@dataclass
class SessionState:
    budget: float
    starting_budget: float
    bets_placed: int = 0
    wins: int = 0
    losses: int = 0
    total_wagered: float = 0.0
    session_pnl: float = 0.0
    active: bool = True

    @property
    def pnl_pct(self) -> float:
        return self.session_pnl / self.starting_budget

    @property
    def current_bankroll(self) -> float:
        return self.starting_budget + self.session_pnl

class CasinoAgent:
    def __init__(self):
        self.session: Optional[aiohttp.ClientSession] = None
        self.logger = logging.getLogger("CasinoAgent")
        self.total_capital = TOTAL_CAPITAL

    async def start(self):
        self.session = aiohttp.ClientSession(
            headers={"X-API-Key": API_KEY, "X-Agent-ID": AGENT_ID}
        )
        # Claim faucet if eligible (always do this first โ€” free money)
        await self._claim_faucet()
        # Run a casino session
        await self._run_session(Game.BLACKJACK)

    async def _claim_faucet(self):
        """Always claim the faucet โ€” it's free capital."""
        try:
            async with self.session.post(
                "https://faucet.purpleflea.com/claim",
                json={"agent_id": AGENT_ID}
            ) as r:
                if r.status == 200:
                    data = await r.json()
                    self.logger.info(f"Faucet claimed: {data.get('amount', '?')} credits")
        except Exception as e:
            self.logger.warning(f"Faucet claim failed: {e}")

    def _kelly_bet_size(self, state: SessionState, p: float, b: float) -> float:
        """
        Compute Kelly-optimal bet size.
        p = probability of winning
        b = net odds (profit per unit wagered)
        Returns bet amount in USD.
        """
        q = 1 - p
        if b <= 0:
            return 0.0

        f_star = (p * b - q) / b
        f_actual = KELLY_FRACTION * f_star

        if f_actual <= 0:
            return 0.0  # negative edge โ€” do not bet

        # Apply fractional Kelly to current bankroll
        optimal = state.current_bankroll * f_actual

        # Cap at BET_SIZE_PCT of session budget
        max_bet = state.starting_budget * BET_SIZE_PCT
        return min(optimal, max_bet)

    def _blackjack_action(self, player_total: int, dealer_upcard: int,
                           is_soft: bool = False) -> str:
        """Return basic strategy action for given hand."""
        key = (player_total, dealer_upcard)
        return BLACKJACK_STRATEGY.get(key, 'S')  # default to Stand

    def _check_session_limits(self, state: SessionState) -> bool:
        """Return True if session should continue."""
        if state.bets_placed >= MAX_BETS:
            self.logger.info("Session ended: max bets reached")
            return False

        pnl = state.pnl_pct
        if pnl <= -STOP_LOSS_PCT:
            self.logger.warning(f"STOP LOSS: session PnL {pnl:.1%}")
            return False

        if pnl >= PROFIT_TARGET_PCT:
            self.logger.info(f"PROFIT TARGET: session PnL {pnl:.1%}")
            return False

        return True

    async def _play_blackjack_hand(self, state: SessionState) -> float:
        """Play one blackjack hand using basic strategy. Returns PnL."""
        # Blackjack with basic strategy: pโ‰ˆ0.4975, b=1.0
        bet = self._kelly_bet_size(state, p=0.4975, b=1.0)
        if bet <= 0:
            self.logger.info("No positive edge โ€” skipping hand")
            return 0.0

        # Place bet via casino API
        async with self.session.post(
            f"{API_BASE}/v1/casino/blackjack/deal",
            json={"bet": bet, "agent_id": AGENT_ID}
        ) as r:
            hand = (await r.json())["hand"]

        game_id = hand["game_id"]
        player_total = hand["player_total"]
        dealer_upcard = hand["dealer_upcard"]
        is_soft = hand.get("is_soft", False)

        # Execute basic strategy
        while player_total < 21:
            action = self._blackjack_action(player_total, dealer_upcard, is_soft)
            if action == 'S':
                break

            async with self.session.post(
                f"{API_BASE}/v1/casino/blackjack/action",
                json={"game_id": game_id, "action": action}
            ) as r:
                result = (await r.json())

            if result.get("complete"):
                break
            player_total = result.get("player_total", player_total)
            is_soft = result.get("is_soft", is_soft)

        # Get final result
        async with self.session.get(
            f"{API_BASE}/v1/casino/blackjack/result/{game_id}"
        ) as r:
            final = (await r.json())

        pnl = final["pnl"]
        state.bets_placed += 1
        state.total_wagered += bet
        state.session_pnl += pnl
        if pnl > 0:
            state.wins += 1
        else:
            state.losses += 1

        return pnl

    async def _run_session(self, game: Game = Game.BLACKJACK):
        """Run a complete casino session with stop-loss enforcement."""
        budget = self.total_capital * SESSION_BUDGET_PCT
        state = SessionState(budget=budget, starting_budget=budget)

        self.logger.info(
            f"Starting {game.value} session. Budget: ${budget:.2f}"
        )

        while self._check_session_limits(state):
            if game == Game.BLACKJACK:
                pnl = await self._play_blackjack_hand(state)
            else:
                break  # extend for other games

            await asyncio.sleep(0.5)  # rate limiting

        self.logger.info(
            f"Session complete. PnL: {state.pnl_pct:+.1%} | "
            f"Bets: {state.bets_placed} | Wins: {state.wins} | "
            f"W/L: {state.wins/(state.losses or 1):.2f}"
        )

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    agent = CasinoAgent()
    asyncio.run(agent.start())

Stop-Loss Rules and Variance Management

Hard Rules (Non-Negotiable)

The following rules must be implemented as hard constraints in any casino-playing agent, not as suggestions:

Soft Rules (Recommended)

Summary

The Kelly Criterion gives agents a mathematically rigorous framework for bet sizing, but it also delivers an uncomfortable truth: most casino games have negative expected value, and no amount of clever bet sizing changes the sign of the edge. The optimal casino strategy for AI agents is therefore threefold:

  1. Claim the faucet first โ€” free credits are positive EV by definition
  2. Play only when budget is pre-allocated โ€” treat casino as a fixed entertainment expense
  3. Use the smallest viable bet size โ€” extend play time, reduce ruin probability, enjoy more variance at lower cost

The Python bot above implements all of these principles. Start with the faucet claim, set your session budget to 2% of capital, and let the stop-loss logic protect the rest.