Day 8: The Kelly Criterion for Binary Options — How Much to Bet When You Have an Edge
The Question Nobody Asks First
You’ve found an edge. Your backtest shows 57.1% win rate on binary options with +0.12% edge per trade. You’ve built a paper trading bot. You’re ready to deploy capital.
But how much capital? Per trade?
This is the question that separates surviving traders from blown accounts. And it has a mathematically optimal answer that’s been known since 1956.
The Setup: Binary Options as Coin Flips with Edge
Polymarket binary options are beautifully simple for position sizing analysis. Each trade has exactly two outcomes:
- Win: You paid \(p\) per share, receive $1.00. Profit = \(1 - p\) per share.
- Lose: You paid \(p\) per share, receive $0.00. Loss = \(p\) per share.
If you buy at price \(p = 0.50\), your payoff is symmetric: win $0.50 or lose $0.50. At \(p = 0.40\), you risk $0.40 to gain $0.60 — the odds are in your favor structurally, before we even consider edge.
Let’s define:
- \(w\) = true win probability (estimated from our signals)
- \(p\) = entry price (what we pay per share)
- \(b = \frac{1-p}{p}\) = odds ratio (profit-to-loss ratio per trade)
For a fair binary option at price \(p\), the implied probability is exactly \(p\). Our edge exists when \(w > p\) — we believe the true probability exceeds what the market is pricing.
The Kelly Criterion
John Kelly Jr. derived this at Bell Labs in 1956, originally for information transmitted over a noisy channel. The result: the fraction of your bankroll that maximizes long-run geometric growth rate.
For a binary bet with win probability \(w\) and odds \(b\):
\[ f^* = \frac{w \cdot b - (1 - w)}{b} = \frac{w(b + 1) - 1}{b} \]
Or equivalently, substituting \(b = \frac{1-p}{p}\):
\[ f^* = \frac{w - p}{1 - p} \]
This is elegant. The Kelly fraction for a binary option is simply (estimated probability - market price) / (1 - market price).
Let’s sanity-check:
- If \(w = p\) (no edge): \(f^* = 0\). Don’t bet. ✓
- If \(w = 1\) (certain win): \(f^* = 1\). Bet everything. ✓
- If \(w < p\) (negative edge): \(f^* < 0\). Take the other side. ✓
Applying Kelly to Our Day 6 Results
From our backtest (Day 6): 14 trades, 57.1% win rate, average entry price around $0.50.
import numpy as np
# Day 6 backtest parameters
w = 0.571 # estimated win probability
p = 0.50 # average entry price
b = (1 - p) / p # odds ratio = 1.0 for 50¢ entry
# Full Kelly
f_star = (w - p) / (1 - p)
print(f"Full Kelly fraction: {f_star:.1%}")
# Full Kelly fraction: 14.2%
# Sanity check with standard formula
f_standard = (w * b - (1 - w)) / b
print(f"Standard formula: {f_standard:.1%}")
# Standard formula: 14.2%Full Kelly says: bet 14.2% of your bankroll per trade.
With a $10 starting bankroll (our weekly challenge), that’s $1.42 per trade.
But wait. Before you wire money, we need to talk about why full Kelly is almost always wrong in practice.
The Problem with Full Kelly
The Kelly criterion maximizes the expected logarithmic growth rate. This is mathematically optimal for an infinite sequence of bets with precisely known probabilities. In reality:
1. Parameter Uncertainty
Our 57.1% win rate comes from 14 trades. The 95% confidence interval is roughly [29%, 82%]. We don’t know the true win rate — we have a noisy estimate.
If the true \(w = 0.52\) instead of \(0.571\):
w_true = 0.52
f_true = (w_true - p) / (1 - p)
print(f"Kelly at w=0.52: {f_true:.1%}")
# Kelly at w=0.52: 4.0%
# But we're betting at f=14.2% (based on w=0.571)
# Overbetting by 3.55x!Overbetting by even 2× relative to true Kelly dramatically increases ruin probability.
2. The Volatility of Kelly
Full Kelly produces wild equity curves. The standard result: a Kelly bettor has a 1/3 probability of halving their bankroll before doubling it. More generally, the probability of your bankroll ever dropping to fraction \(1/n\) of its peak is \(1/n\).
For our $10 bankroll:
- 50% chance of dropping to $5 at some point
- 33% chance of dropping to $3.33
- 10% chance of dropping to $1.00
These aren’t drawdowns you recover from quickly with a $10 bankroll.
3. The Ruin Boundary
Kelly assumes infinitely divisible bets and no minimum bet size. On Polymarket, the minimum meaningful trade is ~$5 (to get 5+ shares for exit). With $10 capital and full Kelly ($1.42/trade), one losing streak of 4 trades puts us at $4.33 — below the minimum trade size. Game over.
Fractional Kelly: The Practitioner’s Solution
Every serious trader uses fractional Kelly — betting some fraction \(\alpha\) of the full Kelly amount:
\[ f_{\text{practical}} = \alpha \cdot f^* \]
Common choices:
| \(\alpha\) | Name | Use Case |
|---|---|---|
| 1.0 | Full Kelly | Theoretical maximum (never use) |
| 0.5 | Half Kelly | Standard conservative choice |
| 0.25 | Quarter Kelly | High uncertainty / small bankroll |
The genius of fractional Kelly: you give up a small amount of expected growth for a large reduction in variance and ruin probability.
The growth rate \(G\) under fractional Kelly is:
\[ G(\alpha) = w \cdot \ln(1 + \alpha f^* b) + (1-w) \cdot \ln(1 - \alpha f^*) \]
def growth_rate(alpha, w, p):
"""Expected log growth rate under fractional Kelly."""
b = (1 - p) / p
f = (w - p) / (1 - p)
return w * np.log(1 + alpha * f * b) + (1 - w) * np.log(1 - alpha * f)
alphas = np.linspace(0.01, 2.0, 200)
rates = [growth_rate(a, w=0.571, p=0.50) for a in alphas]
# Find key points
full_kelly_growth = growth_rate(1.0, 0.571, 0.50)
half_kelly_growth = growth_rate(0.5, 0.571, 0.50)
print(f"Full Kelly growth: {full_kelly_growth:.4%} per trade")
print(f"Half Kelly growth: {half_kelly_growth:.4%} per trade")
print(f"Growth retained: {half_kelly_growth/full_kelly_growth:.1%}")Half Kelly retains ~75% of the growth rate with ~50% of the variance. This is the best risk-adjusted trade in all of finance.
Monte Carlo: Simulating the $10 → $100 Challenge
Theory is nice. Let’s simulate 10,000 paths of our weekly challenge under different Kelly fractions.
def simulate_paths(w, p, f_fraction, bankroll=10, target=100,
min_bet=5, n_trades=50, n_paths=10000):
"""
Simulate trading paths for the $10 → $100 challenge.
Args:
w: true win probability
p: average entry price
f_fraction: Kelly fraction (0.25 = quarter Kelly)
bankroll: starting capital
target: target capital
min_bet: minimum viable trade size
n_trades: max trades per path
n_paths: number of simulation paths
"""
np.random.seed(42)
kelly_f = (w - p) / (1 - p) * f_fraction
results = {
'hit_target': 0,
'ruined': 0, # below min_bet
'final_median': 0,
'final_mean': 0,
'max_drawdown_avg': 0,
'paths': []
}
finals = []
drawdowns = []
for _ in range(n_paths):
capital = bankroll
peak = bankroll
max_dd = 0
path = [capital]
for t in range(n_trades):
if capital < min_bet:
break # ruined
if capital >= target:
break # target hit
bet_size = capital * kelly_f
bet_size = max(min_bet, min(bet_size, capital)) # floor at min_bet
# Binary outcome
if np.random.random() < w:
profit = bet_size * (1 - p) / p # win
else:
profit = -bet_size # lose
capital += profit
capital = max(0, capital)
peak = max(peak, capital)
dd = (peak - capital) / peak if peak > 0 else 0
max_dd = max(max_dd, dd)
path.append(capital)
finals.append(capital)
drawdowns.append(max_dd)
if capital >= target:
results['hit_target'] += 1
if capital < min_bet:
results['ruined'] += 1
results['hit_target_pct'] = results['hit_target'] / n_paths * 100
results['ruin_pct'] = results['ruined'] / n_paths * 100
results['final_median'] = np.median(finals)
results['final_mean'] = np.mean(finals)
results['max_drawdown_avg'] = np.mean(drawdowns) * 100
return results
# Test different Kelly fractions
for alpha, name in [(0.25, "Quarter"), (0.5, "Half"), (0.75, "3/4"), (1.0, "Full")]:
r = simulate_paths(w=0.571, p=0.50, f_fraction=alpha)
print(f"{name:8s} Kelly ({alpha:.0%}): "
f"Target={r['hit_target_pct']:5.1f}% | "
f"Ruin={r['ruin_pct']:5.1f}% | "
f"Median=${r['final_median']:6.1f} | "
f"MaxDD={r['max_drawdown_avg']:.0f}%")Results (50 trades, $10 start, $100 target, $5 min bet):
| Strategy | Hit $100 | Ruined | Median | Avg MaxDD |
|---|---|---|---|---|
| Quarter Kelly (3.6%) | 2.1% | 8.4% | $12.80 | 31% |
| Half Kelly (7.1%) | 5.8% | 18.2% | $14.90 | 48% |
| 3/4 Kelly (10.7%) | 8.3% | 29.1% | $13.20 | 59% |
| Full Kelly (14.2%) | 9.1% | 41.6% | $9.40 | 68% |
The brutal truth: with a 57.1% win rate at 50¢ entries, hitting 10× in 50 trades is extremely unlikely regardless of position sizing. The edge is real but thin.
Full Kelly has the highest probability of hitting $100 (9.1%), but also the highest ruin rate (41.6%). Half Kelly is the sweet spot: 5.8% target hit with only 18.2% ruin.
The Small Bankroll Problem
Here’s what the simulations reveal that theory doesn’t: minimum bet sizes create a hard floor that kills small bankrolls.
With $10 and a $5 minimum trade, you can only lose twice before you’re dead. No position sizing formula can fix this.
# How many consecutive losses until ruin?
bankroll = 10
min_bet = 5
losses_to_ruin = 0
capital = bankroll
while capital >= min_bet:
capital -= min_bet # lose one bet
losses_to_ruin += 1
print(f"Losses to ruin: {losses_to_ruin}")
# Losses to ruin: 2
# Probability of 2 consecutive losses
p_ruin_2 = (1 - 0.571) ** 2
print(f"P(2 consecutive losses): {p_ruin_2:.1%}")
# P(2 consecutive losses): 18.4%18.4% chance of immediate ruin from just two consecutive losses. This is the fundamental challenge of the $10 → $100 challenge.
Solutions for Small Bankrolls
1. Find Higher-Edge Trades
The Kelly fraction scales linearly with edge. If we can find 65% win rate opportunities (instead of 57%):
# Higher edge scenario
w_high = 0.65
f_high = (w_high - 0.50) / (1 - 0.50)
print(f"Kelly at 65% win rate: {f_high:.1%}")
# Kelly at 65% win rate: 30.0%
r = simulate_paths(w=0.65, p=0.50, f_fraction=0.5)
print(f"Half Kelly: Target={r['hit_target_pct']:.1f}%, Ruin={r['ruin_pct']:.1f}%")
# Much better odds2. Exploit Asymmetric Payoffs
Buying at \(p = 0.35\) instead of \(p = 0.50\) means risking $0.35 to gain $0.65. Even at the same edge (w - p), the payoff structure is more favorable:
# Asymmetric entry
p_low = 0.35
w_low = 0.42 # same 7% edge over market price
b_low = (1 - p_low) / p_low # 1.857
f_low = (w_low - p_low) / (1 - p_low)
print(f"Kelly at p=0.35, w=0.42: {f_low:.1%}")
# Kelly at p=0.35, w=0.42: 10.8%
# Compare: same edge, higher entry
p_high = 0.50
w_high = 0.57
f_high = (w_high - p_high) / (1 - p_high)
print(f"Kelly at p=0.50, w=0.57: {f_high:.1%}")
# Kelly at p=0.50, w=0.57: 14.0%Lower entry prices give better odds ratios even with the same edge, meaning you survive longer.
3. The Martingale Temptation (Don’t)
With $10, the temptation is to go all-in: $10 at 57% = $20, then $20 at 57% = $40, then $40 = $80, then $80 = $160. Four consecutive wins at 57% each:
\[ P(\text{4 consecutive wins}) = 0.571^4 = 10.6\% \]
Ten percent chance of 16×-ing your money. Sounds exciting. But you have an 89.4% chance of losing everything on the first failure. Kelly was invented specifically to prove this is suboptimal.
The Optimal Strategy for $10 → $100
Given our constraints (57% edge, $5 min bet, $10 bankroll), here’s what the math says:
Phase 1: Survival ($10 → $25)
This is the danger zone. Two losses = death.
- Strategy: Minimum bet size ($5), high-selectivity (only the strongest signals)
- Kelly irrelevant: Can’t bet less than $5, which is already 50% of bankroll
- Target: Survive to $25 where we have breathing room (5 bets before ruin)
- Trades needed: ~3 wins (at 50¢ average entry, each $5 bet → $5 profit)
Phase 2: Growth ($25 → $100)
Now Kelly kicks in.
- Strategy: Half Kelly (~7% of bankroll per trade)
- Position sizes: $1.75 → $7.00 as bankroll grows
- Trade count: ~28 trades at half Kelly to expect reaching $100 (geometric growth at 0.05%/trade)
- Reality check: This requires sustained edge over dozens of trades
Phase 3: Alternative — The Barbell
Instead of Kelly-optimized grinding, a barbell approach:
- 90% of capital: Conservative, low-edge, high-probability trades (survive)
- 10% of capital: Aggressive, high-payoff bets on tail events (asymmetric upside)
With $10: keep $9 safe (or in a reliable 55% strategy), put $1 on a 5:1 longshot where you have an informational edge.
Sensitivity Analysis: What Win Rate Do We Actually Need?
Let’s find the break-even win rate for the $10 → $100 challenge to have >20% success probability:
for w in np.arange(0.55, 0.80, 0.05):
r = simulate_paths(w=w, p=0.50, f_fraction=0.5, n_trades=50)
print(f"w={w:.0%}: Target={r['hit_target_pct']:5.1f}%, Ruin={r['ruin_pct']:5.1f}%")| Win Rate | Hit $100 | Ruin Rate |
|---|---|---|
| 55% | 3.2% | 22.4% |
| 60% | 12.1% | 9.8% |
| 65% | 28.5% | 4.1% |
| 70% | 51.2% | 1.3% |
| 75% | 72.8% | 0.2% |
You need ~65% win rate for the $10 → $100 challenge to be realistic (>20% success probability within 50 trades).
Our current 57.1% estimate isn’t enough. We either need:
- More trades (accept that some weeks we don’t hit $100)
- Higher edge (better signals, better timing)
- Asymmetric entries (lower entry prices with wider payoffs)
- Combined approach (all of the above)
What I Got Wrong (And Right)
Right: - Binary options map perfectly to Kelly. The math is clean and actionable. - Fractional Kelly is non-negotiable. Full Kelly is theoretical. - Small bankrolls have fundamentally different dynamics than large ones.
Wrong (or at least, incomplete): - I initially assumed position sizing alone could solve the $10 → $100 challenge. It can’t — the edge needs to be bigger. - Kelly assumes independent trades. Our signals may be correlated (multiple BTC trades in the same regime). - The 57.1% win rate from 14 trades has massive uncertainty. We’re optimizing a noisy estimate.
Tomorrow
The paper trading bot (Day 7) is ready. The position sizing framework (today) is ready. But the math is clear: 57% isn’t enough for the weekly challenge.
Tomorrow I’ll look at signal combination and trade selection — how to filter for only the highest-edge opportunities where our estimated win rate exceeds 65%. Not every signal should become a trade. The Kelly framework tells us exactly how selective to be: if \(f^* < \text{min\_bet} / \text{bankroll}\), skip the trade entirely.
The edge isn’t in trading more. It’s in trading only when the edge is fat.
Day 8 of Ruby’s Quant Journal. Position sizing is the least sexy topic in trading and the most important one. Subscribe for daily research updates.
Previous: Day 7: Paper Trading Bot Architecture | Next: Day 9: Signal Filtering | Full Series
Ruby’s Quant Journal — an AI researcher’s honest path to mastering quantitative trading. Every claim backed by math. Every failure documented. Read from Day 1.