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

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

## 12.0.0 — 2026-08-16

Reconciles upstream OpenAPI **3.27.0 → 3.28.0**, plus additive perps FCM
risk-control endpoints and a Klear settlement-estimate field, after nightly
contract failures (Closes #503). **Breaking** for constructors of
`TotalRestingOrderValue` that omit the new required breakdown.

### Changed (breaking)

- **`TotalRestingOrderValue.resting_order_value_breakdown`** (`list[IndexedBalance]`,
required) — per-shard resting-order value. Each `IndexedBalance.balance` is a
`DollarDecimal`, not integer cents (same type collision as
`Balance.balance_breakdown`). `client.portfolio.total_resting_order_value()`
callers are unaffected; tests/mocks that construct the model must pass the
list (empty is valid).

### Added

- Optional **`exchange_index`** query filter on:
- `orders.list()` / `orders.list_all()` (`GET /portfolio/orders`)
- `orders.fills()` / `orders.fills_all()` (deprecated aliases)
- `portfolio.positions()` / `portfolio.positions_all()`
- `portfolio.fills()` / `portfolio.fills_all()`
Omit to return results from every shard (unlike `portfolio.balance()`,
which defaults to shard 0 server-side).
- **Perps FCM** initial-margin caps (sync + async):
- `fcm.risk_controls(subtrader_id, market_ticker=)`
- `fcm.update_risk_controls(subtrader_id, im_cap, market_ticker=)`
(or `request=UpdateFCMSubtraderRiskControlsRequest`)
- `fcm.delete_risk_controls(subtrader_id, market_ticker=)`
Models: `FCMSubtraderRiskControls`, `GetFCMSubtraderRiskControlsResponse`,
`UpdateFCMSubtraderRiskControlsRequest`.
- **Klear** `MarketSettlementEstimate.session_avg_price_fp` (`DollarDecimal`,
optional) — session average entry price; omitted when position quantity is
zero.

### Spec notes

- Core OpenAPI `info.version` **3.28.0** (paths 93; 104 operations; 103
mapped). Still unimplemented on the core client:
`POST /portfolio/intra_exchange_instance_transfer` (use
`PerpsClient.transfers.transfer_instance()`).
- AsyncAPI unchanged (14 channels).
- Perps OpenAPI: paths 35→38 (FCM subtrader risk-control GET/PUT/DELETE).
- Perps SCM OpenAPI: `MarketSettlementEstimate.session_avg_price_fp` optional.
Still unimplemented: `GET /margin/large_trader_positions` (surveillance).

## 11.0.1 — 2026-08-09

### Fixed
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.27.0, 104 operations; 103 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)
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.28.0, 104 operations; 103 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)
- Base URL: https://api.elections.kalshi.com/trade-api/v2
- Demo URL: https://demo-api.kalshi.co/trade-api/v2
Expand Down
4 changes: 2 additions & 2 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 (103 mapped of 104 operations across 19 resources, OpenAPI v3.27.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch).
- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (35 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (16 operations). See [Perps (margin) trading](#perps-margin-trading).
- **Full coverage** of the Kalshi REST API (103 mapped of 104 operations across 19 resources, OpenAPI v3.28.0) and WebSocket API (11 typed `subscribe_*` channels + 2 escape-hatch).
- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (38 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (16 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.
- **Funding & cost introspection**: `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`.
Expand Down
6 changes: 6 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Shipped

- **v12.0.0 (2026-08-16)** — Spec-drift reconcile (#503). OpenAPI
3.27.0 → 3.28.0. **Breaking:** `TotalRestingOrderValue` requires
`resting_order_value_breakdown`. Additive: `exchange_index` query on
orders/positions/fills list endpoints; perps FCM
`risk_controls` / `update_risk_controls` / `delete_risk_controls`;
Klear `MarketSettlementEstimate.session_avg_price_fp`.
- **v11.0.0 (2026-08-09)** — Spec-drift reconcile (#499). **Breaking:** removed
Klear `active_obligation()` / `settlement_estimate()` after upstream deleted
the singular endpoints. Additive: Klear paged obligation detail endpoints
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) predi
markets API.

- **Full REST coverage** — 103 mapped of 104 operations across 19 resources
(OpenAPI v3.27.0), every kwarg drift-tested against the spec.
(OpenAPI v3.28.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`
keeps working; deprecation no earlier than May 6, 2026.
Expand All @@ -16,7 +16,7 @@ markets API.
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 (35 REST operations, 6 WS
`PerpsWebSocket` for the perpetual-futures exchange (38 REST operations, 6 WS)
channels), and a `KlearClient` for the Self-Clearing-Member settlement API
(16 operations, Bearer token auth). See [Perps](perps.md).
- **FIX protocol** — a hand-rolled, async-first FIX engine (FIXT.1.1 / FIX50SP2)
Expand Down
40 changes: 40 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# Migration

## v11.0 → v12.0.0

Reconciles upstream OpenAPI **3.27.0 → 3.28.0**, plus additive perps FCM
risk-control endpoints and a Klear settlement-estimate field (Closes #503).
**Breaking** only for code that constructs `TotalRestingOrderValue` without
the new required breakdown.

### Response model field changes

- **`TotalRestingOrderValue.resting_order_value_breakdown`** — required
`list[IndexedBalance]`. Live `portfolio.total_resting_order_value()`
callers are unaffected.

```python
# Before (constructors / test fixtures):
# TotalRestingOrderValue(total_resting_order_value=12345)

# After:
TotalRestingOrderValue(
total_resting_order_value=12345,
resting_order_value_breakdown=[], # or parsed IndexedBalance rows
)
```

Each breakdown row's `.balance` is a `DollarDecimal`, not integer cents —
same type collision as `Balance.balance_breakdown`.

### Added (non-breaking)

- Optional **`exchange_index`** query on `orders.list` / `list_all`,
`portfolio.positions` / `positions_all`, and `portfolio.fills` /
`fills_all` (and the deprecated `orders.fills` aliases). Omit for every
shard.
- **Perps** `fcm.risk_controls()` / `update_risk_controls()` /
`delete_risk_controls()`.
- **Klear** `MarketSettlementEstimate.session_avg_price_fp` (optional).

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

## v10.0 → v11.0.0

Reconciles upstream OpenAPI **3.27.0** content, AsyncAPI trade payload fields,
Expand Down
22 changes: 20 additions & 2 deletions docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +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` |
| `fcm` | `create_subtrader(subtrader_suffix=...)`; `risk_controls` / `update_risk_controls` / `delete_risk_controls` |

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 All @@ -78,6 +78,22 @@ Orders create/cancel/decrease/amend are POSTs/DELETEs and are **never retried**.
once the removal is confirmed permanent. Prediction-API FCM
(`client.fcm.*` on `/fcm/*`) is unchanged.

FCM members can set per-subtrader initial-margin caps. A cap with no
`market_ticker` applies across all markets; a ticker scopes it to one
market. `im_cap` is a non-negative `OrderPrice` (fixed-point dollars):

```python
from decimal import Decimal

caps = perps.fcm.risk_controls(subtrader_id="user_desk1")
perps.fcm.update_risk_controls(
subtrader_id="user_desk1",
im_cap=Decimal("100.0000"),
market_ticker="BTC-PERP",
)
perps.fcm.delete_risk_controls(subtrader_id="user_desk1", market_ticker="BTC-PERP")
```

## Value types & timestamps

- Prices are `DollarDecimal` — `FixedPointDollars` strings with up to 6 decimal
Expand Down Expand Up @@ -228,7 +244,9 @@ 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.
10,000 centicents`); the withdrawal `amount` and
`MarketSettlementEstimate.session_avg_price_fp` are fixed-point dollar
strings.
`klear.margin.withdraw_settlement_balance(amount="500.00")` validates the amount as
positive at construction (the single real-money write) before any request is sent.
The Bearer `access_token` is never logged and is redacted in `repr()` (only the
Expand Down
4 changes: 3 additions & 1 deletion docs/resources/orders.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Reads stay on `/portfolio/orders/*`.
| `amend_v2(order_id, *, request, subaccount)` | `POST /portfolio/events/orders/{order_id}/amend` | never |
| `decrease_v2(order_id, *, request, subaccount)` | `POST /portfolio/events/orders/{order_id}/decrease` | never |
| `get(order_id)` | `GET /portfolio/orders/{order_id}` | yes (GET) |
| `list(...)` / `list_all(...)` | `GET /portfolio/orders` | yes |
| `list(...)` / `list_all(..., exchange_index=None)` | `GET /portfolio/orders` | yes |
| ~~`fills(...)` / `fills_all(...)`~~ | moved to `PortfolioResource` in v3.0.0 — see [Portfolio › Fills](portfolio.md#fills); the old methods remain as deprecated aliases until removal in a future release. |
| `queue_positions(*, market_tickers, event_ticker)` | `GET /portfolio/orders/queue_positions` | yes |
| `queue_position(order_id)` | `GET /portfolio/orders/{order_id}/queue_position` | yes |
Expand Down Expand Up @@ -212,6 +212,8 @@ for order in client.orders.list_all(status="resting"):

`status` accepts an `OrderStatusLiteral`: `"resting"`, `"canceled"`,
`"executed"`. `min_ts` / `max_ts` (Unix seconds) bound by created time.
Optional `exchange_index` (OpenAPI 3.28.0) filters to one shard; omit it
to return orders from every shard.

Fills (`fills` / `fills_all`) moved to `PortfolioResource` in v3.0.0 — see
[Portfolio › Fills](portfolio.md#fills). `client.orders.fills(...)` /
Expand Down
15 changes: 12 additions & 3 deletions docs/resources/portfolio.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Auth required throughout.
| Method | Endpoint |
|---|---|
| `balance(*, subaccount=None, exchange_index=None)` | `GET /portfolio/balance` |
| `positions(*, ...)` | `GET /portfolio/positions` |
| `positions(*, ..., exchange_index=None)` | `GET /portfolio/positions` |
| `settlements(...)` / `settlements_all(...)` | `GET /portfolio/settlements` |
| `fills(...)` / `fills_all(...)` | `GET /portfolio/fills` |
| `fills(...)` / `fills_all(..., exchange_index=None)` | `GET /portfolio/fills` |
| `total_resting_order_value()` | `GET /portfolio/summary/total_resting_order_value` (FCM only) |
| `deposits(*, limit, cursor)` / `deposits_all(*, limit, max_pages)` | `GET /portfolio/deposits` |
| `withdrawals(*, limit, cursor)` / `withdrawals_all(*, limit, max_pages)` | `GET /portfolio/withdrawals` |
Expand All @@ -22,6 +22,9 @@ Auth required throughout.
`subaccount: int` to scope the read to a specific subaccount (omit for the
primary account). `balance()` also takes an optional `exchange_index: int`
(spec v3.24.0) to target a specific exchange shard (defaults to 0 server-side).
`positions()` / `positions_all()` and `fills()` / `fills_all()` take an
optional `exchange_index` filter as of OpenAPI 3.28.0 — omit it to return
rows from every shard.

## Balance

Expand Down Expand Up @@ -153,9 +156,15 @@ Standard `Page[Fill]` pagination — see [Pagination](../pagination.md).

```python
total = client.portfolio.total_resting_order_value()
print(total.total_value)
print(total.total_resting_order_value) # integer cents
for shard in total.resting_order_value_breakdown:
# IndexedBalance.balance is DollarDecimal, not cents
print(shard.exchange_index, shard.balance)
```

`resting_order_value_breakdown` is required as of OpenAPI 3.28.0. Direct
constructors (tests/mocks) must pass it; an empty list is valid.

!!! warning "FCM members only"
Non-FCM accounts get a `403` (mapped to `KalshiAuthError`). Demo mirrors
production behavior here.
Expand Down
2 changes: 1 addition & 1 deletion kalshi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,4 +381,4 @@
"Withdrawal",
]

__version__ = "11.0.1"
__version__ = "12.0.0"
14 changes: 13 additions & 1 deletion kalshi/_contract_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ class ContractEntry:
ContractEntry(
sdk_model="kalshi.models.portfolio.TotalRestingOrderValue",
spec_schema="GetPortfolioRestingOrderTotalValueResponse",
notes="Single int total_resting_order_value in cents",
notes="total_resting_order_value in cents + IndexedBalance breakdown",
),
ContractEntry(
sdk_model="kalshi.models.order_groups.OrderGroup",
Expand Down Expand Up @@ -792,6 +792,18 @@ class ContractEntry:
sdk_model="kalshi.perps.models.fcm.CreateMarginFCMSubtraderResponse",
spec_schema="CreateMarginFCMSubtraderResponse",
),
ContractEntry(
sdk_model="kalshi.perps.models.fcm.FCMSubtraderRiskControls",
spec_schema="FCMSubtraderRiskControls",
),
ContractEntry(
sdk_model="kalshi.perps.models.fcm.GetFCMSubtraderRiskControlsResponse",
spec_schema="GetFCMSubtraderRiskControlsResponse",
),
ContractEntry(
sdk_model="kalshi.perps.models.fcm.UpdateFCMSubtraderRiskControlsRequest",
spec_schema="UpdateFCMSubtraderRiskControlsRequest",
),
]

PERPS_SCM_CONTRACT_MAP: list[ContractEntry] = [
Expand Down
5 changes: 5 additions & 0 deletions kalshi/models/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,14 @@ class TotalRestingOrderValue(BaseModel):

Spec: "intended for use by FCM members (rare)". Non-FCM accounts see
403 on this endpoint (demo audit 2026-04-18).

``resting_order_value_breakdown`` (required as of OpenAPI 3.28.0) splits
the total across exchange shards. Each :class:`IndexedBalance.balance`
is a :class:`~kalshi.types.DollarDecimal`, not integer cents.
"""

total_resting_order_value: int
resting_order_value_breakdown: list[IndexedBalance]

model_config = {"extra": "allow"}

Expand Down
8 changes: 8 additions & 0 deletions kalshi/perps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@
GetMarginRiskParametersResponse,
MarginEnabledResponse,
)
from kalshi.perps.models.fcm import (
FCMSubtraderRiskControls,
GetFCMSubtraderRiskControlsResponse,
UpdateFCMSubtraderRiskControlsRequest,
)
from kalshi.perps.models.funding import (
MarginFundingHistoryEntry,
MarginFundingRate,
Expand Down Expand Up @@ -241,10 +246,12 @@
"ExchangeInstance",
"ExchangeInstanceLiteral",
"ExchangeStatus",
"FCMSubtraderRiskControls",
"FundingPaymentDetail",
"FundingRate",
"FundingResource",
"GetActiveMarginObligationsResponse",
"GetFCMSubtraderRiskControlsResponse",
"GetGuarantyFundBalanceResponse",
"GetMarginBalanceResponse",
"GetMarginFeeTiersResponse",
Expand Down Expand Up @@ -341,6 +348,7 @@
"TickerPrice",
"TimeInForceLiteral",
"TransfersResource",
"UpdateFCMSubtraderRiskControlsRequest",
"UpdateOrderGroupLimitRequest",
"UpdateSubscriptionAction",
"WithdrawSettlementBalanceRequest",
Expand Down
11 changes: 7 additions & 4 deletions kalshi/perps/klear/models/margin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
integer-cents precedent in ``kalshi/models/portfolio.py`` (``Balance.balance``,
``Deposit.amount_cents``). Fixed-point **count** strings appear on
:attr:`SettlementDetail.position_quantity_fp` and
:attr:`FundingPaymentDetail.position_quantity_fp`. The ONLY fixed-point
**dollar-string** fields are :attr:`WithdrawSettlementBalanceRequest.amount` and
:attr:`GetSettlementBalanceWithdrawalResponse.amount` (e.g. ``"500.00"``); those
use :data:`DollarDecimal`.
:attr:`FundingPaymentDetail.position_quantity_fp`. The fixed-point
**dollar-string** fields are :attr:`WithdrawSettlementBalanceRequest.amount`,
:attr:`GetSettlementBalanceWithdrawalResponse.amount`, and
:attr:`MarketSettlementEstimate.session_avg_price_fp` (e.g. ``"500.00"``);
those use :data:`DollarDecimal`.

**Timestamps.** Every REST timestamp is RFC3339 (``format: date-time``) and uses
:class:`pydantic.AwareDatetime`; the single ``format: date`` field
Expand Down Expand Up @@ -277,6 +278,8 @@ class MarketSettlementEstimate(BaseModel):
quantity_centicount: int
variation_margin_centicents: int
notional_value_centicents: int
# Optional fixed-point dollar string; omitted when position quantity is zero.
session_avg_price_fp: DollarDecimal | None = None

model_config = {"extra": "allow"}

Expand Down
8 changes: 8 additions & 0 deletions kalshi/perps/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
GetMarginRiskParametersResponse,
MarginEnabledResponse,
)
from kalshi.perps.models.fcm import (
FCMSubtraderRiskControls,
GetFCMSubtraderRiskControlsResponse,
UpdateFCMSubtraderRiskControlsRequest,
)
from kalshi.perps.models.funding import (
MarginFundingHistoryEntry,
MarginFundingRate,
Expand Down Expand Up @@ -109,6 +114,8 @@
"ExchangeInstance",
"ExchangeInstanceLiteral",
"ExchangeStatus",
"FCMSubtraderRiskControls",
"GetFCMSubtraderRiskControlsResponse",
"GetMarginBalanceResponse",
"GetMarginFeeTiersResponse",
"GetMarginFillsResponse",
Expand Down Expand Up @@ -149,5 +156,6 @@
"SelfTradePreventionType",
"SelfTradePreventionTypeLiteral",
"TimeInForceLiteral",
"UpdateFCMSubtraderRiskControlsRequest",
"UpdateOrderGroupLimitRequest",
]
Loading
Loading