Kelly Criterion Position Sizing for Trading Bots
>This covers position sizing. Use Claude to Build an AI Trading Bot goes deeper on backtesting, multi-agent systems, and a 90-day go-live deployment plan.

Use Claude to Build an AI Trading Bot One Weekend
Turn $500 into $10K in 90 Days with Stocks, Options, and Prediction Markets
Summary:
- Calculate optimal bet size from your backtest win rate and profit factor using Kelly Criterion.
- Apply quarter-Kelly with a 2% hard cap for real-world trading.
- Copy-paste a RiskManager class that integrates Kelly sizing with stop-losses and daily loss limits.
- Three worked examples showing when to bet big, when to bet small, and when to bet nothing.
Not financial advice. This article teaches the math behind position sizing. The examples use hypothetical numbers. Paper trade any system for at least 3 months before using real capital. Past performance does not predict future results.
A trader on r/algotrading posted his results last month. His bot had a 61% win rate and a 1.4 Sharpe ratio. He lost $14,000 in two weeks. No position sizing rules. His bot put 15% of the account into a single TSLA options trade. TSLA gapped down 8% on earnings. That one position wiped out three months of gains.
The strategy was fine. The risk management did not exist. Two different problems that most people confuse.
What is the Kelly Criterion and how does it work for trading?

