Kelly Criterion

A mathematical framework for optimal bet sizing to maximise long‑run wealth growth. Derivation, interactive coin‑toss simulation, and continuous portfolio application.

Mathematical Foundation

Consider a binary bet with probability \(p\) of winning a fraction \(g\) of the stake, and probability \(q = 1-p\) of losing a fraction \(l\).

\[ W_{t+1} = \begin{cases} W_t (1 + f g) & \text{with prob. } p \\[4pt] W_t (1 - f l) & \text{with prob. } q \end{cases} \]

After \(n\) independent trials the wealth is

\[ W_n = W_0 \, (1+fg)^{np}\,(1-fl)^{nq} \]

The exponential growth rate per trial is

\[ G(f) = \frac{1}{n}\ln\frac{W_n}{W_0} = p\ln(1+fg) + q\ln(1-fl) \]

Maximising \(G(f)\) w.r.t. \(f\) gives the optimal fraction

\[ f^* = \frac{p}{l} - \frac{q}{g} \]

For the common case where you win or lose the full stake (\(g=l=1\)), this simplifies to

\[ f^* = p - q = 2p - 1 \]
Interpretation: Betting exactly \(f^*\) maximises the long‑term growth rate. Over‑betting (\(f > f^*\)) increases volatility and reduces growth; under‑betting is safer but sub‑optimal.

Interactive Coin‑Toss Simulation

Kelly fraction \(f^*\)
0.200
Wealth trajectories (10 paths)
Kelly Half‑Kelly 2× Kelly All‑in (f=1)

Continuous Portfolio – Kelly for Stocks

For a risky asset with expected return \(\mu\) and volatility \(\sigma\) (risk‑free rate \(r\)), the optimal fraction is

\[ f^* = \frac{\mu - r}{\sigma^2} \]

Kelly \(f^*\) = 0.15
MC optimal (log) ≈
FractionMean wealthP(loss>50%)
0.0 (cash)
Kelly
1.0 (all stocks)

Kelly Surface: Optimal Fraction vs \(\mu\) and \(\sigma\)

The red dot marks the current parameters \(\mu = 0.08,\ \sigma = 0.20\). Surface shows \(f^* = (\mu-r)/\sigma^2\) with \(r=0.02\).

Python Implementation – Vectorised Simulation

Efficient simulation of a continuously rebalanced Kelly portfolio using geometric Brownian motion.

import numpy as np

def simulate_kelly(mu, vol, rf, frac, sim=10000, n=252):
    """
    Vectorised Kelly simulation with daily rebalancing.
    Returns array of shape (sim, n+1), initial wealth = 1.
    """
    dt = 1 / n
    z = np.random.randn(sim, n)
    port_mu = frac * mu + (1 - frac) * rf
    port_vol = frac * vol
    log_drift = (port_mu - 0.5 * port_vol**2) * dt
    log_shocks = port_vol * np.sqrt(dt) * z
    log_returns = log_drift + log_shocks
    cum_log = np.cumsum(log_returns, axis=1)
    wealth = np.exp(cum_log)
    return np.column_stack([np.ones(sim), wealth])