Kelly Criterion Python: Size Crypto Trades Like a Pro
Master the Kelly Criterion in Python to calculate optimal position sizes for your crypto trades on Binance and Bybit, reduce drawdowns, and grow your portfolio consistently over time.
Master the Kelly Criterion in Python to calculate optimal position sizes for your crypto trades on Binance and Bybit, reduce drawdowns, and grow your portfolio consistently over time.
Most traders blow up not because they pick bad trades — but because they size them wrong. A strategy with a 60% win rate can still destroy your account if you bet too much on every trade. The Kelly Criterion is a mathematical formula that tells you exactly how much of your capital to risk on each trade, based on your historical win rate and reward-to-risk ratio. With Python, you can automate this calculation across your entire crypto portfolio in seconds.
The Kelly Criterion was developed by John L. Kelly Jr. in 1956 at Bell Labs — originally to optimize signal transmission, not trading. It was later adopted by gamblers and professional investors, most famously by Ed Thorp (who used it to beat blackjack and later Wall Street) and Charlie Munger.
The core idea is elegant: given a repeatable bet with known odds, there is one exact fraction of your bankroll you should wager to maximize long-term growth. Bet too little and you leave money on the table. Bet too much and volatility will eventually ruin you — even with a positive edge.
For crypto traders, this matters enormously. Markets on Binance, Bybit, and OKX are volatile by nature. A single overleveraged position on a 10x perpetual contract can wipe out weeks of careful gains. Kelly gives you a defensible, mathematically grounded answer to the question every trader wrestles with: how much should I put in this trade?
Key Takeaway: The Kelly Criterion maximizes long-term portfolio growth by finding the optimal fraction of capital to risk per trade — not too much, not too little.
The basic Kelly formula has just two inputs:
Say you have been trading BTC/USDT on Binance for six months. Your strategy wins 58% of the time. Your average winning trade returns $120 while your average losing trade costs $80. That gives you R = 120 / 80 = 1.5. Plugging in: f = 0.58 − (0.42 / 1.5) = 0.58 − 0.28 = 0.30. The formula says risk 30% of capital per trade.
In practice, most professional traders use Half Kelly — 15% in this case — or even Quarter Kelly. Real-world trading involves estimation errors, slippage, and changing market conditions that pure math ignores. Scaling down buys you resilience.
Key Takeaway: Full Kelly is mathematically optimal but psychologically brutal. Half Kelly delivers roughly 75% of the growth with dramatically smaller drawdowns. Most professionals use it as a ceiling, not a target.
Here is a clean Python implementation you can run locally or integrate directly into a trading bot. No external libraries are needed for the core calculation.
def kelly_fraction(win_rate: float, win_loss_ratio: float) -> float:
"""
Calculate optimal position size using the Kelly Criterion.
win_rate: probability of a winning trade (0.0 to 1.0)
win_loss_ratio: average win / average loss
Returns: fraction of capital to risk (0.0 to 1.0)
"""
q = 1 - win_rate
kelly = win_rate - (q / win_loss_ratio)
return max(0.0, kelly) # Kelly can be negative — never bet negative
# Example: BTC/USDT strategy on Binance
win_rate = 0.58
avg_win_usdt = 120
avg_loss_usdt = 80
win_loss_ratio = avg_win_usdt / avg_loss_usdt # 1.5
full_kelly = kelly_fraction(win_rate, win_loss_ratio)
half_kelly = full_kelly / 2
print(f"Full Kelly: {full_kelly:.2%}") # 30.00%
print(f"Half Kelly: {half_kelly:.2%}") # 15.00%
account_balance = 10_000 # USDT
position_size_full = account_balance * full_kelly
position_size_half = account_balance * half_kelly
print(f"Full Kelly position: ${position_size_full:,.2f}")
print(f"Half Kelly position: ${position_size_half:,.2f}")
This is the foundation. But in real trading you do not know your win rate from a handful of trades — you estimate it from your execution history. The next step is computing Kelly dynamically from your actual trade log.
import numpy as np
def kelly_from_trade_history(returns: list) -> dict:
"""
Calculate Kelly fraction from a list of trade returns.
returns: list of percentage returns, e.g. [0.05, -0.03, 0.08]
Positive = profit, Negative = loss.
"""
if len(returns) < 10:
raise ValueError("Need at least 10 trades for a reliable estimate")
wins = [r for r in returns if r > 0]
losses = [abs(r) for r in returns if r < 0]
if not wins or not losses:
return {"kelly": 0.0, "half_kelly": 0.0}
win_rate = len(wins) / len(returns)
avg_win = np.mean(wins)
avg_loss = np.mean(losses)
ratio = avg_win / avg_loss
kelly = win_rate - ((1 - win_rate) / ratio)
kelly = max(0.0, kelly)
return {
"win_rate": round(win_rate, 4),
"avg_win": round(avg_win, 4),
"avg_loss": round(avg_loss, 4),
"win_loss_ratio": round(ratio, 4),
"full_kelly": round(kelly, 4),
"half_kelly": round(kelly / 2, 4),
}
# Replace with your actual trade history from Bybit or OKX
trades = [
0.05, -0.03, 0.08, -0.02, 0.12, -0.04,
0.06, 0.09, -0.05, 0.07, -0.02, 0.11,
-0.06, 0.04, 0.08, -0.03, 0.05, -0.04,
0.10, -0.02
]
result = kelly_from_trade_history(trades)
for key, value in result.items():
print(f"{key}: {value}")
A Kelly calculation is only as good as the data behind it. Using made-up numbers defeats the purpose. Here is how to pull your actual execution history from Binance and feed it directly into the calculator.
# Requires: pip install python-binance numpy
from binance.client import Client
import numpy as np
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
client = Client(API_KEY, API_SECRET)
def get_binance_trade_returns(symbol: str, limit: int = 200) -> list:
"""
Fetch closed trades for a symbol and compute percentage returns.
Groups consecutive buy/sell trades into round trips.
"""
trades = client.get_my_trades(symbol=symbol, limit=limit)
returns = []
buy_price = None
for trade in trades:
price = float(trade["price"])
is_buyer = trade["isBuyer"]
if is_buyer:
buy_price = price
elif buy_price is not None:
pnl = (price - buy_price) / buy_price
returns.append(pnl)
buy_price = None
return returns
# Pull BTC/USDT history and calculate Kelly
returns = get_binance_trade_returns("BTCUSDT", limit=200)
if len(returns) >= 10:
result = kelly_from_trade_history(returns)
print(f"Win rate: {result['win_rate']:.0%}")
print(f"Recommended position size: {result['half_kelly']:.2%} of account")
else:
print("Not enough closed trades yet. Keep trading and check back.")
For Bybit users, the pybit library provides a similar interface via get_executions(). On OKX, the REST endpoint /api/v5/trade/fills returns your fill history in the same structure. The Kelly math is identical — only the data source changes.
Key Takeaway: Use at least 30–50 closed trades before trusting your Kelly output. Fewer trades means high variance in your win rate estimate, which produces unreliable position sizes.
Kelly Criterion tells you how much to risk. It does not tell you when to enter. That is where signal platforms like VoiceOfChain come in. VoiceOfChain provides real-time trading signals across major crypto assets along with historical accuracy metrics — which map directly onto the win_rate input in your Kelly formula.
The workflow: VoiceOfChain emits a signal for ETH/USDT on Binance with a stated 65% historical accuracy over the past 90 days. You feed that win rate into Kelly alongside your own average win/loss ratio from past ETH trades. The result is a position size calibrated to both the signal quality and your personal execution history — two inputs most traders never combine systematically.
import requests
VOC_API_URL = "https://api.voiceofchain.com/v1/signals"
VOC_API_KEY = "your_voc_api_key"
def get_signal_win_rate(symbol: str) -> float:
"""Fetch historical signal accuracy for a symbol from VoiceOfChain."""
response = requests.get(
f"{VOC_API_URL}/{symbol}/stats",
headers={"Authorization": f"Bearer {VOC_API_KEY}"}
)
data = response.json()
return data.get("win_rate", 0.5) # default 50% if unavailable
# Your personal average reward-to-risk from past ETH trades
avg_win = 0.08
avg_loss = 0.05
ratio = avg_win / avg_loss # 1.6
signal_win_rate = get_signal_win_rate("ETHUSDT")
kelly = kelly_fraction(signal_win_rate, ratio)
print(f"Signal win rate: {signal_win_rate:.0%}")
print(f"Full Kelly: {kelly:.2%}")
print(f"Half Kelly (recommended): {kelly/2:.2%}")
This combination adapts automatically. If VoiceOfChain signals for a specific asset have been underperforming, the win rate input drops, and Kelly reduces your position size without any emotional override. The system self-calibrates.
The Kelly Criterion is simple in theory and easy to misapply in practice. These are the mistakes that cost traders money:
| Kelly Fraction | Interpretation | Recommended Action |
|---|---|---|
| Negative or 0% | No detectable edge | Do not trade this strategy |
| 1%–5% | Small edge, high uncertainty | Use Quarter Kelly or gather more data |
| 5%–15% | Moderate edge | Use Half Kelly — standard for most strategies |
| 15%–25% | Strong edge | Use Half Kelly — double-check for overfitting |
| Above 25% | Unusually high edge | Be suspicious — likely too few trades or curve-fitting |
The Kelly Criterion will not turn a losing strategy into a profitable one — but it will stop a winning strategy from destroying your account through poor position sizing. Most traders fail not on the entry signal but on the bet size: too large when confident, too small when scared, no consistency either way.
With the Python code above, you can compute your optimal fraction in seconds, update it as your trade history grows, and plug it directly into a workflow powered by real-time signals from platforms like VoiceOfChain. Start with Half Kelly, track your results over 30+ trades, and adjust. The traders who survive long enough to compound are the ones who never let a single bad trade end the game.
Key Takeaway: Kelly Criterion is not magic — it is discipline made mathematical. Implement it once, use it consistently, and it becomes one of the most valuable tools in your trading stack.