The Kelly Criterion

The Kelly Criterion is a formula used to optimise the size of a series of bets to maximise long term gain while minimising the risk of ruin (losing everything).
Let be the probability of winning, and be the probability of losing.
Let be the profit you make when you win the bet. For example, if is 2 this means if you pay £1 to play then you will receive £2 for the win and your original £1 back, for a total of £3. Another way of looking at it is this is the decimal odds you get on the bet, subtract one.
The Kelly Criterion tells us the fraction of our bankroll to invest at each point. It is given by ...
With this value of , the expected growth in each iteration is ...
Let's do an example. A fair coin is flipped, and if it is heads, I win double my money (plus the original stake). If it is tails, I get nothing.
This is a great deal! The expected return in each iteration is 1.5. However, if I invest all my money each time, I run the risk of losing everything with probability 0.5 each flip. It is easy to see that the long term growth rate of the invest-everything strategy goes to 0. Let's instead look at what the Kelly Criterion says we should invest.
Here and , which makes , so only a quarter of the bankroll each time. This makes , which is much less than the seen earlier, but is still significantly greater than .
Let's dive into some code to run some simulations of different strategies, using different values. Let's do 1000 flips in each game, and run each game 1000 times, averaging the value of .
Here's the code:
use rand::Rng;
const NUM_TRIALS: usize = 1000;
const NUM_FLIPS: usize = 1000;
fn main() {
let mut rng = rand::rng();
let f_values = [0.05, 0.1, 0.25, 0.5, 0.9, 0.95];
for f in f_values {
let mut g_values = Vec::new();
for _ in 0..NUM_TRIALS {
let mut money: f64 = 1.0;
for _ in 0..NUM_FLIPS {
if rng.random::<f64>() < 0.5 {
money += 2.0 * f * money;
} else {
money -= f * money;
}
}
let g = money.powf(1.0 / NUM_FLIPS as f64);
g_values.push(g);
}
let mean_g = g_values.iter().sum::<f64>() / NUM_TRIALS as f64;
println!("f: {}, g: {}", f, mean_g);
}
}And here are the results:
f: 0.05, g: 1.0222125653347862
f: 0.1, g: 1.0387865199602482
f: 0.25, g: 1.0607328550610706
f: 0.5, g: 1.0002234265601362
f: 0.9, g: 0.5219178011535526
f: 0.95, g: 0The value corresponding to 0.25 matches what we calculated! Going too high increases the chance of ruin considerably, with the breakeven point at . Going too low slows down growth a lot, but it will never go below 1.
For completeness, here is the graph of against in this example (, ).