Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@

All notable changes to kalshi-sdk will be documented in this file.

## 22.4.0 — 2026-10-26

Reconciles nightly spec drift (#556, #557). Additive on the RFQ-batch
child request and on perps `MarginMarket`. No breaking changes.

### Added

- Optional **`CreateRFQBatchChild.contracts`** /
**`CreateRFQBatchChild.target_cost`** — per-child size in place of
the batch-level size. Python names drop the `_fp` / `_dollars`
suffixes; serialize as `contracts_fp` / `target_cost_dollars` and
accept both wire and Python names. Batch-level `contracts` /
`target_cost` still apply to every child that sets no size of its
own. (Closes #557).

- Optional **`MarginMarket.halt_sources`** (`list[str] | None`). The
spec marks it required; the SDK keeps it optional so this stays a
minor release. Why the
market is halted (`"initial_setup"`, `"corporate_action"`, …). Empty
means the market is not halted or the reason has not been observed —
do not read empty as proof the market is live. (Closes #557).

### Changed

- Re-vendored `specs/` (OpenAPI 3.34.0 still). Core AsyncAPI dropped
the `batch_communications` channel (16 → 15); RFQ-batch notifications
now ride `communications`. The SDK never shipped a typed
`subscribe_batch_communications` helper — `subscribe_communications()`
documents the move. Perps AsyncAPI and Klear SCM are unchanged.
(Closes #556).

## 22.3.0 — 2026-10-22

Reconciles nightly spec drift (#553). Maps the ten core RFQ batch
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ tests/
## API Reference

- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.34.0, 128 operations; 127 mapped in the core SDK — `POST /portfolio/intra_exchange_instance_transfer` is implemented on `PerpsClient.transfers.transfer_instance` and left unimplemented on the core client)
- AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (16 WebSocket channels; 12 typed `subscribe_*` + escape-hatch)
- AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (15 WebSocket channels; 12 typed `subscribe_*` + escape-hatch)
- Base URL: https://api.elections.kalshi.com/trade-api/v2
- Demo URL: https://demo-api.kalshi.co/trade-api/v2
- Auth: RSA-PSS / SHA256 / MGF1(SHA256) / salt_length=DIGEST_LENGTH / base64
Expand Down
6 changes: 5 additions & 1 deletion docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ perps.portfolio.cancel_cross_exit_trigger("BTC-PERP", trig.id)
(`"dampen"` / `"exclude"`): how funding averages a second with no
premium (no orderbook sample, no index price, or a trading halt).
`dampen` counts that second as a premium of 0; `exclude` leaves it
out of the average.
out of the average. `MarginMarket.halt_sources` is optional
(`list[str] | None`; spec-required, `None` if absent): why the market is halted (`"initial_setup"`,
`"corporate_action"`, …). Empty means the market is not halted or
the reason has not been observed — do not read empty as proof the
market is live.
- **Mark prices** (since the v3.21.0 spec sync) — `MarginMarket` carries three
optional reference-price objects: `settlement_mark_price`,
`liquidation_mark_price`, and `reference_price`, each a nested `TickerPrice`
Expand Down
2 changes: 2 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ every exception class.

::: kalshi.models.communications.CreateRFQBatchRequest

::: kalshi.models.communications.CreateRFQBatchChild

::: kalshi.models.communications.CreateRFQBatchQuoteRequest

::: kalshi.models.communications.AcceptRFQBatchRequest
Expand Down
3 changes: 3 additions & 0 deletions docs/request-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ exposed by each resource method stays in lockstep with the OpenAPI spec.
| `client.communications.accept_quote` | `AcceptQuoteRequest` |
| `client.communications.block_trade_proposals.create` | `ProposeBlockTradeRequest` |
| `client.communications.block_trade_proposals.accept` | `AcceptBlockTradeProposalRequest` |
| `client.communications.rfq_batches.create` | `CreateRFQBatchRequest` (wraps `list[CreateRFQBatchChild]`) |
| `client.multivariate_collections.create_market` | `CreateMarketInMultivariateEventCollectionRequest` |
| `client.order_groups.create` | `CreateOrderGroupRequest` |
| `client.order_groups.update_limit` | `UpdateOrderGroupLimitRequest` |
Expand All @@ -79,6 +80,8 @@ an awkward wire name:
| Model field | Wire name |
|---|---|
| `CreateRFQRequest.target_cost` | `target_cost_dollars` |
| `CreateRFQBatchRequest.contracts` / `CreateRFQBatchChild.contracts` | `contracts_fp` |
| `CreateRFQBatchRequest.target_cost` / `CreateRFQBatchChild.target_cost` | `target_cost_dollars` |
| `StructuredTarget.target_type` (response) | `type` (avoids shadowing the builtin) |

You never type these long names — they're an internal implementation detail.
Expand Down
16 changes: 9 additions & 7 deletions docs/resources/communications.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,14 @@ batch; each child is accepted from whichever maker quotes it best on your
side. Accepting fills every child or none.

```python
# Create a batch of two combos, sized the same way.
# Create a batch of two combos. Size the batch, or set contracts /
# target_cost on each child in place of the batch-level size.
resp = client.communications.rfq_batches.create(
children=[
{"market_ticker": "COMBO-A"},
{"market_ticker": "COMBO-B"},
{"market_ticker": "COMBO-A", "contracts": "10.00"},
{"market_ticker": "COMBO-B", "contracts": "5.00"},
],
contracts="10.00",
rest_remainder=True,
rest_remainder=False,
)
batch_id = resp.rfq_batch.id

Expand Down Expand Up @@ -272,9 +272,11 @@ you on that one batch.

Prices on create-quote entries use `yes_bid` / `no_bid` (no `_dollars`
suffix), matching `quotes.create()`. Accept `min_bids` use Python name
`bid`, serialized as `bid_dollars`. Batch create sizes every child via
`bid`, serialized as `bid_dollars`. Batch create sizes children via
`contracts` (`contracts_fp` on the wire) or `target_cost`
(`target_cost_dollars`).
(`target_cost_dollars`) on the batch (applied to every child that sets
no size of its own) or on each `CreateRFQBatchChild`. `rest_remainder`
must be false: a batch executes as one fill-or-kill.

## Migrating from v2.x

Expand Down
4 changes: 4 additions & 0 deletions docs/websockets.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ SDK's perspective on it.
The `type` column matters when filtering raw logs — note the singular forms
for `user_order` and `market_position`. The standalone `multivariate` /
`multivariate_lookup` channel was removed from AsyncAPI (SDK v10.0.0).
The standalone `batch_communications` channel was removed from AsyncAPI
(SDK v22.4.0, 16 → 15 spec channels); RFQ-batch notifications now ride
`communications`. The SDK never shipped a typed
`subscribe_batch_communications` helper.

!!! warning "Migration (v3.1.0): `event_fee_update` rides `market_lifecycle_v2`"
Since the v3.20.0 spec sync (SDK v3.1.0) the `market_lifecycle_v2` channel
Expand Down
2 changes: 1 addition & 1 deletion kalshi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,4 +477,4 @@
"Withdrawal",
]

__version__ = "22.3.0"
__version__ = "22.4.0"
1 change: 1 addition & 0 deletions kalshi/_contract_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ class ContractEntry:
ContractEntry(
sdk_model="kalshi.models.communications.CreateRFQBatchChild",
spec_schema="CreateRFQBatchChild",
notes="Uses serialization_alias + AliasChoices for contracts_fp / target_cost_dollars",
),
ContractEntry(
sdk_model="kalshi.models.communications.RFQBatchQuoteEntry",
Expand Down
25 changes: 20 additions & 5 deletions kalshi/models/communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,22 +385,37 @@ class GetRFQBatchesResponse(BaseModel):
class CreateRFQBatchChild(BaseModel):
"""One combo to request quotes on. Set exactly one of ``market_ticker``
and ``selected_markets``; the spec does not encode the exclusivity.

Optional ``contracts`` / ``target_cost`` size this child in place of
the batch-level size. Provide at most one; the spec does not encode
the exclusivity, so the SDK does not enforce it.
"""

market_ticker: str | None = None
selected_markets: list[TickerPair] | None = None
contracts: FixedPointCount | None = Field(
default=None,
serialization_alias="contracts_fp",
validation_alias=AliasChoices("contracts_fp", "contracts"),
)
target_cost: DollarDecimal | None = Field(
default=None,
serialization_alias="target_cost_dollars",
validation_alias=AliasChoices("target_cost_dollars", "target_cost"),
)

model_config = {"extra": "forbid"}


class CreateRFQBatchRequest(BaseModel):
"""Body for POST /communications/rfq_batches.

Spec sizes every child the same way via ``contracts_fp`` or
``target_cost_dollars``. SDK Python names drop those suffixes and
serialize with ``serialization_alias``. Provide exactly one of
``contracts`` and ``target_cost``; the spec does not encode the
exclusivity, so the SDK does not enforce it.
Spec sizes children via ``contracts_fp`` or ``target_cost_dollars``
on the batch (applied to every child that sets no size of its own)
or on each child. SDK Python names drop those suffixes and serialize
with ``serialization_alias``. Provide at most one of ``contracts``
and ``target_cost``; the spec does not encode the exclusivity, so
the SDK does not enforce it.
"""

children: list[CreateRFQBatchChild]
Expand Down
7 changes: 7 additions & 0 deletions kalshi/perps/models/markets.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ class MarginMarket(BaseModel):
# index price, or a trading halt), configured at market creation.
# ``dampen`` counts the second as premium 0; ``exclude`` omits it.
funding_invalid_premium_handling: Literal["dampen", "exclude"]
# Spec marks it required; the SDK keeps it optional so older payloads
# and existing constructors still validate (minor release). Why the
# market is halted (``"initial_setup"``,
# ``"corporate_action"``, …). Empty means the market is not halted or
# the reason has not been observed — do not read empty as proof the
# market is live.
halt_sources: list[str] | None = None

leverage_estimate: MultiplierDecimal | None = None
# Leverage (1 / margin_rate) keyed by notional position size in dollars
Expand Down
12 changes: 12 additions & 0 deletions kalshi/resources/communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,12 @@ def create(
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQBatchResponse:
"""Create an RFQ batch.

Size the batch via ``contracts`` / ``target_cost``, or set
``contracts`` / ``target_cost`` on each :class:`CreateRFQBatchChild`
in place of the batch-level size.
"""
self._require_auth()
body = _build_create_rfq_batch_body(
request,
Expand Down Expand Up @@ -2360,6 +2366,12 @@ async def create(
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQBatchResponse:
"""Create an RFQ batch.

Size the batch via ``contracts`` / ``target_cost``, or set
``contracts`` / ``target_cost`` on each :class:`CreateRFQBatchChild`
in place of the batch-level size.
"""
self._require_auth()
body = _build_create_rfq_batch_body(
request,
Expand Down
8 changes: 8 additions & 0 deletions kalshi/ws/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,14 @@ async def subscribe_communications(
shard_key: int | None = None,
maxsize: int = 1000,
) -> AsyncIterator[CommunicationsMessage]:
"""Subscribe to RFQ and quote notifications.

RFQ-batch notifications (``rfq_batch_created``, ``rfq_batch_deleted``,
``batch_quote_*``, ``rfq_batch_best_quotes``, ``rfq_batch_executed``)
now arrive on this channel. The standalone ``batch_communications``
channel was removed from the AsyncAPI spec (SDK v22.4.0); the SDK
never shipped a typed subscribe helper for it.
"""
params: dict[str, Any] = {}
if shard_factor is not None:
params["shard_factor"] = shard_factor
Expand Down
4 changes: 4 additions & 0 deletions kalshi/ws/models/communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ class CommunicationsMessage(BaseModel):

Users who want typed parsing can use the individual payload models
(RfqCreatedPayload, QuoteCreatedPayload, etc.) to validate msg contents.

RFQ-batch notifications now arrive on this channel. The standalone
``batch_communications`` channel was removed from the AsyncAPI spec
(SDK v22.4.0).
"""

type: Literal["communications"] = "communications"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kalshi-sdk"
version = "22.3.0"
version = "22.4.0"
description = "A professional Python SDK for the Kalshi prediction markets and Perps (margin) APIs"
readme = "README.md"
license = { text = "MIT" }
Expand Down
Loading
Loading