Kelly Criterion tells you exactly how much of your account to put into each trade. You feed it your win rate and your average winner-to-loser ratio. It returns a percentage. That percentage maximizes long-term growth while keeping you from blowing up on a single bad week.
The formula:
def kelly_percentage(win_rate, profit_factor):
"""Calculate Kelly Criterion percentage.
win_rate: decimal (0.537 = 53.7%)
profit_factor: avg_win / avg_loss (1.79 means winners are 1.79x losers)
Returns: fraction of capital to risk per trade
"""
kelly = win_rate - (1 - win_rate) / profit_factor
return kelly
# From a real backtest: 53.7% win rate, 1.79 profit factor
result = kelly_percentage(0.537, 1.79)
print(f"Full Kelly: {result:.1%}") # 27.8%
print(f"Half Kelly: {result/2:.1%}") # 13.9%
print(f"Quarter Kelly: {result/4:.1%}") # 6.95%
Full Kelly says risk 27.8% per trade. That is insane for real trading. The math is correct but the volatility will make you override the bot at 2 AM. In practice, every professional trader uses fractional Kelly.
The proof is in Thorp’s own track record. His hedge fund Princeton/Newport Partners ran from 1969 to 1988 using Kelly-based position sizing on convertible bond arbitrage:
| Metric | Princeton/Newport Partners | S&P 500 (same period) |
|---|---|---|
| Annualized return | 15.1% net of fees | 10.1% |
| Std deviation | 4.3% | 17.3% |
| Down quarters | 0 | Multiple |
Half Kelly, three-quarters of the return, half the volatility. Thorp proved it over 19 years of live trading, not a backtest. (Source: Edward Thorp, “Understanding the Kelly Criterion”)
How do you calculate Kelly for three real scenarios?
Scenario 1: Strong strategy. Win rate 53.7%, average winner 3.82%, average loser -2.14%, profit factor 1.79.
kelly = 0.537 - (1 - 0.537) / 1.79 # = 0.278 (27.8%)
quarter_kelly = 0.278 * 0.25 # = 6.95%
# On a $100K account, stock at $200, 3% stop-loss ($6 risk/share)
risk_budget = 100_000 * 0.0695 # = $6,950
shares = int(risk_budget / 6) # = 1,158 shares
# But 2% hard cap: $2,000 / $6 = 333 shares. Use the cap.
Quarter-Kelly says 1,158 shares. The 2% hard cap says 333. You use 333. The cap protects you when Kelly gets aggressive.
Scenario 2: Thin edge. Win rate 52%, profit factor 1.3.
kelly = 0.52 - (1 - 0.52) / 1.3 # = 0.151 (15.1%)
quarter_kelly = 0.151 * 0.25 # = 3.8%
# On $50K account, contract at $0.35 each
risk_budget = 50_000 * 0.038 # = $1,900
contracts = int(1_900 / 0.35) # = 54 contracts
Kelly recommends a smaller bet because the edge is thinner. This is exactly right.
Scenario 3: No edge. Win rate 48%, profit factor 0.9.
kelly = 0.48 - (1 - 0.48) / 0.9 # = -0.098 (negative)
# STOP. Do not trade this strategy. Kelly is telling you it loses money.
A negative Kelly is the clearest signal in quantitative finance: this strategy destroys capital over time. No position size fixes a negative expected value.
How sensitive is Kelly to input errors?
| Win Rate | Full Kelly | Quarter Kelly |
|---|---|---|
| 51.7% (-2%) | 22.1% | 5.5% |
| 53.7% (base) | 27.8% | 6.95% |
| 55.7% (+2%) | 33.5% | 8.4% |
A 2% error in win rate changes your position size by ~25%. This is why quarter-Kelly exists. It absorbs estimation error without blowing up your account.
What broke when I first implemented Kelly?
I used full Kelly for two weeks on paper. The equity curve looked like a heart monitor. Up 12% one week, down 9% the next. The math was correct but the ride was unbearable. Switched to quarter-Kelly and the drawdowns dropped from 19% to about 6%. The returns dropped too, but I could actually sleep.
The second problem: I applied the same Kelly fraction to every trade regardless of the stock’s volatility. A 3% stop-loss on NVDA (which moves 2-3% on a normal day) is a different risk than a 3% stop on a stock that moves 0.5% daily. TSLA regularly moves 3-4% intraday. A 3% stop on TSLA gets triggered by normal volatility, not by your thesis being wrong. Widen the stop to 5% for volatile stocks and reduce position size proportionally.
| Stock | Price | Volatility | Stop-Loss | Risk/Share | Max Shares ($100K, 2% cap) |
|---|---|---|---|---|---|
| NVDA | $925 | Medium | 3% | $27.75 | 72 shares ($66,600) |
| F | $12 | Low | 3% | $0.36 | 5,555 shares ($66,660) |
| TSLA | $240 | High | 5% | $12.00 | 166 shares ($39,840) |
Same dollar risk, different position sizes, different stop widths. That is the whole point.
How do you build a reusable RiskManager class?
Here is a copy-paste module that integrates Kelly sizing with five hard rules:
import os
from dotenv import load_dotenv
from alpaca.trading.client import TradingClient
load_dotenv()
MAX_RISK_PER_TRADE = 0.02 # 2% hard cap
MAX_DAILY_LOSS = 0.06 # 6% circuit breaker
DEFAULT_STOP_LOSS = 0.03 # 3% default stop
KELLY_FRACTION = 0.25 # Quarter-Kelly
class RiskManager:
def __init__(self, alpaca_client):
self.alpaca = alpaca_client
self.daily_start = None
def initialize_day(self):
account = self.alpaca.get_account()
self.daily_start = float(account.portfolio_value)
return self.daily_start
def calculate_position_size(self, entry_price, stop_pct=None,
win_rate=0.537, profit_factor=1.79):
"""Kelly + hard cap position sizing."""
account = self.alpaca.get_account()
value = float(account.portfolio_value)
sl = stop_pct or DEFAULT_STOP_LOSS
risk_per_share = entry_price * sl
# Hard cap
cap_risk = value * MAX_RISK_PER_TRADE
cap_shares = int(cap_risk / risk_per_share)
# Kelly
kelly = win_rate - (1 - win_rate) / profit_factor
kelly = max(0, kelly * KELLY_FRACTION)
kelly_risk = value * kelly
kelly_shares = int(kelly_risk / risk_per_share)
return max(1, min(cap_shares, kelly_shares))
def check_daily_limit(self):
"""Returns False if daily loss limit hit."""
if not self.daily_start:
self.initialize_day()
account = self.alpaca.get_account()
current = float(account.portfolio_value)
change = (current - self.daily_start) / self.daily_start
return change > -MAX_DAILY_LOSS
def evaluate_trade(self, ticker, price, win_rate=0.537,
profit_factor=1.79, stop_pct=None):
"""Full risk check before any trade."""
if not self.check_daily_limit():
return {"allowed": False,
"reason": "Daily loss limit hit"}
shares = self.calculate_position_size(
price, stop_pct, win_rate, profit_factor)
sl = stop_pct or DEFAULT_STOP_LOSS
max_loss = price * shares * sl
return {"allowed": True, "shares": shares,
"stop_loss": f"{sl:.0%}",
"max_loss": f"${max_loss:,.2f}"}
def test_risk_manager_position_size():
rm = RiskManager(bankroll=10000, max_position_pct=0.25)
size = rm.kelly_position(win_rate=0.537, profit_factor=1.79)
assert 600 < size < 800 # Quarter-Kelly on $10K
def test_risk_manager_hard_cap():
rm = RiskManager(bankroll=10000, max_position_pct=0.25)
size = rm.kelly_position(win_rate=0.9, profit_factor=5.0)
assert size <= 2500 # Hard cap at 25% of bankroll
Integration with any bot from the book:
rm = RiskManager(alpaca_client)
rm.initialize_day()
# Before every trade:
check = rm.evaluate_trade("NVDA", 925.00)
if check["allowed"]:
# Place trade with check["shares"]
pass
else:
print(f"BLOCKED: {check['reason']}")
What should you actually do?
- If you have backtest results: plug your win rate and profit factor into the Kelly formula. If Kelly returns negative, stop. Fix the strategy first.
- If you are paper trading: use the RiskManager class with quarter-Kelly and track every trade. After 50 trades, recalculate Kelly with your live win rate instead of the backtest estimate.
- If you are going live: start with the 2% hard cap only. After 30 days of live data, enable Kelly sizing. The cap protects you while you collect enough data for Kelly to be meaningful.
bottom_line
- Kelly Criterion tells you the mathematically optimal bet size. Quarter-Kelly with a 2% cap gives you 70% of the growth with 25% of the drawdowns.
- A negative Kelly is a gift. It tells you the strategy loses money before you lose money. Treat it as a hard stop signal.
- Position sizing does more for your P&L than signal quality. The r/algotrading poster with the 61% win rate lost $14,000 because of sizing, not because of bad picks.
Frequently Asked Questions
Should I use full Kelly or fractional Kelly for my trading bot?+
Use quarter-Kelly with a 2% hard cap. Full Kelly is mathematically optimal but produces stomach-churning volatility. Quarter-Kelly sacrifices some theoretical growth for dramatically smoother equity curves.
What happens when Kelly Criterion gives a negative number?+
A negative Kelly means the strategy loses money over time and you should bet zero. Do not trade that strategy. Go back to the backtest and fix the win rate or profit factor before risking any capital.
How do I calculate Kelly Criterion from my backtest results?+
Plug your win rate and profit factor into the formula: Kelly % = W - (1-W)/R, where W is win rate as a decimal and R is average win divided by average loss. Then multiply by 0.25 for quarter-Kelly.
More from this Book
How to Build an AI Stock Screener Bot with Python
Build a Python stock screener that uses Claude and Unusual Whales MCP to scan live options flow, score multi-signal convergence, and output a ranked watchlist.
from: Use Claude to Build an AI Trading Bot One Weekend
How to Build a Polymarket Prediction Bot with Claude
Build a Claude-powered Python bot that scans active Polymarket contracts, estimates true probabilities, calculates expected value, and surfaces mispriced bets.
from: Use Claude to Build an AI Trading Bot One Weekend
Monte Carlo Backtesting for Trading Strategies
Build a Monte Carlo backtesting framework in Python that runs 1,000 simulations, produces a fan chart, and tells you if your strategy has a real edge.
from: Use Claude to Build an AI Trading Bot One Weekend
How to Build a Multi-Agent AI Trading System
Build a 4-agent trading system in Python where Claude instances specialize as analyst, risk manager, executor, and monitor with adversarial disagreement.
from: Use Claude to Build an AI Trading Bot One Weekend