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

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

## 22.2.0 — 2026-10-20

### Added

- Perps **`margin.corporate_actions(*, ticker=, market_id=)`** (sync +
async) — `GET /margin/corporate_actions`. Lists scheduled notices and
notices updated in the last seven days for margin markets (cash
dividends, special dividends, spin-offs, stock splits, final
settlements, ticker changes). Optional `ticker` and `market_id`
filters; omit both for all margin markets. Public market data (no
auth). Amounts are `DollarDecimal`. Cancelled notices omit terms and
`execution_time`. (Closes #550).

## 22.1.0 — 2026-10-17

Reconciles nightly spec drift (#547). Additive and optional only — no
Expand Down
34 changes: 27 additions & 7 deletions docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async with AsyncPerpsClient.from_env(demo=True) as perps:
| `orders` | `create()`, `get()`, `list()` / `list_all()`, `cancel()`, `cancel_all()`, `decrease()`, `amend()` |
| `order_groups` | `list()`, `get()`, `create()`, `delete()`, `reset()`, `trigger()`, `update_limit()` |
| `portfolio` | `positions()`, `fills()` / `fills_all()`, `trades()` / `trades_all()`, cross/isolated exit triggers |
| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `fee_tier_rates()`, `api_limits()` |
| `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `fee_tier_rates()`, `api_limits()`, `corporate_actions()` |
| `funding` | `rate_estimate()`, `historical_rates()`, `premium_index()`, `history()` |
| `transfers` | `transfer_instance()`, `create_subaccount()`, `transfer_subaccount()` |
| `fcm` | `create_subtrader(subtrader_suffix=..., require_category_cap=...)`; `risk_controls` / `update_risk_controls` / `delete_risk_controls`; `update_notional_risk_limit` / `delete_notional_risk_limit` |
Expand Down Expand Up @@ -254,6 +254,22 @@ 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`).

`perps.margin.corporate_actions()` (`GET /margin/corporate_actions`) lists
scheduled notices and notices updated in the last seven days. Optional
`ticker` / `market_id` filters; omit both for all margin markets. Public
market data (no auth). Scheduled, applied, and lapsed notices include
exactly one terms object matching `type` (`cash`, `split`,
`final_settlement`, or `ticker_change`). Cancelled notices omit terms and
`execution_time`. Amounts are `DollarDecimal` US dollar strings.

```python
notices = perps.margin.corporate_actions(ticker="AAPL-PERP")
for action in notices.corporate_actions:
print(action.id, action.type, action.status)
if action.cash is not None:
print(action.cash.cash_per_contract, action.cash.ex_date)
```

## WebSocket streaming

```python
Expand All @@ -267,12 +283,16 @@ async with ws.connect() as session:
print(msg.msg.funding_rate.rate, msg.msg.funding_rate.next_funding_time_ms)
```

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 prediction-only channels (`market_positions`, `multivariate*`, `communications`,
`market_lifecycle_v2`, `cfbenchmarks_value`) have no perps counterpart.
Seven data channels — `orderbook_delta` (snapshot + delta, sequenced), `ticker`,
`trade`, `fill`, `user_orders`, `order_group_updates`, `corporate_actions`. 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 prediction-only channels
(`market_positions`, `multivariate*`, `communications`, `market_lifecycle_v2`,
`cfbenchmarks_value`) have no perps counterpart. `corporate_actions` streams
applied split / final-settlement / ticker-change notices (cash actions are
REST-only); subscribe with `session.subscribe(channel="corporate_actions")`.
Upcoming and recent notices are listed by `margin.corporate_actions()`.

## Self-Clearing-Member "Klear" API

Expand Down
2 changes: 1 addition & 1 deletion kalshi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,4 +431,4 @@
"Withdrawal",
]

