API Reference¶
Auto-generated from docstrings on the public surface of the kalshi package.
For narrative coverage of any of these, follow the section links — every class here also has a dedicated user-facing page elsewhere in the docs.
Clients¶
kalshi.client.KalshiClient ¶
KalshiClient(
*,
key_id: str | None = None,
private_key_path: str | Path | None = None,
private_key: str | bytes | None = None,
auth: KalshiAuth | None = None,
config: KalshiConfig | None = None,
demo: bool = False,
base_url: str | None = None,
timeout: float | None = None,
max_retries: int | None = None,
transport: BaseTransport | None = None
)
Synchronous client for the Kalshi prediction markets API.
Usage
with KalshiClient(key_id="...", private_key_path="~/.kalshi/key.pem") as client: markets = client.markets.list(status="open") for market in markets: print(market.ticker, market.yes_ask)
Or without context manager
client = KalshiClient(key_id="...", private_key_path="~/.kalshi/key.pem") try: markets = client.markets.list() finally: client.close()
is_authenticated
property
¶
Whether this client has auth credentials configured.
from_env
classmethod
¶
Create client from environment variables.
Reads
KALSHI_KEY_ID (optional — omit for unauthenticated access) KALSHI_PRIVATE_KEY (PEM string) or KALSHI_PRIVATE_KEY_PATH (file path) KALSHI_API_BASE_URL (optional, overrides base_url) KALSHI_DEMO (optional, "true" for demo environment)
Returns an unauthenticated client if no credentials are configured.
kalshi.async_client.AsyncKalshiClient ¶
AsyncKalshiClient(
*,
key_id: str | None = None,
private_key_path: str | Path | None = None,
private_key: str | bytes | None = None,
auth: KalshiAuth | None = None,
config: KalshiConfig | None = None,
demo: bool = False,
base_url: str | None = None,
timeout: float | None = None,
max_retries: int | None = None,
transport: AsyncBaseTransport | None = None
)
Asynchronous client for the Kalshi prediction markets API.
Usage
async with AsyncKalshiClient(key_id="...", private_key_path="~/.kalshi/key.pem") as client: markets = await client.markets.list(status="open") for market in markets: print(market.ticker, market.yes_ask)
is_authenticated
property
¶
Whether this client has auth credentials configured.
ws
property
¶
WebSocket client for real-time streaming.
.. note::
Each access returns a new KalshiWebSocket instance.
Per-instance state (callbacks registered via .on(),
pending subscriptions) does not carry across accesses —
assign the property to a local variable if you need to share
state across multiple operations::
ws = client.ws # capture once
@ws.on("ticker_v2")
async def handle(msg): ...
async with ws.connect() as session: ...
The sync ``KalshiClient`` does not expose ``.ws``; WebSocket
access is async-only.
Usage::
async with client.ws.connect() as session:
async for msg in session.subscribe_ticker(tickers=["ECON-GDP-25Q1"]):
print(msg.msg.yes_bid)
from_env
classmethod
¶
Create async client from environment variables.
Reads
KALSHI_KEY_ID (optional — omit for unauthenticated access) KALSHI_PRIVATE_KEY (PEM string) or KALSHI_PRIVATE_KEY_PATH (file path) KALSHI_API_BASE_URL (optional, overrides base_url) KALSHI_DEMO (optional, "true" for demo environment)
Returns an unauthenticated client if no credentials are configured.
Configuration¶
kalshi.config.KalshiConfig
dataclass
¶
KalshiConfig(
base_url: str = PRODUCTION_BASE_URL,
timeout: float = DEFAULT_TIMEOUT,
max_retries: int = DEFAULT_MAX_RETRIES,
retry_base_delay: float = 0.5,
retry_max_delay: float = 30.0,
total_timeout: float | None = None,
extra_headers: Mapping[str, str] = dict(),
ws_base_url: str = PRODUCTION_WS_URL,
ws_max_retries: int = DEFAULT_WS_MAX_RETRIES,
http2: bool = False,
limits: Limits | None = None,
ws_ping_interval: float = 20.0,
ws_close_timeout: float = 5.0,
rest_json_loads: Callable[[bytes], Any] | None = None,
ws_json_loads: (
Callable[[bytes | str], Any] | None
) = None,
ws_json_dumps: (
Callable[[Any], bytes | str] | None
) = None,
allow_unknown_host: bool = False,
)
Client configuration.
Attributes:
| Name | Type | Description |
|---|---|---|
base_url |
str
|
API base URL. Defaults to production. |
timeout |
float
|
Request timeout in seconds. Defaults to 30. |
max_retries |
int
|
Max retry attempts for transient errors. Defaults to 3. |
retry_base_delay |
float
|
Base delay in seconds for exponential backoff. Defaults to 0.5. |
retry_max_delay |
float
|
Maximum delay in seconds for backoff. Defaults to 30. |
total_timeout |
float | None
|
Hard cap on cumulative time spent inside a single
request including retries, in seconds. |
http2 |
bool
|
Enable HTTP/2 for REST requests. Off by default for compat.
Requires the |
limits |
Limits | None
|
Custom |
ws_ping_interval |
float
|
Interval (s) between WS keepalive pings. Default 20. |
ws_close_timeout |
float
|
Time (s) to wait for graceful WS close handshake. Default 5. |
rest_json_loads |
Callable[[bytes], Any] | None
|
Optional callable used to parse REST response
bodies. |
ws_json_loads |
Callable[[bytes | str], Any] | None
|
Optional callable used to parse WS frames. |
ws_json_dumps |
Callable[[Any], bytes | str] | None
|
Optional callable used to serialize outbound WS
commands. |
Authentication¶
kalshi.auth.KalshiAuth ¶
RSA-PSS request signer for the Kalshi API.
The private key is loaded once at construction time and cached in memory. Thread-safe: RSA signing is stateless.
private_key
property
¶
The loaded RSA private key.
Exposed (read-only) so other signing surfaces in the SDK can reuse the
same key without re-loading the PEM — notably the FIX logon signer
(:class:kalshi.fix.auth.FixSigner), which signs a different payload
(the FIX Logon pre-hash string) with the identical RSA-PSS scheme.
from_key_path
classmethod
¶
from_key_path(
key_id: str,
key_path: str | Path,
*,
password: (
bytes | str | Callable[[], bytes | str] | None
) = None
) -> KalshiAuth
Load auth from a PEM private key file.
Supports ~ expansion (e.g., ~/kalshi.pem).
password accepts str / bytes / a zero-arg callable that
returns either (the callable form lets you defer fetching the secret
from a vault until load-time).
from_pem
classmethod
¶
from_pem(
key_id: str,
pem_data: str | bytes,
*,
password: (
bytes | str | Callable[[], bytes | str] | None
) = None
) -> KalshiAuth
Load auth from PEM-encoded private key content.
password accepts str / bytes / a zero-arg callable that
returns either; pass None (default) for an unencrypted PEM.
from_env
classmethod
¶
Load auth from environment variables.
Reads
KALSHI_KEY_ID (required) KALSHI_PRIVATE_KEY (PEM string) or KALSHI_PRIVATE_KEY_PATH (file path) KALSHI_PRIVATE_KEY_PASSPHRASE (optional; used when the PEM is encrypted)
The explicit password= keyword overrides
KALSHI_PRIVATE_KEY_PASSPHRASE when both are supplied.
try_from_env
classmethod
¶
try_from_env(
*,
password: (
bytes | str | Callable[[], bytes | str] | None
) = None
) -> KalshiAuth | None
Load auth from environment variables, returning None if not configured.
Returns None if KALSHI_KEY_ID is not set, or if neither KALSHI_PRIVATE_KEY nor KALSHI_PRIVATE_KEY_PATH is set.
Note: Does not raise on missing variables, but may still raise KalshiAuthError if variables are set with invalid data (e.g., malformed PEM content or nonexistent key file path).
See :meth:from_env for the password /
KALSHI_PRIVATE_KEY_PASSPHRASE semantics.
sign_request ¶
Sign a request and return the auth headers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
str
|
HTTP method (GET, POST, DELETE, etc.) |
required |
path
|
str
|
Full API path (e.g., /trade-api/v2/markets). Query strings and URL fragments are stripped. |
required |
timestamp_ms
|
int | None
|
Unix timestamp in milliseconds. Auto-generated if None. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, str]
|
Dict with KALSHI-ACCESS-KEY, KALSHI-ACCESS-SIGNATURE, KALSHI-ACCESS-TIMESTAMP. |
sign_request_async
async
¶
Async variant of :meth:sign_request that offloads to a dedicated executor.
Uses loop.run_in_executor(self._sign_executor, ...) so the
~1-10 ms of RSA-PSS CPU does not block the asyncio event loop.
The executor is dedicated (not asyncio's default pool) so signs
don't queue behind getaddrinfo / file I/O / other to_thread()
work — relevant during WS reconnect storms where DNS dominates.
The sync :meth:sign_request API is unchanged; callers running
on the sync transport should keep using it.
close ¶
Shut down the sign-offload executor and mark this auth closed.
Idempotent. Safe to call without an executor having been initialised. Long-lived clients ordinarily rely on interpreter-shutdown cleanup (atexit-joined executor threads); this hook is for tests and other callers that want deterministic teardown.
Terminal: a subsequent :meth:sign_request_async raises
RuntimeError rather than silently respawning a fresh executor.
The synchronous :meth:sign_request is unaffected — it never used
the executor — but callers should treat the instance as retired.
Errors¶
See Errors — that page is the canonical autodoc reference for every exception class.
Pagination¶
kalshi.models.common.Page ¶
Bases: BaseModel, Generic[T]
A page of results from a list endpoint.
Iterable over items. Also exposes cursor metadata for manual pagination.
Usage
page = client.markets.list(limit=50) for item in page: # iterate items process(item) print(page.cursor) # access cursor print(page.has_next) # check if more pages exist
Custom types¶
kalshi.types ¶
Custom types and Pydantic field helpers for the Kalshi SDK.
DollarDecimal
module-attribute
¶
DollarDecimal = Annotated[
Decimal,
BeforeValidator(_coerce_decimal),
PlainSerializer(
_decimal_to_str, return_type=str, when_used="json"
),
]
A Decimal field that handles bidirectional conversion for Kalshi dollar values.
- Parse: Accepts str/int/float/Decimal, converts via Decimal(str(value))
- Serialize: Outputs string representation for API requests
FixedPointCount
module-attribute
¶
FixedPointCount = Annotated[
Decimal,
BeforeValidator(_coerce_decimal),
PlainSerializer(
_decimal_to_str, return_type=str, when_used="json"
),
]
A Decimal field that handles bidirectional conversion for Kalshi count/volume values.
- Parse: Accepts str/int/float/Decimal, converts via Decimal(str(value))
- Serialize: Outputs string representation for API requests
MultiplierDecimal
module-attribute
¶
MultiplierDecimal = Annotated[
Decimal,
BeforeValidator(_coerce_decimal),
PlainSerializer(
_decimal_to_str, return_type=str, when_used="json"
),
]
A Decimal field for fee/rate multipliers (e.g. Series.fee_multiplier).
Wire shape is type: number, format: double per spec. Using the bare
float annotation commits the value to binary float at parse time
(0.65 -> 0.65000000000000002...); using bare Decimal skips
_coerce_decimal entirely and routes JSON numbers through float. This
alias applies the same coercion as :data:DollarDecimal /
:data:FixedPointCount so multipliers honor the #225 invariant.
NullableList
module-attribute
¶
A list[T] that tolerates server-returned null by coercing to [].
Use on response-model fields (extra='allow') whose spec says 'array' but where the live API has been observed to return null. Do NOT use on request bodies — those should enforce strict validation.
UnixSecondsTimestamp
module-attribute
¶
Server-side format: int64 Unix-seconds timestamp.
Same wire shape as plain int; the alias exists to make it obvious at the
field site that the value is seconds (not milliseconds, not a datetime).
Callers wanting a :class:datetime.datetime can use
datetime.fromtimestamp(value, tz=timezone.utc).
StrictInt
module-attribute
¶
int that rejects bool — use on every Request-model integer field. See #295.
OrderPrice
module-attribute
¶
OrderPrice = Annotated[
Decimal,
BeforeValidator(_coerce_decimal),
AfterValidator(_ensure_request_price),
PlainSerializer(
_decimal_to_str, return_type=str, when_used="json"
),
]
Request-side dollar price with sign and $0.0001 tick guards (#343).
Same wire shape as :data:DollarDecimal (BeforeValidator coerces to
Decimal without float drift, PlainSerializer emits a fixed-point
string on mode="json"), but adds an :class:AfterValidator that
rejects negatives and sub-tick precision. Use on
CreateOrderRequest / AmendOrderRequest / V2 equivalents so a
caller passing Decimal('-0.65') or Decimal('0.12345') fails at
construction instead of round-tripping to a server 400.
to_decimal ¶
Convert a user-supplied price/count value to Decimal safely.
Always goes through str() to avoid float representation errors. e.g., to_decimal(0.65) returns Decimal("0.65"), not Decimal(0.65).
Rejects bool inputs (#225 pattern) and non-finite values
(NaN/Infinity); delegates to :func:_coerce_decimal so the
public helper and the :data:DollarDecimal / :data:FixedPointCount
validators share a single guard (#325).
Request models¶
kalshi.models.orders.CreateOrderV2Request ¶
Bases: BaseModel
Body for POST /portfolio/events/orders — the event-market order API.
Notable shape:
sideisBookSideLiteral(bid/ask). V2 narrows the type on the model itself since there is no kwarg overload at the resource-method boundary (the V2 surface is model-only).client_order_idis required by this model because the server uses it for idempotency on V2 orders. (OpenAPI v3.20.0 relaxed it to optional server-side, but the SDK keeps it required by design so every V2 order is idempotent.)- Price is a single
pricefield (dollars).
kalshi.models.orders.AmendOrderV2Request ¶
Bases: BaseModel
Body for POST /portfolio/events/orders/{order_id}/amend.
kalshi.models.orders.DecreaseOrderV2Request ¶
Bases: BaseModel
Body for POST /portfolio/events/orders/{order_id}/decrease.
Spec marks all fields optional but server requires exactly one of
reduce_by or reduce_to. Enforced at construction.
kalshi.models.orders.BatchCreateOrdersV2Request ¶
Bases: BaseModel
Body for POST /portfolio/events/orders/batched.
kalshi.models.orders.BatchCancelOrdersV2Request ¶
Bases: BaseModel
Body for DELETE /portfolio/events/orders/batched.
kalshi.models.orders.BatchCancelOrdersV2RequestOrder ¶
Bases: BaseModel
Single entry in BatchCancelOrdersV2Request.orders.
kalshi.models.api_keys.CreateApiKeyRequest ¶
Bases: BaseModel
Body for POST /api_keys — register a caller-supplied public key.
public_key must be a PEM-encoded RSA public key (i.e. starts
with -----BEGIN PUBLIC KEY-----). scopes defaults to full
access (["read", "write"]) server-side when omitted. If
"write" is included, "read" must be too.
kalshi.models.api_keys.GenerateApiKeyRequest ¶
Bases: BaseModel
Body for POST /api_keys/generate — let Kalshi mint a key pair.
kalshi.models.communications.CreateRFQRequest ¶
Bases: BaseModel
Body for POST /communications/rfqs.
Spec allows contracts (integer) or contracts_fp (fixed-point string);
SDK commits to the integer form. Target cost uses _dollars wire suffix.
target_cost_centi_cents is deprecated upstream — omitted here.
Spec only requires market_ticker and rest_remainder; contracts
and target_cost are both optional and may both be omitted (a "shopping
around" RFQ with no committed size or cost). The SDK does not enforce a
contracts-or-cost invariant because the spec doesn't.
kalshi.models.communications.CreateQuoteRequest ¶
Bases: BaseModel
Body for POST /communications/quotes.
Unlike order/amend requests, the spec names the wire fields yes_bid /
no_bid (no _dollars suffix) for this request. The Quote response
fields, however, use the _dollars suffix — handled on the response model.
kalshi.models.communications.AcceptQuoteRequest ¶
Bases: BaseModel
Body for PUT /communications/quotes/{quote_id}/accept.
kalshi.models.communications.ProposeBlockTradeRequest ¶
Bases: BaseModel
Body for POST /communications/block-trade-proposals.
price_centi_cents (centi-cents) and centicount (centicounts) are
plain integers per spec, both minimum: 1. buyer_subtrader_id /
buyer_subaccount are mutually exclusive (same for the seller pair), but
the spec does not encode the exclusivity, so the SDK does not enforce it.
kalshi.models.communications.AcceptBlockTradeProposalRequest ¶
Bases: BaseModel
Body for POST /communications/block-trade-proposals/{id}/accept.
Both fields are optional (spec requestBody required: false); accept as
primary by sending an empty body. subtrader_id / subaccount are
mutually exclusive but the spec does not encode it, so it is not enforced.
kalshi.models.multivariate.CreateMarketInMultivariateEventCollectionRequest ¶
Bases: BaseModel
Parameters for POST /multivariate_event_collections/{collection_ticker}.
Matches spec components.schemas.CreateMarketInMultivariateEventCollectionRequest.
Required: selected_markets. Optional: with_market_payload.
Carve-out: extra="forbid" on this model rejects unknown top-level
keys but NOT unknown keys inside each TickerPair in
selected_markets — TickerPair itself is extra="allow" (see
its docstring for why). Phantom keys nested inside a TickerPair
currently pass through to the wire. Tracked as a v0.9 follow-up.
See kalshi.resources.multivariate.MultivariateCollectionsResource.create_market
— v0.8.0 builds this model internally; method signature unchanged.
kalshi.models.multivariate.LookupTickersForMarketInMultivariateEventCollectionRequest ¶
Bases: BaseModel
Parameters for PUT /multivariate_event_collections/{collection_ticker}/lookup.
Matches spec
components.schemas.LookupTickersForMarketInMultivariateEventCollectionRequest.
Only selected_markets, required.
Carve-out: extra="forbid" on this model rejects unknown top-level
keys but NOT unknown keys inside each TickerPair in
selected_markets — TickerPair itself is extra="allow" (see
its docstring for why). Phantom keys nested inside a TickerPair
currently pass through to the wire. Tracked as a v0.9 follow-up.
See kalshi.resources.multivariate.MultivariateCollectionsResource.lookup_tickers
— v0.8.0 builds this model internally; method signature unchanged.
kalshi.models.order_groups.CreateOrderGroupRequest ¶
Bases: BaseModel
Create body. SDK sends integer form; spec's contracts_limit_fp string variant unused.
kalshi.models.order_groups.UpdateOrderGroupLimitRequest ¶
Bases: BaseModel
Update-limit body. No subaccount — spec omits SubaccountQuery on /limit.
kalshi.models.subaccounts.ApplySubaccountTransferRequest ¶
Bases: BaseModel
Body for POST /portfolio/subaccounts/transfer.
amount_cents is integer cents per spec (e.g. 500 for $5.00, never
a Decimal). from_subaccount / to_subaccount use 0 for the
primary account and a positive integer for numbered subaccounts. The
server is the source of truth for the upper bound: spec describes
1-63 in prose but defines no JSON-schema maximum, and demo has
been observed allocating values above 32. The SDK validates only the
lower bound (ge=0) so server-assigned numbers always round-trip.
kalshi.models.subaccounts.UpdateSubaccountNettingRequest ¶
Bases: BaseModel
Body for PUT /portfolio/subaccounts/netting.
Response models¶
Markets, events, series¶
kalshi.models.markets ¶
Market-related models.
MarketStatusLiteral
module-attribute
¶
Market status filter for GET /markets. Spec: MarketStatusQuery enum.
MveFilterLiteral
module-attribute
¶
Multivariate-event filter for GET /markets. Spec: MveFilterQuery enum.
Market ¶
Bases: BaseModel
A Kalshi prediction market.
Price fields accept both _dollars-suffixed names from the API
(e.g. yes_bid_dollars) and short names (e.g. yes_bid).
Volume/count fields accept both _fp-suffixed names and short names.
OrderbookLevel ¶
Bases: BaseModel
A single price level in the orderbook.
Quantity is FixedPointCount — fractional contract counts are sent as
FixedPointCount strings like "100.50" from the API.
Orderbook ¶
Bases: BaseModel
Orderbook for a market.
BidAskDistribution ¶
Bases: BaseModel
OHLC data for bid/ask prices within a candlestick period.
PriceDistribution ¶
Bases: BaseModel
OHLC data for trade prices within a candlestick period.
Fields are nullable because there may be no trades in a period.
Candlestick ¶
Bases: BaseModel
A candlestick data point for a market.
The API returns nested OHLC objects for yes_bid, yes_ask, and price, plus volume and open interest as FixedPointCount strings.
MarketCandlesticks ¶
Bases: BaseModel
Per-market candlestick bundle in a bulk response.
Spec schema MarketCandlesticksResponse — wraps a ticker and its
candlesticks. The outer bulk response is {markets: [...]}.
GetMarketCandlesticksResponse ¶
Bases: BaseModel
Spec GetMarketCandlesticksResponse — single-market candlesticks envelope.
ticker and candlesticks are spec-required. candlesticks uses
:data:~kalshi.types.NullableList so a missing key hard-fails (surfacing
spec drift instead of silently returning []) while a null value —
Kalshi's observed "empty-as-null" convention — coerces to [] (the prior
data.get(...) extraction would TypeError on a null array).
BatchGetMarketCandlesticksResponse ¶
Bases: BaseModel
Spec BatchGetMarketCandlesticksResponse — bulk-candlesticks envelope.
markets is spec-required (NullableList: missing key -> error, null -> []).
GetMarketOrderbooksResponse ¶
Bases: BaseModel
Spec GetMarketOrderbooksResponse — bulk-orderbooks envelope.
orderbooks is spec-required (NullableList: missing key -> error, null ->
[]). Items stay raw dicts — the resource transforms each via
_orderbook_from_item (the yes/no FixedPoint parsing it already owns).
kalshi.models.events ¶
Event-related models.
EventStatusLiteral
module-attribute
¶
Event status filter for GET /events. Spec: GetEvents.status query enum.
SettlementSource ¶
Bases: BaseModel
A settlement source for an event.
Event ¶
Bases: BaseModel
A Kalshi event (container for one or more markets).
EventFeeChange ¶
Bases: BaseModel
A scheduled event-level fee override (GET /events/fee_changes).
Event fees layer on top of the parent series' fee structure.
fee_type_override and fee_multiplier_override are both None
when the override has been cleared (the event falls back to the series
fee). Both are spec-required keys (EventFeeChange.required) but
nullable — the key must be present; None is a meaningful "override
cleared" signal, so neither carries a default.
MarketMetadata ¶
Bases: BaseModel
Metadata for a market within an event.
EventMetadata ¶
Bases: BaseModel
Metadata for an event including images and settlement sources.
kalshi.models.series ¶
Series-related models.
Series ¶
Bases: BaseModel
A Kalshi series (template for recurring events).
SeriesFeeChange ¶
Bases: BaseModel
A scheduled fee change for a series.
EventCandlesticks ¶
Bases: BaseModel
Event-level candlestick data spanning multiple markets.
Unlike market candlesticks (flat list for one market), event candlesticks return a nested structure: one candlestick list per market in the event.
PercentilePoint ¶
Bases: BaseModel
A single forecast value at a given percentile.
ForecastPercentilesPoint ¶
Bases: BaseModel
A forecast data point with percentile breakdowns.
Trading¶
kalshi.models.orders.Order ¶
Bases: BaseModel
A Kalshi order.
Price/cost fields accept both _dollars-suffixed names from the API
(e.g. yes_price_dollars) and short names (e.g. yes_price).
kalshi.models.orders.Fill ¶
Bases: BaseModel
A filled trade.
Price fields accept both _dollars-suffixed names from the API
and short names. Count accepts _fp-suffixed name.
kalshi.models.orders.AmendOrderV2Response ¶
Bases: BaseModel
Response from POST /portfolio/events/orders/{order_id}/amend.
kalshi.models.orders.OrderQueuePosition ¶
Bases: BaseModel
Queue position for a single resting order.
kalshi.models.portfolio ¶
Portfolio-related models.
SettlementStatusLiteral
module-attribute
¶
Position settlement status filter for GET /fcm/positions. Spec: settlement_status query enum.
PaymentStatusLiteral
module-attribute
¶
Status of a Deposit/Withdrawal. Spec defines two structurally-identical inline enums (Deposit.status, Withdrawal.status); the SDK shares one alias since the values are identical.
PaymentTypeLiteral
module-attribute
¶
Payment method used for a deposit/withdrawal.
IndexedBalance ¶
Bases: BaseModel
Balance for a single exchange shard. Added by spec v3.18.0 alongside
the balance_breakdown field on :class:Balance.
Currently only exchange_index=0 is supported per spec.
Type note: balance here is DollarDecimal (fixed-point
dollar string per spec), unlike :attr:Balance.balance which is
integer cents. Same field name, different semantics — be deliberate
when reading from balance.balance_breakdown[i].balance versus
balance.balance. The :attr:Balance.balance_dollars field
rendered in dollars matches the breakdown's units.
Balance ¶
Bases: BaseModel
Account balance.
balance is integer cents (legacy field). balance_dollars is the
same value as a fixed-point dollar string, added by spec v3.18.0 and
now required on every response. balance_breakdown (optional) splits
the total across exchange shards when present.
TotalRestingOrderValue ¶
Bases: BaseModel
Total value of resting orders in cents.
Spec: "intended for use by FCM members (rare)". Non-FCM accounts see 403 on this endpoint (demo audit 2026-04-18).
MarketPosition ¶
Bases: BaseModel
A position in a single market.
total_traded and position are typed DollarDecimal | None /
FixedPointCount | None without default=None because the OpenAPI
spec (MarketPosition.required) marks total_traded_dollars and
position_fp as required response keys. The | None admits the
server's observed null on flat positions while a missing key still
raises ValidationError — silent omission is treated as a schema regression,
not a default.
EventPosition ¶
Bases: BaseModel
A position aggregated at the event level.
PositionsResponse ¶
Bases: BaseModel
Response from the positions endpoint containing both market and event positions.
Deposit ¶
Bases: BaseModel
A single deposit history entry. Amounts are integer cents.
Withdrawal ¶
Bases: BaseModel
A single withdrawal history entry. Amounts are integer cents.
Settlement ¶
Bases: BaseModel
A settled market position.
Account, subaccounts, API keys, FCM¶
kalshi.models.account ¶
Account-scoped models — API tier limits, etc.
RateLimit ¶
Bases: BaseModel
Per-direction (read/write) token-bucket rate limit.
The server enforces a token bucket per direction: bucket_capacity
tokens are allowed in a burst; refill_rate tokens are added per
second up to the cap. Requests above the cap return 429.
EndpointTokenCost ¶
Bases: BaseModel
Configured token cost for a single API endpoint.
AccountEndpointCosts ¶
Bases: BaseModel
Response from GET /account/endpoint_costs.
Lists API v2 endpoints whose configured token cost differs from
default_cost. Endpoints using the default are omitted.
ApiUsageLevelGrant ¶
Bases: BaseModel
One API usage-level grant for a single exchange lane.
Spec ApiUsageLevelGrant. Each grant applies to its exchange_instance
(event_contract or margined); level is the usage level it confers
(e.g. premier/paragon/prime). source records how it was
created (volume for trading-volume earned, manual for Kalshi-assigned).
expires_ts is a Unix-seconds expiry, absent (None) for permanent grants.
AccountApiLimits ¶
Bases: BaseModel
Rate limits associated with the authenticated user's API tier.
NOTE: The published OpenAPI spec (v3.13.0) declares read_limit and
write_limit as ints, but the live server returns nested token-bucket
objects under read and write. The SDK matches the server. If the
spec is corrected upstream, the contract-drift test will flag it.
grants lists the caller's active usage-level grants across exchange
lanes; usage_tier is the effective tier reported by this endpoint.
AccountApiUsageLevelVolumeGoal ¶
Bases: BaseModel
A single volume goal for one API usage level.
Spec AccountApiUsageLevelVolumeGoal. earn_volume_goal is the
trailing-30-day contract volume needed to earn this level;
keep_volume_goal is the (typically lower) volume needed to keep a
level already held. Both are fixed-point contract counts.
AccountApiUsageLevelVolumeProgress ¶
Bases: BaseModel
One cron-computed volume-progress snapshot for the predictions lane.
Spec AccountApiUsageLevelVolumeProgress. computed_ts is the Unix
timestamp (seconds) at which this snapshot was computed; trailing_30d_volume
is the trailing-30-day fixed-point contract volume ending at that time.
goals lists the per-level earn/keep volume thresholds.
AccountVolumeProgress ¶
Bases: BaseModel
Response from GET /account/api_usage_level/volume_progress.
Spec GetAccountApiUsageLevelVolumeProgressResponse. Wraps the list of
latest cron-computed volume-progress snapshots toward volume-based API
usage tiers for the predictions (event_contract) lane.
kalshi.models.subaccounts ¶
Subaccount models — multi-account workflows under one authenticated user.
CreateSubaccountResponse ¶
Bases: BaseModel
Response from POST /portfolio/subaccounts — the new subaccount number.
ApplySubaccountTransferRequest ¶
Bases: BaseModel
Body for POST /portfolio/subaccounts/transfer.
amount_cents is integer cents per spec (e.g. 500 for $5.00, never
a Decimal). from_subaccount / to_subaccount use 0 for the
primary account and a positive integer for numbered subaccounts. The
server is the source of truth for the upper bound: spec describes
1-63 in prose but defines no JSON-schema maximum, and demo has
been observed allocating values above 32. The SDK validates only the
lower bound (ge=0) so server-assigned numbers always round-trip.
SubaccountBalance ¶
Bases: BaseModel
Balance for a single subaccount.
Note: updated_ts is a Unix seconds integer per spec
(format: int64), not an ISO datetime. RFQ/Quote timestamps are
format: date-time and surface as datetime; subaccount
timestamps follow the spec's int wire format. Callers wanting a
datetime can datetime.fromtimestamp(obj.updated_ts, tz=timezone.utc).
GetSubaccountBalancesResponse ¶
Bases: BaseModel
Response from GET /portfolio/subaccounts/balances.
SubaccountTransfer ¶
Bases: BaseModel
A past transfer between subaccounts.
created_ts is a Unix seconds integer per spec (format: int64),
matching SubaccountBalance.updated_ts. This is intentionally
different from RFQ/Quote timestamps, which are ISO datetime strings.
UpdateSubaccountNettingRequest ¶
Bases: BaseModel
Body for PUT /portfolio/subaccounts/netting.
SubaccountNettingConfig ¶
Bases: BaseModel
Netting state for a single subaccount.
GetSubaccountNettingResponse ¶
Bases: BaseModel
Response from GET /portfolio/subaccounts/netting.
kalshi.models.api_keys ¶
API Key models — programmatic credentials management.
API keys allow programmatic access without username/password. Each key
has a unique api_key_id and a user-provided name for bookkeeping.
create accepts a caller-supplied RSA public key (PEM); generate
has Kalshi mint a fresh key pair and returns the private key once
(store it — it cannot be retrieved again).
ApiKey ¶
Bases: BaseModel
An API key registered on the authenticated user's account.
scopes uses NullableList for the same reason envelope-level list
fields do — Kalshi has historically returned JSON null for required
list fields, and silently coercing None -> [] is safer than raising
a Pydantic ValidationError on a field most callers don't use.
GetApiKeysResponse ¶
Bases: BaseModel
Response from GET /api_keys.
api_keys uses NullableList since Kalshi has returned JSON null
for required list fields in other envelopes (see v0.9.0 Series fix).
Coercing None -> [] matches the envelope-list pattern established
across the rest of the SDK.
CreateApiKeyRequest ¶
Bases: BaseModel
Body for POST /api_keys — register a caller-supplied public key.
public_key must be a PEM-encoded RSA public key (i.e. starts
with -----BEGIN PUBLIC KEY-----). scopes defaults to full
access (["read", "write"]) server-side when omitted. If
"write" is included, "read" must be too.
CreateApiKeyResponse ¶
Bases: BaseModel
Response from POST /api_keys — the new key's ID.
GenerateApiKeyRequest ¶
Bases: BaseModel
Body for POST /api_keys/generate — let Kalshi mint a key pair.
GenerateApiKeyResponse ¶
Bases: BaseModel
Response from POST /api_keys/generate.
private_key is a PEM-encoded RSA private key returned ONLY in this
response. Store it securely — Kalshi does not retain a copy and it
cannot be retrieved later. The field is typed as :class:pydantic.SecretStr
so it prints as '**********' in repr()/logs; call
response.private_key.get_secret_value() to retrieve the PEM string
when you need to persist it.
Exchange, historical, search¶
kalshi.models.exchange ¶
Exchange-related models.
ExchangeIndexStatus ¶
Bases: BaseModel
Operational status of a single exchange index (shard).
ExchangeStatus ¶
Bases: BaseModel
Current exchange operational status.
DailySchedule ¶
Bases: BaseModel
A single trading session within a day.
WeeklySchedule ¶
Bases: BaseModel
Weekly trading hours with per-day sessions.
MaintenanceWindow ¶
Bases: BaseModel
A scheduled maintenance window.
Schedule ¶
Bases: BaseModel
Exchange operating schedule.
Announcement ¶
Bases: BaseModel
An exchange-wide announcement.
UserDataTimestamp ¶
Bases: BaseModel
Timestamp when user-scoped exchange data was last validated.
Combine with WebSocket feeds for a live view — REST endpoints like
GetBalance/GetOrders/GetFills/GetPositions may lag the exchange by
a short delay; as_of_time is the upper bound on that lag.
kalshi.models.historical ¶
Historical data models.
MveHistoricalFilterLiteral
module-attribute
¶
Multivariate-event filter for GET /historical/markets. Spec: MveHistoricalFilterQuery.
HistoricalCutoff ¶
Bases: BaseModel
Timestamps defining the boundary between live and historical data.
Trade ¶
Bases: BaseModel
A public trade on the exchange.
count is typed FixedPointCount | None without default=None
because the OpenAPI spec (Trade.required) marks count_fp as a
required response key. The | None admits server-side null
on rare degenerate trades while a missing key still raises
ValidationError — schema regression, not a default.
RFQ / Quote, multivariate, live data, milestones, structured targets, incentive programs, order groups¶
kalshi.models.communications ¶
Communications / RFQ models — request-for-quote and quote subsystem.
UserFilterLiteral
module-attribute
¶
Filter for items created by the authenticated user. Spec: UserFilter enum.
RfqStatusLiteral
module-attribute
¶
RFQ status filter for GET /communications/rfqs. Spec: RFQ.status enum.
QuoteStatusLiteral
module-attribute
¶
Quote status filter for GET /communications/quotes. Spec: Quote.status enum.
MveSelectedLeg ¶
Bases: BaseModel
A selected leg within a multivariate event collection RFQ.
RFQ ¶
Bases: BaseModel
An RFQ — request for quote on a market.
Quote ¶
Bases: BaseModel
A quote responding to an RFQ.
GetCommunicationsIDResponse ¶
Bases: BaseModel
Wraps the caller's public communications ID.
GetRFQsResponse ¶
Bases: BaseModel
Paginated list of RFQs.
GetRFQResponse ¶
Bases: BaseModel
Single-RFQ envelope.
CreateRFQRequest ¶
Bases: BaseModel
Body for POST /communications/rfqs.
Spec allows contracts (integer) or contracts_fp (fixed-point string);
SDK commits to the integer form. Target cost uses _dollars wire suffix.
target_cost_centi_cents is deprecated upstream — omitted here.
Spec only requires market_ticker and rest_remainder; contracts
and target_cost are both optional and may both be omitted (a "shopping
around" RFQ with no committed size or cost). The SDK does not enforce a
contracts-or-cost invariant because the spec doesn't.
CreateRFQResponse ¶
Bases: BaseModel
Wraps the newly-created RFQ's id.
GetQuotesResponse ¶
Bases: BaseModel
Paginated list of quotes.
GetQuoteResponse ¶
Bases: BaseModel
Single-quote envelope.
CreateQuoteRequest ¶
Bases: BaseModel
Body for POST /communications/quotes.
Unlike order/amend requests, the spec names the wire fields yes_bid /
no_bid (no _dollars suffix) for this request. The Quote response
fields, however, use the _dollars suffix — handled on the response model.
CreateQuoteResponse ¶
Bases: BaseModel
Wraps the newly-created quote's id.
AcceptQuoteRequest ¶
Bases: BaseModel
Body for PUT /communications/quotes/{quote_id}/accept.
BlockTradeProposal ¶
Bases: BaseModel
A block trade proposal — bilateral negotiated trade awaiting both sides.
price_centi_cents and centicount are plain integers in the spec
(centi-cents and centicounts respectively), NOT FixedPointDollars/_fp
fixed-point wire fields, so they are not DollarDecimal/FixedPointCount.
GetBlockTradeProposalsResponse ¶
Bases: BaseModel
Paginated list of block trade proposals.
ProposeBlockTradeRequest ¶
Bases: BaseModel
Body for POST /communications/block-trade-proposals.
price_centi_cents (centi-cents) and centicount (centicounts) are
plain integers per spec, both minimum: 1. buyer_subtrader_id /
buyer_subaccount are mutually exclusive (same for the seller pair), but
the spec does not encode the exclusivity, so the SDK does not enforce it.
ProposeBlockTradeResponse ¶
Bases: BaseModel
Wraps the newly-created block trade proposal's id.
AcceptBlockTradeProposalRequest ¶
Bases: BaseModel
Body for POST /communications/block-trade-proposals/{id}/accept.
Both fields are optional (spec requestBody required: false); accept as
primary by sending an empty body. subtrader_id / subaccount are
mutually exclusive but the spec does not encode it, so it is not enforced.
kalshi.models.multivariate ¶
Multivariate event collection models.
MultivariateCollectionStatusLiteral
module-attribute
¶
Multivariate collection status filter.
Spec: GetMultivariateEventCollections.status query enum.
AssociatedEvent ¶
Bases: BaseModel
An event associated with a multivariate collection.
MultivariateEventCollection ¶
Bases: BaseModel
A multivariate event collection (combo contract template).
TickerPair ¶
Bases: BaseModel
A market+event ticker pair with side, used in create/lookup request bodies.
Note: extra="allow" is intentional — the spec's TickerPair schema
has no additionalProperties: false, and some multivariate responses
echo back extra provider-specific keys. Because extra does not
inherit, request models that embed list[TickerPair] cannot rely on
their own extra="forbid" to reject phantom keys inside items. See
v0.9 TODO on nested-model drift coverage.
CreateMarketInMultivariateEventCollectionRequest ¶
Bases: BaseModel
Parameters for POST /multivariate_event_collections/{collection_ticker}.
Matches spec components.schemas.CreateMarketInMultivariateEventCollectionRequest.
Required: selected_markets. Optional: with_market_payload.
Carve-out: extra="forbid" on this model rejects unknown top-level
keys but NOT unknown keys inside each TickerPair in
selected_markets — TickerPair itself is extra="allow" (see
its docstring for why). Phantom keys nested inside a TickerPair
currently pass through to the wire. Tracked as a v0.9 follow-up.
See kalshi.resources.multivariate.MultivariateCollectionsResource.create_market
— v0.8.0 builds this model internally; method signature unchanged.
LookupTickersForMarketInMultivariateEventCollectionRequest ¶
Bases: BaseModel
Parameters for PUT /multivariate_event_collections/{collection_ticker}/lookup.
Matches spec
components.schemas.LookupTickersForMarketInMultivariateEventCollectionRequest.
Only selected_markets, required.
Carve-out: extra="forbid" on this model rejects unknown top-level
keys but NOT unknown keys inside each TickerPair in
selected_markets — TickerPair itself is extra="allow" (see
its docstring for why). Phantom keys nested inside a TickerPair
currently pass through to the wire. Tracked as a v0.9 follow-up.
See kalshi.resources.multivariate.MultivariateCollectionsResource.lookup_tickers
— v0.8.0 builds this model internally; method signature unchanged.
CreateMarketResponse ¶
Bases: BaseModel
Response from creating a market in a multivariate collection.
LookupTickersResponse ¶
Bases: BaseModel
Response from looking up tickers in a multivariate collection.
LookupPoint ¶
Bases: BaseModel
A point in the lookup history of a multivariate collection.
kalshi.models.live_data ¶
Live data models — real-time event state keyed by milestone.
LiveData.details is deliberately a loose dict[str, Any]: the spec
marks it additionalProperties: true with no fixed schema because the
shape varies per milestone type (e.g., football vs. political race).
GetGameStatsResponse.pbp (play-by-play) is similarly loose — each
period/event is a free-form object.
LiveData ¶
Bases: BaseModel
Live-data payload for a specific milestone.
GetLiveDataResponse ¶
Bases: BaseModel
Response from GET /live_data/milestone/{milestone_id}.
GetLiveDatasResponse ¶
Bases: BaseModel
Response from GET /live_data/batch — multiple milestones at once.
PlayByPlayPeriod ¶
Bases: BaseModel
A single period within a game's play-by-play.
events is a loose list of free-form objects (spec has no fixed
event schema) because each sport emits different event shapes.
NullableList coerces server-returned null to [] (Kalshi has
historically sent null for required list fields).
PlayByPlay ¶
Bases: BaseModel
Play-by-play data organized by period.
GetGameStatsResponse ¶
Bases: BaseModel
Response from GET /live_data/milestone/{milestone_id}/game_stats.
pbp is None for unsupported milestone types or milestones
without a Sportradar ID (spec: "Returns null for unsupported milestone
types or milestones without a Sportradar ID").
kalshi.models.milestones ¶
Milestone models — structured markers for sports/elections/crypto events.
Milestones anchor live-data feeds (see :mod:kalshi.models.live_data).
Each milestone has a type (e.g., football_game, political_race)
that determines what the details object contains. last_updated_ts
and start_date are ISO date-time strings per spec (unlike
subaccount timestamps which are Unix ints).
Milestone ¶
Bases: BaseModel
A structured event milestone (game, race, tournament, etc.).
GetMilestoneResponse ¶
Bases: BaseModel
Response from GET /milestones/{milestone_id}.
GetMilestonesResponse ¶
Bases: BaseModel
Response from GET /milestones — paginated list.
milestones uses NullableList since Kalshi has returned JSON null
for required list fields in other envelopes (see the v0.9.0 Series
fix). Coercing None -> [] here matches the pattern established on
Milestone.related_event_tickers and primary_event_tickers.
kalshi.models.structured_targets ¶
Structured targets — external entities (players, teams, etc.) markets anchor to.
Unlike most spec schemas, every StructuredTarget field is optional per
spec — Kalshi returns partial records depending on the target type. The
SDK mirrors that: all fields default to None / {}.
details is flexible JSON (additionalProperties: true) whose keys
depend on type (e.g., a basketball_player carries different keys
than a horse_race_entry). Consumers are expected to branch on type.
StructuredTarget ¶
Bases: BaseModel
An external entity a market can be structured against.
target_type is exposed under the SDK-local name (spec wire key
is type) to avoid shadowing the Python built-in — same convention
as the SDK's query-param renames on this and related resources.
GetStructuredTargetsResponse ¶
Bases: BaseModel
Paginated response for GET /structured_targets.
GetStructuredTargetResponse ¶
Bases: BaseModel
Response for GET /structured_targets/{id}.
kalshi.models.incentive_programs ¶
Incentive programs — market-level liquidity/volume reward programs.
Note on period_reward: spec says "Total reward for the period in
centi-cents". The SDK exposes it as a plain int (caller converts to
dollars by dividing by 10 000). Fractional values come through
target_size_fp (FixedPointCount string).
IncentiveProgramStatusLiteral
module-attribute
¶
Incentive program status filter. Spec: GetIncentivePrograms.status query enum.
IncentiveProgramTypeLiteral
module-attribute
¶
Incentive program type filter. Spec: GetIncentivePrograms.type query enum.
IncentiveProgram ¶
Bases: BaseModel
A single incentive program rewarding liquidity or volume on a market.
GetIncentiveProgramsResponse ¶
Bases: BaseModel
Paginated response for GET /incentive_programs.
Uses next_cursor (not cursor) as the pagination key — unique
to this endpoint.
kalshi.models.order_groups ¶
Order Groups models — rolling 15-second contracts-limit groups for linked orders.
OrderGroup ¶
Bases: BaseModel
A single order group (list response entry).
GetOrderGroupResponse ¶
Bases: BaseModel
Single-group response — omits id (path param), adds member order IDs.
CreateOrderGroupResponse ¶
Bases: BaseModel
Create response — wraps the new group's id.
CreateOrderGroupRequest ¶
Bases: BaseModel
Create body. SDK sends integer form; spec's contracts_limit_fp string variant unused.
UpdateOrderGroupLimitRequest ¶
Bases: BaseModel
Update-limit body. No subaccount — spec omits SubaccountQuery on /limit.
WebSocket¶
See WebSocket for the narrative version.
kalshi.ws.client.KalshiWebSocket ¶
KalshiWebSocket(
auth: KalshiAuth,
config: KalshiConfig,
heartbeat_timeout: float = 30.0,
on_state_change: _StateChangeCb | None = None,
on_error: (
Callable[[ErrorMessage], Awaitable[None]] | None
) = None,
)
WebSocket client for real-time Kalshi market data.
Usage::
ws = KalshiWebSocket(auth=auth, config=config)
async with ws.connect() as session:
async for msg in session.subscribe_ticker(tickers=["ECON-GDP-25Q1"]):
print(msg.msg.yes_bid)
subscribe_orderbook_delta
async
¶
subscribe_orderbook_delta(
*, tickers: list[str] | None = None, maxsize: int = 1000
) -> AsyncIterator[
OrderbookSnapshotMessage | OrderbookDeltaMessage
]
Subscribe to orderbook_delta for the given tickers.
Note: OrderbookSnapshotMessage.msg.yes and .no are live dicts
owned by the :class:OrderbookManager after this dispatch — they
mutate on every delta. Use :meth:orderbook if you need an immutable
snapshot view.
subscribe_market_lifecycle
async
¶
subscribe_market_lifecycle(
*, tickers: list[str] | None = None, maxsize: int = 1000
) -> AsyncIterator[
MarketLifecycleMessage | EventFeeUpdateMessage
]
Subscribe to the market_lifecycle_v2 channel.
Yields :class:MarketLifecycleMessage for lifecycle events and
:class:EventFeeUpdateMessage for event_fee_update frames — both
ride this channel. Discriminate on the .type field.
subscribe_cfbenchmarks_value
async
¶
subscribe_cfbenchmarks_value(
*,
index_ids: list[str] | None = None,
maxsize: int = 1000
) -> AsyncIterator[
CFBenchmarksValueMessage | CFBenchmarksIndexListMessage
]
Subscribe to the auth-required cfbenchmarks_value index feed.
Seed index_ids (e.g. ["BRTI", "ETHUSD_RTI"] or ["all"]) to
receive values immediately; subscribing with no ids yields nothing until
indices are added. The stream yields both cfbenchmarks_value data
messages and cfbenchmarks_value_indexlist control responses, so
discriminate with isinstance(msg, CFBenchmarksValueMessage) (or check
msg.type) before reading msg.msg.
unsubscribe
async
¶
Tear down a subscription and its associated local state (#206).
Removes any local :class:OrderbookManager books seeded by the
sub's current server_sid (works for both explicit-tickers
and all-markets orderbook subscriptions via the manager's per-sid
index), then delegates to
:meth:SubscriptionManager.unsubscribe to send the wire command,
push the consumer-iterator sentinel, and drop the bookkeeping.
Resets the seq watermark too so any sid reuse after server-side
renumber doesn't replay against a stale floor.
on ¶
Decorator to register a callback for a channel.
Works both before and after connect(). Callbacks registered before connect are buffered and applied when the session starts.
run_forever
async
¶
Block until the recv loop terminates. Use with the callback API.
Requires at least one prior subscribe_* (or generic
:meth:subscribe) call in the same session — the recv loop is
started lazily by the subscribe machinery, and without it there
is nothing to drain. Registering an @ws.on(channel) callback
does NOT subscribe; the server only sends frames for channels you
have explicitly subscribed to, so a callback without a matching
subscribe sees nothing.
:param stop_event: optional asyncio.Event used to terminate
run_forever() cooperatively (#177). When set — typically
from a signal handler such as add_signal_handler(SIGINT,
stop.set) — this method clears _running, closes the
connection, and drains the recv loop. The recv loop sees
ConnectionClosed on its next read and exits via the
normal not self._running branch, NOT via cancellation,
so no CancelledError leaks out. When None (the
default) the method blocks on _recv_task directly and
external cancellation still propagates as before.
External cancellation of ``run_forever()`` itself (e.g.,
``task.cancel()`` on the awaiting task) while ``stop_event``
is provided still propagates — the cancellation cleans up the
internal ``stop_waiter`` task but does NOT trigger the
cooperative shutdown branch. ``_recv_task`` keeps running
until the session's ``__aexit__`` calls ``_stop()`` for the
full teardown. Use the event for graceful exit; rely on
``__aexit__`` for hard cancellation.
:raises KalshiSubscriptionError: run_forever() was called
before any subscribe_* request landed (formerly a silent
no-op return — fixed in #175).
orderbook
async
¶
Subscribe to orderbook_delta and yield full Orderbook on each update.
kalshi.ws.connection.ConnectionState ¶
Bases: Enum
WebSocket connection lifecycle states.
kalshi.ws.backpressure.OverflowStrategy ¶
Bases: Enum
What to do when the message queue is full.
kalshi.ws.backpressure.MessageQueue ¶
MessageQueue(
maxsize: int = 1000,
overflow: OverflowStrategy = OverflowStrategy.DROP_OLDEST,
*,
channel: str | None = None,
client_id: int | None = None
)
Bases: Generic[T]
Bounded async queue with configurable overflow behavior.
Implements AsyncIterator so consumers can async for msg in queue.
Iteration stops when a sentinel is pushed (graceful shutdown), and
raises the wrapped exception when an error sentinel from
:meth:put_error is reached (#207).
put_sentinel
async
¶
Push shutdown sentinel. Causes async iteration to stop.
Idempotent: after the queue is closed (sentinel or error sentinel
already pushed), subsequent calls are no-ops. Without this the
deque's maxlen=maxsize+1 would evict a real buffered item to
make room for a redundant sentinel — losing the last in-flight
message between e.g. an ERROR-overflow put_error and the
recv-loop's broadcast put_sentinel fan-out.
put_error
async
¶
Push a terminal error sentinel.
The iterator yields any items already in the buffer first, then
raises exc when it reaches the sentinel. Subsequent put /
put_error / put_sentinel calls are silently dropped (the
queue is now closed). Used by the recv loop on
KalshiBackpressureError so consumers see the failure rather
than a silent StopAsyncIteration (#207).
qsize ¶
Number of items currently in the queue (excludes sentinel). O(1).
Derived from len(self._buffer) minus one when a terminal sentinel
has been appended (put_sentinel / put_error both flip
_closed and push exactly one sentinel-shaped item).
kalshi.ws.models ¶
WebSocket message models for all 11 Kalshi channels.
BaseMessage ¶
Bases: BaseModel
Base for all WebSocket messages.
ErrorMessage ¶
Bases: BaseModel
Error response from the server.
ErrorPayload ¶
Bases: BaseModel
Error message payload.
OkMessage ¶
Bases: BaseModel
Generic success response (list_subscriptions, update_subscription).
SubscribedMessage ¶
Bases: BaseModel
Response to a subscribe command.
SubscriptionInfo ¶
Bases: BaseModel
Subscription confirmation payload.
UnsubscribedMessage ¶
Bases: BaseModel
Response to an unsubscribe command.
CFBenchmarksAvgData ¶
Bases: BaseModel
Windowed-average metadata for a CF Benchmarks index value.
value is an exact 8-dp decimal string. The tracked indices (e.g. BRTI,
ETHUSD_RTI) are USD-denominated reference rates, so it is typed
:data:~kalshi.types.DollarDecimal — exact Decimal from the string, no
binary-float drift. window_size counts the ticks in the window;
window_start_ts_ms/window_end_ts_exclusive bound it in Unix
milliseconds (end-exclusive).
CFBenchmarksIndexListMessage ¶
Bases: BaseModel
cfbenchmarks_value_indexlist message — response to the indexlist action.
id echoes the command id that requested the list; it is absent on
unsolicited frames, so it is optional.
CFBenchmarksIndexListPayload ¶
Bases: BaseModel
cfbenchmarks_value_indexlist.msg — the available index IDs.
CFBenchmarksValueMessage ¶
Bases: BaseModel
cfbenchmarks_value data message envelope.
CFBenchmarksValuePayload ¶
Bases: BaseModel
cfbenchmarks_value.msg — one index value with trailing averages.
data is the raw CF Benchmarks JSON frame as a string (upstream-defined and
index-specific, so it is left unparsed — call json.loads(msg.data) to read
it). avg_60s_data is always present (trailing 60-second average).
last_60s_windowed_average_15min is present only in the final minute before a
quarter-hour close (:00/:15/:30/:45), so it is optional.
CommunicationsMessage ¶
Bases: BaseModel
RFQ and quote notifications. Payload varies by sub-type, so msg is a dict.
Users who want typed parsing can use the individual payload models (RfqCreatedPayload, QuoteCreatedPayload, etc.) to validate msg contents.
QuoteAcceptedPayload ¶
Bases: BaseModel
Quote accepted notification payload.
QuoteCreatedPayload ¶
Bases: BaseModel
Quote created notification payload.
QuoteExecutedPayload ¶
Bases: BaseModel
Quote executed notification payload.
RfqCreatedPayload ¶
Bases: BaseModel
RFQ created notification payload.
Wire format per AsyncAPI spec: created_ts is an RFC3339 date-time
string; target_cost_dollars is a dollar string; contracts_fp is a
2-decimal fixed-point count string.
RfqDeletedPayload ¶
Bases: BaseModel
RFQ deleted notification payload.
deleted_ts is an RFC3339 date-time string per AsyncAPI spec.
EventFeeUpdateMessage ¶
Bases: BaseModel
event_fee_update message delivered on the market_lifecycle_v2 channel.
Rides the same channel as :class:MarketLifecycleMessage; subscribers to
subscribe_market_lifecycle receive both message types. NO required seq.
EventFeeUpdatePayload ¶
Bases: BaseModel
Payload for event_fee_update messages (market_lifecycle_v2 channel).
Emitted when an event-level fee override is set or cleared.
fee_type_override and fee_multiplier_override are both None
when the override has been cleared (the event falls back to the parent
series' fee structure). Both are spec-required keys but nullable — the
key is present; None is the meaningful "override cleared" signal, so
neither carries a default.
FillMessage ¶
Bases: BaseModel
Fill update message. NO required seq.
FillPayload ¶
Bases: BaseModel
Payload for fill messages (private channel).
Wire format per AsyncAPI spec: yes_price_dollars is a dollar-decimal
string; count_fp / post_position_fp are fixed-point count strings;
fee_cost is a dollar-decimal string; ts is an integer Unix
timestamp (seconds).
MarketLifecycleMessage ¶
Bases: BaseModel
Market lifecycle v2 update message. NO required seq.
MarketLifecyclePayload ¶
Bases: BaseModel
Payload for market_lifecycle_v2 messages (public channel).
Discriminated by event_type field. Conditional fields depend on event_type: - created/activated: open_ts, close_ts, title, subtitle, series_ticker - determined: result, determination_ts - settled: settlement_value, settled_ts - deactivated: is_deactivated
MarketPositionsMessage ¶
Bases: BaseModel
Market positions update message. NO required seq.
MarketPositionsPayload ¶
Bases: BaseModel
Payload for market_positions messages (private channel).
Dollar-denominated fields use :data:DollarDecimal per the CLAUDE.md
convention. position is a fixed-point contract count (string).
MultivariateLifecycleMessage ¶
Bases: BaseModel
Multivariate market lifecycle message. Same payload as MarketLifecycleMessage.
MultivariateMessage ¶
Bases: BaseModel
Multivariate update message. NO required seq.
MultivariatePayload ¶
Bases: BaseModel
Payload for multivariate messages (public channel).
SelectedMarket ¶
Bases: BaseModel
A selected market within a multivariate collection.
OrderGroupMessage ¶
Bases: BaseModel
Order group update message. HAS required seq (one of only 2 channels).
OrderGroupPayload ¶
Bases: BaseModel
Payload for order_group_updates messages (private channel).
OrderbookDeltaMessage ¶
Bases: BaseModel
Incremental orderbook update.
OrderbookDeltaPayload ¶
Bases: BaseModel
Payload for orderbook_delta messages.
Wire format per AsyncAPI spec and confirmed on demo: price_dollars is
a dollar-decimal string (e.g. "0.0200"), delta_fp is a fixed-point
count string (e.g. "10.00", may be negative), ts is an RFC3339
timestamp string (e.g. "2026-04-19T18:43:37.662364Z").
OrderbookSnapshotMessage ¶
Bases: BaseModel
Full orderbook snapshot, sent on initial subscribe.
OrderbookSnapshotPayload ¶
Bases: BaseModel
Payload for orderbook_snapshot messages.
Wire format per AsyncAPI spec: yes_dollars_fp and no_dollars_fp are
arrays of [price_in_dollars, contract_count_fp] string pairs. Each row
is two JSON strings, e.g. ["0.5500", "100.00"].
These are validated directly into dict[Decimal, Decimal] (price ->
count) in a single walk so OrderbookManager._apply_snapshot_inplace
can adopt the map with no second iteration (#263).
Both sides are required (#268): a snapshot envelope missing
yes_dollars_fp or no_dollars_fp is schema drift or a partial
server response, not "an empty book on that side". Surfacing a
ValidationError lets the recv loop's malformed-frame handler
log it and roll back the seq watermark for #241 rather than silently
resetting the local book to empty.
Aliasing note: when delivered through
:meth:KalshiWSClient.subscribe_orderbook_delta, yes / no are live
dicts owned by the :class:OrderbookManager after dispatch — they
mutate on every subsequent delta.
TickerMessage ¶
Bases: BaseModel
Ticker update message. NO required seq.
TickerPayload ¶
Bases: BaseModel
Payload for ticker messages (public channel).
Wire format per AsyncAPI spec: yes_bid_dollars / yes_ask_dollars are
dollar-decimal strings; ts is an integer Unix timestamp (seconds);
_fp fields are fixed-point count strings.
TradeMessage ¶
Bases: BaseModel
Trade update message. NO required seq.
TradePayload ¶
Bases: BaseModel
Payload for trade messages (public channel).
Wire format per AsyncAPI spec: yes_price_dollars / no_price_dollars
are dollar-decimal strings; count_fp is a fixed-point count string;
ts is an integer Unix timestamp (seconds).
UserOrdersMessage ¶
Bases: BaseModel
User orders update message. NO required seq.
UserOrdersPayload ¶
Bases: BaseModel
Payload for user_orders messages (private channel).
Wire format captured on demo 2026-04-19: yes_price_dollars is a dollar
string with up to 4 decimals ("0.0100"); *_fp counts are 2-decimal
fixed-point strings; taker_fill_cost_dollars / maker_fill_cost_dollars
/ taker_fees_dollars / maker_fees_dollars are dollar strings with
up to 6 decimals. All stored as :class:decimal.Decimal via
:data:DollarDecimal.
FIX protocol¶
See FIX protocol for the narrative version.
kalshi.fix.client.FixClient ¶
FixClient(
signer: FixSigner,
*,
environment: FixEnvironment = FixEnvironment.PRODUCTION,
config: FixConfig | None = None,
ssl_context: SSLContext | None = None
)
Bases: _BaseFixClient
FIX client for the prediction (event-contract) gateway.
Supports all six session types: order entry (NR/RT), drop copy, market data,
post trade, and RFQ. Construct from a :class:FixSigner, or from an existing
:class:KalshiAuth via :meth:from_auth / the KALSHI_* env via
:meth:from_env.
from_env
classmethod
¶
from_env(
*,
environment: FixEnvironment = FixEnvironment.PRODUCTION,
config: FixConfig | None = None,
ssl_context: SSLContext | None = None,
password: (
bytes | str | Callable[[], bytes | str] | None
) = None
) -> Self
Build a prediction FIX client from the KALSHI_* environment vars.
password (or KALSHI_PRIVATE_KEY_PASSPHRASE) decrypts a
passphrase-protected private key — parity with the REST client.
post_trade ¶
post_trade(
*,
on_message: MessageHandler | None = None,
on_state_change: StateChangeHandler | None = None,
on_decode_error: DecodeErrorHandler | None = None
) -> FixSession
A post-trade (KalshiPT) session for market-settlement reports.
rfq ¶
rfq(
*,
on_message: MessageHandler | None = None,
on_state_change: StateChangeHandler | None = None,
on_decode_error: DecodeErrorHandler | None = None
) -> FixSession
An RFQ (KalshiRFQ) market-maker session.
kalshi.perps.fix.MarginFixClient ¶
MarginFixClient(
signer: FixSigner,
*,
environment: FixEnvironment = FixEnvironment.PRODUCTION,
config: FixConfig | None = None,
ssl_context: SSLContext | None = None
)
Bases: _BaseFixClient
FIX client for the margin (perps) gateway.
Supports order entry (NR/RT), drop copy, and market data. Construct from a
:class:~kalshi.fix.auth.FixSigner, from an existing
:class:~kalshi.auth.KalshiAuth via :meth:from_auth, or from the
KALSHI_PERPS_* environment via :meth:from_env.
from_env
classmethod
¶
from_env(
*,
environment: FixEnvironment = FixEnvironment.PRODUCTION,
config: FixConfig | None = None,
ssl_context: SSLContext | None = None,
password: (
bytes | str | Callable[[], bytes | str] | None
) = None
) -> Self
Build a margin FIX client from the KALSHI_PERPS_* environment vars.
kalshi.fix.config.FixConfig
dataclass
¶
FixConfig(
product: FixProduct = FixProduct.PREDICTION,
environment: FixEnvironment = FixEnvironment.PRODUCTION,
host: str | None = None,
port: int | None = None,
use_tls: bool = True,
heartbeat_interval: int = DEFAULT_HEARTBEAT_INTERVAL,
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
max_retries: int = DEFAULT_MAX_RETRIES,
retry_base_delay: float = 0.5,
retry_max_delay: float = 30.0,
cancel_orders_on_disconnect: bool = False,
listener_session: bool = False,
skip_pending_exec_reports: bool = False,
receive_settlement_reports: bool | None = None,
use_dollars: bool | None = None,
allow_unknown_host: bool = False,
)
FIX connectivity + session configuration.
A single config describes a product/environment plus tuning; the concrete
host/port are resolved per :class:FixSessionType (one TCP connection per
session, one connection per API key). host / port overrides target a
mock server or a local TLS proxy (stunnel) and, when set, apply to every
session.
A non-loopback host override outside the known Kalshi FIX endpoints is
rejected unless allow_unknown_host=True or the process-wide
KALSHI_FIX_ALLOW_UNKNOWN_HOST=1 environment variable is set (an escape
hatch for CI / staging proxies).
Use :meth:prediction / :meth:margin for the canonical environments.
allowed_sessions
property
¶
Session types valid for this product.
effective_use_dollars
property
¶
Resolved UseDollars flag: margin is always on; prediction opt-in.
prediction
classmethod
¶
prediction(
environment: FixEnvironment = FixEnvironment.PRODUCTION,
*,
host: str | None = None,
port: int | None = None,
use_tls: bool = True,
heartbeat_interval: int = DEFAULT_HEARTBEAT_INTERVAL,
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
max_retries: int = DEFAULT_MAX_RETRIES,
retry_base_delay: float = 0.5,
retry_max_delay: float = 30.0,
cancel_orders_on_disconnect: bool = False,
listener_session: bool = False,
skip_pending_exec_reports: bool = False,
receive_settlement_reports: bool | None = None,
use_dollars: bool | None = None,
allow_unknown_host: bool = False
) -> FixConfig
Config for the prediction (event-contract) FIX gateway.
margin
classmethod
¶
margin(
environment: FixEnvironment = FixEnvironment.PRODUCTION,
*,
host: str | None = None,
port: int | None = None,
use_tls: bool = True,
heartbeat_interval: int = DEFAULT_HEARTBEAT_INTERVAL,
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
max_retries: int = DEFAULT_MAX_RETRIES,
retry_base_delay: float = 0.5,
retry_max_delay: float = 30.0,
cancel_orders_on_disconnect: bool = False,
listener_session: bool = False,
skip_pending_exec_reports: bool = False,
receive_settlement_reports: bool | None = None,
use_dollars: bool = True,
allow_unknown_host: bool = False
) -> FixConfig
Config for the margin (perps) FIX gateway (fixed-point dollars enforced).
host_for ¶
Resolve the TCP host for session (honoring a host override).
port_for ¶
Resolve the TCP port for session (honoring a port override).
target_comp_id ¶
The wire TargetCompID (tag 56) for session.
supports_retransmission ¶
Whether session supports ResendRequest/SequenceReset recovery.
When False the session must logon with ResetSeqNumFlag=Y and a
forward sequence gap is unrecoverable.
kalshi.fix.session.FixSession ¶
FixSession(
signer: FixSigner,
config: FixConfig,
session_type: FixSessionType,
*,
on_message: MessageHandler | None = None,
on_state_change: StateChangeHandler | None = None,
on_decode_error: DecodeErrorHandler | None = None,
ssl_context: SSLContext | None = None
)
A single async FIX session.
Usage::
signer = FixSigner.from_env()
config = FixConfig.prediction(environment=FixEnvironment.DEMO)
session = FixSession(signer, config, FixSessionType.ORDER_ENTRY_NR,
on_message=handle)
async with session:
... # send order-entry messages, receive execution reports
on_message receives every inbound application message as a raw
:class:~kalshi.fix.codec.RawMessage (decode it with
:func:~kalshi.fix.messages.decode_app_message). Setting on_decode_error
additionally routes a registered-but-malformed message (a real message lost
to one off-spec field) — but it makes the session run a full Pydantic
validation on every inbound application message to detect failures, so on a
high-rate session that is real per-message overhead (and the consumer's own
decode in on_message is then a second pass). Leave it unset unless you
need malformed messages surfaced; there is no cost when it is None.
session_type
property
¶
The session type (TargetCompID) this session connects as.
outbound_seq_num
property
¶
The MsgSeqNum that will be used for the next outbound message.
close
async
¶
Stop background tasks, send a best-effort (bounded) Logout, close the socket.
The heartbeat loop is cancelled first so it cannot race the Logout send,
and the Logout is wrapped in a timeout so a dead/half-open connection
(where writer.drain() never returns) cannot hang close().
send
async
¶
Send an application message; returns the assigned MsgSeqNum.
Admin messages are managed by the session itself — use the typed order-entry / market-data helpers (later phases) rather than sending session-layer messages directly.
kalshi.fix.orderbook.FixOrderBook ¶
Reconstructs aggregated market books from FIX W snapshots + X incrementals.
Usage::
book = FixOrderBook()
book.apply(decode_app_message(raw)) # W or X (others ignored)
view = book.get("KXNBAGAME-...") # MarketDataBook | None
apply_snapshot ¶
Replace a market's book from a full snapshot (35=W).
A snapshot without a symbol is ignored (nothing to key on). Unknown
MDEntryType entries are skipped, as are non-positive-size levels (a
0 size means "no level" — parity with an incremental Delete). An
empty snapshot is a valid empty book.
apply_incremental ¶
Apply incremental level changes (35=X). Returns the number applied.
Each entry routes by its own symbol. Change sets the level's new
absolute size (a non-positive size removes the level); Delete removes
it. An entry for a market with no snapshot yet, or with an unknown
MDUpdateAction/MDEntryType, is skipped without mutating the book.
apply ¶
Apply a decoded W or X message; anything else is ignored.
Convenience for feeding :func:~kalshi.fix.messages.decode_app_message
output straight in without a type switch at the call site.
get ¶
The current book view for symbol, or None if not seeded.
kalshi.fix.settlement.SettlementReassembler ¶
Reassemble paginated UMS fragments into one report per settlement batch.
Usage::
reasm = SettlementReassembler()
complete = reasm.add(decode_app_message(raw)) # MarketSettlementReport | None
if complete is not None:
... # all parties for the batch are in complete.parties
Assumptions / limitations. Symbol is the only correlation field (the report
id differs per page), so this keys solely on it and assumes a batch's fragments
arrive as one contiguous run terminated by LastFragment=Y (or a standalone
report). It does not reconcile against TotNumMarketSettlementReports
(20106). Consequences:
- Two batches for the same symbol must not interleave — a second batch's
terminal arriving while the first is still buffered would coalesce both into
one report. A well-formed gateway sends a symbol's fragments contiguously
(each batch ends with
LastFragment=Ybefore the next begins), so this does not occur in practice; sequential same-symbol batches reassemble independently because the buffer clears on each terminal. - If a terminal fragment never arrives (mid-batch disconnect), the partial
parties stay buffered — see :meth:
pending_symbols— until :meth:clear(call it on reconnect). - A re-delivered terminal fragment re-emits its report, so consumers should handle emitted reports idempotently.
add ¶
Feed one fragment; return the assembled report on the terminal fragment.
Returns None while more fragments are expected (LastFragment=N).
A terminal fragment (LastFragment True or absent) returns the
report with every accumulated party; a standalone report is returned
unchanged. The assembled report carries the terminal fragment's
MarketSettlementReportID (each page has its own — there is no canonical
batch id), so key any dedup off Symbol + clearing date, not that id.
pending_symbols ¶
Symbols with buffered fragments still awaiting their terminal page.
clear ¶
Drop all buffered fragments (e.g. on reconnect).
Warns when discarding non-empty buffers so silently-dropped partial settlement data is observable.
kalshi.fix.messages ¶
Typed FIX message models (FIX Dictionary v1.03).
Exposes the base framework (scalar + repeating-group fields), the session-layer (admin) messages, the shared repeating-group entry components, and the order-entry message flow. Market-data, drop-copy, and RFQ/settlement flows are added in later phases — see GH #402.
FixGroup ¶
Bases: _FixFieldModel
Base for one entry of a repeating group (no MsgType of its own).
FixGroupMeta
dataclass
¶
Annotated metadata marking a repeating-group field.
Declare a group as Annotated[list[EntryModel], FixGroupMeta(count_tag,
EntryModel)] where count_tag is the NumInGroup tag and
EntryModel is the :class:FixGroup subclass describing one entry.
FixMessage ¶
Bases: _FixFieldModel
Base for all typed FIX messages. Subclasses set the MSG_TYPE class var.
FixType ¶
Bases: StrEnum
Wire type of a FIX field, governing string<->Python conversion.
CollateralAmountChange ¶
MarketSettlementParty ¶
Bases: FixGroup
One NoMarketSettlementPartyIDs (20108) entry — delimiter (20109).
Nests NoCollateralAmountChanges (1703) and NoMiscFees (136).
MultivariateSelectedLeg ¶
EventResendComplete ¶
EventResendReject ¶
Bases: FixMessage
EventResendReject (35=U3) — the resend request could not be fulfilled.
event_resend_reject_reason is a raw int (compare against
:class:~kalshi.fix.enums.EventResendRejectReason).
EventResendRequest ¶
MarketDataIncrementalRefresh ¶
Bases: FixMessage
MarketDataIncrementalRefresh (35=X) — changed book levels.
Each entry carries its own symbol (the group spans markets).
MarketDataRequest ¶
Bases: FixMessage
MarketDataRequest (35=V) — request order-book snapshots / updates.
Prefer the :meth:snapshot / :meth:subscribe / :meth:unsubscribe /
:meth:unsubscribe_all constructors: NoRelatedSym/Symbol are required
for snapshot and subscribe, and a cancel-all request omits them entirely.
MDReqID (262) is intentionally omitted: the Kalshi MD docs do not define
it on 35=V/W/X/Y (subscriptions and rejects correlate by Symbol, not a
request id), unlike generic FIX 5.0SP2.
snapshot
classmethod
¶
A one-shot snapshot request (263=0) for the given market tickers.
subscribe
classmethod
¶
A snapshot-plus-updates subscription (263=1) for the given tickers.
unsubscribe
classmethod
¶
Cancel the subscriptions (263=2) for the given tickers.
unsubscribe_all
classmethod
¶
Cancel every subscription on the session (263=2 with no symbols).
MarketDataRequestReject ¶
Bases: FixMessage
MarketDataRequestReject (35=Y) — a MarketDataRequest could not be accepted.
md_req_rej_reason is a raw char (compare against
:class:~kalshi.fix.enums.MDReqRejReason).
MarketDataSnapshotFullRefresh ¶
Bases: FixMessage
MarketDataSnapshotFullRefresh (35=W) — the full aggregated book for a market.
Correlate by symbol. An empty entries list is a valid empty book
(the server returns one for a symbol it has no order book for).
MDIncrementalEntry ¶
Bases: FixGroup
One NoMDEntries (268) entry in an incremental — delimiter MDUpdateAction (279).
md_update_action / md_entry_type are raw chars (compare against
:class:~kalshi.fix.enums.MDUpdateAction / MDEntryType). md_entry_size
is the new absolute size at the level (0 when the level is deleted).
Price/size are optional for inbound robustness (a Delete that omits them
still parses), matching the order-entry inbound convention.
MDSnapshotEntry ¶
Bases: FixGroup
One NoMDEntries (268) entry in a snapshot — delimiter MDEntryType (269).
md_entry_type is a raw char (compare against
:class:~kalshi.fix.enums.MDEntryType: 0=bid, 1=offer). Price/size
are optional for inbound robustness (parse an off-spec entry rather than
rejecting the whole message), matching the order-entry inbound convention.
SecurityStatus ¶
Bases: FixMessage
SecurityStatus (35=f) — a market's trading-status change.
security_trading_status is a raw int (compare against
:class:~kalshi.fix.enums.SecurityTradingStatus).
SecurityStatusRequest ¶
Bases: FixMessage
SecurityStatusRequest (35=e) — subscribe/unsubscribe a market's status stream.
BusinessMessageReject ¶
ExecutionReport ¶
Bases: FixMessage
ExecutionReport (35=8) — order state change from the exchange.
exec_type / ord_status / side are raw chars (compare against
:class:~kalshi.fix.enums.ExecType / OrdStatus / Side); rejection and
STP codes are raw ints. Position/fee/collateral detail is present on
ExecType=Trade via the misc-fees and collateral groups.
NewOrderSingle ¶
Bases: FixMessage
NewOrderSingle (35=D) — submit a new order.
TransactTime (60) is intentionally not sent: the Kalshi dictionary v1.03
does not include it on 35=D (the gateway stamps its own time), unlike the
generic FIX 5.0SP2 message.
OrderCancelReject ¶
OrderCancelReplaceRequest ¶
OrderCancelRequest ¶
OrderMassCancelReport ¶
OrderMassCancelRequest ¶
OrderGroupRequest ¶
Bases: FixMessage
OrderGroupRequest (35=UOG) — create/reset/delete/trigger/update a group.
Field order follows the docs examples (action, id, contracts-limit, account); FIX scalar field order is not significant, so the helpers below are the intended construction path.
create
classmethod
¶
Create a group with a contracts limit (the server assigns the id).
reset
classmethod
¶
Reset a group's rolling contract count.
delete
classmethod
¶
Delete a group (cancels all resting orders in it).
trigger
classmethod
¶
Trigger a group — immediately cancel all its orders (TriggerCancel=4).
update
classmethod
¶
update(
order_group_id: str,
contracts_limit: int,
*,
alloc_account: int | None = None
) -> OrderGroupRequest
Update a group's contracts limit.
OrderGroupResponse ¶
Bases: FixMessage
OrderGroupResponse (35=UOH) — the gateway's reply to an OrderGroupRequest.
order_group_contracts_limit is echoed only on Create and Update responses.
Fields are optional for inbound robustness; business-logic errors arrive as a
BusinessMessageReject (35=j), malformed fields as a session Reject (35=3).
AcceptQuote ¶
Bases: FixMessage
AcceptQuote (35=UA) — creator accepts a maker's quote.
NB: side (tag 54) is inverted for AcceptQuote per the Kalshi docs —
Side.BUY_YES (1) accepts the maker's NO quote and Side.SELL_NO (2)
accepts the maker's YES quote (the field reuses the shared FIX Side, so the
enum member names read opposite to the accept semantics).
AcceptQuoteStatus ¶
Bases: FixMessage
AcceptQuoteStatus (35=UC) — exchange response to an AcceptQuote.
accept_quote_status is a raw int (compare against
:class:~kalshi.fix.enums.AcceptQuoteResult); accepted_quote_id may differ
from the requested quote when PreferBetterQuote was set.
Quote ¶
Bases: FixMessage
Quote (35=S) — submit a quote (maker) / quote notification (creator).
Either bid_px or offer_px may be zero, but not both (zero = no quote
for that side). Use :meth:submit for the maker path.
submit
classmethod
¶
submit(
quote_id: str,
quote_req_id: str,
symbol: str,
*,
bid_px: DollarDecimal | None = None,
offer_px: DollarDecimal | None = None,
alloc_account: int | None = None,
rest_remainder: bool | None = None
) -> Quote
Build a maker quote for quote_req_id on symbol.
At least one of bid_px / offer_px is required (a quote with neither
side is rejected by the exchange) — None means that side is absent,
distinct from Decimal(0) (an explicit zero = no quote for that side).
The maker does not send sizes — the
exchange derives size from the RFQ's OrderQty — so bid_size /
offer_size / order_qty are not parameters here; they appear on the
model only for the inbound (Exchange -> Creator) quote notification.
QuoteCancel ¶
QuoteCancelStatus ¶
Bases: FixMessage
QuoteCancelStatus (35=U9) — exchange response to a QuoteCancel.
quote_cancel_status is a raw int (compare against
:class:~kalshi.fix.enums.QuoteCancelResult).
QuoteConfirm ¶
QuoteConfirmStatus ¶
Bases: FixMessage
QuoteConfirmStatus (35=U8) — exchange response to a QuoteConfirm.
quote_confirm_status is a raw int (compare against
:class:~kalshi.fix.enums.QuoteConfirmResult).
QuoteRequest ¶
Bases: FixMessage
QuoteRequest (35=R) — create an RFQ (creator) / RFQ notification (maker).
Fields are optional so the inbound notification parses robustly; use
:meth:create to build a well-formed outbound RFQ. NoRelatedSym is always
1 (single market); Symbol xor the MVE collection+legs identify the market,
and OrderQty xor CashOrderQty size it.
create
classmethod
¶
create(
quote_req_id: str,
symbol: str,
*,
order_qty: FixedPointCount | None = None,
cash_order_qty: DollarDecimal | None = None,
alloc_account: int | None = None,
rest_remainder: bool | None = None,
replace_existing: bool | None = None
) -> QuoteRequest
Create a single-market RFQ for symbol.
Requires at least one of order_qty / cash_order_qty to size it.
MVE/parlay RFQs (collection ticker + legs instead of a symbol) construct
:class:QuoteRequest directly.
QuoteRequestAck ¶
Bases: FixMessage
QuoteRequestAck (35=b) — exchange ack of a creator's QuoteRequest.
quote_request_type is a raw int (compare against
:class:~kalshi.fix.enums.QuoteRequestType); rfq_id is the server id.
QuoteRequestReject ¶
Bases: FixMessage
QuoteRequestReject (35=AG) — exchange rejected/cancelled an RFQ request.
quote_request_reject_reason is a raw int (compare against
:class:~kalshi.fix.enums.QuoteRequestRejectReason).
QuoteStatusReport ¶
Bases: FixMessage
QuoteStatusReport (35=AI) — quote lifecycle update to the maker.
quote_status is a raw int (compare against
:class:~kalshi.fix.enums.QuoteStatus); side (AcceptedSide) is a raw char
(FIX CHAR — not an int like the status codes), present only when ACCEPTED.
RFQCancel ¶
Bases: FixMessage
RFQCancel (35=UE) — creator cancels an active RFQ.
Identify the RFQ by either quote_req_id (client id) or rfq_id (server
id); use :meth:for_req_id / :meth:for_rfq_id. The helpers cover the common
case; an FCM on-behalf-of cancel sets parties via direct construction,
e.g. RFQCancel(quote_req_id=..., parties=[Party(...)]).
RFQCancelStatus ¶
Bases: FixMessage
RFQCancelStatus (35=UB) — exchange response to an RFQCancel.
rfq_cancel_status is a raw int (compare against
:class:~kalshi.fix.enums.RFQCancelResult).
Heartbeat ¶
Logon ¶
Bases: FixMessage
Logon (35=A).
The raw_data field carries the base64 RSA-PSS logon signature; the codec
auto-emits its RawDataLength (95) field. heartbeat_interval must be
3 (Kalshi default 30).
reset_seq_num_flagmust beTrueon non-retransmission sessions (KalshiNR / KalshiDC).
Logout ¶
Reject ¶
ResendRequest ¶
SequenceReset ¶
TestRequest ¶
MarketSettlementReport ¶
Bases: FixMessage
MarketSettlementReport (35=UMS) — a market's settlement detail.
market_result is a raw str (compare against
:class:~kalshi.fix.enums.MarketResult). settlement_price rides the FIX
Price field as a no-float Decimal (DollarDecimal is just the
coercion type); the value is in cents, 2 dp — Decimal("100.00") is 100c
(full YES payout) and Decimal("30.60") a scalar market — not dollars.
clearing_business_date is the raw YYYYMMDD string. last_fragment
is False on non-final pages of a paginated batch, True (or absent) on
the last.
fixfield ¶
Declare a scalar FIX field.
tag is the FIX tag number, fix_type its wire type. Omit default
for a required field (Pydantic treats ... as required).
groupfield ¶
Declare a repeating-group field (defaults to an empty list).
The NumInGroup tag and entry model ride on the field's
:class:Annotated :class:FixGroupMeta; this only supplies the default.
decode_app_message ¶
Decode an inbound application :class:RawMessage to its typed model.
Returns None for message types without a registered model (an admin
message or a not-yet-implemented application flow), and also None if the
payload fails schema validation — a malformed inbound message is logged and
swallowed rather than raised into the consumer's on_message handler. To
distinguish those two cases (e.g. to route a malformed known message to a
dead-letter), use :func:decode_app_message_strict or FixSession's
on_decode_error hook.
decode_app_message_strict ¶
Decode an inbound application :class:RawMessage, raising on a malformed one.
Returns None only for an unregistered message type (an admin message or
a not-yet-implemented flow). A registered model whose payload fails schema
validation raises :class:~kalshi.fix.errors.FixDecodeError (chaining the
underlying error) rather than returning None, so a genuine message lost to
a single off-spec field is observable. Used by FixSession's
on_decode_error hook; direct callers wanting the distinction can call this.
kalshi.fix.enums ¶
FIX enumerations for the Kalshi dialect (FIXT.1.1 / FIX50SP2).
Values are the exact on-the-wire tokens from the Kalshi FIX Dictionary v1.03.
String/char enums subclass :class:enum.StrEnum so member.value is the
wire string; integer enums subclass :class:enum.IntEnum and serialize via
str(int(member)). The codec / message layer converts in both directions.
Defining the full enum surface up front (including order-entry and RFQ values not exercised by the foundation layer) keeps a single spec-aligned source of truth for the later message-flow phases.
MsgType ¶
Bases: StrEnum
Tag 35 — message type. Admin (session) + application message identifiers.
EncryptMethod ¶
Bases: IntEnum
Tag 98 — Kalshi FIX uses transport TLS, so message-level encryption is None.
ApplVerID ¶
Bases: StrEnum
Tags 1128 / 1137 — application version. Kalshi is FIX50SP2.
Side ¶
Bases: StrEnum
Tag 54 — order side in Kalshi's yes/no contract vocabulary.
OrdType ¶
Bases: StrEnum
Tag 40 — Kalshi supports limit orders only.
TimeInForce ¶
Bases: StrEnum
Tag 59 — order time in force.
OrdStatus ¶
Bases: StrEnum
Tag 39 — order status.
ExecType ¶
Bases: StrEnum
Tag 150 — execution report type.
ExecInst ¶
Bases: StrEnum
Tag 18 — execution instructions (multi-value string; Kalshi uses POST_ONLY).
SelfTradePreventionType ¶
Bases: IntEnum
Tag 2964 — self-trade prevention behaviour.
SessionRejectReason ¶
Bases: IntEnum
Tag 373 — reason a session-level Reject (35=3) was issued.
BusinessRejectReason ¶
Bases: IntEnum
Tag 380 — reason a BusinessMessageReject (35=j) was issued.
CxlRejReason ¶
Bases: IntEnum
Tag 102 — order cancel/replace rejection reason.
CxlRejResponseTo ¶
Bases: StrEnum
Tag 434 — which request an OrderCancelReject (35=9) responds to.
OrdRejReason ¶
Bases: IntEnum
Tag 103 — order rejection reason.
MassCancelRequestType ¶
Bases: StrEnum
Tag 530 — scope of an OrderMassCancelRequest (35=q).
MassCancelResponse ¶
Bases: StrEnum
Tag 531 — result of an OrderMassCancelRequest.
PartyRole ¶
Bases: IntEnum
Tag 452 — role of a party in the NoPartyIDs group.
OrderGroupAction ¶
Bases: IntEnum
Tag 20131 — order-group lifecycle operation (35=UOG).
MarketResult ¶
Bases: StrEnum
Tag 20107 — market outcome on a MarketSettlementReport (35=UMS).
CollateralAmountType ¶
Bases: StrEnum
Tag 1705 — kind of collateral change in a settlement collateral entry.
EventResendRejectReason ¶
Bases: IntEnum
Tag 21004 — why a drop-copy EventResendRequest (35=U3) was rejected.
QuoteRequestType ¶
Bases: IntEnum
Tag 303 — QuoteRequestAck (35=b) request type.
QuoteStatus ¶
Bases: IntEnum
Tag 297 — QuoteStatusReport (35=AI) quote status.
QuoteRequestRejectReason ¶
Bases: IntEnum
Tag 658 — QuoteRequestReject (35=AG) reason.
QuoteConfirmResult ¶
Bases: IntEnum
Tag 21010 — QuoteConfirmStatus (35=U8) outcome.
QuoteCancelResult ¶
Bases: IntEnum
Tag 298 — QuoteCancelStatus (35=U9) outcome.
RFQCancelResult ¶
Bases: IntEnum
Tag 21013 — RFQCancelStatus (35=UB) outcome.
Spelled CANCELLED to match :class:QuoteCancelResult / :class:QuoteStatus
even though the dictionary describes tag 21013 value 0 as "CANCELED" — the
member name is SDK-chosen and not wire-significant (the wire value is 0).
AcceptQuoteResult ¶
Bases: IntEnum
Tag 21025 — AcceptQuoteStatus (35=UC) outcome.
SubscriptionRequestType ¶
Bases: StrEnum
Tag 263 — market-data / security-status subscription action.
For MarketDataRequest (35=V): 0=snapshot, 1=snapshot plus updates,
2=disable a previous subscription. For SecurityStatusRequest (35=e)
only 1 (subscribe) and 2 (unsubscribe) are valid.
MDEntryType ¶
Bases: StrEnum
Tag 269 — side of a market-data book level.
MDUpdateAction ¶
Bases: StrEnum
Tag 279 — action for an incremental market-data entry (35=X).
FIX 5.0SP2 also defines New=0; Kalshi MD does not emit it, so it is intentionally absent (an action='0' is dropped as unknown).
MDReqRejReason ¶
Bases: StrEnum
Tag 281 — why a MarketDataRequest (35=V) was rejected (35=Y).
SecurityTradingStatus ¶
Bases: IntEnum
Tag 326 — per-market trading-status change streamed via SecurityStatus (35=f).
Testing helpers¶
See Testing for the narrative version.
kalshi.testing ¶
Record/replay mock layer for testing against the Kalshi SDK.
Drop a :class:RecordingTransport (or :class:AsyncRecordingTransport) into the
client once to capture real API calls to disk, then swap in
:class:ReplayTransport (or :class:AsyncReplayTransport) to run tests offline.
Requests are matched by HTTP method + URL path + sorted query parameters. The
KALSHI-ACCESS-SIGNATURE and KALSHI-ACCESS-TIMESTAMP headers are ignored,
so signature drift between record and replay does not cause misses.
.. warning::
Recorded fixtures contain the full response **body** AND response
**headers** returned by Kalshi. If you record against an account with
real funds you will write balances, positions, order history, and any
PII the API returns to disk; response headers may carry rate-limit
counters, request IDs, and server timestamps.
:class:`RecordingTransport` (and the async variant) scrub a small set
of high-risk response headers by default — ``Set-Cookie``,
``Authorization``, and anything matching
``(?i)^x-kalshi-.*-(id|key|account|user).*$`` — but **do not assume the
default is sufficient for your environment**. Always ``.gitignore`` the
fixture directory unless you have manually scrubbed the JSON, prefer
recording against the demo environment whenever possible, and pass
``response_header_filter=`` to extend the deny-list.
Usage::
from pathlib import Path
from kalshi import KalshiClient
from kalshi.testing import RecordingTransport, ReplayTransport
# Record once against the real API:
with KalshiClient.from_env(transport=RecordingTransport(Path("fixtures"))) as c:
c.exchange.status()
# Replay in tests, no network:
with KalshiClient(transport=ReplayTransport(Path("fixtures"))) as c:
c.exchange.status() # served from fixtures/GET_trade-api_v2_exchange_status.json
AsyncRecordingTransport ¶
AsyncRecordingTransport(
dir_path: str | Path,
*,
real_transport: AsyncBaseTransport | None = None,
response_header_filter: ResponseHeaderFilter = None
)
Bases: AsyncBaseTransport
Async equivalent of :class:RecordingTransport.
RecordingTransport ¶
RecordingTransport(
dir_path: str | Path,
*,
real_transport: BaseTransport | None = None,
response_header_filter: ResponseHeaderFilter = None
)
Bases: BaseTransport
Sync httpx transport that proxies real requests and records each pair to disk.
Wraps any underlying httpx.BaseTransport (defaults to a fresh
httpx.HTTPTransport) so real network calls go through, while every
request/response pair is buffered in memory and flushed to disk on
close() — see #105 for the prior O(N²) per-request rewrite this fixes.
response_header_filter controls which response headers are persisted.
The default predicate drops Set-Cookie, Authorization, and any
header matching (?i)^x-kalshi-.*-(id|key|account|user).*$ so common
credential/identity leaks don't end up in checked-in fixtures. Supply a
callable (name, value) -> bool (return True to drop) or an iterable
of header names to deny — pass lambda _n, _v: False to keep every
header verbatim.
AsyncReplayTransport ¶
Bases: AsyncBaseTransport
Async equivalent of :class:ReplayTransport.
FixtureNotFoundError ¶
Bases: LookupError
Raised when no recorded fixture matches an incoming request.
ReplayTransport ¶
Bases: BaseTransport
Sync httpx transport that serves responses from a directory of fixtures.
Reads JSON fixture files written by :class:RecordingTransport. Requests are
matched by HTTP method, URL path, and sorted query parameters (body and auth
headers are ignored).
Raises :class:FixtureNotFoundError when no matching fixture exists — never
returns a synthetic 404 silently.