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() -> Nonebalance_usd
balance_usd() -> floatcandles
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') -> OrderAckBuy 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 | NoneKalshi’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.datetimelive_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) -> Marketmarket_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) -> OrderBookorders
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] | NoneOne 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]