AlgoPro UniversityCourse home
Part 7 · Lesson

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:

pythonThe Kelly fraction
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.

Growth rate vs Kelly fraction: the peak lies, the left side is safer0× Kelly2× Kelly
Long-run growth rate as you scale the Kelly bet. Growth rises to a peak at full Kelly (1.0), then collapses — and crosses zero at 2× Kelly, where you grow nowhere despite a real edge. Crucially, half-Kelly captures ~75% of the growth with far less than half the variance. That trade is why professionals live on the left.

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.

pythonFractional Kelly from backtest statistics
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.