__version__ = "22.1.0"
__version__ = "22.2.0"
24 changes: 24 additions & 0 deletions kalshi/_contract_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,30 @@ class ContractEntry:
sdk_model="kalshi.perps.models.margin_account.GetMarginFeeTierRatesResponse",
spec_schema="GetMarginFeeTierRatesResponse",
),
ContractEntry(
sdk_model="kalshi.perps.models.margin_account.GetMarginCorporateActionsResponse",
spec_schema="GetMarginCorporateActionsResponse",
),
ContractEntry(
sdk_model="kalshi.perps.models.margin_account.MarginCorporateAction",
spec_schema="MarginCorporateAction",
),
ContractEntry(
sdk_model="kalshi.perps.models.margin_account.MarginCorporateActionCashTerms",
spec_schema="MarginCorporateActionCashTerms",
),
ContractEntry(
sdk_model="kalshi.perps.models.margin_account.MarginCorporateActionSplitTerms",
spec_schema="MarginCorporateActionSplitTerms",
),
ContractEntry(
sdk_model="kalshi.perps.models.margin_account.MarginCorporateActionFinalSettlementTerms",
spec_schema="MarginCorporateActionFinalSettlementTerms",
),
ContractEntry(
sdk_model="kalshi.perps.models.margin_account.MarginCorporateActionTickerChangeTerms",
spec_schema="MarginCorporateActionTickerChangeTerms",
),
ContractEntry(
# /account/limits/perps returns the same shape as the prediction API's
# /account/limits, so the perps resource reuses kalshi.models.account.AccountApiLimits.
Expand Down
16 changes: 16 additions & 0 deletions kalshi/perps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,17 @@
from kalshi.perps.models.margin_account import (
FeeScheduleLiteral,
GetMarginBalanceResponse,
GetMarginCorporateActionsResponse,
GetMarginFeeTierRatesResponse,
GetMarginFeeTiersResponse,
GetMarginRiskResponse,
MarginCorporateAction,
MarginCorporateActionCashTerms,
MarginCorporateActionFinalSettlementTerms,
MarginCorporateActionSplitTerms,
MarginCorporateActionStatusLiteral,
MarginCorporateActionTickerChangeTerms,
MarginCorporateActionTypeLiteral,
MarginFeeTierRate,
MarginRiskPosition,
MarginSubaccountBalance,
Expand Down Expand Up @@ -283,6 +291,7 @@
"GetFCMSubtraderRiskControlsResponse",
"GetGuarantyFundBalanceResponse",
"GetMarginBalanceResponse",
"GetMarginCorporateActionsResponse",
"GetMarginFeeTierRatesResponse",
"GetMarginFeeTiersResponse",
"GetMarginFillsResponse",
Expand Down Expand Up @@ -313,6 +322,13 @@
"LastUpdateReasonLiteral",
"MaintenanceMarginDetail",
"MarginAccountResource",
"MarginCorporateAction",
"MarginCorporateActionCashTerms",
"MarginCorporateActionFinalSettlementTerms",
"MarginCorporateActionSplitTerms",
"MarginCorporateActionStatusLiteral",
"MarginCorporateActionTickerChangeTerms",
"MarginCorporateActionTypeLiteral",
"MarginEnabledResponse",
"MarginFeeTierRate",
"MarginFill",
Expand Down
16 changes: 16 additions & 0 deletions kalshi/perps/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,17 @@
from kalshi.perps.models.margin_account import (
FeeScheduleLiteral,
GetMarginBalanceResponse,
GetMarginCorporateActionsResponse,
GetMarginFeeTierRatesResponse,
GetMarginFeeTiersResponse,
GetMarginRiskResponse,
MarginCorporateAction,
MarginCorporateActionCashTerms,
MarginCorporateActionFinalSettlementTerms,
MarginCorporateActionSplitTerms,
MarginCorporateActionStatusLiteral,
MarginCorporateActionTickerChangeTerms,
MarginCorporateActionTypeLiteral,
MarginFeeTierRate,
MarginRiskPosition,
MarginSubaccountBalance,
Expand Down Expand Up @@ -143,6 +151,7 @@
"GetExitTriggersResponse",
"GetFCMSubtraderRiskControlsResponse",
"GetMarginBalanceResponse",
"GetMarginCorporateActionsResponse",
"GetMarginFeeTierRatesResponse",
"GetMarginFeeTiersResponse",
"GetMarginFillsResponse",
Expand All @@ -158,6 +167,13 @@
"IntraExchangeInstanceTransferResponse",
"LastUpdateReason",
"LastUpdateReasonLiteral",
"MarginCorporateAction",
"MarginCorporateActionCashTerms",
"MarginCorporateActionFinalSettlementTerms",
"MarginCorporateActionSplitTerms",
"MarginCorporateActionStatusLiteral",
"MarginCorporateActionTickerChangeTerms",
"MarginCorporateActionTypeLiteral",
"MarginEnabledResponse",
"MarginFeeTierRate",
"MarginFill",
Expand Down
127 changes: 120 additions & 7 deletions kalshi/perps/models/margin_account.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
"""Perps margin-account models — balance, risk, notional risk limit, fee tiers.

Response models for the four read-only direct-margin account endpoints (#394):
``GetMarginBalance``, ``GetMarginRisk``, ``GetMarginNotionalRiskLimit`` and
``GetMarginFeeTiers``. All are pure response models — every one carries
``model_config = {"extra": "allow"}`` so additive spec fields don't break
parsing, and none have request bodies (the endpoints are GETs with at most a
single query flag).
Response models for the read-only direct-margin account endpoints (#394) plus
``GetMarginCorporateActions`` (#550). All are pure response models — every one
carries ``model_config = {"extra": "allow"}`` so additive spec fields don't
break parsing, and none have request bodies (the endpoints are GETs with at
most query flags).

Money fields use :data:`~kalshi.types.DollarDecimal` (the perps spec's
``FixedPointDollars`` string), and the signed position quantity uses
Expand All @@ -19,9 +18,10 @@

from __future__ import annotations

from datetime import date
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field
from pydantic import AwareDatetime, BaseModel, ConfigDict, Field

from kalshi.types import (
DollarDecimal,
Expand Down Expand Up @@ -136,3 +136,116 @@ class GetMarginFeeTierRatesResponse(BaseModel):
model_config = ConfigDict(extra="allow")

fee_tier_rates: list[MarginFeeTierRate]


MarginCorporateActionTypeLiteral = Literal[
"cash_dividend",
"special_dividend",
"spin_off",
"stock_split",
"final_settlement",
"ticker_change",
]
"""Spec ``MarginCorporateAction.type`` — which terms object is present."""


MarginCorporateActionStatusLiteral = Literal[
"scheduled",
"cancelled",
"applied",
"lapsed",
]
"""Spec ``MarginCorporateAction.status`` — public notice status."""


class MarginCorporateActionCashTerms(BaseModel):
"""Terms for ``cash_dividend``, ``special_dividend``, or ``spin_off``.

Spec ``MarginCorporateActionCashTerms``. Omitted for other types and
cancelled notices. All properties are optional.
"""

model_config = ConfigDict(extra="allow")

cash_per_share: DollarDecimal | None = None
cash_per_contract: DollarDecimal | None = None
ex_date: date | None = None


class MarginCorporateActionSplitTerms(BaseModel):
"""Terms for ``stock_split``.

Spec ``MarginCorporateActionSplitTerms``. Omitted for other types and
cancelled notices. All properties are optional.
"""

model_config = ConfigDict(extra="allow")

ratio_old: str | None = None
ratio_new: str | None = None
ex_date: date | None = None


class MarginCorporateActionFinalSettlementTerms(BaseModel):
"""Terms for ``final_settlement``.

Spec ``MarginCorporateActionFinalSettlementTerms``. Omitted for other
types and cancelled notices. ``reason`` is spec-required when the object
is present.
"""

model_config = ConfigDict(extra="allow")

reason: str
settlement_price_per_share: DollarDecimal | None = None
settlement_price_per_contract: DollarDecimal | None = None


class MarginCorporateActionTickerChangeTerms(BaseModel):
"""Terms for ``ticker_change``.

Spec ``MarginCorporateActionTickerChangeTerms``. Omitted for other types
and cancelled notices. ``old_ticker`` and ``new_ticker`` are required
when the object is present.
"""

model_config = ConfigDict(extra="allow")

old_ticker: str
new_ticker: str


class MarginCorporateAction(BaseModel):
"""Spec ``MarginCorporateAction`` — a margin corporate action notice.

Scheduled, applied, and lapsed notices have exactly one terms object
matching ``type``. Cancelled notices omit all four terms objects and
``execution_time``.
"""

model_config = ConfigDict(extra="allow")

id: str
market_id: str
market_ticker: str
type: MarginCorporateActionTypeLiteral
status: MarginCorporateActionStatusLiteral
updated_ts: AwareDatetime
execution_time: AwareDatetime | None = None
cash: MarginCorporateActionCashTerms | None = None
split: MarginCorporateActionSplitTerms | None = None
final_settlement: MarginCorporateActionFinalSettlementTerms | None = None
ticker_change: MarginCorporateActionTickerChangeTerms | None = None


class GetMarginCorporateActionsResponse(BaseModel):
"""Spec ``GetMarginCorporateActionsResponse`` — notices envelope.

``corporate_actions`` is spec-required and uses
:data:`~kalshi.types.NullableList`: a missing key raises
``ValidationError``, while a ``null`` array coerces to ``[]``.
"""

model_config = ConfigDict(extra="allow")

corporate_actions: NullableList[MarginCorporateAction]
Loading
Loading