Optimal Position Sizing

Kelly Criterion for
AI Trading Agents

Without a mathematical framework for position sizing, autonomous agents over-bet on winning streaks and blow up on losing ones. Kelly Criterion solves this by maximizing the long-run growth rate of your agent's bankroll — not just the expected value of each individual trade.


What Is Kelly Criterion?

The Kelly Criterion, developed by John L. Kelly Jr. at Bell Labs in 1956, answers a fundamental question: given a bet with known odds and probabilities, what fraction of your bankroll should you wager to maximize the long-run rate of wealth accumulation?

The answer is the fraction that maximizes the expected logarithm of wealth — not the expected value itself. This distinction is critical. Maximizing expected value can lead to betting your entire bankroll on a positive-EV bet, which risks ruin. Maximizing expected log wealth naturally accounts for the catastrophic effect of going broke.

f* = (b × p − q) / b
where f* = optimal fraction of bankroll to bet  |  b = net odds received on the bet (win amount per $1 wagered)  |  p = probability of winning  |  q = probability of losing = 1 − p

For trading applications, where wins and losses are not symmetric coin flips, the formula is expressed in terms of average win size (W) and average loss size (L):

f* = (W/L × win_rate − loss_rate) / (W/L)
Simplified trading form. W/L is the win-to-loss ratio from your backtest or live trade history. This reduces to the classic form when W = L = 1 (equal bet sizing).

Why AI Agents Need Position Sizing

An AI trading agent without position sizing is dangerous. Large language models and reinforcement learning agents will naturally gravitate toward action — placing trades — without any internal model of how bet size affects long-run survival. The result is agents that over-bet on high-confidence signals and rapidly deplete their capital.

The Kelly Criterion provides agents with a mathematically grounded upper bound on position size. It converts the agent's internal confidence estimate (win probability) and historical edge (win/loss ratio) into a specific, defensible fraction of bankroll. This makes the agent's behavior auditable: every position size has a provable mathematical justification.

Without Kelly sizing, a string of winning trades can lead an agent to bet 80% of its bankroll on a single position — and one loss wipes out years of gains. With Kelly, the bet size automatically shrinks as your edge diminishes and grows as it strengthens.


Implementing Kelly in Python

A production-ready KellySizer class with fractional Kelly support, bankroll tracking, and integration with the Purple Flea Trading API.

kelly_sizer.py
class KellySizer: def __init__(self, win_rate, win_loss_ratio, kelly_fraction=0.25): """ win_rate: float, historical probability of a winning trade (0.0–1.0) win_loss_ratio: float, average win size divided by average loss size kelly_fraction: float, fraction of full Kelly to bet (0.25 = 25% Kelly, safer) """ self.win_rate = win_rate self.win_loss_ratio = win_loss_ratio self.kelly_fraction = kelly_fraction # use fractional Kelly (much safer) def full_kelly(self): """Calculate the theoretically optimal Kelly fraction.""" loss_rate = 1 - self.win_rate f_star = (self.win_loss_ratio * self.win_rate - loss_rate) / self.win_loss_ratio return max(0.0, f_star) # no negative bets — if edge is negative, don't bet def bet_size(self, bankroll): """Return the dollar amount to bet given current bankroll.""" f_star = self.full_kelly() return bankroll * f_star * self.kelly_fraction def position_size_usd(self, bankroll, max_position=None): """Bet size capped at max_position (optional hard limit).""" size = self.bet_size(bankroll) if max_position: size = min(size, max_position) return size def summary(self, bankroll): full = self.full_kelly() fractional = full * self.kelly_fraction print(f"Full Kelly: {full:.2%} of bankroll = ${bankroll * full:,.2f}") print(f"Fractional Kelly: {fractional:.2%} of bankroll = ${bankroll * fractional:,.2f}") print(f"Edge: {(self.win_rate * self.win_loss_ratio - (1 - self.win_rate)):.4f}") # Example usage sizer = KellySizer(win_rate=0.55, win_loss_ratio=1.5, kelly_fraction=0.25) bankroll = 10000 # $10,000 USDC bet = sizer.position_size_usd(bankroll) print(f"Recommended position: ${bet:.2f}") # → $331.25 sizer.summary(bankroll) # Full Kelly: 13.25% = $1,325.00 # Fractional Kelly: 3.31% = $331.25 # Edge: 0.1500

