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
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@

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

## 17.0.0 — 2026-09-29

Reconciles upstream OpenAPI **3.31.0** content drift plus matching perps and
AsyncAPI updates after nightly contract failures (Closes #522).
**Breaking** for constructors of `Market` that omit the new required
`settlement_bounds_type`. Re-vendored `specs/openapi.yaml` (hash
`dde105932d49e4ea6e197068b82741178ea87edbc991d8fd7676c101162c1c1e`),
`specs/asyncapi.yaml` (hash
`49ea4f5b4f35feba19ba2109e554f6bbcb0a1e123e58feb64e5cfd8182bc72e7`), and
`specs/perps_openapi.yaml` (hash
`bfeb993d2a270e9a319b61d520facacd624e72039c8fe79e45fded10f0578eb7`).

### Changed (breaking)

- **`Market.settlement_bounds_type`** (`"default"` / `"floor"`, required) —
which settlement bounds apply. `default` means none. Live
`markets.list` / `markets.get` callers are unaffected; tests/mocks that
construct `Market` must pass the field.

### Added

- Optional **`Market.settlement_floor`** (`Decimal | None`), parsed from
`settlement_floor_dollars`. Only filled when `settlement_bounds_type` is
`floor`.
- Optional **`CreateRFQRequest.obscure_creator_id`** and the same kwarg on
`communications.rfqs.create` / deprecated `create_rfq` (sync + async).
Hides the RFQ creator ID from other users until successful execution.
Omit to keep the server default (`false`).
- Optional **`AmendOrderV2Request.expiration_time`** (`int | None`,
minimum 0). int64 Unix seconds. Omit preserves the current expiry; `0`
removes it (good-till-canceled). A nonzero value must be in the future.
- Optional **`AmendMarginOrderRequest.expiration_time`** and
`perps.orders.amend(..., expiration_time=)` (sync + async) — same
semantics as the event-market amend.

### Spec notes

- Core OpenAPI `info.version` still **3.31.0** (content-only). Still
unimplemented on the core client:
`POST /portfolio/intra_exchange_instance_transfer`.
- `GET /markets/{ticker}/orderbook` and `GET /markets/orderbooks` dropped
their `security` block upstream. The SDK methods still call
`_require_auth()`; unauthenticated orderbook reads are unchanged.
- AsyncAPI still 15 channels. Description-only updates on communications
creator ids (`"0"` when `obscure_creator_id` is enabled).
- Perps OpenAPI: still 50 operations. Amend-order description now covers
expiration time; queue position is preserved for an expiry-only amend.

## 16.0.1 — 2026-09-28

Reconciles upstream perps OpenAPI description-only drift on exit-trigger
Expand Down
35 changes: 35 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Migration

## v16.0 → v17.0.0

Reconciles upstream OpenAPI **3.31.0** content drift plus matching perps and
AsyncAPI updates (Closes #522). **Breaking** only for code that constructs
`Market` without `settlement_bounds_type`.

### Response model field changes

- **`Market.settlement_bounds_type`** — required `"default"` or `"floor"`.
`floor` markets may also include optional `settlement_floor` (parsed from
`settlement_floor_dollars`). Live `markets.list` / `markets.get` callers
are unaffected; constructors and fixtures must pass the new field.

```python
# Before (constructors / test fixtures):
# Market(...) # settlement_bounds_type omitted

# After:
Market(..., settlement_bounds_type="default")
# or "floor", optionally with settlement_floor="0.1000"
```

### Added (non-breaking)

- `CreateRFQRequest.obscure_creator_id` and
`communications.rfqs.create(..., obscure_creator_id=)` — hide the RFQ
creator ID from other users until successful execution.
- `AmendOrderV2Request.expiration_time` — int64 Unix seconds; omit keeps
the current expiry, `0` clears it (good-till-canceled).
- Perps `orders.amend(..., expiration_time=)` /
`AmendMarginOrderRequest.expiration_time` — same semantics.

See the [changelog](https://github.com/TexasCoding/kalshi-python-sdk/blob/main/CHANGELOG.md)
for the full list.

## v15.0 → v16.0.0

Reconciles upstream OpenAPI **3.30.0 → 3.31.0** plus matching perps and
Expand Down
2 changes: 2 additions & 0 deletions docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ async with AsyncPerpsClient.from_env(demo=True) as perps:

The margin order side is `bid` / `ask` (not the prediction API's `yes` / `no`).
Orders create/cancel/decrease/amend are POSTs/DELETEs and are **never retried**.
`orders.amend(..., expiration_time=)` is int64 Unix seconds: omit it to keep
the current expiry, or pass `0` to clear it (good-till-canceled).

!!! warning "Deprecated in v7.2.0 — `list_fcm` / `list_all_fcm`"
Kalshi removed `GET /margin/fcm/orders` from the perps OpenAPI. The SDK
Expand Down
1 change: 1 addition & 0 deletions docs/resources/communications.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ rfq = client.communications.rfqs.create(
contracts=500,
rest_remainder=True,
target_cost_excludes_fees=True, # optional; principal-only target cost
obscure_creator_id=True, # optional; hide creator id until execution
)
print(rfq.rfq.rfq_id)

Expand Down
1 change: 1 addition & 0 deletions docs/resources/orders.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ resp = client.orders.amend_v2(
price=Decimal("0.66"),
count=Decimal("12"), # total/max fillable count
exchange_index=0, # body field
expiration_time=0, # optional; 0 clears expiry (GTC)
),
)
print(resp.old_order.order_id, resp.order.order_id)
Expand Down
2 changes: 1 addition & 1 deletion kalshi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,4 +427,4 @@
"Withdrawal",
]

__version__ = "16.0.1"
__version__ = "17.0.0"
3 changes: 3 additions & 0 deletions kalshi/models/communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ class CreateRFQRequest(BaseModel):
subtrader_id: str | None = None
subaccount: StrictInt | None = Field(default=None, ge=0)
target_cost_excludes_fees: bool | None = None
# Hide the creator id from other users until the RFQ executes. Omit keeps
# the server default (false).
obscure_creator_id: bool | None = None

model_config = {"extra": "forbid"}

Expand Down
6 changes: 6 additions & 0 deletions kalshi/models/markets.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ class Market(BaseModel):
expected_expiration_time: AwareDatetime | None = None
expiration_time: AwareDatetime | None = None
settlement_ts: AwareDatetime | None = None
# ``floor`` markets may also send settlement_floor_dollars.
settlement_bounds_type: Literal["default", "floor"]
settlement_floor: DollarDecimal | None = Field(
default=None,
validation_alias=AliasChoices("settlement_floor_dollars", "settlement_floor"),
)
occurrence_datetime: AwareDatetime | None = None

# Metadata
Expand Down
2 changes: 2 additions & 0 deletions kalshi/models/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ class AmendOrderV2Request(BaseModel):
count: FixedPointCount
client_order_id: str | None = None
updated_client_order_id: str | None = None
# Unix seconds. Omit preserves the current expiry; 0 removes it (GTC).
expiration_time: StrictInt | None = Field(default=None, ge=0)
exchange_index: StrictInt | None = None

model_config = {"extra": "forbid"}
Expand Down
2 changes: 2 additions & 0 deletions kalshi/perps/models/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ class AmendMarginOrderRequest(BaseModel):
count: FixedPointCount = Field(gt=0)
client_order_id: str | None = None
updated_client_order_id: str | None = None
# Unix seconds. Omit preserves the current expiry; 0 removes it (GTC).
expiration_time: StrictInt | None = Field(default=None, ge=0)


# ── Response models (extra="allow") ──────────────────────────────────────────
Expand Down
13 changes: 13 additions & 0 deletions kalshi/perps/resources/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def _build_amend_body(
count: int | float | str | None,
client_order_id: str | None,
updated_client_order_id: str | None,
expiration_time: int | None,
) -> dict[str, Any]:
_check_request_exclusive(
request,
Expand All @@ -171,6 +172,7 @@ def _build_amend_body(
count=count,
client_order_id=client_order_id,
updated_client_order_id=updated_client_order_id,
expiration_time=expiration_time,
)
if request is None:
if ticker is None or side is None or price is None or count is None:
Expand All @@ -185,6 +187,7 @@ def _build_amend_body(
count=to_decimal(count),
client_order_id=client_order_id,
updated_client_order_id=updated_client_order_id,
expiration_time=expiration_time,
)
return request.model_dump(exclude_none=True, by_alias=True, mode="json")

Expand Down Expand Up @@ -472,6 +475,7 @@ def amend(
count: int | float | str,
client_order_id: str | None = ...,
updated_client_order_id: str | None = ...,
expiration_time: int | None = ...,
subaccount: int | None = ...,
extra_headers: dict[str, str] | None = None,
) -> AmendMarginOrderResponse: ...
Expand All @@ -486,13 +490,18 @@ def amend(
count: int | float | str | None = None,
client_order_id: str | None = None,
updated_client_order_id: str | None = None,
expiration_time: int | None = None,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None,
) -> AmendMarginOrderResponse:
"""Amend a margin order's price/size (POST /margin/orders/{order_id}/amend).

``subaccount`` is a query param. Increasing size or changing price
forfeits queue position (server-side). Not retried.

``expiration_time`` is int64 Unix seconds. Omit it to preserve the
current expiry; ``0`` removes the expiry (good-till-canceled). A
nonzero value must be in the future.
"""
self._require_auth()
body = _build_amend_body(
Expand All @@ -503,6 +512,7 @@ def amend(
count=count,
client_order_id=client_order_id,
updated_client_order_id=updated_client_order_id,
expiration_time=expiration_time,
)
params = _params(subaccount=subaccount)
data = self._post(
Expand Down Expand Up @@ -813,6 +823,7 @@ async def amend(
count: int | float | str,
client_order_id: str | None = ...,
updated_client_order_id: str | None = ...,
expiration_time: int | None = ...,
subaccount: int | None = ...,
extra_headers: dict[str, str] | None = None,
) -> AmendMarginOrderResponse: ...
Expand All @@ -827,6 +838,7 @@ async def amend(
count: int | float | str | None = None,
client_order_id: str | None = None,
updated_client_order_id: str | None = None,
expiration_time: int | None = None,
subaccount: int | None = None,
extra_headers: dict[str, str] | None = None,
) -> AmendMarginOrderResponse:
Expand All @@ -840,6 +852,7 @@ async def amend(
count=count,
client_order_id=client_order_id,
updated_client_order_id=updated_client_order_id,
expiration_time=expiration_time,
)
params = _params(subaccount=subaccount)
data = await self._post(
Expand Down
13 changes: 13 additions & 0 deletions kalshi/resources/communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def _build_create_rfq_body(
subtrader_id: str | None,
subaccount: int | None,
target_cost_excludes_fees: bool | None,
obscure_creator_id: bool | None,
) -> dict[str, Any]:
_check_request_exclusive(
request,
Expand All @@ -157,6 +158,7 @@ def _build_create_rfq_body(
subtrader_id=subtrader_id,
subaccount=subaccount,
target_cost_excludes_fees=target_cost_excludes_fees,
obscure_creator_id=obscure_creator_id,
)
if request is None:
if market_ticker is None or rest_remainder is None:
Expand All @@ -172,6 +174,7 @@ def _build_create_rfq_body(
subtrader_id=subtrader_id,
subaccount=subaccount,
target_cost_excludes_fees=target_cost_excludes_fees,
obscure_creator_id=obscure_creator_id,
)
return request.model_dump(exclude_none=True, by_alias=True, mode="json")

Expand Down Expand Up @@ -426,6 +429,7 @@ def create(
subtrader_id: str | None = ...,
subaccount: int | None = ...,
target_cost_excludes_fees: bool | None = ...,
obscure_creator_id: bool | None = ...,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQResponse: ...
def create(
Expand All @@ -440,6 +444,7 @@ def create(
subtrader_id: str | None = None,
subaccount: int | None = None,
target_cost_excludes_fees: bool | None = None,
obscure_creator_id: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQResponse:
self._require_auth()
Expand All @@ -453,6 +458,7 @@ def create(
subtrader_id=subtrader_id,
subaccount=subaccount,
target_cost_excludes_fees=target_cost_excludes_fees,
obscure_creator_id=obscure_creator_id,
)
data = self._post("/communications/rfqs", json=body, extra_headers=extra_headers)
return CreateRFQResponse.model_validate(data)
Expand Down Expand Up @@ -1011,6 +1017,7 @@ def create_rfq(
subtrader_id: str | None = None,
subaccount: int | None = None,
target_cost_excludes_fees: bool | None = None,
obscure_creator_id: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQResponse:
""".. deprecated:: 3.0.0 Use :meth:`client.communications.rfqs.create` instead."""
Expand All @@ -1024,6 +1031,7 @@ def create_rfq(
subtrader_id=subtrader_id,
subaccount=subaccount,
target_cost_excludes_fees=target_cost_excludes_fees,
obscure_creator_id=obscure_creator_id,
extra_headers=extra_headers,
)

Expand Down Expand Up @@ -1250,6 +1258,7 @@ async def create(
subtrader_id: str | None = ...,
subaccount: int | None = ...,
target_cost_excludes_fees: bool | None = ...,
obscure_creator_id: bool | None = ...,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQResponse: ...
async def create(
Expand All @@ -1264,6 +1273,7 @@ async def create(
subtrader_id: str | None = None,
subaccount: int | None = None,
target_cost_excludes_fees: bool | None = None,
obscure_creator_id: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQResponse:
self._require_auth()
Expand All @@ -1277,6 +1287,7 @@ async def create(
subtrader_id=subtrader_id,
subaccount=subaccount,
target_cost_excludes_fees=target_cost_excludes_fees,
obscure_creator_id=obscure_creator_id,
)
data = await self._post("/communications/rfqs", json=body, extra_headers=extra_headers)
return CreateRFQResponse.model_validate(data)
Expand Down Expand Up @@ -1827,6 +1838,7 @@ async def create_rfq(
subtrader_id: str | None = None,
subaccount: int | None = None,
target_cost_excludes_fees: bool | None = None,
obscure_creator_id: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> CreateRFQResponse:
""".. deprecated:: 3.0.0 Use :meth:`client.communications.rfqs.create` instead."""
Expand All @@ -1840,6 +1852,7 @@ async def create_rfq(
subtrader_id=subtrader_id,
subaccount=subaccount,
target_cost_excludes_fees=target_cost_excludes_fees,
obscure_creator_id=obscure_creator_id,
extra_headers=extra_headers,
)

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 = "16.0.1"
version = "17.0.0"
description = "A professional Python SDK for the Kalshi prediction markets and Perps (margin) APIs"
readme = "README.md"
license = { text = "MIT" }
Expand Down
1 change: 1 addition & 0 deletions scripts/bench_page_to_dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def _market_dict(ticker: str) -> dict[str, Any]:
"close_time": "2026-12-31T23:59:59Z",
"latest_expiration_time": "2026-12-31T23:59:59Z",
"settlement_timer_seconds": 0,
"settlement_bounds_type": "default",
"result": "",
"can_close_early": False,
"fractional_trading_enabled": False,
Expand Down
Loading
Loading