Docs menu
Integrations

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.
You need an API key from the KellyIQ app under Settings, API. Every plan includes it, Free too. Python 3.9 or later.

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

MethodEndpointCounts against the allowance
optimize(slate)POST /v1/optimizeYes
sports()GET /v1/sportsNo
me()GET /v1/meNo
devig(markets)POST /v1/devigNo
implied_probability(odds)POST /v1/implied-probabilityNo
kelly(bets, bankroll, fraction)POST /v1/kellyNo

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.

ExceptionCode
ValidationErrorVALIDATION_ERROR, INVALID_JSON (400)
AuthenticationErrorINVALID_API_KEY (401)
UpgradeRequiredErrorUPGRADE_REQUIRED (403)
PermissionDeniedErrorAPI_NOT_ENABLED and any other 403
SlateNotOptimizableErrorSLATE_NOT_OPTIMIZABLE (422)
DailyLimitErrorDAILY_LIMIT_EXCEEDED (429)
BurstLimitErrorBURST_LIMIT_EXCEEDED (429), retried for you
ServiceBusyErrorSERVICE_BUSY (503), retried for you
ServerErrorOther 5xx
APIConnectionErrorNo 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 yourself

Examples

  • 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>.