diff --git a/README.md b/README.md index 88f7d0a..0f03357 100644 --- a/README.md +++ b/README.md @@ -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 (99 operations across 19 resources, OpenAPI v3.20.0) and WebSocket API (11 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 (10 operations). See [Perps (margin) trading](#perps-margin-trading). +- **Full coverage** of the Kalshi REST API (99 operations across 19 resources, OpenAPI v3.20.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. - **Funding & cost introspection**: `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`. @@ -193,15 +193,16 @@ async def main() -> None: asyncio.run(main()) ``` -Available channels (11 typed + 2 escape-hatch). Eleven have dedicated +Available channels (12 typed + 2 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`, -`subscribe_multivariate_lifecycle`, `subscribe_communications`. The +`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 +[docs/websockets.md](docs/websockets.md#the-12-channels) for the full channel table. ## Perps (margin) trading @@ -248,7 +249,7 @@ Prices are `DollarDecimal` (FixedPointDollars, up to 6 decimals); counts are **WebSocket timestamps are Unix epoch milliseconds** (`*_ms` fields). The Self-Clearing-Member "Klear" settlement API (margin reports, settlement balances, obligations, withdrawals) is a third surface exposed via `KlearClient`, which uses -**cookie-session + MFA** login (`client.login(email=..., password=..., code=...)`) +**Bearer token** auth (`KlearClient(admin_user_id=..., access_token=...)`) rather than RSA-PSS. Full guide: [docs/perps.md](docs/perps.md). ## FIX protocol (low-latency trading) diff --git a/docs/concepts.md b/docs/concepts.md index 1481bc5..2a1ae4b 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -143,7 +143,7 @@ See [Incentive programs](resources/incentive-programs.md). ## Subaccount A logical wallet partition under your main account. Used to isolate strategies -or risk pools. Subaccount `0` is your primary account; `1`–`32` are numbered +or risk pools. Subaccount `0` is your primary account; `1`–`63` are numbered extras. Most resource methods accept a `subaccount=` kwarg to route the call. See [Subaccounts](resources/subaccounts.md). diff --git a/docs/index.md b/docs/index.md index b44a5c5..ed10560 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,14 +10,14 @@ markets API. keeps working; deprecation no earlier than May 6, 2026. - **Funding + cost introspection** — `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`. -- **Full WebSocket coverage** — 11 channels with sequence-gap detection, automatic +- **Full WebSocket coverage** — 12 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 (34 REST operations, 6 WS channels), and a `KlearClient` for the Self-Clearing-Member settlement API - (10 operations, cookie-session + MFA auth). See [Perps](perps.md). + (9 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 diff --git a/docs/migration.md b/docs/migration.md index 2652415..accab8d 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1,5 +1,26 @@ # Migration +## v3 → v4.0.0 + +v4.0.0 has **one breaking change** — the Self-Clearing-Member "Klear" API +migrated from cookie-session login to a pre-generated **Bearer token**, because +upstream removed `POST /log_in`. Everything else in v4.0.0 is additive +(`cfbenchmarks_value` WS channel, `AccountResource.upgrade()`, +`AccountApiLimits.grants`, perps `api_limits()`, perps market notional/leverage +fields). The prediction and perps trade-api surfaces are unchanged. + +For full BEFORE/AFTER snippets see [v3-to-v4.md](migrations/v3-to-v4.md). Quick +summary of the Klear break: + +| Was (v3.x) | Now (v4.0.0) | +|---|---| +| `KlearClient(demo=True)` then `client.login(email=..., password=..., code=...)` | `KlearClient(admin_user_id=..., access_token=..., demo=True)` | +| `KlearClient.from_env()` (URL routing only — no credential env vars) | `KlearClient.from_env()` reads `KALSHI_KLEAR_ADMIN_USER_ID` / `KALSHI_KLEAR_ACCESS_TOKEN` | +| `client.is_authenticated`, `client.auth`, `LogInRequest`, `LogInResponse` | removed | + +Generate the token / find your admin user id at +(the "Security" page). + ## v2.7 → v3.0 v3.0.0 is the first major release in the v3 line. It renames three groups of diff --git a/docs/migrations/v3-to-v4.md b/docs/migrations/v3-to-v4.md new file mode 100644 index 0000000..37a2ff0 --- /dev/null +++ b/docs/migrations/v3-to-v4.md @@ -0,0 +1,104 @@ +# Migrating from v3.x to v4.0.0 + +v4.0.0 has **one breaking change**: the Self-Clearing-Member "Klear" API +(`KlearClient` / `AsyncKlearClient`) switched from cookie-session login to a +pre-generated **Bearer token**, because upstream removed `POST /log_in`. + +If you do not use the Klear API, **v4.0.0 requires no code changes** — everything +else is additive. The prediction and perps trade-api surfaces (RSA-PSS auth, all +REST resources, the WebSocket and FIX clients) are unchanged. + +## TL;DR — Klear cheat sheet + +| Find (v3.x) | Replace (v4.0.0) | +|---|---| +| `KlearClient(demo=True)` + `client.login(email=..., password=...)` (or `code=...` for MFA) | `KlearClient(admin_user_id=..., access_token=..., demo=True)` | +| `await client.login(...)` / `client.is_authenticated` | (removed — credentials are supplied at construction) | +| `from kalshi import LogInRequest, LogInResponse` | (removed) | +| `client.auth.log_in(...)` | (removed) | + +Generate the Bearer token and find your admin user id at + (the "Security" page). Treat the token as a secret. + +## 1. Klear (SCM) Bearer authentication (breaking) + +Upstream removed `POST /log_in` and the cookie-session flow. The Klear API now +authenticates with a static header on every request: + +``` +Authorization: Bearer : +``` + +`KlearClient` / `AsyncKlearClient` now **require** `admin_user_id` and +`access_token` (keyword-only) at construction. `KlearAuth` is now a Bearer +credential holder rather than a session-state tracker. + +### Before (v3.x) + +```python +from kalshi import KlearClient + +with KlearClient(demo=True) as klear: + resp = klear.login(email="me@example.com", password="...") + if resp.required_mfa_method: # MFA challenge + klear.login(email="me@example.com", password="...", code="123456") + assert klear.is_authenticated + reports = klear.margin.margin_reports(start_date="2026-01-01", end_date="2026-02-01") +``` + +### After (v4.0.0) + +```python +from kalshi import KlearClient + +# Credentials at construction: +with KlearClient( + admin_user_id="your-admin-user-id", + access_token="your-bearer-token", + demo=True, +) as klear: + reports = klear.margin.margin_reports(start_date="2026-01-01", end_date="2026-02-01") + +# Or from the environment (KALSHI_KLEAR_ADMIN_USER_ID / KALSHI_KLEAR_ACCESS_TOKEN): +with KlearClient.from_env(demo=True) as klear: + bal = klear.margin.settlement_balance() +``` + +### Removed + +- `KlearClient.login()` / `AsyncKlearClient.login()` +- `KlearClient.is_authenticated` property +- the `client.auth` resource (`AuthResource` / `AsyncAuthResource`) +- the `LogInRequest` / `LogInResponse` models (and their `kalshi` / `kalshi.perps` + re-exports) + +### Changed + +- `KlearAuth()` (no-arg session holder) → `KlearAuth(admin_user_id, access_token)`, + a Bearer-credential holder. The `access_token` is redacted from `repr()`. +- New environment variables: `KALSHI_KLEAR_ADMIN_USER_ID`, `KALSHI_KLEAR_ACCESS_TOKEN` + (read by `KlearClient.from_env()`). The old credentials were never read from the + environment. + +## 2. Additive changes (no migration needed) + +These are new surfaces — existing code keeps working: + +- **`cfbenchmarks_value` WebSocket channel** — stream CF Benchmarks reference index + values (e.g. `BRTI`, `ETHUSD_RTI`) via + `KalshiWebSocket.subscribe_cfbenchmarks_value(index_ids=[...])`. New models + (`CFBenchmarksValueMessage`, `CFBenchmarksValuePayload`, `CFBenchmarksAvgData`, + `CFBenchmarksIndexListMessage`, `CFBenchmarksIndexListPayload`) are exported from + `kalshi.ws.models`. See [WebSocket](../websockets.md#cf-benchmarks-index-values). +- **`AccountResource.upgrade()`** — `POST /account/api_usage_level/upgrade` requests + a permanent Advanced API usage-level grant. See + [Account](../resources/account.md#api-usage-level-grants). +- **`AccountApiLimits.grants`** — a new field listing active usage-level grants, + plus a new exported `ApiUsageLevelGrant` model. +- **`MarginAccountResource.api_limits()`** — `GET /account/limits/perps` for the + Perps API tier limits (reuses `AccountApiLimits`). +- **Perps market notional/leverage fields** — `MarginMarket` gains + `leverage_estimates` and `*_notional_value` fields; `MarginMarketCandlestick` + and the margin ticker WS payload gain notional-value fields. + +The subaccount range documented in prose is now 1–63 (no validation change). diff --git a/docs/perps.md b/docs/perps.md index 3aedebe..c69080f 100644 --- a/docs/perps.md +++ b/docs/perps.md @@ -63,7 +63,7 @@ async with AsyncPerpsClient.from_env(demo=True) as perps: | `orders` | `create()`, `get()`, `list()` / `list_all()`, `cancel()`, `decrease()`, `amend()`, `list_fcm()` / `list_all_fcm()` | | `order_groups` | `list()`, `get()`, `create()`, `delete()`, `reset()`, `trigger()`, `update_limit()` | | `portfolio` | `positions()`, `fills()` / `fills_all()`, `trades()` / `trades_all()` | -| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()` | +| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `api_limits()` | | `funding` | `rate_estimate()`, `historical_rates()`, `history()` | | `transfers` | `transfer_instance()`, `create_subaccount()`, `transfer_subaccount()` | @@ -82,6 +82,13 @@ Orders create/cancel/decrease/amend are POSTs/DELETEs and are **never retried**. - **WebSocket** timestamps are Unix epoch **milliseconds** on `*_ms`-suffixed fields (`ts_ms`, `created_ts_ms`, `next_funding_time_ms`, …) — a real parsing difference from the event-contract WS. +- **Notional values** — `MarginMarket` (REST) and the margin ticker WS payload + carry optional `volume_notional_value` / `volume_24h_notional_value` / + `open_interest_notional_value` (`DollarDecimal | None`); `MarginMarketCandlestick` + carries the same fields as **required** (inherent to a settled historical + record). `MarginMarket.leverage_estimates` maps notional position sizes + (`"1000"`, `"10000"`, …) to `MultiplierDecimal` leverage, or `None` when margin + config / price data is unavailable. ## Funding mechanics @@ -112,6 +119,11 @@ for pos in risk.positions: print(pos.market_ticker, pos.position_leverage, pos.estimated_liquidation_price) ``` +`perps.margin.api_limits()` (`GET /account/limits/perps`) returns the Perps API +tier limits in the same shape as the prediction API's `client.account.limits()` +(an `AccountApiLimits` with `usage_tier`, `read`/`write` token buckets, and the +`grants` list of `ApiUsageLevelGrant`). + ## WebSocket streaming ```python @@ -129,8 +141,8 @@ Six data channels — `orderbook_delta` (snapshot + delta, sequenced), `ticker`, `trade`, `fill`, `user_orders`, `order_group_updates`. The connection, sequence-gap detection, reconnect, and backpressure machinery are reused from the event-contract WS stack (see [WebSocket](websockets.md)); the perps orderbook is `bid` / `ask`. -The equities-only channels (`market_positions`, `multivariate*`, `communications`, -`market_lifecycle_v2`) have no perps counterpart. +The prediction-only channels (`market_positions`, `multivariate*`, `communications`, +`market_lifecycle_v2`, `cfbenchmarks_value`) have no perps counterpart. ## Self-Clearing-Member "Klear" API @@ -155,7 +167,8 @@ 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. `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. -Credentials and the session cookie are never logged or shown in `repr()`. +The Bearer `access_token` is never logged and is redacted in `repr()` (only the +non-secret `admin_user_id` is shown). ## Perps over FIX diff --git a/docs/resources/account.md b/docs/resources/account.md index e6f2ea2..7faaead 100644 --- a/docs/resources/account.md +++ b/docs/resources/account.md @@ -10,6 +10,7 @@ Auth required. |---|---| | `limits()` | `GET /account/limits` | | `endpoint_costs()` | `GET /account/endpoint_costs` | +| `upgrade()` | `POST /account/api_usage_level/upgrade` | ## Read tier limits @@ -23,7 +24,8 @@ print(limits.write.bucket_capacity, limits.write.refill_rate) `AccountApiLimits.read` and `.write` are `RateLimit` objects with `bucket_capacity` and `refill_rate` fields (token-bucket parameters). Use them to drive client-side throttling if you fan out many concurrent -calls. +calls. `AccountApiLimits.grants` (new in v4.0.0) lists the caller's active +usage-level grants — see [API usage-level grants](#api-usage-level-grants) below. !!! note "Differs from the OpenAPI spec shape" The published spec describes `read_limit` and `write_limit` as integers; @@ -47,6 +49,36 @@ Endpoints not present in `endpoint_costs` use `default_cost`. Batch endpoints typically appear here with a per-item multiplier (e.g. `POST /portfolio/orders/batched` costs ~10 tokens per order in the batch). +## API usage-level grants + +New in v4.0.0. `AccountApiLimits.grants` is a list of `ApiUsageLevelGrant` +(exported from the top-level `kalshi` package) describing the caller's active +usage-level grants across exchange lanes. Each grant has: + +- `exchange_instance` — the exchange lane: `"event_contract"` or `"margined"`. +- `level` — the API usage level the grant confers (e.g. `"premier"`, + `"paragon"`, `"prime"`). +- `source` — how it was created: `"volume"` (earned from trading volume) or + `"manual"` (assigned by Kalshi). +- `expires_ts` — Unix-seconds expiry, or `None` for a permanent grant. + +```python +limits = client.account.limits() +for grant in limits.grants: + print(grant.exchange_instance, grant.level, grant.source, grant.expires_ts) +``` + +`upgrade()` requests a **permanent Advanced** API usage-level grant +(`POST /account/api_usage_level/upgrade`). It requires that at least one of your +last 100 Predictions orders was API-created; otherwise the server returns 403 +(mapped to `KalshiAuthError`). It returns `None` on success (HTTP 201) — re-read +`limits()` to see the resulting grant: + +```python +client.account.upgrade() +print(client.account.limits().grants) +``` + ## Reference ::: kalshi.resources.account.AccountResource diff --git a/docs/resources/subaccounts.md b/docs/resources/subaccounts.md index 1215aa8..e22616f 100644 --- a/docs/resources/subaccounts.md +++ b/docs/resources/subaccounts.md @@ -1,7 +1,7 @@ # Subaccounts Logical wallet partitions under your main account. Subaccount `0` is your -primary; `1`–`32` are numbered extras. Auth required throughout. +primary; `1`–`63` are numbered extras. Auth required throughout. ## Quick reference diff --git a/docs/websockets.md b/docs/websockets.md index f92b87b..5f1156a 100644 --- a/docs/websockets.md +++ b/docs/websockets.md @@ -1,7 +1,7 @@ # WebSocket The SDK ships an async-only WebSocket client, `KalshiWebSocket`, that covers -all 11 Kalshi channels. It handles RSA-PSS auth on the upgrade handshake, +all 12 Kalshi channels. 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 queue. @@ -29,7 +29,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 | |---|---|---|---|---| @@ -44,6 +44,7 @@ SDK's perspective on it. | `subscribe_market_positions` | `market_positions` | `market_position` (singular) | `MarketPositionsMessage` | private | | `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 | The `type` column matters when filtering raw logs — note the singular forms for `user_order`, `market_position`, and the `multivariate_lookup` / @@ -67,8 +68,8 @@ for `user_order`, `market_position`, and the `multivariate_lookup` / print(msg.msg.market_ticker, msg.msg.event_type) ``` - This is a second message **type** on the same channel — the channel count - stays 11. The override payload mirrors the REST + This is a second message **type** on the same channel — it does not add a + channel. The override payload mirrors the REST [`EventFeeChange`](resources/events.md#event-fee-changes): `EventFeeUpdatePayload` carries `event_ticker`, `fee_type_override`, and `fee_multiplier_override` (the latter two `None` when the override is @@ -78,6 +79,40 @@ Two channels carry monotonic `seq` numbers and have built-in sequence-gap recovery: `orderbook_delta` (which delivers both snapshot and delta envelopes under one subscription) and `order_group_updates`. +## CF Benchmarks index values + +The auth-required `cfbenchmarks_value` channel (new in v4.0.0) streams CF +Benchmarks reference index values — e.g. `BRTI` (Bitcoin Real-Time Index) and +`ETHUSD_RTI` — each with a trailing 60-second average and, only in the final +minute before a quarter-hour close (`:00`/`:15`/`:30`/`:45`), a quarter-hour +windowed average. + +Seed the index list at subscribe time with `index_ids` (`["all"]` tracks every +available index). The stream yields a **union** of `CFBenchmarksValueMessage` +(data) and `CFBenchmarksIndexListMessage` (the response to an `indexlist` +action), so discriminate with `isinstance` (or `msg.type`) before reading +`msg.msg`. The `data` field is the raw upstream CF Benchmarks JSON frame as a +string — call `json.loads(...)` to parse it. + +```python +import json +from kalshi.ws.models import CFBenchmarksIndexListMessage, CFBenchmarksValueMessage + +async for msg in session.subscribe_cfbenchmarks_value(index_ids=["BRTI", "ETHUSD_RTI"]): + if isinstance(msg, CFBenchmarksValueMessage): + frame = json.loads(msg.msg.data) # raw upstream frame + avg60 = msg.msg.avg_60s_data.value # trailing 60s average (Decimal) + q15 = msg.msg.last_60s_windowed_average_15min # None outside the final minute + else: # CFBenchmarksIndexListMessage + print(msg.msg.index_ids) # available index IDs +``` + +Subscribing with no `index_ids` yields nothing until indices are added; this +channel does not accept `market_ticker`/`market_tickers`. The +`CFBenchmarksValueMessage`, `CFBenchmarksValuePayload`, `CFBenchmarksAvgData`, +`CFBenchmarksIndexListMessage`, and `CFBenchmarksIndexListPayload` models are +exported from `kalshi.ws.models`. + ## Connect and subscribe ```python diff --git a/mkdocs.yml b/mkdocs.yml index 594004f..2b14496 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -106,5 +106,6 @@ nav: - Testing: testing.md - Migration: - Overview: migration.md + - v3 → v4: migrations/v3-to-v4.md - v2 → v3: migrations/v2-to-v3.md - API Reference: reference.md