Skip to main content
Kelly Criterion with Live Odds in Python
Back to Blog

Kelly Criterion with Live Odds in Python

James Whitfield

James Whitfield

4 min read

The Kelly criterion answers the one question every bettor with an edge eventually faces: how much should I stake? Bet too little and you leave growth on the table. Bet too much and variance wipes you out even when your edge is real. Kelly gives the stake that maximizes long-run bankroll growth.

In this post we cover the math in plain terms, why almost everyone should use fractional Kelly, and a Python implementation that pulls live odds from Odds-API.io. If you just want the answer without code, use our free Kelly calculator.

The Kelly Formula

For decimal odds, the Kelly fraction is:

f = (p * (odds - 1) - (1 - p)) / (odds - 1)

f     = fraction of bankroll to stake
p     = your estimated probability of winning
odds  = decimal odds offered

Example: you think a team wins 55% of the time and a bookmaker offers 2.00.

f = (0.55 * 1.0 - 0.45) / 1.0 = 0.10

Kelly says stake 10% of your bankroll. If your estimate is right, no other fixed staking fraction grows your bankroll faster over time. If the numerator is zero or negative, you have no edge at that price and the correct stake is nothing.

Why Fractional Kelly

Full Kelly assumes your probability estimate is exact. It never is. Overestimate your edge and full Kelly systematically overbets, which is far more damaging than underbetting. Full Kelly also produces brutal swings: a 50% bankroll drawdown is entirely normal.

The standard fix is to bet a fixed fraction of the Kelly stake, usually a quarter or a half. Half Kelly gives about 75% of the growth rate with half the variance. Quarter Kelly is calmer still. Serious bettors almost universally run fractional.

stake = bankroll * kelly_fraction * f   # kelly_fraction = 0.25 or 0.5

A Kelly Calculator in Python

The math is a few lines:

def kelly(p, odds, fraction=0.5):
    """Return the fraction of bankroll to stake. 0 means no bet."""
    b = odds - 1
    f = (p * b - (1 - p)) / b
    return max(0.0, f * fraction)

print(kelly(0.55, 2.00))        # 0.05 -> half Kelly, 5% of bankroll
print(kelly(0.55, 2.00, 1.0))   # 0.10 -> full Kelly
print(kelly(0.45, 2.00))        # 0.0  -> no edge, no bet

Wiring In Live Odds

Kelly needs the current price, and prices move. Here's a script that pulls live odds for an event from the API and sizes a bet at the best available price across your bookmakers. Get a free key at odds-api.io (100 requests/hour, no credit card).

import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://api.odds-api.io/v3"

def best_price(event_id, bookmakers, side="home", market="ML"):
    resp = requests.get(f"{BASE}/odds", params={
        "apiKey": API_KEY,
        "eventId": event_id,
        "bookmakers": ",".join(bookmakers),
    })
    resp.raise_for_status()
    data = resp.json()
    best = None
    for book, markets in data["bookmakers"].items():
        for m in markets:
            if m["name"] != market:
                continue
            for line in m["odds"]:
                price = float(line.get(side, 0))
                if best is None or price > best[0]:
                    best = (price, book)
    return data, best

def kelly(p, odds, fraction=0.5):
    b = odds - 1
    return max(0.0, (p * b - (1 - p)) / b * fraction)

BANKROLL = 1000.0
MY_PROB = 0.55  # your model's probability for the home side

data, best = best_price(123456, ["Bet365", "SingBet", "Unibet"])
if best:
    odds, book = best
    f = kelly(MY_PROB, odds, fraction=0.5)
    print(f"{data['home']} vs {data['away']}")
    print(f"Best home price: {odds} at {book}")
    if f > 0:
        print(f"Half Kelly stake: {BANKROLL * f:.2f} ({f:.1%} of bankroll)")
    else:
        print("No edge at this price. Skip the bet.")

Replace 123456 with a real event ID from the /events endpoint:

curl "https://api.odds-api.io/v3/events?apiKey=YOUR_API_KEY&sport=football&limit=10"

Shopping the best price matters more than people expect. Kelly stakes scale with your edge, and the difference between taking 1.95 and 2.02 on the same bet is often the difference between a bet and a pass.

Where Does p Come From?

Kelly is only as good as your probability estimate. Common sources: your own model, de-vigged sharp bookmaker prices, or the API's /value-bets endpoint, which compares each price against a sharp consensus and flags positive expected value bets. A simple, honest approach is to de-vig a sharp book's price and only bet when a soft book offers meaningfully more.

def devig_two_way(odds_a, odds_b):
    """Remove the margin from a two-way market, return fair probabilities."""
    inv_a, inv_b = 1 / odds_a, 1 / odds_b
    total = inv_a + inv_b
    return inv_a / total, inv_b / total

p_home, p_away = devig_two_way(1.90, 2.02)
print(round(p_home, 4), round(p_away, 4))  # 0.5153 0.4847

Practical Rules

  • Run half Kelly or less. Your probability estimates are noisier than you think.
  • Never bet when f is zero or negative. Kelly discipline is mostly about the bets you skip.
  • Recompute at the current price immediately before betting. A stale price invalidates the stake.
  • Cap stakes at a hard maximum (say 5% of bankroll) regardless of what the formula says. A huge Kelly number usually means your probability is wrong, not that the bet is amazing.
  • Track every bet. Closing line value tells you whether your edge is real long before your profit curve does.

Try It

The fastest path: use the Kelly calculator for one-off bets, and the script above when you want it automated against live prices. A free API key takes a minute and needs no credit card.