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

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

## 5.0.0 — 2026-06-26

Syncs the upstream OpenAPI spec **3.21.0 → 3.22.0** (#454, #458). The headline
change is **breaking**: Kalshi removed the V1 order-write endpoints from the
spec, so the SDK removes the V1 order methods and their models. Order writes now
go exclusively through the V2 `/portfolio/events/orders` family (the `*_v2`
methods, which have existed since 3.18.0). See `docs/migration.md` for the
V1 → V2 mapping.

### Removed (breaking)

- **V1 order-write methods** on `client.orders` (sync + async): `create`,
`cancel`, `batch_create`, `batch_cancel`, `amend`, `decrease`. The underlying
endpoints (`POST/DELETE /portfolio/orders`, `/portfolio/orders/batched`,
`/portfolio/orders/{id}/amend`, `/portfolio/orders/{id}/decrease`) were
removed from the spec in 3.22.0. Use the `*_v2` equivalents instead.
- **V1 order models** (no longer exported from `kalshi` / `kalshi.models`):
`CreateOrderRequest`, `AmendOrderRequest`, `AmendOrderResponse`,
`DecreaseOrderRequest`, `BatchCreateOrdersRequest`, `BatchCreateOrdersResponse`,
`BatchCreateOrdersResponseEntry`, `BatchCancelOrdersRequest`,
`BatchCancelOrdersResponse`, `BatchCancelOrdersResponseEntry`,
`BatchCancelOrdersRequestOrder`, and the `ActionLiteral` (`buy`/`sell`) alias.
- **Dead quote filters** — `event_ticker` and `market_ticker` were removed from
`GET /communications/quotes` upstream, so they are removed from
`client.communications.quotes.list` / `list_all` and the
`communications.list_quotes` / `list_all_quotes` facade methods.

### Added

- **RFQ-scoped quote actions** (spec 3.22.0) on `client.communications.quotes`
(sync + async): `accept_for_rfq(rfq_id, quote_id, ...)`,
`confirm_for_rfq(rfq_id, quote_id)`, `delete_for_rfq(rfq_id, quote_id)` —
backing `PUT/DELETE /communications/rfqs/{rfq_id}/quotes/{quote_id}[/accept|/confirm]`.
- **`cancel_v2` `market_ticker` query param** — required when `exchange_index`
is `-1` (auto-route by ticker).
- **`BatchCancelOrdersV2RequestOrder.market_ticker`** — same auto-route semantics
for batch V2 cancels.
- **`SubaccountBalance.exchange_index`** — the exchange shard a balance is held on.
- **Perps SCM** (`kalshi.perps.klear`): new `MarketSettlementEstimate` model;
`SettlementEstimate.positions` (per-market breakdown map); and
`GetSettlementEstimateResponse.prev_settlement_prices` (market → last
settlement price, centicents).

## 4.2.0 — 2026-06-19

Reconciles in-place upstream spec drift detected by the nightly run (#451):
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.21.0, 104 operations)
- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.22.0, 101 operations)
- 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
64 changes: 21 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ 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 (104 operations across 19 resources, OpenAPI v3.21.0) and WebSocket API (12 typed `subscribe_*` channels + 2 escape-hatch).
- **Full coverage** of the Kalshi REST API (101 operations across 19 resources, OpenAPI v3.22.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 (9 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/*`. Legacy `/portfolio/orders` keeps working — deprecated no earlier than May 6, 2026.
- **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()`.
- **Sync and async** clients sharing one transport — no thread-pool wrapping.
- **Typed end-to-end**: Pydantic v2 models, `mypy --strict` clean, ships `py.typed`. `Literal` types on fixed-enum kwargs.
Expand Down Expand Up @@ -113,44 +113,9 @@ with KalshiClient(demo=True) as client:

## Placing orders

```python
from kalshi import KalshiClient

with KalshiClient.from_env() as client:
order = client.orders.create(
ticker="EXAMPLE-25-T",
side="yes",
action="buy",
count=10,
yes_price="0.65", # 65 cents
time_in_force="good_till_canceled",
client_order_id="my-uuid", # idempotency key
)
print(order.order_id, order.status)
```

Prices are decimal dollars (e.g. `"0.65"`) per the Kalshi spec. Internally
the SDK uses `Decimal` via the `DollarDecimal` type — never `float`.

Every POST/PUT/DELETE-with-body method also accepts a pre-built request model
as an alternative to individual kwargs (useful for programmatic order
construction):

```python
from kalshi import CreateOrderRequest

client.orders.create(request=CreateOrderRequest(
ticker="EXAMPLE-25-T", side="yes", action="buy",
count=10, yes_price="0.65",
))
```

### V2 event-market orders

Spec v3.18.0 introduced the V2 family on `/portfolio/events/orders/*` —
event-scoped semantics with single-book `bid`/`ask` sides and fixed-point
dollar prices. Legacy `/portfolio/orders` keeps working and will be
deprecated no earlier than May 6, 2026.
Orders are written through the V2 event-market family on
`/portfolio/events/orders/*` — event-scoped semantics with single-book
`bid`/`ask` sides and fixed-point dollar prices.

```python
import uuid
Expand All @@ -163,15 +128,28 @@ with KalshiClient.from_env() as client:
client_order_id=str(uuid.uuid4()), # required + server idempotency key
side="bid", # BookSideLiteral: "bid" | "ask"
count=Decimal("10"),
price=Decimal("0.50"),
price=Decimal("0.50"), # 50 cents
time_in_force="good_till_canceled",
self_trade_prevention_type="taker_at_cross",
))
print(resp.order_id, resp.remaining_count, resp.fill_count)
```

The V2 surface is model-only (no kwarg overload); pass a fully-constructed
request model. See [V2 orders docs](https://texascoding.github.io/kalshi-python-sdk/resources/orders/#v2-event-market-orders) for amend/decrease/batch variants.
Prices and counts are `Decimal` — never `float`. Internally the SDK uses the
`DollarDecimal` type for prices (FixedPointDollars on the wire). `side` is the
book side (`"bid"` / `"ask"`), not `"yes"` / `"no"`, and `client_order_id` is
required on `CreateOrderV2Request` (the server uses it for idempotency).

The V2 surface is **model-only** — there is no kwarg overload. Every write
takes a fully-constructed request model whose `model_config = {"extra":
"forbid"}` rejects phantom keys at construction time. Cancel takes the order id
directly:

```python
client.orders.cancel_v2(resp.order_id)
```

See [V2 orders docs](https://texascoding.github.io/kalshi-python-sdk/resources/orders/#v2-event-market-orders) for `amend_v2` / `decrease_v2` / `batch_create_v2` / `batch_cancel_v2`.

## WebSocket streaming

Expand Down
58 changes: 31 additions & 27 deletions docs/concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@ The three-level ticker hierarchy:
Every market belongs to exactly one event; every event belongs to exactly one
series.

## YES, NO, side, action
## YES, NO, book side

A Kalshi market trades two complementary contracts that always sum to $1:
**YES** and **NO**. Each trade has a `side` (which contract) and an `action`:
**YES** and **NO**. Orders are placed against a single order book, and the
`side` you specify is the **book side**, not the contract:

- `side="yes"`, `action="buy"` — buying YES.
- `side="yes"`, `action="sell"` — selling YES.
- `side="no"`, `action="buy"` — buying NO (equivalent to selling YES against
the orderbook, but accounted separately).
- `side="bid"` — you are bidding (buying into the book).
- `side="ask"` — you are asking (selling into the book).

These are `Literal` types — see [Types & literals](types.md).
`BookSideLiteral` is a `Literal["bid", "ask"]` — see
[Types & literals](types.md).

## Prices

Prices live in `[0.00, 1.00]` and represent dollars (a YES at $0.65 implies a
65% market-implied probability). Always pass them as strings or `Decimal`:

```python
order = client.orders.create(..., yes_price="0.65")
order = client.orders.create(..., yes_price=Decimal("0.65"))
from kalshi.models.orders import CreateOrderV2Request

CreateOrderV2Request(..., price="0.65")
CreateOrderV2Request(..., price=Decimal("0.65"))
```

Float is a footgun; the SDK accepts it but normalizes through `str()` to avoid
Expand Down Expand Up @@ -74,12 +76,11 @@ separate, future change.
A few fields are **integer cents**, not dollars:

- `Balance.balance` / `portfolio_value` — cents.
- `CreateOrderRequest.buy_max_cost` — cents.
- `ApplySubaccountTransferRequest.amount_cents` — cents.

These are typed `int`. Passing a `Decimal` or `float` raises `ValueError` at
construction. The rule: anything with `_cents` / `buy_max_cost` is cents;
anything with `_dollars` or `yes_price` / `no_price` is `Decimal` dollars.
construction. The rule: anything with `_cents` is cents; anything with
`_dollars` or a `price` field is `Decimal` dollars.

## Order, fill, position, settlement

Expand All @@ -89,13 +90,11 @@ anything with `_dollars` or `yes_price` / `no_price` is `Decimal` dollars.
- **Position** — your aggregate exposure on a market (signed by side).
- **Settlement** — what the exchange paid out when the market resolved.

Orders come in two families:

- **V1** — `/portfolio/orders/*`. Yes/no sides, paired `yes_price` /
`no_price`. Stable surface; deprecation no earlier than May 6, 2026.
- **V2** — `/portfolio/events/orders/*`. Event-scoped, single-book
`bid` / `ask` sides, single `price` field, required `client_order_id`
acting as an idempotency key. Use this for new event-market integrations.
Orders are written through the **V2** surface — `/portfolio/events/orders/*`.
It is event-scoped with single-book `bid` / `ask` sides, a single `price`
field, and a required `client_order_id` that acts as an idempotency key.
Every write builds a request model (e.g. `CreateOrderV2Request`) — there is
no kwarg overload, so always construct the model and pass it as `request=`.

See [Orders](resources/orders.md), [Portfolio](resources/portfolio.md).

Expand Down Expand Up @@ -173,14 +172,19 @@ doesn't belong on every request (set those via ``KalshiConfig.extra_headers``).

```python
import uuid

order = client.orders.create(
ticker="KXPRES-24-DJT",
side="yes",
action="buy",
count=1,
yes_price="0.65",
client_order_id="cli-1",
from decimal import Decimal
from kalshi.models.orders import CreateOrderV2Request

order = client.orders.create_v2(
request=CreateOrderV2Request(
ticker="KXPRES-24-DJT",
client_order_id="cli-1",
side="bid",
count=Decimal("1"),
price=Decimal("0.65"),
time_in_force="fill_or_kill",
self_trade_prevention_type="taker_at_cross",
),
extra_headers={"Idempotency-Key": str(uuid.uuid4())},
)
```
Expand Down
36 changes: 21 additions & 15 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,34 +99,40 @@ asyncio.run(main())

## Place an order (demo)

Order writes go through the V2 `/portfolio/events/orders` family. There is no
keyword overload — you always build a request model and pass it as `request=`:

```python
import uuid
from decimal import Decimal
from kalshi import KalshiClient
from kalshi.models import CreateOrderV2Request

with KalshiClient.from_env() as client:
order = client.orders.create(
ticker="EXAMPLE-25-T",
side="yes",
action="buy",
count=10,
yes_price="0.65", # 65¢ — strings or Decimals, never float
time_in_force="good_till_canceled",
client_order_id=str(uuid.uuid4()), # idempotency key (see below)
order = client.orders.create_v2(
request=CreateOrderV2Request(
ticker="EXAMPLE-25-T",
client_order_id=str(uuid.uuid4()), # idempotency key (see below)
side="bid", # book side: "bid"/"ask", not "yes"/"no"
count=Decimal("10"),
price=Decimal("0.65"), # 65¢ — strings or Decimals, never float
time_in_force="good_till_canceled",
self_trade_prevention_type="taker_at_cross",
)
)
print(order.order_id, order.status)
print(order.order_id, order.fill_count, order.remaining_count)
```

!!! warning "POST is never retried automatically"
The transport never retries `POST` (or `DELETE`) requests, to avoid duplicate
orders. Pass a fresh `client_order_id` on every `create()` call so you can safely
orders. Pass a fresh `client_order_id` on every `create_v2()` call so you can safely
retry from your application layer without double-filling. See
[Retries & idempotency](retries.md).

!!! warning "`buy_max_cost` is integer cents, not dollars"
`CreateOrderRequest.buy_max_cost` is `int` cents — `500` means $5.00. Passing a
`Decimal` or `float` raises `ValueError` at construction. Other price fields
(`yes_price`, `no_price`) are `DollarDecimal` and accept strings or
`Decimal`.
!!! note "`side` is the book side, not the outcome"
`CreateOrderV2Request.side` is `"bid"` or `"ask"` (the resting book side), not
`"yes"`/`"no"`. `client_order_id` is required on the model, and `count` and
`price` are `Decimal` — passing a `float` for a price is rejected.

Prices are decimal dollars per the Kalshi spec. Internally the SDK uses
`Decimal` via the [`DollarDecimal`](types.md) type — never `float`.
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** — 104 operations across 19 resources (OpenAPI v3.21.0),
- **Full REST coverage** — 101 operations across 19 resources (OpenAPI v3.22.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
71 changes: 71 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,76 @@
# Migration

## v4 → v5.0.0

**Breaking: the V1 order-write API is gone.** Kalshi removed the V1 order
endpoints from the OpenAPI spec in 3.22.0, so the SDK removed the matching
methods and models. Order writes now go exclusively through the V2
`/portfolio/events/orders` family (the `*_v2` methods, available since 3.18.0).

Read endpoints are unchanged: `orders.get`, `orders.list`, `orders.list_all`,
`orders.queue_positions`, `orders.queue_position`, and `portfolio.fills` all keep
working exactly as before.

### Removed methods → replacement

| Removed (v4) | Use instead (v5) |
| ------------------------------------ | -------------------------------------------------- |
| `orders.create(...)` | `orders.create_v2(request=CreateOrderV2Request(...))` |
| `orders.cancel(order_id)` | `orders.cancel_v2(order_id, ...)` |
| `orders.amend(order_id, ...)` | `orders.amend_v2(order_id, request=AmendOrderV2Request(...))` |
| `orders.decrease(order_id, ...)` | `orders.decrease_v2(order_id, request=DecreaseOrderV2Request(...))` |
| `orders.batch_create(orders=[...])` | `orders.batch_create_v2(request=BatchCreateOrdersV2Request(orders=[...]))` |
| `orders.batch_cancel(orders=[...])` | `orders.batch_cancel_v2(request=BatchCancelOrdersV2Request(orders=[...]))` |

The V2 models use a single `price` with a `side` of `"bid"`/`"ask"` (book side),
rather than the V1 `yes_price` / `no_price` + `yes`/`no`:

```python
from decimal import Decimal
from kalshi.models import CreateOrderV2Request

# v4 (removed)
# order = client.orders.create(ticker="MKT-A", side="yes", action="buy", count=1, yes_price="0.56")

# v5
resp = client.orders.create_v2(
request=CreateOrderV2Request(
ticker="MKT-A",
client_order_id="my-idempotency-key",
side="bid", # book side, not yes/no
count=Decimal("1"),
price=Decimal("0.56"),
time_in_force="good_till_canceled",
self_trade_prevention_type="taker_at_cross",
)
)
```

### Removed models

These are no longer exported from `kalshi` / `kalshi.models`:
`CreateOrderRequest`, `AmendOrderRequest`, `AmendOrderResponse`,
`DecreaseOrderRequest`, `BatchCreateOrdersRequest`, `BatchCreateOrdersResponse`,
`BatchCreateOrdersResponseEntry`, `BatchCancelOrdersRequest`,
`BatchCancelOrdersResponse`, `BatchCancelOrdersResponseEntry`,
`BatchCancelOrdersRequestOrder`, and the `ActionLiteral` alias. Use the `…V2…`
models instead.

### Other breaking changes

- `communications.quotes.list` / `list_all` (and the `communications.list_quotes`
/ `list_all_quotes` facades) no longer accept `event_ticker` / `market_ticker`
— those filters were removed from `GET /communications/quotes` upstream.

### New in 5.0.0

- RFQ-scoped quote actions: `communications.quotes.accept_for_rfq`,
`confirm_for_rfq`, and `delete_for_rfq` (take both `rfq_id` and `quote_id`).
- `cancel_v2(..., market_ticker=...)` and `BatchCancelOrdersV2RequestOrder.market_ticker`
for `-1` (auto-route) exchange indices.
- `SubaccountBalance.exchange_index`; perps SCM `MarketSettlementEstimate`,
`SettlementEstimate.positions`, `GetSettlementEstimateResponse.prev_settlement_prices`.

## v3 → v4.0.0

v4.0.0 has **one breaking change** — the Self-Clearing-Member "Klear" API
Expand Down
Loading
Loading