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

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

## 14.0.0 — 2026-09-06

Reconciles upstream OpenAPI **3.29.0** content drift plus matching perps,
Klear, and AsyncAPI updates after nightly contract failures (Closes #510,
Closes #511). **Breaking** for constructors of perps WS `MarginFillPayload`
and `MarginUserOrderPayload` that omit the new required `order_source`.

### Changed (breaking)

- **Perps WS** `MarginFillPayload.order_source` and
`MarginUserOrderPayload.order_source` (`Literal["user", "system"]`,
required). `system` marks liquidations and margin exit / trailing-stop
triggers; `user` is every other order. Live stream callers are
unaffected; tests/mocks that construct these payloads must pass the
source.

### Added

- **`live_data.weather_calibrations(city)`** —
`GET /live_data/weather/{city}/calibrations`. Published weather-index
configuration timeline (launch config + weekly offset calibrations).
- Optional **`WeatherIndexPoint.receipt_basis`** (historical-backfill
points only).
- **`api_keys.list(*, fcm_subtrader_id)`** and optional
**`fcm_subtrader_id`** on `create()` / `generate()` / `ApiKey`
(mutually exclusive with `subaccount`). Optional **`warning`** on
create/generate responses when a bound FCM subtrader has no IM cap.
- **`portfolio.set_target_balance_allocation(..., resting_margin_reservation=)`**
(`max` / `sum`; server defaults to `sum`).
- **`fcm.orders` / `orders_all`**: `subtrader_id` is now optional when
`client_order_ids` is supplied (comma-separated, max 100).
- Optional **`subaccount`** query on `historical.positions()` /
`positions_all()`.
- **Perps** `margin.fee_tier_rates()` — `GET /margin/fee_tier_rates`.
- Optional **`MarginMarket.asset_class`**.
- **Klear** FCM margin API keys: `list_fcm_api_keys` /
`create_fcm_api_key` / `generate_fcm_api_key` / `delete_fcm_api_key`.
- **Klear** `member_funding_payments()` / `member_funding_payments_all()`
— `GET /margin/funding_payments`.
- **Klear** `estimate_maintenance_margin(..., date=, clearing_type=)`
plus optional breakdown fields on the response (`base_margin_fp`,
`hvar_fp`, `apc_fp`, `funding_addon_fp`, `liquidation_addon_fp`).
- **WS** `subscribe_cfbenchmarks_value_5hz()` — high-frequency CF
Benchmarks tick feed (up to 5 Hz). Optional `sid` / `seq` on
`ErrorMessage`.

### Changed (non-breaking)

- **`Event.available_on_brokers`** is optional (`bool | None = None`).
The spec dropped the field; existing constructors that pass it still
work.

### Spec notes

- Core OpenAPI `info.version` still **3.29.0** (paths 96; 109 operations;
108 mapped). Still unimplemented on the core client:
`POST /portfolio/intra_exchange_instance_transfer`.
- AsyncAPI 14 → 15 channels (`cfbenchmarks_value_5hz`). 12 typed
`subscribe_*` helpers.
- Perps OpenAPI: 47 → 48 operations.
- Perps SCM OpenAPI: 19 → 24 operations. Still unimplemented:
`GET /margin/large_trader_positions` (surveillance).

## 13.0.0 — 2026-08-28

Reconciles upstream OpenAPI **3.28.0 → 3.29.0**, plus matching perps, Klear,
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ tests/

## API Reference

- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.29.0, 108 operations; 107 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 (14 WebSocket channels; 11 typed `subscribe_*` + escape-hatch)
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.29.0, 109 operations; 108 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 (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
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) predi
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Type checked: mypy strict](https://img.shields.io/badge/mypy-strict-blue.svg)](https://mypy.readthedocs.io/)

- **Full coverage** of the Kalshi REST API (107 mapped of 108 operations across 19 resources, OpenAPI v3.29.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch).
- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (47 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (18 operations). See [Perps (margin) trading](#perps-margin-trading).
- **Full coverage** of the Kalshi REST API (108 mapped of 109 operations across 19 resources, OpenAPI v3.29.0) and WebSocket API (12 typed `subscribe_*` channels + escape-hatch).
- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (48 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (23 operations). See [Perps (margin) trading](#perps-margin-trading).
- **FIX protocol**: an async-first FIX engine (FIXT.1.1 / FIX50SP2) for both products — order-entry, drop-copy, market-data, post-trade (prediction), and RFQ (prediction) sessions (plus order-group management over the order-entry session) with typed message models, sequence recovery, and order-book / settlement reassembly. `from kalshi import FixClient` / `MarginFixClient`. See [FIX protocol](#fix-protocol-low-latency-trading).
- **V2 event-market orders**: `create_v2` / `amend_v2` / `decrease_v2` / `cancel_v2` / `cancel_all_v2` plus batched variants on `/portfolio/events/orders/*` — the only order-write surface.
- **Funding & cost introspection**: `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`.
Expand Down Expand Up @@ -171,15 +171,16 @@ async def main() -> None:
asyncio.run(main())
```

Available channels (11 typed + 2 escape-hatch). Eleven have dedicated
Available channels (12 typed + escape-hatch). Twelve have dedicated
`subscribe_*` methods — `subscribe_ticker`, `subscribe_trade`,
`subscribe_orderbook_delta`, `subscribe_fill`, `subscribe_market_positions`,
`subscribe_user_orders`, `subscribe_order_group`,
`subscribe_market_lifecycle`, `subscribe_multivariate_lifecycle`,
`subscribe_communications`, `subscribe_cfbenchmarks_value`. The
AsyncAPI-declared `control_frames` and `root` channels are reachable
through the generic `subscribe(channel, ...)` escape hatch. See
[docs/websockets.md](docs/websockets.md#the-11-channels) for the full
`subscribe_communications`, `subscribe_cfbenchmarks_value`,
`subscribe_cfbenchmarks_value_5hz`. The AsyncAPI-declared `control_frames`,
`root`, and `pyth_value` channels are reachable through the generic
`subscribe(channel, ...)` escape hatch. See
[docs/websockets.md](docs/websockets.md#the-12-channels) for the full
channel table.

## Perps (margin) trading
Expand Down
7 changes: 7 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Shipped

- **v14.0.0 (2026-09-06)** — Spec-drift reconcile (#510 / #511). OpenAPI
3.29.0 content + perps/Klear/AsyncAPI. **Breaking:** perps WS
`MarginFillPayload` / `MarginUserOrderPayload` require `order_source`.
Additive: weather calibrations, FCM-bound API keys, target-balance
`resting_margin_reservation`, FCM `client_order_ids`, historical
`subaccount`, perps `fee_tier_rates`, Klear FCM API keys + member
funding payments, WS `cfbenchmarks_value_5hz`.
- **v13.0.0 (2026-08-28)** — Spec-drift reconcile (#507 / #508). OpenAPI
3.28.0 → 3.29.0. **Breaking:** `Fill` / `MarketPosition` / `Settlement`
require `exchange_index`. Additive: weather index, target-balance
Expand Down
8 changes: 4 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@
A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) prediction
markets API.

- **Full REST coverage** — 107 mapped of 108 operations across 19 resources
- **Full REST coverage** — 108 mapped of 109 operations across 19 resources
(OpenAPI v3.29.0), every kwarg drift-tested against the spec.
- **V2 event-market orders** — new `create_v2` / `amend_v2` / `decrease_v2` /
`cancel_v2` / `cancel_all_v2` family on `/portfolio/events/orders/*`. Legacy `/portfolio/orders`
keeps working; deprecation no earlier than May 6, 2026.
- **Funding + cost introspection** — `portfolio.deposits()`,
`portfolio.withdrawals()`, `portfolio.intra_exchange_transfers()`,
`account.endpoint_costs()`.
- **Full WebSocket coverage** — 11 typed channels with sequence-gap detection,
- **Full WebSocket coverage** — 12 typed channels with sequence-gap detection,
automatic reconnection (with resubscribe-window frame stashing for high-volume
channels), backpressure strategies, and an in-memory orderbook builder.
Async-only — access via `AsyncKalshiClient.ws`.
- **Perps (margin) API** — standalone `PerpsClient` / `AsyncPerpsClient` +
`PerpsWebSocket` for the perpetual-futures exchange (47 REST operations, 6 WS
`PerpsWebSocket` for the perpetual-futures exchange (48 REST operations, 6 WS
channels), and a `KlearClient` for the Self-Clearing-Member settlement API
(18 operations, Bearer token auth). See [Perps](perps.md).
(23 operations, Bearer token auth). See [Perps](perps.md).
- **FIX protocol** — a hand-rolled, async-first FIX engine (FIXT.1.1 / FIX50SP2)
for both products: order-entry, drop-copy, market-data, post-trade (prediction),
and RFQ (prediction) sessions — plus order-group management over the order-entry
Expand Down
38 changes: 38 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
# Migration

## v13.0 → v14.0.0

Reconciles upstream OpenAPI **3.29.0** content drift plus matching perps,
Klear, and AsyncAPI updates (Closes #510, Closes #511). **Breaking** only
for code that constructs perps WS fill / user-order payloads without
`order_source`.

### Response model field changes

- **Perps WS** `MarginFillPayload.order_source` and
`MarginUserOrderPayload.order_source` — required `Literal["user", "system"]`.
Live `subscribe_fill()` / `subscribe_user_orders()` callers are
unaffected.

```python
# Before (constructors / test fixtures):
# MarginFillPayload(..., post_position="15.00")

# After:
MarginFillPayload(..., post_position="15.00", order_source="user")
```

### Added (non-breaking)

- `live_data.weather_calibrations(city)`
- `api_keys.list(*, fcm_subtrader_id)` and `create`/`generate(fcm_subtrader_id=)`
- `portfolio.set_target_balance_allocation(..., resting_margin_reservation=)`
- `fcm.orders(*, client_order_ids=)` (`subtrader_id` now optional if IDs given)
- `historical.positions(*, subaccount=)`
- Perps `margin.fee_tier_rates()`
- Klear FCM API keys + `member_funding_payments`
- WS `subscribe_cfbenchmarks_value_5hz()`

`Event.available_on_brokers` is now optional (the spec dropped the field).

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

## v12.0 → v13.0.0

Reconciles upstream OpenAPI **3.28.0 → 3.29.0**, plus matching perps, Klear,
Expand Down
20 changes: 18 additions & 2 deletions docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async with AsyncPerpsClient.from_env(demo=True) as perps:
| `orders` | `create()`, `get()`, `list()` / `list_all()`, `cancel()`, `cancel_all()`, `decrease()`, `amend()` |
| `order_groups` | `list()`, `get()`, `create()`, `delete()`, `reset()`, `trigger()`, `update_limit()` |
| `portfolio` | `positions()`, `fills()` / `fills_all()`, `trades()` / `trades_all()`, cross/isolated exit triggers |
| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `api_limits()` |
| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `fee_tier_rates()`, `api_limits()` |
| `funding` | `rate_estimate()`, `historical_rates()`, `history()` |
| `transfers` | `transfer_instance()`, `create_subaccount()`, `transfer_subaccount()` |
| `fcm` | `create_subtrader(subtrader_suffix=...)`; `risk_controls` / `update_risk_controls` / `delete_risk_controls` |
Expand Down Expand Up @@ -241,7 +241,23 @@ in the v11.0.0 reconcile.)
`klear.margin.settlement_prices(asset_class="Crypto", settlement_time=...)`
returns a ticker → centicents map at a settlement cycle.
`klear.margin.estimate_maintenance_margin(asset_class="Crypto", positions=[...])`
margins a hypothetical portfolio.
margins a hypothetical portfolio. Optional `date=` (YYYY-MM-DD) and
`clearing_type=` (`"FCM"` / `"SelfClearing"`) select the matrix day and
clearing arrangement.

`klear.margin.member_funding_payments(funding_time=...)` returns the
member's funding payments for one funding execution (distinct from the
obligation-scoped `funding_payments(obligation_id)`).

FCM-bound margin API keys:

```python
keys = klear.margin.list_fcm_api_keys()
created = klear.margin.create_fcm_api_key(
name="desk", public_key=pem, fcm_subtrader_id="user_desk1"
)
klear.margin.delete_fcm_api_key(created.api_key_id)
```

When an `ObligationEntry` inline detail array is capped at 1000 rows, the
matching `*_truncated` flag is set; page the full set via:
Expand Down
15 changes: 12 additions & 3 deletions docs/resources/api-keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Auth required throughout (you need an existing key to manage keys).

| Method | Endpoint |
|---|---|
| `list()` | `GET /api_keys` |
| `create(*, name, public_key, scopes=None, subaccount=None)` | `POST /api_keys` |
| `generate(*, name, scopes=None, subaccount=None)` | `POST /api_keys/generate` |
| `list(*, fcm_subtrader_id=None)` | `GET /api_keys` |
| `create(*, name, public_key, scopes=None, subaccount=None, fcm_subtrader_id=None)` | `POST /api_keys` |
| `generate(*, name, scopes=None, subaccount=None, fcm_subtrader_id=None)` | `POST /api_keys/generate` |
| `delete(api_key)` | `DELETE /api_keys/{api_key}` |

!!! note "Subaccount-scoped keys (spec v3.23.0)"
Expand All @@ -20,6 +20,15 @@ Auth required throughout (you need an existing key to manage keys).
bounded to `0-63` client-side; `ApiKey.subaccount` echoes it back on `list()`
(`None` for account-wide keys).

!!! note "FCM-bound keys"
FCM members can pass `fcm_subtrader_id="{user_id}_{suffix}"` instead of
`subaccount` to bind a key to a single FCM subtrader. The two are
mutually exclusive. A bound key is the institution's trading credential
for that subtrader (FIX + margin WebSocket) and is denied on every REST
endpoint. `list(fcm_subtrader_id=...)` filters to keys bound to that
subtrader. Create/generate may return `warning` when the subtrader has
no initial-margin cap.

## List

```python
Expand Down
8 changes: 4 additions & 4 deletions docs/resources/fcm.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
Futures Commission Merchant routes. **FCM-member accounts only** — non-FCM
calls come back 401/403. Auth required throughout.

`subtrader_id` is the required discriminator on every call — every FCM
request scopes to one subtrader under your member account.
`subtrader_id` is required on positions. On orders it is required **unless**
`client_order_ids` is supplied (the two filters are alternatives).

## Quick reference

| Method | Endpoint |
|---|---|
| `orders(*, subtrader_id, ...)` | `GET /fcm/orders` |
| `orders_all(*, subtrader_id, ...)` | walks `orders` |
| `orders(*, subtrader_id=None, client_order_ids=None, ...)` | `GET /fcm/orders` |
| `orders_all(*, subtrader_id=None, client_order_ids=None, ...)` | walks `orders` |
| `positions(*, subtrader_id, ...)` | `GET /fcm/positions` |

## List orders
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/historical.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ analytics; live trading needs the real-time surfaces.
| `trades(...)` / `trades_all(...)` | `GET /historical/trades` | no |
| `fills(...)` / `fills_all(...)` | `GET /historical/fills` | **yes** |
| `orders(...)` / `orders_all(...)` | `GET /historical/orders` | **yes** |
| `positions(...)` / `positions_all(...)` | `GET /historical/positions` | **yes** |
| `positions(*, subaccount=None, ...)` / `positions_all(*, subaccount=None, ...)` | `GET /historical/positions` | **yes** |

## Cutoff

Expand Down
11 changes: 11 additions & 0 deletions docs/resources/live-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Public — no auth required.
| `batch(milestone_ids, *, include_player_stats=None)` | `GET /live_data/batch` |
| `game_stats(milestone_id)` | `GET /live_data/milestone/{milestone_id}/game_stats` |
| `weather(city, *, from_ts, to, last_sec, detailed)` | `GET /live_data/weather/{city}` |
| `weather_calibrations(city)` | `GET /live_data/weather/{city}/calibrations` |
| `get_typed(milestone_type, milestone_id)` | `GET /live_data/{type}/milestone/{milestone_id}` (legacy) |

## Get one milestone's live data
Expand Down Expand Up @@ -82,6 +83,16 @@ for point in idx.timeseries:
`last_sec` is mutually exclusive with `from_ts`/`to` per spec. `detailed=True`
attaches per-station audit readings on every point.

```python
cals = client.live_data.weather_calibrations("miami")
for rec in cals.calibrations:
print(rec.config_version, rec.effective_at_ms, rec.city_reference_c)
```

`weather_calibrations` returns the launch configuration plus every weekly
offset calibration, ascending by effective time. Units are always Celsius
on this endpoint (the published index value itself remains Fahrenheit).

## Legacy `get_typed`

```python
Expand Down
7 changes: 5 additions & 2 deletions docs/resources/portfolio.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Auth required throughout.
| `intra_exchange_transfers(...)` / `intra_exchange_transfers_all(...)` | `GET /portfolio/intra_exchange_instance_transfers` |
| `get_intra_exchange_transfer(transfer_id)` | `GET /portfolio/intra_exchange_instance_transfers/{transfer_id}` |
| `target_balance_allocation()` | `GET /portfolio/target_balance_allocation` |
| `set_target_balance_allocation(*, allocations)` | `POST /portfolio/target_balance_allocation` |
| `set_target_balance_allocation(*, allocations, resting_margin_reservation=None)` | `POST /portfolio/target_balance_allocation` |

`balance()`, `positions()` / `positions_all()`, `settlements()` /
`settlements_all()`, and `fills()` / `fills_all()` all take an optional
Expand Down Expand Up @@ -235,7 +235,10 @@ client.portfolio.set_target_balance_allocation(
)
```

Percents are integers 0–100. At most 101 allocation rows.
Percents are integers 0–100. At most 101 allocation rows. Optional
`resting_margin_reservation` is `"max"` or `"sum"` (server defaults to
`"sum"`) — collateral an automatic rebalance leaves behind for resting
orders.

`Fill`, `MarketPosition`, and `Settlement` each carry a required
`exchange_index` as of OpenAPI 3.29.0 (SDK v13.0.0). Live list callers
Expand Down
5 changes: 3 additions & 2 deletions docs/websockets.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# WebSocket

The SDK ships an async-only WebSocket client, `KalshiWebSocket`, that covers
the Kalshi market-data WebSocket surface (11 typed channels plus the generic
the Kalshi market-data WebSocket surface (12 typed channels plus the generic
escape hatch). It handles RSA-PSS auth on the upgrade handshake,
per-subscription sequence-gap detection, automatic reconnection with
re-subscription, and a configurable backpressure strategy on each per-channel
Expand Down Expand Up @@ -30,7 +30,7 @@ SDK's perspective on it.
on every delta.
- `on_state_change=` and `on_error=` hooks on the constructor for observability.

## The 11 channels
## The 12 channels

| SDK method | Wire channel | Message `type` field | Message class | Auth |
|---|---|---|---|---|
Expand All @@ -45,6 +45,7 @@ SDK's perspective on it.
| `subscribe_order_group` | `order_group_updates` | `order_group_updates` | `OrderGroupMessage` | private |
| `subscribe_communications` | `communications` | `communications` | `CommunicationsMessage` | private |
| `subscribe_cfbenchmarks_value` | `cfbenchmarks_value` | `cfbenchmarks_value` / `cfbenchmarks_value_indexlist` | `CFBenchmarksValueMessage` / `CFBenchmarksIndexListMessage` | private |
| `subscribe_cfbenchmarks_value_5hz` | `cfbenchmarks_value_5hz` | `cfbenchmarks_value_5hz` / `cfbenchmarks_value_5hz_indexlist` | `CFBenchmarksValue5HzMessage` / `CFBenchmarks5HzIndexListMessage` | private |

The `type` column matters when filtering raw logs — note the singular forms
for `user_order` and `market_position`. The standalone `multivariate` /
Expand Down
Loading
Loading