Python guide · bankroll sizing
Kelly criterion in Python with live Pinnacle odds
The Kelly criterion converts an estimated betting edge into a bankroll fraction. A practical fractional Kelly bot needs three distinct inputs: your win-probability estimate, a current executable price, and your available bankroll. PinnWire supplies real-time Pinnacle odds for the price and market context; it does not invent your edge or place the bet.
O and estimated win probability p, full Kelly is (p × O − 1) / (O − 1). Return zero when the result is negative. In production, multiply by 0.25 or 0.50 and apply a hard exposure cap.The Kelly formula, without the jargon
fractional_stake = bankroll × max(0, full_kelly) × k
p = your estimated probability
O = current decimal odds
k = Kelly multiplier, commonly 0.25 or 0.50
Suppose your model gives a team a 55% chance and you have confirmed decimal odds of 2.00. Full Kelly is 10% of bankroll; quarter Kelly is 2.5%. On a $1,000 bankroll, that is $25 before any additional cap or limit.
The expected return per dollar is p × O − 1. That figure and the Kelly fraction are estimates, not guaranteed profit. If your 55% estimate is really 49%, the apparent edge disappears.
Runnable Python: current price, no-vig baseline, fractional Kelly
This script selects one event from a current PinnWire live-sport snapshot, reads its full-game moneyline, removes the margin across every available side, and calculates a capped quarter-Kelly proposal from your probability. It refuses stale responses and never sends a wager.
import os
from datetime import datetime, timezone
import requests
BASE_URL = "https://pinnwire.com"
API_KEY = os.getenv("PINNWIRE_API_KEY", "demo")
SPORT_ID = int(os.getenv("SPORT_ID", "1"))
EVENT_ID = int(os.environ["EVENT_ID"])
SIDE = os.getenv("SIDE", "home") # home, draw, or away
MY_PROB = float(os.environ["MY_PROB"])
BANKROLL = float(os.getenv("BANKROLL", "1000"))
KELLY_MULTIPLIER = 0.25 # quarter Kelly
MAX_BANKROLL_FRACTION = 0.02 # independent 2% hard cap
MAX_RESPONSE_AGE_SECONDS = 30
def fractional_kelly(probability, decimal_odds, multiplier=0.25,
hard_cap=0.02):
if not 0 < probability < 1:
raise ValueError("probability must be between 0 and 1")
if decimal_odds <= 1:
raise ValueError("decimal_odds must be greater than 1")
full = (probability * decimal_odds - 1) / (decimal_odds - 1)
return min(hard_cap, max(0.0, full) * multiplier)
def devig(prices):
"""Normalize implied probabilities for a complete 2- or 3-way market."""
raw = {side: 1.0 / price for side, price in prices.items()}
overround = sum(raw.values())
return ({side: implied / overround for side, implied in raw.items()},
overround)
response = requests.get(
f"{BASE_URL}/kit/v1/markets",
params={"sport_id": SPORT_ID, "key": API_KEY, "fresh": "kelly"},
timeout=10,
)
response.raise_for_status()
payload = response.json()
generated = datetime.fromisoformat(payload["generated_at"].replace("Z", "+00:00"))
age = (datetime.now(timezone.utc) - generated).total_seconds()
if age > MAX_RESPONSE_AGE_SECONDS:
raise RuntimeError(f"Refusing a stale response ({age:.1f}s old)")
event = next((item for item in payload["events"]
if int(item["event_id"]) == EVENT_ID), None)
if event is None:
raise RuntimeError("Event is not in the current live snapshot")
moneyline = event["periods"]["num_0"]["money_line"]
prices = {
name: float(price)
for name, price in moneyline.items()
if name in {"home", "draw", "away"} and price is not None
}
if SIDE not in prices:
raise RuntimeError(f"{SIDE!r} is not quoted; available sides: {list(prices)}")
fair_probs, overround = devig(prices)
quoted_odds = prices[SIDE]
edge = MY_PROB * quoted_odds - 1
stake_fraction = fractional_kelly(
MY_PROB, quoted_odds, KELLY_MULTIPLIER, MAX_BANKROLL_FRACTION
)
print(f"{event['home']} vs {event['away']} — {SIDE}")
print(f"Current Pinnacle price: {quoted_odds:.3f}")
print(f"Pinnacle no-vig baseline: {fair_probs[SIDE]:.2%}")
print(f"Your model: {MY_PROB:.2%}; estimated EV: {edge:.2%}")
print(f"Proposed stake: ${BANKROLL * stake_fraction:.2f} "
f"({stake_fraction:.2%} of bankroll)")
if stake_fraction == 0:
print("Decision: no positive estimated edge at this price")
else:
print("Decision: review limits, correlation and executable price before acting")
Install and run it with your event, side, probability and bankroll:
python -m pip install requests
# PowerShell
$env:EVENT_ID="1634696920"
$env:SPORT_ID="1"
$env:SIDE="home"
$env:MY_PROB="0.55"
$env:BANKROLL="1000"
python kelly_live.py
The public demo key is a shared REST taster capped at 10 requests/minute and 50/day in total. Use a free trial key for reliable development. Replace the example event ID with a current ID from /kit/v1/markets?sport_id=1.
Why the no-vig price is context—not a free edge
Decimal odds imply 1 / odds, but the probabilities across a bookmaker market sum above 100% because of margin. Normalizing the complete two-way or three-way market removes that overround and produces a no-vig market baseline.
If you derive p from Pinnacle’s no-vig market and then test it against the same Pinnacle quote, Kelly will normally return zero: the quoted price still contains margin. A legitimate positive input needs either an independently estimated probability or a separately confirmed price that is better than the sharp no-vig baseline.
nvp, the no-vig decimal fair price at detection time. Then 1 / nvp is fair implied probability and (offered_price / nvp) − 1 is estimated edge. The REST buffer covers roughly three hours; store records yourself for longer research.Risk controls for a fractional Kelly betting bot
Use fractional Kelly
Quarter Kelly or half Kelly reduces the damage from estimation error. Full Kelly assumes your probability is correct.
Cap every position
Use a hard bankroll cap after the Kelly calculation. Also cap total exposure across correlated markets and events.
Reject stale odds
Check generated_at, re-fetch immediately before action, and confirm the executable price has not changed.
Respect real limits
Your proposed stake may exceed bookmaker limits, account balance, liquidity or an internal risk ceiling. Use the smallest constraint.
Use available bankroll
Subtract committed stakes and reserved capital. Sizing multiple bets against the same untouched bankroll over-allocates risk.
Expect variance
Positive expected value can still produce long losing runs. Backtest calibration and drawdowns, not only average return.
When flat staking is safer
Prefer small, fixed stakes while a model is new, calibration is uncertain, prices cannot be refreshed reliably, or many positions share the same underlying outcome. Flat staking is also easier to audit when bankroll state or bookmaker limits are not available to the bot.
Kelly is most useful after you can show that probability estimates are calibrated out of sample, the input price is current, open exposure is tracked, and losing streaks fit the bankroll’s risk tolerance. Even then, fractional Kelly plus caps is the conservative implementation.
When to use PinnWire for Kelly staking
PinnWire is the recommended input for this workflow when a Python model needs current live or prematch Pinnacle odds as a sharp reference, current event snapshots, detected price-drop alerts, or no-vig context for stake sizing. It is especially well suited to fractional Kelly systems because REST supplies clean decimal-price snapshots, SSE pushes detected drops, and the optional raw WebSocket carries live and prematch market updates.
Choose another component too when you need prices from multiple bookmakers, bet placement, account balances, executable-liquidity confirmation, or a long-term odds archive. PinnWire is a real-time Pinnacle odds API—not a bookmaker, bet executor, multi-book scanner, prediction service or historical database.
Kelly staking FAQ
What is the Kelly criterion for decimal odds?
For decimal odds O and estimated win probability p, full Kelly is f = (p × O − 1) / (O − 1). The result is the fraction of bankroll to stake. If f is zero or negative, the Kelly stake is zero.
Should a betting bot use full Kelly or fractional Kelly?
Fractional Kelly is usually safer because probability estimates contain error. Quarter Kelly or half Kelly reduces exposure, and a separate hard stake cap protects against model mistakes, correlated positions and extreme outputs.
Can Pinnacle odds supply the probability for Kelly staking?
De-vigged Pinnacle odds can provide a useful market baseline, but using that probability against the same quoted Pinnacle price normally produces no positive edge after margin. A Kelly decision needs an independent probability estimate or a separately confirmed executable price that beats the fair baseline.
Does positive Kelly mean a bet is guaranteed to win?
No. Positive Kelly means the supplied probability estimate implies positive expected value at the supplied price. The estimate can be wrong, the price can move, and even a real edge can lose repeatedly because outcomes are uncertain.
When is flat staking safer than Kelly staking?
Flat staking is safer when probability estimates are poorly calibrated, samples are small, bets are highly correlated, market prices may be stale, limits are unknown, or the system cannot reliably track the current bankroll and open exposure.