Fractional Kelly: Why You Should Use It

Full Kelly betting is theoretically optimal only when your estimates of win rate and win/loss ratio are perfectly accurate. In practice, these estimates always contain estimation error — and Kelly's growth rate is highly sensitive to overestimating your edge.

If you think your win rate is 60% but it is actually 52%, full Kelly will cause you to significantly over-bet, leading to drawdowns far larger than the math predicts. For AI agents where parameters are estimated from finite data, this is always the case.

Use 25% Kelly as a default. At a quarter of full Kelly, you capture approximately 75% of the maximum growth rate while reducing variance by a factor of 16. This is the sweet spot for production agents where parameter uncertainty is unavoidable.

Full Kelly warning: At full Kelly, the probability of your bankroll halving at some point during a long series of bets approaches 50% — even with a genuine positive edge. At half Kelly, that probability drops to 25%. At quarter Kelly, to about 11%. Always use fractional Kelly in production.


Updating Kelly Estimates in Real Time

As your agent places trades, its estimates of win rate and W/L ratio should update. Use an exponential moving average to give more weight to recent trades.

adaptive_kelly.py
class AdaptiveKellySizer(KellySizer): def __init__(self, alpha=0.1, kelly_fraction=0.25): """ alpha: EMA smoothing factor (0.1 = slow adaptation, 0.3 = fast adaptation) Starts with neutral prior: 50% win rate, 1:1 W/L ratio """ super().__init__(win_rate=0.50, win_loss_ratio=1.0, kelly_fraction=kelly_fraction) self.alpha = alpha self.ema_win = 0.5 # EMA of win indicator (1=win, 0=loss) self.ema_wl = 1.0 # EMA of win/loss ratio per trade self.trade_count = 0 def record_trade(self, profit, loss_if_negative=None): """ profit: positive for wins, negative for losses (in dollar terms) """ self.trade_count += 1 win_indicator = 1.0 if profit > 0 else 0.0 self.ema_win = self.alpha * win_indicator + (1 - self.alpha) * self.ema_win if profit > 0: # Rolling EMA of W/L ratio on winning trades self.ema_wl = self.alpha * profit + (1 - self.alpha) * self.ema_wl else: self.ema_wl = self.alpha * abs(profit) + (1 - self.alpha) * self.ema_wl # Update parent Kelly parameters self.win_rate = self.ema_win self.win_loss_ratio = self.ema_wl if self.trade_count % 10 == 0: print(f"Trade #{self.trade_count}: win_rate={self.win_rate:.2%} W/L={self.win_loss_ratio:.2f} full_kelly={self.full_kelly():.2%}") # The sizer adapts as your agent gains experience sizer = AdaptiveKellySizer(alpha=0.1, kelly_fraction=0.25) sizer.record_trade(150) # win: $150 sizer.record_trade(-80) # loss: $80 sizer.record_trade(200) # win: $200

Kelly for Casino Games

Casino games have fixed, known probabilities — making them the ideal test case for Kelly Criterion. Your agent can calculate the exact optimal bet size before every wager on the Purple Flea Casino API.

Coin Flip (p = 0.49, b = 1)

f* = (1 × 0.49 − 0.51) / 1 = -0.02. A negative Kelly fraction means do not bet. The house edge makes this a losing proposition in the long run, regardless of short-term variance. Your agent should refuse to play when Kelly is negative.

Dice (target under 50, payout 2x)

p = 49/100 = 0.49, b = 2. f* = (2 × 0.49 − 0.51) / 2 = 0.235. A 23.5% Kelly bet — but use 25% Kelly so bet 5.9% of bankroll. This is only marginally positive; even small estimation errors flip it negative.

Crash Game (cashout at 2x)

With a fair crash game, the probability of reaching 2x is approximately 50%. f* = (1 × 0.50 − 0.50) / 1 = 0. Exactly break-even — the Kelly fraction is zero, meaning you have no mathematical edge. A house edge of even 1% makes Kelly negative. Only play crash games where you have an informational edge on the cashout timing.


Kelly for Perpetual Futures

