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

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

## 3.3.0 — 2026-06-06

Adds a complete **FIX protocol** subsystem (FIXT.1.1 / FIX50SP2) for both the
prediction and perps products. Additive release: no changes to the existing REST,
WebSocket, or Perps surfaces.

### Added

- **FIX engine (`kalshi.fix`)** — a hand-rolled, async-first FIX client for both
products: `FixClient` (prediction) and `MarginFixClient` (margin, in
`kalshi.perps.fix`), re-exported from the top-level `kalshi` package alongside
`FixConfig`, `FixEnvironment`, and `FixSessionType`. Covers five session types —
order entry (NR/RT), drop copy, market data, post-trade settlement (prediction),
and RFQ (prediction), plus order-group management over the order-entry session —
on a session state machine with
RSA-PSS logon (reusing the REST key), heartbeat / test-request liveness,
sequence tracking with gap-fill / resend on the retransmission sessions, and
AWS-style full-jitter reconnect. (Epic #402.)
- **Typed FIX messages** — Pydantic v2 models for every Kalshi FIX message across
order entry, order groups, market data, RFQ/quoting, drop copy, and market
settlement, with `DollarDecimal` / `FixedPointCount` money types (no float
drift) and a central inbound dispatch via `decode_app_message`.
- **`FixOrderBook`** — aggregated order-book reconstruction from market-data
snapshots + incrementals; **`SettlementReassembler`** — paginated
settlement-report reassembly.
- **`FixSession.on_decode_error`** hook + `decode_app_message_strict` — surface a
registered-but-malformed inbound message instead of silently dropping it (#432).
- **FIX documentation** — new `docs/fix.md` guide, plus FIX coverage in the
README, errors, authentication, configuration, environment-variables, and the
API reference.
- **FIX dictionary drift test** — `specs/kalshi-fix-dictionary.xml` checked in
with a model↔dictionary drift test, the FIX analogue of the REST contract-drift
suite (#437).

### Fixed

- Documentation accuracy pass: `fcm.positions_all()` is now documented (the FCM
page previously stated it did not exist); the `portfolio` reads document their
`subaccount` parameter; the `markets.is_block_trade` version note is corrected
to SDK v3.1.0; the `OrderPrice` and `MultiplierDecimal` types are documented;
and the docs landing page's WebSocket channel count is corrected to 11.

## 3.2.0 — 2026-06-05

Adds full SDK support for the Kalshi **Perps (margin) API** — a separate
Expand Down Expand Up @@ -81,7 +123,7 @@ prediction-API surface. Additive release: no changes to `KalshiClient`.
- README / `docs/index.md` banners note the perps surface (34 REST operations,
6 WS channels, 10 SCM operations).

## 3.1.0 — 2026-06-03
## 3.1.0 — 2026-06-04

OpenAPI + AsyncAPI spec sync from v3.19.0 → v3.20.0 (`#385`). Adds the
new `GET /events/fee_changes` endpoint plus three smaller additive
Expand Down
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) predi

- **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).
- **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()`.
- **Sync and async** clients sharing one transport — no thread-pool wrapping.
Expand Down Expand Up @@ -250,6 +251,42 @@ obligations, withdrawals) is a third surface exposed via `KlearClient`, which us
**cookie-session + MFA** login (`client.login(email=..., password=..., code=...)`)
rather than RSA-PSS. Full guide: [docs/perps.md](docs/perps.md).

## FIX protocol (low-latency trading)

For persistent, low-latency sessions the SDK includes 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 session — with typed message models,
automatic logon/heartbeat/sequence recovery, and order-book / settlement
reassembly. It reuses the same RSA-PSS key as the REST client (`KALSHI_*` for
prediction, `KALSHI_PERPS_*` for margin).

```python
import asyncio
from decimal import Decimal
from kalshi import FixClient, FixEnvironment
from kalshi.fix import NewOrderSingle, ExecutionReport, Side, decode_app_message

async def main() -> None:
async def on_message(raw) -> None:
msg = decode_app_message(raw)
if isinstance(msg, ExecutionReport):
print(msg.cl_ord_id, msg.exec_type, msg.ord_status)

client = FixClient.from_env(environment=FixEnvironment.DEMO)
async with client.order_entry(on_message=on_message) as session:
await session.send(NewOrderSingle(
cl_ord_id="order-1", symbol="KXNBAGAME-26MAY25NYKCLE-NYK",
side=Side.BUY_YES, order_qty=Decimal("10"), price=Decimal("0.55"),
))
await asyncio.sleep(2)

asyncio.run(main())
```

Prediction uses `FixClient`; margin uses `MarginFixClient`. Full guide:
[docs/fix.md](docs/fix.md).

## Error handling

All SDK errors inherit from `KalshiError`:
Expand Down
8 changes: 8 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Shipped

- **v3.3.0 (2026-06-06)** — complete **FIX protocol** subsystem (FIXT.1.1 /
FIX50SP2) for both products: `FixClient` (prediction) + `MarginFixClient`
(margin) over a hand-rolled async session engine, with five session types
(order entry, drop copy, market data, post-trade settlement, RFQ; order groups
ride the order-entry session), typed message models, order-book / settlement
reassembly, and a
model↔dictionary drift test (epic #402; follow-ups #432, #437). New
[FIX guide](docs/fix.md). Additive — no changes to REST / WebSocket / Perps.
- **v3.2.0 (2026-06-05)** — full **Perps (margin) API** support: standalone
`PerpsClient` / `AsyncPerpsClient` (REST), `PerpsWebSocket`, and the
Self-Clearing-Member `KlearClient` (cookie-session + MFA settlement API),
Expand Down
7 changes: 7 additions & 0 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,11 @@ API is unchanged for sync-transport callers.
directly if you construct `KalshiAuth` standalone (e.g. for the WebSocket
with no REST client alongside).

## FIX authentication

The [FIX subsystem](fix.md) reuses this same RSA-PSS key — the logon signature
rides the FIX `RawData` field. `FixClient` reads the same `KALSHI_*` variables (or
`from_auth(auth)` reuses an existing `KalshiAuth`); the margin `MarginFixClient`
uses the separate `KALSHI_PERPS_*` key. No FIX-specific credentials are needed.

See the [API reference](reference.md) for the full surface.
7 changes: 7 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ async with AsyncKalshiClient.from_env() as client:
...
```

## FIX connectivity

The [FIX subsystem](fix.md) has its own `FixConfig` (host/port per product +
environment + session type, TLS, heartbeat, retry policy, and FIX logon options).
It is independent of `KalshiConfig`; see [FIX → Connectivity &
configuration](fix.md#connectivity-configuration).

## Reference

::: kalshi.config.KalshiConfig
12 changes: 11 additions & 1 deletion docs/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ these:
| `KALSHI_KEY_ID` | unset | Key ID. If unset, `from_env()` returns an unauthenticated client. |
| `KALSHI_PRIVATE_KEY` | unset | PEM string. **Conflicts** with `KALSHI_PRIVATE_KEY_PATH` — set exactly one. |
| `KALSHI_PRIVATE_KEY_PATH` | unset | Path to PEM file. `~` is expanded. Conflicts with `KALSHI_PRIVATE_KEY`. |
| `KALSHI_PRIVATE_KEY_PASSPHRASE` | unset | Passphrase for an encrypted PEM (read by `try_from_env()`). |
| `KALSHI_PRIVATE_KEY_PASSPHRASE` | unset | Passphrase for an encrypted PEM (read by `from_env()` / `try_from_env()`, and by `FixClient.from_env()`). |
| `KALSHI_DEMO` | `false` | The exact string `true` (case-insensitive) selects the demo URLs. Other values (`1`, `yes`, `on`) are **not** recognized. |
| `KALSHI_API_BASE_URL` | unset | Overrides `base_url` entirely. Wins over `KALSHI_DEMO`. |
| `KALSHI_ALLOW_UNKNOWN_HOST` | unset | Set to `1` to allow `base_url`/`ws_base_url` hosts outside `{api.elections.kalshi.com, demo-api.kalshi.co, localhost}`. Default-fail prevents typos like `kalsi.com` from silently signing to an attacker (#250). |
Expand Down Expand Up @@ -80,3 +80,13 @@ read only environment selectors:
| `KALSHI_KLEAR_DEMO` | `false` | `true` (case-insensitive) selects the demo Klear endpoint. |
| `KALSHI_KLEAR_API_BASE_URL` | unset | Overrides the Klear `base_url`. |
| `KALSHI_KLEAR_ALLOW_UNKNOWN_HOST` | unset | Set to `1` to allow non-Kalshi Klear hosts. |

## FIX

The [FIX subsystem](fix.md) reuses the credential variables above:
`FixClient.from_env()` reads the prediction `KALSHI_KEY_ID` /
`KALSHI_PRIVATE_KEY[_PATH]` / `KALSHI_PRIVATE_KEY_PASSPHRASE`, and
`MarginFixClient.from_env()` reads the perps `KALSHI_PERPS_*` set. FIX host/port is
resolved by `FixConfig` (not an env var); use a `host=`/`port=` override plus
`allow_unknown_host=True` or `KALSHI_FIX_ALLOW_UNKNOWN_HOST=1` only for a mock or
local TLS proxy.
41 changes: 41 additions & 0 deletions docs/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,31 @@ client-side id. You won't see `KalshiConnectionError` from inside `async for`;
you'll see it from the `connect()` context manager if the socket can't be
re-established at all.

## FIX errors

The [FIX subsystem](fix.md) has its own sub-hierarchy under `KalshiFixError`
(itself a subclass of `KalshiError`, so `except KalshiError` still catches it).
Import them from `kalshi.fix`. FIX is a TCP/TLS protocol with no HTTP status, so
`status_code` is always `None`.

- **`FixConnectionError`** — TCP/TLS connect failed, was refused, or the
reconnect attempts were exhausted. Original transport error via `__cause__`.
- **`FixLogonError`** — the gateway rejected the logon; `.reason` carries the
`Text` from the Logout when present (bad signature, CompID, SendingTime skew, a
missing `ResetSeqNumFlag=Y`).
- **`FixSequenceError`** — an unrecoverable sequence condition (a backwards
`MsgSeqNum`, or a forward gap on a non-retransmission session); carries
`.expected` / `.received`.
- **`FixCodecError`** — a malformed frame (`BeginString` / `BodyLength` /
`CheckSum` / `tag=value`); `.raw` holds the offending bytes when available.
- **`FixDecodeError`** — a *registered* inbound message failed schema validation
(one off-spec field); carries `.raw` + `.msg_type`, original via `__cause__`.
See [FIX → Error handling](fix.md#error-handling).
- **`FixRejectError`** — the gateway rejected a message we sent (session Reject
35=3 or BusinessMessageReject 35=j); carries the structured reject fields.
- **`FixSessionError`** — a session-level protocol violation or unexpected
lifecycle event.

## See also

- [Retries & idempotency](retries.md) — what does and doesn't get retried, why
Expand Down Expand Up @@ -202,3 +227,19 @@ re-established at all.
::: kalshi.errors.KalshiOrderbookUnavailableError

::: kalshi.errors.KalshiSubscriptionError

::: kalshi.fix.KalshiFixError

::: kalshi.fix.FixConnectionError

::: kalshi.fix.FixLogonError

::: kalshi.fix.FixSequenceError

::: kalshi.fix.FixCodecError

::: kalshi.fix.FixDecodeError

::: kalshi.fix.FixRejectError

::: kalshi.fix.FixSessionError
Loading