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

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

## 9.0.0 — 2026-07-31

Syncs upstream core OpenAPI **3.26.0 → 3.27.0** (paths stay 92; 103 operations /
102 mapped) and the matching perps + SCM OpenAPI content (Closes #492).
**Breaking** for callers of the subaccount position-transfer API.

### Removed (breaking)

- **`subaccounts.transfer_position()`** (sync + async) and models
`ApplySubaccountPositionTransferRequest` /
`ApplySubaccountPositionTransferResponse`. Upstream deleted
`POST /portfolio/subaccounts/positions/transfer` and the matching schemas
from OpenAPI 3.27.0. Cash `transfer()` is unchanged.

### Added

- **`live_data.get_event(event_ticker, *, range=None)`** (sync + async) —
`GET /live_data/events/{event_ticker}`. Returns `EventLiveData` (`type`,
`details`, optional `is_historical` / `default_range` / `range_options`).
Models: `EventLiveData`, `GetEventLiveDataResponse`.
- **`DecreaseOrderV2Request.market_ticker`** (`str | None`) — required by the
server when `exchange_index` is `-1` (auto-route by ticker).
- **`Series.exchange_index`** (`int | None`) — exchange shard for the series.
- **WS** `MarketLifecyclePayload.exchange_index` (`int | None`) — optional on
market-lifecycle `created` events (AsyncAPI).
- **Perps** `PerpsClient.fcm.create_subtrader(...)` (sync + async) —
`POST /margin/fcm/subtraders`. Body:
`CreateMarginFCMSubtraderRequest(subtrader_suffix=...)` (`^[a-z0-9]{1,16}$`);
returns `CreateMarginFCMSubtraderResponse.subtrader_id`.
- **Klear/SCM** subtrader groups on `KlearClient.margin` (sync + async):
- `list_subtrader_groups()` — `GET /fcm/margin/subtrader_groups`
- `create_subtrader_group(subtrader_ids=...)` — `POST /fcm/margin/subtrader_groups`
- `update_subtrader_group(group_id, subtrader_ids=...)` — `PUT .../{group_id}`
- `delete_subtrader_group(group_id)` — `DELETE .../{group_id}`
Models: `MarginSubtraderGroup`, `GetMarginSubtraderGroupsResponse`,
`CreateMarginSubtraderGroupRequest` / `Response`,
`UpdateMarginSubtraderGroupRequest`.
- **Klear settlement estimates**: optional `group_breakdowns` /
`omitted_group_count` on `GetSettlementEstimateResponse` and
`AssetClassSettlementEstimate`; optional `margin_group_id` on
`MaintenanceMarginDetail`.

### Spec notes

- Core OpenAPI `info.version` **3.27.0** (103 operations / 102 mapped). Still
unimplemented: `POST /portfolio/intra_exchange_instance_transfer` (use
`PerpsClient.transfers.transfer_instance()` on the margin product).
- Perps OpenAPI: +1 operation (`POST /margin/fcm/subtraders`).
- Perps SCM OpenAPI: +4 subtrader-group operations.

## 8.0.0 — 2026-07-27

Reconciles upstream core OpenAPI / AsyncAPI content under version string
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ tests/

## API Reference

- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.26.0, 103 operations; 102 mapped in the core SDK — `POST /portfolio/intra_exchange_instance_transfer` is currently not available upstream)
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.27.0, 103 operations; 102 mapped in the core SDK — `POST /portfolio/intra_exchange_instance_transfer` is currently not available upstream)
- AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (13 WebSocket channels)
- Base URL: https://api.elections.kalshi.com/trade-api/v2
- Demo URL: https://demo-api.kalshi.co/trade-api/v2
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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 (102 operations across 19 resources, OpenAPI v3.26.0) and WebSocket API (12 typed `subscribe_*` channels + 2 escape-hatch).
- **Full coverage** of the Kalshi REST API (102 operations across 19 resources, OpenAPI v3.27.0) and WebSocket API (12 typed `subscribe_*` channels + 2 escape-hatch).
- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (34 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (11 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` plus batched variants on `/portfolio/events/orders/*` — the only order-write surface.
Expand Down
5 changes: 5 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Shipped

- **v9.0.0 (2026-07-31)** — OpenAPI sync 3.26.0 → 3.27.0 (#492). **Breaking:**
removed `subaccounts.transfer_position()` after upstream deleted position
transfers. Additive: `live_data.get_event()`,
`DecreaseOrderV2Request.market_ticker`, `Series.exchange_index`, perps
`fcm.create_subtrader()`, Klear subtrader groups + settlement group fields.
- **v8.0.0 (2026-07-27)** — Spec-drift reconcile under OpenAPI 3.26.0
(#489 / #490). **Breaking:** removed the settlement-advance subaccount
surface added in v7.4.0 (`lock_settlement_advance` /
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) prediction
markets API.

- **Full REST coverage** — 102 operations across 19 resources (OpenAPI v3.26.0),
- **Full REST coverage** — 102 operations across 19 resources (OpenAPI v3.27.0),
every kwarg drift-tested against the spec.
- **V2 event-market orders** — new `create_v2` / `amend_v2` / `decrease_v2` /
`cancel_v2` family on `/portfolio/events/orders/*`. Legacy `/portfolio/orders`
Expand Down
54 changes: 54 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
# Migration

## v8.0 → v9.0.0

Syncs the SDK to core OpenAPI **3.27.0** (and the matching perps / SCM
content) after Kalshi removed subaccount **position** transfers and added
event-keyed live data plus FCM/SCM group surfaces (Closes #492).
**Breaking** for callers of `subaccounts.transfer_position()`.

### Removed

- **`subaccounts.transfer_position()`** (sync + async) and
`ApplySubaccountPositionTransferRequest` /
`ApplySubaccountPositionTransferResponse`. Upstream deleted
`POST /portfolio/subaccounts/positions/transfer` and the matching schemas.

```python
# No longer available — the upstream endpoint 404s:
# client.subaccounts.transfer_position(
# client_transfer_id=...,
# from_subaccount=0,
# to_subaccount=1,
# market_ticker="...",
# side="yes",
# count=1,
# price=Decimal("0.50"),
# )

# Cash transfers are unchanged:
client.subaccounts.transfer(
client_transfer_id=...,
from_subaccount=0,
to_subaccount=1,
amount_cents=500,
)
```

### Added (non-breaking)

- **`live_data.get_event(event_ticker, *, range=None)`** → `EventLiveData`
(`GET /live_data/events/{event_ticker}`).
- **`DecreaseOrderV2Request.market_ticker`** (optional; required when
`exchange_index=-1`).
- **`Series.exchange_index`** (optional).
- **WS** `MarketLifecyclePayload.exchange_index` (optional).
- **Perps** `fcm.create_subtrader(subtrader_suffix=...)`
(`POST /margin/fcm/subtraders`).
- **Klear/SCM** subtrader groups: `list_subtrader_groups`,
`create_subtrader_group`, `update_subtrader_group`,
`delete_subtrader_group` under `klear.margin`, plus optional
`group_breakdowns` / `omitted_group_count` on settlement estimates and
`margin_group_id` on `MaintenanceMarginDetail`.

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

## v7.4 → v8.0.0

Reconciles upstream core OpenAPI / AsyncAPI content under version string
Expand Down
14 changes: 13 additions & 1 deletion docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ async with AsyncPerpsClient.from_env(demo=True) as perps:
| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `api_limits()` |
| `funding` | `rate_estimate()`, `historical_rates()`, `history()` |
| `transfers` | `transfer_instance()`, `create_subaccount()`, `transfer_subaccount()` |
| `fcm` | `create_subtrader(subtrader_suffix=...)` — `POST /margin/fcm/subtraders` |

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**.
Expand Down Expand Up @@ -201,7 +202,18 @@ Settlement-estimate responses also expose optional
`omitted_subtrader_count` (`int | None`, SDK v7.3.0) on
`GetSettlementEstimateResponse` and each `AssetClassSettlementEstimate` — the
number of subtraders left out of `subtrader_breakdowns` (their amounts remain
in `user_breakdown`).
in `user_breakdown`). SDK v9.0.0 adds optional `group_breakdowns` /
`omitted_group_count` for netted subtrader groups, and optional
`margin_group_id` on `MaintenanceMarginDetail`.

Subtrader groups (SDK v9.0.0) — margined as one netted portfolio:

```python
groups = klear.margin.list_subtrader_groups()
created = klear.margin.create_subtrader_group(subtrader_ids=["st-a", "st-b"])
klear.margin.update_subtrader_group(created.group_id, subtrader_ids=["st-a", "st-c"])
klear.margin.delete_subtrader_group(created.group_id)
```

Money fields on the Klear margin schemas are integer **centicents** (`1 USD =
10,000 centicents`); only the withdrawal `amount` is a fixed-point dollar string.
Expand Down
1 change: 0 additions & 1 deletion docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ every exception class.

::: kalshi.models.subaccounts.ApplySubaccountTransferRequest

::: kalshi.models.subaccounts.ApplySubaccountPositionTransferRequest

::: kalshi.models.subaccounts.UpdateSubaccountNettingRequest

Expand Down
1 change: 0 additions & 1 deletion docs/request-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ exposed by each resource method stays in lockstep with the OpenAPI spec.
| `client.order_groups.create` | `CreateOrderGroupRequest` |
| `client.order_groups.update_limit` | `UpdateOrderGroupLimitRequest` |
| `client.subaccounts.transfer` | `ApplySubaccountTransferRequest` |
| `client.subaccounts.transfer_position` | `ApplySubaccountPositionTransferRequest` |
| `client.subaccounts.update_netting` | `UpdateSubaccountNettingRequest` |

All are importable from the top-level `kalshi` package.
Expand Down
43 changes: 30 additions & 13 deletions docs/resources/live-data.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Live data

Real-time state attached to a [milestone](milestones.md) — score, clock,
period, weather, etc. Pair with milestones to render live UI alongside
Kalshi markets.
Real-time state for markets — either keyed by a [milestone](milestones.md)
(score, clock, period, weather, …) or by an **event ticker** (crypto charts,
commodity timeseries, weather observations).

Public — no auth required.

Expand All @@ -11,17 +11,32 @@ Public — no auth required.
| Method | Endpoint |
|---|---|
| `get(milestone_id, *, include_player_stats=None)` | `GET /live_data/milestone/{milestone_id}` |
| `get_event(event_ticker, *, range=None)` | `GET /live_data/events/{event_ticker}` |
| `batch(milestone_ids, *, include_player_stats=None)` | `GET /live_data/batch` |
| `game_stats(milestone_id)` | `GET /live_data/milestone/{milestone_id}/game_stats` |
| `get_typed(live_data_type, milestone_id)` | `GET /live_data/{type}/milestone/{milestone_id}` (legacy) |
| `get_typed(milestone_type, milestone_id)` | `GET /live_data/{type}/milestone/{milestone_id}` (legacy) |

## Get one milestone's live data

```python
live = client.live_data.get("ms_abc", include_player_stats=True)
print(live.live_data_type, live.payload)
print(live.type, live.milestone_id, live.details)
```

`LiveData.details` is a loose `dict[str, Any]` — the shape varies by
`type` (football vs political race vs weather).

## Get event-keyed live data

```python
live = client.live_data.get_event("KXBTCD-25", range="1h")
print(live.type, live.details, live.default_range, live.range_options)
print(live.is_historical) # True for matured crypto snapshots
```

`EventLiveData` has no `milestone_id`. Optional `range` is a chart-window
hint (`15min`, `1h`, `1d`, …) when the underlying type supports it.

## Batch (up to 100 milestones)

```python
Expand All @@ -30,7 +45,7 @@ entries = client.live_data.batch(
include_player_stats=False,
)
for entry in entries:
print(entry.milestone_id, entry.payload)
print(entry.milestone_id, entry.type, entry.details)
```

`milestone_ids` is required and non-empty — passing `[]` raises `ValueError`.
Expand All @@ -39,17 +54,18 @@ Cap: 100 ids per call.
## Game stats / play-by-play

```python
pbp = client.live_data.game_stats("ms_abc")
if pbp.pbp is None:
resp = client.live_data.game_stats("ms_abc")
if resp.pbp is None:
print("no play-by-play for this milestone type")
else:
for period in pbp.pbp.periods:
for play in period.plays:
print(play.timestamp, play.description)
for period in resp.pbp.periods:
for event in period.events:
print(event) # free-form dict; shape varies by sport
```

`game_stats` works only for sports milestones with play-by-play coverage.
Other milestone types return `pbp=None`.
Other milestone types return `pbp=None`. Each period's `events` is a list of
loose dicts (no fixed play schema upstream).

## Legacy `get_typed`

Expand All @@ -59,7 +75,8 @@ live = client.live_data.get_typed("sports_game", "ms_abc")

Prefer `get()` over `get_typed()`. The latter wraps the legacy
`/live_data/{type}/milestone/{id}` path and is retained only for callers that
still depend on it.
still depend on it. The Python kwarg is `milestone_type` (not `type`) to avoid
shadowing the built-in; the wire path still uses `{type}`.

## Reference

Expand Down
29 changes: 5 additions & 24 deletions docs/resources/subaccounts.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ primary; `1`–`63` are numbered extras. Auth required throughout.
|---|---|
| `create(*, exchange_index=None)` | `POST /portfolio/subaccounts` |
| `transfer(*, client_transfer_id, from_subaccount, to_subaccount, amount_cents)` | `POST /portfolio/subaccounts/transfer` |
| `transfer_position(*, client_transfer_id, from_subaccount, to_subaccount, market_ticker, side, count, price)` | `POST /portfolio/subaccounts/positions/transfer` |
| `list_balances()` | `GET /portfolio/subaccounts/balances` |
| `list_transfers(*, cursor=None, limit=None)` | `GET /portfolio/subaccounts/transfers` |
| `list_all_transfers(*, limit=None, max_pages=None)` | walks `list_transfers` |
Expand Down Expand Up @@ -50,27 +49,10 @@ client.subaccounts.transfer(
`client_transfer_id` accepts a `UUID` or a `str`. On a network failure, retry
with the same id; the server dedupes.

## Transfer a position between subaccounts

Spec v3.23.0 added `transfer_position()` for moving open contracts (not cash)
between subaccounts. Unlike `transfer()`, it returns a `position_transfer_id`.
`price` (spec v3.24.0 renamed it from `price_cents`) is the per-contract cost
basis in **fixed-point dollars** (0–1.0) — pass a `Decimal`:

```python
from decimal import Decimal

resp = client.subaccounts.transfer_position(
client_transfer_id=uuid.uuid4(), # or str
from_subaccount=0,
to_subaccount=1,
market_ticker="KXBTC-25DEC31-B100000",
side="yes", # "yes" | "no"
count=10, # contracts (> 0)
price=Decimal("0.55"), # per-contract dollars, 0–1.0
)
print(resp.position_transfer_id)
```
!!! note "Position transfers removed in OpenAPI 3.27.0"
`subaccounts.transfer_position()` and the
`POST /portfolio/subaccounts/positions/transfer` endpoint were deleted
upstream and removed from the SDK in v9.0.0. Only cash transfers remain.

## List balances

Expand All @@ -96,8 +78,7 @@ for t in client.subaccounts.list_all_transfers():
```

Standard `Page[SubaccountTransfer]` pagination. `t.created_ts` is Unix
seconds. Rows are **cash transfers only** — position moves use
`transfer_position()` and are not listed here.
seconds. Rows are **cash transfers only**.

## Netting

Expand Down
10 changes: 5 additions & 5 deletions kalshi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
Announcement,
ApiKey,
ApiUsageLevelGrant,
ApplySubaccountPositionTransferRequest,
ApplySubaccountPositionTransferResponse,
ApplySubaccountTransferRequest,
AssociatedEvent,
Balance,
Expand Down Expand Up @@ -77,6 +75,7 @@
Event,
EventCandlesticks,
EventFeeChange,
EventLiveData,
EventMetadata,
EventPosition,
EventStatusLiteral,
Expand All @@ -89,6 +88,7 @@
GetApiKeysResponse,
GetBlockTradeProposalsResponse,
GetCommunicationsIDResponse,
GetEventLiveDataResponse,
GetFiltersBySportsResponse,
GetGameStatsResponse,
GetIncentiveProgramsResponse,
Expand Down Expand Up @@ -203,8 +203,6 @@
"Announcement",
"ApiKey",
"ApiUsageLevelGrant",
"ApplySubaccountPositionTransferRequest",
"ApplySubaccountPositionTransferResponse",
"ApplySubaccountTransferRequest",
"AssociatedEvent",
"AsyncBlockTradeProposalsResource",
Expand Down Expand Up @@ -250,6 +248,7 @@
"Event",
"EventCandlesticks",
"EventFeeChange",
"EventLiveData",
"EventMetadata",
"EventPosition",
"EventStatusLiteral",
Expand All @@ -266,6 +265,7 @@
"GetApiKeysResponse",
"GetBlockTradeProposalsResponse",
"GetCommunicationsIDResponse",
"GetEventLiveDataResponse",
"GetFiltersBySportsResponse",
"GetGameStatsResponse",
"GetIncentiveProgramsResponse",
Expand Down Expand Up @@ -379,4 +379,4 @@
"Withdrawal",
]

__version__ = "8.0.0"
__version__ = "9.0.0"
Loading
Loading