The Kelly criterion
The mathematically optimal bet size for maximum long-run growth — and why betting the full amount is almost always a mistake.
In 1956, John Kelly derived the bet size that maximises the long-run growth rate of a bankroll. Given a real edge, there is one fraction of your capital that grows your account faster, over time, than any other. Bet less and you leave growth on the table; bet more and — counter-intuitively — you grow *slower* and risk ruin. That optimum is the Kelly fraction, and understanding it (and why you should never use all of it) is essential financial literacy.
The formula
For a bet that wins a fraction b of the amount risked with probability p (and loses the amount risked with probability q = 1 − p), the Kelly fraction of capital to bet is:
def kelly_fraction(win_rate, win_loss_ratio):
class="s">""class="s">"
win_rate : probability of a winning trade, p (class="n">0..class="n">1)
win_loss_ratio : b = average win / average loss (the payoff odds)
returns : fraction of capital to risk per bet
"class="s">""
p = win_rate
q = class="n">1 - p
b = win_loss_ratio
return (b * p - q) / b class="c"># Kelly: (bp - q) / b
class="c"># A strategy that wins class="n">55% of the time, and wins class="n">1.5x what it loses:
f = kelly_fraction(win_rate=class="n">0.55, win_loss_ratio=class="n">1.5)
print(fclass="s">"Full Kelly: bet {f:.class="n">1%} of capital") class="c"># Full Kelly: bet class="n">25.0%
class="c"># A coin-flip class="n">60/class="n">40 edge at even money (b = class="n">1):
print(fclass="s">"{kelly_fraction(class="n">0.60, class="n">1.0):.class="n">1%}") class="c"># class="n">20.0% (the classic class="s">"edge" = 2p-class="n">1)Note the numerator, b·p − q: that is just your expected value per unit risked. If it is zero or negative you have no edge, Kelly returns zero or negative, and the correct bet is *nothing*. Kelly only tells you to bet when the maths says you have a genuine edge — and it scales the bet with both how often you win (p) and how much you win (b).
Why full Kelly is too aggressive
Kelly maximises growth, but it does so at a level of variance that is emotionally and practically unbearable. At full Kelly, drawdowns of 50% or more are not tail events — they are routine. The growth curve peaks at full Kelly and then falls off past it, but the *smart* region is well to the left of the peak, where you give up a little growth for a large reduction in volatility.
Look at the shape. Going from half-Kelly to full-Kelly buys you only the last ~25% of growth, but roughly quadruples your variance (variance scales with the square of the fraction). And past full Kelly the curve dives — at 2× Kelly your long-run growth is zero, even though your edge is exactly the same. Over-betting converts a winning strategy into a break-even one and then into a losing one. This is the mathematical heart of "position sizing can ruin a good system".
Fractional Kelly: what professionals actually use
The standard practice is fractional Kelly — betting a fixed fraction (typically ½ or ¼) of the full Kelly amount. You sacrifice a small, known amount of growth for a large, known reduction in drawdowns. Half-Kelly is the common default; quarter-Kelly for anyone who values sleep or distrusts their edge estimate.
def kelly_from_trades(trade_returns, fraction=class="n">0.5):
class="s">"""
trade_returns : list of per-trade P&L in R-multiples or currency
fraction : Kelly fraction to apply (class="n">0.5 = half-Kelly)
returns : recommended risk fraction of capital
class="s">"""
wins = [r for r in trade_returns if r > class="n">0]
losses = [r for r in trade_returns if r < class="n">0]
if not wins or not losses:
return class="n">0.0
win_rate = len(wins) / len(trade_returns)
avg_win = sum(wins) / len(wins)
avg_loss = abs(sum(losses) / len(losses))
b = avg_win / avg_loss
full = kelly_fraction(win_rate, b)
return max(class="n">0.0, full * fraction) class="c"># never negative; clamp at class="n">0
class="c"># class="n">200 backtested trades:
import random
trades = [random.choice([class="n">2.0, class="n">2.0, -class="n">1.0, -class="n">1.0, -class="n">1.0]) for _ in range(class="n">200)]
full = kelly_from_trades(trades, fraction=class="n">1.0)
half = kelly_from_trades(trades, fraction=class="n">0.5)
print(fclass="s">"Full Kelly: {full:.class="n">1%} Half Kelly: {half:.class="n">1%}")
class="c"># Full Kelly: ~class="n">20% Half Kelly: ~class="n">10% (of capital per trade)How Kelly connects to fixed-fractional sizing
Kelly and the percent-risk method of lesson 2 are the same machinery viewed from two ends. Kelly tells you the *theoretical ceiling* on your risk fraction given your edge; fixed-fractional sizing is *how you implement* whatever fraction you chose, using the stop distance to convert it into units. In practice you use Kelly to sanity-check that your chosen 1–2% is comfortably below the danger zone, then size every trade with the fixed-fractional formula. Growth theory sets the budget; the stop turns the budget into a position.
One trade at a time is only half the story. Real accounts hold several positions at once, and their risks add up — and worse, they can all go wrong together. The final lesson ties it together: stops, take-profits, and the total heat across your whole book.