Position Sizing in Python: Kelly Criterion + Fixed-Fractional
Your signal quality is half the game. The other half is how much capital you risk when the signal fires. A positive-expectancy strategy can still blow up an account if position sizing is wrong — and a mediocre strategy can survive if sizing is conservative enough. This walkthrough implements Kelly Criterion and fixed-fractional sizing in ~25 lines of numpy and pandas, using the same worked example as the Kelly Criterion guide so the two pages agree on every number.
"""AlgoDrill — Kelly fraction and fixed-fractional position sizing.
~25 lines of numpy/pandas to compute full Kelly, half-Kelly,
quarter-Kelly, and fixed-fractional risk from a trade series.
Pure math — no broker, no execution.
Versions: numpy 2.4.6 · pandas 2.3.3 (run 2026-06-05)
Install: pip install numpy pandas
"""
import numpy as np
import pandas as pd
def kelly_fraction(win_rate: float, avg_win: float, avg_loss: float) -> dict:
"""Kelly fraction from win rate and average payoffs."""
loss_rate = 1.0 - win_rate
expectancy = win_rate * avg_win - loss_rate * avg_loss # E[trade]
kelly_f = expectancy / avg_win # f* = E/W
return {
"expectancy": round(expectancy, 4),
"payoff_ratio": round(avg_win / avg_loss, 4),
"kelly_f": round(kelly_f, 4),
"half_kelly": round(kelly_f / 2, 4),
"qtr_kelly": round(kelly_f / 4, 4),
}
# ── Example: 55% WR, $150 avg win, $100 avg loss (matches /kelly-criterion) ──
r = kelly_fraction(0.55, 150.0, 100.0)
print("=== Kelly sizing: 55% WR, $150 avg win, $100 avg loss ===")
print(f"Expectancy ${r['expectancy']:.2f} per trade")
print(f"Payoff ratio {r['payoff_ratio']:.4f}")
print(f"Full Kelly {r['kelly_f']:.1%}")
print(f"Half-Kelly {r['half_kelly']:.1%}")
print(f"Quarter-Kelly {r['qtr_kelly']:.1%}")
print()
# ── Fixed-fractional comparison on a $10,000 account ─────────────────────────
ACCOUNT = 10_000
print(f"=== Fixed-fractional risk on ${ACCOUNT:,} account ===")
for pct in (0.01, 0.02, r["half_kelly"], r["kelly_f"]):
risk = ACCOUNT * pct
print(f" {pct:.1%} → ${risk:>7.2f} at risk per trade")
print()
# ── Batch Kelly from a real trade P&L series (pandas path) ───────────────────
pnl = pd.Series([150, -100, 200, -90, 175, -110, 300, -95, 225, -100])
wins = pnl[pnl > 0]
losses = pnl[pnl < 0]
wr = len(wins) / len(pnl)
r2 = kelly_fraction(wr, float(wins.mean()), float(abs(losses.mean())))
print(f"=== Batch Kelly: 10-trade P&L series ===")
print(f"Trades {len(pnl)} WR {wr:.0%} Avg win ${wins.mean():.2f} Avg loss ${abs(losses.mean()):.2f}")
print(f"Full Kelly {r2['kelly_f']:.1%}")
print(f"Half-Kelly {r2['half_kelly']:.1%}")Script Output
=== Kelly sizing: 55% WR, $150 avg win, $100 avg loss ===
Expectancy $37.50 per trade
Payoff ratio 1.5000
Full Kelly 25.0%
Half-Kelly 12.5%
Quarter-Kelly 6.2%
=== Fixed-fractional risk on $10,000 account ===
1.0% -> $ 100.00 at risk per trade
2.0% -> $ 200.00 at risk per trade
12.5% -> $1250.00 at risk per trade
25.0% -> $2500.00 at risk per trade
=== Batch Kelly: 10-trade P&L series ===
Trades 10 WR 50% Avg win $210.00 Avg loss $99.00
Full Kelly 26.4%
Half-Kelly 13.2%
Line-by-Line Walkthrough
Lines 14–24: The kelly_fraction function
The function takes three inputs: win_rate (fraction of trades that win, e.g., 0.55), avg_win (average dollar profit on winning trades), and avg_loss (average dollar loss magnitude, positive). It computes:
- Expectancy: the average dollar profit per trade across both wins and losses.
expectancy = win_rate * avg_win - loss_rate * avg_loss. For (0.55, 150, 100):0.55 × 150 − 0.45 × 100 = 82.50 − 45.00 = $37.50. A positive expectancy is necessary but not sufficient — you also need enough trades for it to compound reliably. - Payoff ratio:
avg_win / avg_loss = 150 / 100 = 1.50. A ratio above 1.0 means wins are larger than losses on average. Combined with a >50% win rate, this system has clear positive expectancy. Many trend-following strategies operate with win rates below 50% but payoff ratios above 2.0 — they lose more often but win much larger. - Kelly fraction:
kelly_f = expectancy / avg_win = 37.50 / 150 = 0.25 = 25%. This is the fraction of capital to risk per trade that maximizes long-run geometric growth. The formula is derived from maximizing the expected logarithm of portfolio value — the correct objective for multi-period compounding.
Lines 27–36: Reading the sizing output
For the worked example (55% WR, $150/$100):
- Full Kelly (25.0%): risk $2,500 of a $10,000 account per trade. This maximizes long-run growth but implies drawdowns of 30–50% during bad streaks. Most professional traders consider full Kelly too aggressive.
- Half-Kelly (12.5%): risk $1,250 per trade. Captures roughly 75% of full Kelly growth with approximately half the drawdown. This is the standard practitioner starting point for a well-understood edge.
- Quarter-Kelly (6.2%): risk $625 per trade. Appropriate when your trade history is small (<50 trades), the edge is unverified out-of-sample, or you want a large margin of safety against estimation error.
Lines 39–44: Fixed-fractional comparison
Fixed-fractional sizing risks a flat percentage of account equity regardless of Kelly. For a $10,000 account:
- 1% ($100): the conservative default for retail systematic traders. At this level, you need 25× the Kelly-optimal edge to deploy the same capital — growth is slow but the risk of blowing up is minimal.
- 2% ($200): the common upper bound for beginning algorithmic traders. Still far below Kelly-optimal for this example (25%), so growth is suboptimal but protected.
- 12.5% ($1,250) = half-Kelly and 25.0% ($2,500) = full Kelly are shown for comparison.
Lines 47–54: Batch Kelly from a P&L series
The batch section demonstrates how to compute Kelly from a raw list of trade P&L values using pandas. Separate wins (positive P&L) from losses (negative P&L), compute win rate, average win, and average loss magnitude, then apply the formula. For the 10-trade series [150, −100, 200, −90, 175, −110, 300, −95, 225, −100]:
- Win rate: 5/10 = 50%
- Avg win: ($150+$200+$175+$300+$225)/5 = $210.00
- Avg loss: ($100+$90+$110+$95+$100)/5 = $99.00
- Expectancy: 0.50 × 210 − 0.50 × 99 = $55.50 per trade
- Full Kelly: $55.50 / $210.00 = 26.4%
At only 10 trades, these estimates carry wide confidence intervals. The true win rate for a 50% observed rate at n=10 has a 95% confidence interval of approximately 19–81%. Use Kelly at this sample size as a rough directional check, not a precise sizing target.
When to Use Each Approach
| Sizing rule | Best when | Risk |
|---|---|---|
| Fixed 1–2% | Starting out, edge unverified, <50 trades of history | Grows slowly; leaves Kelly value on the table |
| Quarter-Kelly | Edge verified out-of-sample but still building confidence | Still conservative; minimal ruin risk |
| Half-Kelly | 100+ trades of OOS history, stable edge estimates, clear regime | Drawdowns are real; requires discipline to hold through them |
| Full Kelly | Theoretically optimal; rarely used live due to drawdown severity | 30–50% drawdowns typical; estimation errors are catastrophic |
Frequently Asked Questions
- What is the Kelly Criterion formula in Python?
- The Kelly fraction for a binary win/loss system is: f* = expectancy / avg_win, where expectancy = win_rate * avg_win - loss_rate * avg_loss. In Python: loss_rate = 1.0 - win_rate; expectancy = win_rate * avg_win - loss_rate * avg_loss; kelly_f = expectancy / avg_win. For a system with 55% win rate, $150 average win, and $100 average loss: expectancy = 0.55 * 150 - 0.45 * 100 = $37.50 per trade; kelly_f = 37.50 / 150 = 25.0%. A positive Kelly means the system has positive expectancy. A zero or negative Kelly means stop trading the setup — no position sizing formula fixes a losing strategy.
- Why use half-Kelly instead of full Kelly?
- Full Kelly maximizes long-run geometric growth but produces extreme drawdowns — often 30-50% or more. Most traders and funds cannot sustain those drawdowns operationally or psychologically, and deviate from the system before the edge compounds. Half-Kelly captures roughly 75% of full Kelly's long-run growth while cutting drawdowns approximately in half. The additional reason is estimation error: win rate and average payoff computed from a limited trade history are noisy estimates. If your true win rate is 52% but you estimated 55%, full Kelly is 1.5x the correct fraction — in the dangerous overbetting zone. Half-Kelly provides a buffer against estimation error. Quarter-Kelly is appropriate when the trade history is small (fewer than 50 trades) or the parameter uncertainty is high.
- What is fixed-fractional position sizing?
- Fixed-fractional sizing means risking a constant percentage of account equity on each trade — for example, 1% or 2% of current equity per trade. It is simpler than Kelly, does not require estimating win rate and average payoff, and scales automatically with account growth (risk 1% of $10,000 = $100; risk 1% of $20,000 = $200). The drawback is that 1-2% is often well below the Kelly-optimal fraction for a genuinely edge-positive system, so it grows more slowly. Most retail systematic traders start with fixed-fractional sizing (1-2%) and use Kelly as a sanity check — if Kelly says 25% and you are risking 2%, the system has a large margin of safety; if Kelly says 2% and you are risking 2%, you are already at full Kelly and should halve your risk.
- How do I compute Kelly from a list of trade P&L values?
- Separate wins and losses from your P&L series, compute win rate, average win, and average loss, then apply the Kelly formula. In Python with pandas: pnl = pd.Series([your trade list]); wins = pnl[pnl > 0]; losses = pnl[pnl < 0]; wr = len(wins) / len(pnl); kelly_f = (wr * wins.mean() - (1-wr) * abs(losses.mean())) / wins.mean(). The walkthrough script shows this pattern on a 10-trade series (50% WR, $210 avg win, $99 avg loss) producing a Full Kelly of 26.4%. At fewer than 30-50 trades, the confidence interval on win rate is wide enough that you should treat Kelly as directional guidance rather than a precise sizing target.
The Kelly Criterion guide covers the derivation, the relationship to Sharpe Ratio, and failure modes like multi-position correlation.
Full Kelly Criterion Guide → All Code Walkthroughs →