Python SDK
pip install kellyiq, then size a slate in one call. The package has no dependencies, raises a specific exception for each kind of error, and retries for you when the server is busy.Install and size a slate
pip install kellyiq
export KELLYIQ_API_KEY="kiqa_..."from kellyiq import KellyIQ
client = KellyIQ() # reads KELLYIQ_API_KEY
result = client.optimize({
"bankroll": 1000,
"picks": [
{"sport": "baseball_mlb", "team": "Dodgers", "opponent": "Padres",
"odds": -145, "opponentOdds": 125, "confidence": "MODERATE_EDGE"},
{"sport": "baseball_mlb", "team": "Cubs", "opponent": "Cardinals",
"odds": 120, "opponentOdds": -140, "confidence": "MODERATE_EDGE"},
],
})
for stake in result["allocation"]["stakes"]:
print(stake["team"], stake["stake"])The slate and the result are the same JSON as the REST API, as Python dicts, so the API reference describes every field.
Methods
| Method | Endpoint | Counts against the allowance |
|---|---|---|
optimize(slate) | POST /v1/optimize | Yes |
sports() | GET /v1/sports | No |
me() | GET /v1/me | No |
devig(markets) | POST /v1/devig | No |
implied_probability(odds) | POST /v1/implied-probability | No |
kelly(bets, bankroll, fraction) | POST /v1/kelly | No |
Errors
Every error is a KellyIQError carrying the API’s error fields: code, violations, request_id, retry_after, required_tier and resets_at. Catch a subclass to handle one case. The codes are described on Errors.
| Exception | Code |
|---|---|
ValidationError | VALIDATION_ERROR, INVALID_JSON (400) |
AuthenticationError | INVALID_API_KEY (401) |
UpgradeRequiredError | UPGRADE_REQUIRED (403) |
PermissionDeniedError | API_NOT_ENABLED and any other 403 |
SlateNotOptimizableError | SLATE_NOT_OPTIMIZABLE (422) |
DailyLimitError | DAILY_LIMIT_EXCEEDED (429) |
BurstLimitError | BURST_LIMIT_EXCEEDED (429), retried for you |
ServiceBusyError | SERVICE_BUSY (503), retried for you |
ServerError | Other 5xx |
APIConnectionError | No response: network, DNS or timeout |
from kellyiq import DailyLimitError, UpgradeRequiredError, ValidationError
try:
result = client.optimize(slate)
except ValidationError as e:
print("Fix:", e.violations) # every problem, e.g. "picks[2].odds: ..."
except UpgradeRequiredError as e:
print("Needs the", e.required_tier, "plan:", e.violations)
except DailyLimitError as e:
print("No optimizes left until", e.resets_at)Retries
A busy server and a burst of requests never count against your allowance, so the client retries both after the wait the API asks for: up to max_retries times (default 2), and only when the wait is at most max_retry_wait seconds (default 60). A dropped connection is retried for the free GET calls only, because an optimize that timed out may already have run and counted. A daily limit is never retried.
client = KellyIQ(max_retries=0) # handle every error yourselfExamples
- A notebook: the stakes, the probabilities behind them, and the risk at each Kelly fraction, as tables.
- Spreadsheet to stakes: CSV in, CSV of stakes out, with each error handled.
The package is open source (MIT): kellyiq-python. Each request identifies itself as kellyiq-python/<version>.