Skip to content

betting_combat.consumers.kalshi.rest

Async Kalshi REST client — every read the container needs, and order submission.

Reads are paced (basic tier: ~20 reads/s; we stay far below) and retried on timeouts, 429 and 5xx with exponential backoff (Retry-After honoured). Order submission is NEVER retried here: the executor owns that decision, because only it knows whether a duplicate client_order_id is safe.

Receipts for every shape: tests/fixtures/kalshi (recorded 2026-09-26).

Classes

KalshiError

Bases: RuntimeError

KalshiRest

aclose

aclose() -> None

balance_usd

balance_usd() -> float

candles

candles(ticker: str, series_ticker: str, start: dt.datetime, end: dt.datetime, period_minutes: int = 1, archived: bool) -> list[Candle]

:meth:candlesticks, typed.

candlesticks

candlesticks(ticker: str, series_ticker: str, start: dt.datetime, end: dt.datetime, period_minutes: int = 1, archived: bool) -> list[dict[str, Any]]

Candles for one market as Kalshi sends them. Archived markets live under /historical; a 404/400 on the first guess falls through to the other path. Empty = Kalshi has none.

create_order

create_order(ticker: str, count: int, price: float, client_order_id: str, outcome: Literal['yes', 'no'] = 'yes') -> OrderAck

Buy outcome at price or better: an immediate-or-cancel limit. Kalshi’s V2 book is quoted in YES: buying YES bids price; buying NO is the ask side at YES price 1 - price. Never retried here.

cutoffs

cutoffs() -> tuple[dt.datetime, dt.datetime]

(markets, trades): markets settled before the first and trades created before the second are served only under /historical.

event_competition

event_competition(event_ticker: str) -> str | None

Kalshi’s card label: ‘331’ (numbered), ‘Fight Night’, ‘Contender Series’.

fills

fills(since: dt.datetime, order_id: str | None = None, ticker: str | None = None) -> list[Fill]

historical_cutoff

historical_cutoff() -> dt.datetime

live_data

live_data(milestone_ids: list[str]) -> dict[str, LiveDetails]

Live details per milestone id. The batch endpoint needs REPEATED milestone_ids parameters; a comma-joined list returns null (2026-09-26).

market

market(ticker: str) -> Market

market_list

market_list(series_ticker: str, historical: bool, status: str | None = None) -> list[dict[str, Any]]

Every market of a series exactly as Kalshi sends it (for storing verbatim), in the API’s order. historical=True reads the archive.

markets

markets(series_ticker: str, status: str | None = None, historical: bool = False) -> list[Market]

Every market of a series. historical=True reads the archive (settled before Kalshi’s historical cutoff).

markets_for_event

markets_for_event(event_ticker: str) -> list[Market]

milestones

milestones(min_start: dt.datetime, milestone_type: str = 'mma_match') -> list[Milestone]

orderbook

orderbook(ticker: str, depth: int = 10) -> OrderBook

orders

orders(ticker: str, since: dt.datetime) -> list[KalshiOrder]

Our orders on one market since since — how a submission whose answer was lost is reconciled (by client_order_id).

trade_list

trade_list(ticker: str, min_ts: int, max_ts: int, historical: bool) -> list[Trade] | None

One market’s trades with min_ts <= created_time < max_ts (epoch seconds; Kalshi’s own bounds, checked on the live endpoint 2026-09-30) from the archive (/historical/trades) or the live endpoint (/markets/trades). None when the endpoint does not know the market (400/404 on the first page: ask the other one); the same answer on a later page is an error, never a short tape.

trades

trades(ticker: str, since: dt.datetime) -> list[Trade]