Trading does not have fixed odds like casino games. Your win rate and W/L ratio must be estimated from a backtest or from live trade history. The quality of your Kelly estimate is only as good as your sample size and the stability of your edge.

A robust approach: backtest your strategy on 2 years of historical data, compute win rate and W/L in-sample, then validate on an out-of-sample holdout period. If the out-of-sample metrics are within 10% of in-sample, your Kelly estimate is reasonably reliable. Use a further 50% haircut on estimated edge for live trading.

kelly_from_backtest.py
def compute_kelly_from_trades(trade_history): """ trade_history: list of dicts with 'pnl' key (positive=win, negative=loss) Returns KellySizer calibrated to your strategy. """ wins = [t["pnl"] for t in trade_history if t["pnl"] > 0] losses = [abs(t["pnl"]) for t in trade_history if t["pnl"] < 0] if not wins or not losses: raise ValueError("Need at least one win and one loss to estimate Kelly.") win_rate = len(wins) / len(trade_history) avg_win = sum(wins) / len(wins) avg_loss = sum(losses) / len(losses) win_loss_ratio = avg_win / avg_loss print(f"Trades: {len(trade_history)} | Win rate: {win_rate:.2%} | W/L: {win_loss_ratio:.2f}") # Apply 50% haircut to estimated edge for live trading conservative_win_rate = win_rate * 0.95 return KellySizer( win_rate=conservative_win_rate, win_loss_ratio=win_loss_ratio, kelly_fraction=0.25 # always use fractional Kelly ) # Calibrate from your backtest results sizer = compute_kelly_from_trades(backtest_trades) bankroll = 50000 next_position = sizer.position_size_usd(bankroll, max_position=5000) print(f"Next trade size: ${next_position:.2f}")

Purple Flea + Kelly: LangChain Tool

Wrap KellySizer as a LangChain tool so your agent automatically sizes every position before placing the order through the Purple Flea Trading API.

kelly_tool.py
import requests from langchain.tools import tool from purpleflea_langchain import TradingOpenPositionTool, WalletBalanceTool sizer = KellySizer(win_rate=0.55, win_loss_ratio=1.4, kelly_fraction=0.25) @tool def kelly_sized_trade(symbol: str, side: str, leverage: int = 1) -> str: """ Open a Kelly-sized position on Purple Flea. Automatically queries wallet balance, computes optimal position size, and places the order. Args: symbol: perpetual market symbol (e.g., 'ETH-PERP') side: 'long' or 'short' leverage: 1-50 (default 1 for safety) """ # Query current USDC balance from Purple Flea wallet balance_tool = WalletBalanceTool() balance_resp = balance_tool.run({"chain": "ethereum", "token": "USDC"}) bankroll = balance_resp["balance_usd"] # Compute Kelly position size position_usd = sizer.position_size_usd(bankroll, max_position=bankroll * 0.10) # Place the trade via Purple Flea Trading API trade_tool = TradingOpenPositionTool() result = trade_tool.run({ "symbol": symbol, "side": side, "size_usd": position_usd, "leverage": leverage, "order_type": "market", }) return f"Opened {side} {symbol}: ${position_usd:.2f} (Kelly-sized from ${bankroll:.2f} bankroll). ID: {result['position_id']}"

Common Kelly Mistakes

Correlated Bets

Kelly assumes each bet is independent. If you open ETH-PERP and BTC-PERP simultaneously, they are highly correlated — effectively one bet. Apply Kelly to your total correlated exposure, not to each position individually. Diversified, uncorrelated positions each get a separate Kelly allocation.

Ignoring Fees

Transaction costs reduce your effective edge. A strategy with a 55% win rate and 1.4 W/L may have a Kelly fraction of 5% before fees — but if you pay 0.1% per trade and trade frequently, fees can consume half your edge. Always compute win_rate and W/L after deducting all fees.

Using Full Kelly

Full Kelly is theoretically optimal only with perfect knowledge of your edge. In practice, estimation error combined with full Kelly produces drawdowns much larger than expected. Always use fractional Kelly (25%–50%) for any real-money agent. The growth-rate sacrifice is modest; the variance reduction is large.


Related Guides

Give your agent a
mathematically sound edge.

Free to start. 20% referral on trading fees. No KYC required.