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
42 changes: 42 additions & 0 deletions 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.

## 22.6.0 — 2026-10-31

Reconciles nightly spec drift (#563) against the latest Klear (SCM) spec
plus trivial optional fields on core REST/WS and perps models. No public
Python attribute renames.

### Changed

- **Klear settlement money fields** gain additive optional `*_fp`
attributes (`DollarDecimal | None`) beside the existing integer
`*_centicents` fields. The spec sends both: an `int64` centicents value
and the same amount as a fixed-point dollar string (6 decimal places).
There is no aliasing. `*_centicents` stays a plain `int`, a
centicents-only payload leaves `*_fp` as `None`, and serialization emits
both names. Affected attributes: `amount_fp`, `pnl_fp`,
`total_amount_fp`, `notional_value_fp`, `funding_amount_fp`,
`payment_amount_fp`, `variation_margin_fp`, `settlement_balance_fp`,
`balance_available_fp`, `locked_balance_fp`, `balance_delta_fp`,
`locked_balance_delta_fp` on `CorporateActionPaymentDetail`,
`FundingEstimate`, `FundingPaymentDetail`, `GetSettlementBalanceResponse`,
`GetSettlementEstimateByAssetClassResponse`, `MarketFundingEstimate`,
`MarketSettlementEstimate`, `MemberFundingPayment`, `ObligationEntry`,
`ObligationReceiveInfo`, `SettlementBalanceHistoryEntry`,
`SettlementDetail`, `SettlementEstimate`. (Closes #563).
- **`klear.margin.withdraw_settlement_balance`** (sync and async) now
posts to `POST /margin/settlement_balance_withdrawal` (the previous
`/margin/withdraw_settlement_balance` path was removed upstream).
Optional `mfa_code=` is forwarded on the body when set.

### Added

- Optional `RFQ.rfq_batch_id`, `RfqCreatedPayload.rfq_batch_id`,
`RfqDeletedPayload.rfq_batch_id`.
- Optional `OrderbookSnapshotPayload.clock` /
`OrderbookDeltaPayload.clock`.
- Optional `UserOrdersPayload.last_update_reason`.
- Optional perps `ExchangeStatus.exchange_estimated_resume_time` /
`exchange_index_statuses` / `intra_exchange_transfers_active`, plus
`ExchangeIndexStatus`.
- Optional `MarginMarket.market_id`.
- Optional `IntraExchangeInstanceTransfer.client_transfer_id`.

## 22.5.0 — 2026-10-29

Reconciles nightly spec drift (#561) against core OpenAPI 3.35.0 and the
Expand Down
18 changes: 14 additions & 4 deletions docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,9 @@ matching `*_truncated` flag is set; page the full set via the methods below.
cash and split corporate-action payments already included in settlement PnL
— do not add them to the obligation total.
`CorporateActionPaymentDetail.payment_time` is always present and is `null`
for splits. Centicents fields on that model are plain integers;
for splits. Centicents fields on that model stay plain integers.
`payment_amount_fp` is the optional dollar-string counterpart of
`payment_amount_centicents` (separate attribute, not an alias).
`position_quantity_fp` and `post_position_quantity_fp` are fixed-point
contract counts. `corporate_action_payments` / `corporate_action_payments_all`
page the full set when `corporate_action_payments_truncated` is true (limit
Expand Down Expand Up @@ -409,11 +411,19 @@ klear.margin.delete_subtrader_group(created.group_id)
```

Money fields on the Klear margin schemas are integer **centicents** (`1 USD =
10,000 centicents`); the withdrawal `amount` and
10,000 centicents`), plain `int` on the Python models. Where the spec also
publishes a `*_fp` counterpart (the same amount as a fixed-point dollar
string, 6 decimal places), the SDK exposes it as a separate optional
`DollarDecimal` attribute with that `*_fp` name. `None` means the key was
absent. There is no aliasing: a payload can carry both the integer and the
dollar string, and `model_dump(by_alias=True)` emits both names. The
withdrawal `amount`, `GetSettlementBalanceWithdrawalResponse.amount`, and
`MarketSettlementEstimate.session_avg_price_fp` are fixed-point dollar
strings.
`klear.margin.withdraw_settlement_balance(amount="500.00")` validates the amount as
strings that were never centicents.
`klear.margin.withdraw_settlement_balance(amount="500.00")` posts to
`POST /margin/settlement_balance_withdrawal` and validates the amount as
positive at construction (the single real-money write) before any request is sent.
Pass `mfa_code=` when the clearing member requires MFA.
The Bearer `access_token` is never logged and is redacted in `repr()` (only the
non-secret `admin_user_id` is shown).

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

__version__ = "22.5.0"
__version__ = "22.6.0"
2 changes: 2 additions & 0 deletions kalshi/models/communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class RFQ(BaseModel):
creator_subaccount: int | None = None
# Spec 3.30.0: True when target_cost is principal-only (taker fees on top).
target_cost_excludes_fees: bool | None = None
# Set when this RFQ is a child of an RFQ batch; omitted otherwise.
rfq_batch_id: str | None = None

model_config = {"extra": "allow", "populate_by_name": True}

Expand Down
1 change: 1 addition & 0 deletions kalshi/models/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ class IntraExchangeInstanceTransfer(BaseModel):
amount: DollarDecimal
status: IntraExchangeInstanceTransferStatusLiteral
created_ts: int
client_transfer_id: str | None = None

model_config = {"extra": "allow"}

Expand Down
2 changes: 2 additions & 0 deletions kalshi/perps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
SubaccountTypeLiteral,
)
from kalshi.perps.models.exchange import (
ExchangeIndexStatus,
ExchangeStatus,
GetMarginRiskParametersResponse,
MarginEnabledResponse,
Expand Down Expand Up @@ -277,6 +278,7 @@
"Error",
"ErrorResponse",
"ExchangeIndex",
"ExchangeIndexStatus",
"ExchangeInstance",
"ExchangeInstanceLiteral",
"ExchangeStatus",
Expand Down
86 changes: 60 additions & 26 deletions kalshi/perps/klear/models/margin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,26 @@
withdrawal (initiate + status-by-id), and FCM subtrader groups.

**Money typing — read carefully.** Unlike the perps prediction/REST framing
(``FixedPointDollars`` / ``FixedPointCount`` strings), almost every monetary
field on the Klear margin schemas is an **integer ``int64`` in _centicents_**
(the spec states ``1 USD = 10,000 centicents``), serialized as a JSON number
with a ``_centicents`` field-name suffix. Those are **plain ``int``** — they are
NOT :data:`DollarDecimal` / :data:`FixedPointCount`. This mirrors the
integer-cents precedent in ``kalshi/models/portfolio.py`` (``Balance.balance``,
``Deposit.amount_cents``). Fixed-point **count** strings appear on
(``FixedPointDollars`` / ``FixedPointCount`` strings), many monetary fields on
the Klear margin schemas are an **integer ``int64`` in _centicents_** (the spec
states ``1 USD = 10,000 centicents``), serialized as a JSON number with a
``_centicents`` field-name suffix. Those are **plain ``int``** — they are NOT
:data:`DollarDecimal` / :data:`FixedPointCount`. This mirrors the integer-cents
precedent in ``kalshi/models/portfolio.py`` (``Balance.balance``,
``Deposit.amount_cents``). Where the spec also publishes a ``*_fp`` counterpart
(the same amount as a fixed-point dollar string, 6 decimal places), the SDK
exposes it as a separate optional :data:`DollarDecimal` attribute with the same
``*_fp`` name; the ``*_centicents`` integer attributes are unchanged.
Fixed-point **count** strings appear on
:attr:`SettlementDetail.position_quantity_fp`,
:attr:`FundingPaymentDetail.position_quantity_fp`, and
:attr:`CorporateActionPaymentDetail.position_quantity_fp` /
:attr:`CorporateActionPaymentDetail.post_position_quantity_fp`. The fixed-point
**dollar-string** fields are :attr:`WithdrawSettlementBalanceRequest.amount`,
**dollar-string** fields that were never centicents are
:attr:`WithdrawSettlementBalanceRequest.amount`,
:attr:`GetSettlementBalanceWithdrawalResponse.amount`, and
:attr:`MarketSettlementEstimate.session_avg_price_fp` (e.g. ``"500.00"``);
those use :data:`DollarDecimal`.
those also 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 @@ -65,6 +70,7 @@ def _require_positive_withdrawal(value: Decimal) -> Decimal:
WithdrawalAmount = Annotated[DollarDecimal, AfterValidator(_require_positive_withdrawal)]
"""A request-side withdrawal amount: a positive fixed-point dollar value."""


# Spec ``MarginReport.report_type`` enum — surfaced as a Literal alias (mirrors
# the ``PaymentStatusLiteral`` style in kalshi/models/portfolio.py).
MarginReportTypeLiteral = Literal[
Expand Down Expand Up @@ -129,10 +135,11 @@ class ObligationReceiveInfo(BaseModel):
id: str
type: str
amount_centicents: int
amount_fp: DollarDecimal | None = None
external_reference: str
created_ts: AwareDatetime

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class SettlementDetail(BaseModel):
Expand All @@ -143,10 +150,12 @@ class SettlementDetail(BaseModel):
subtrader_id: str
position_quantity_fp: FixedPointCount
pnl_centicents: int
pnl_fp: DollarDecimal | None = None
total_fees_centicents: int
total_amount_centicents: int
total_amount_fp: DollarDecimal | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class MaintenanceMarginDetail(BaseModel):
Expand All @@ -169,7 +178,8 @@ class FundingPaymentDetail(BaseModel):
"""Spec ``FundingPaymentDetail`` — per-market funding payment within an obligation.

``position_quantity_fp`` is a fixed-point contract count string (e.g. ``"1.25"``).
Centicents fields are plain ``int``.
``notional_value_centicents`` / ``funding_amount_centicents`` are
:data:`DollarDecimal` (wire ``*_fp`` or legacy ``*_centicents``).
"""

id: str
Expand All @@ -178,9 +188,11 @@ class FundingPaymentDetail(BaseModel):
funding_time: AwareDatetime
position_quantity_fp: FixedPointCount
notional_value_centicents: int
notional_value_fp: DollarDecimal | None = None
funding_amount_centicents: int
funding_amount_fp: DollarDecimal | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class CorporateActionPaymentDetail(BaseModel):
Expand All @@ -190,8 +202,9 @@ class CorporateActionPaymentDetail(BaseModel):
They are not additional amounts to add to the obligation total.

``position_quantity_fp`` / ``post_position_quantity_fp`` are fixed-point
contract counts. Centicents fields are plain ``int``. ``payment_time`` is
a required key that is ``null`` for splits (no scheduled cash time).
contract counts. ``payment_amount_centicents`` is :data:`DollarDecimal`.
``payment_time`` is a required key that is ``null`` for splits (no
scheduled cash time).
``pre_split_price_centicents`` / ``post_split_price_centicents`` /
``exchange_ts`` are omitted when they do not apply.
"""
Expand All @@ -206,6 +219,7 @@ class CorporateActionPaymentDetail(BaseModel):
position_quantity_fp: FixedPointCount
post_position_quantity_fp: FixedPointCount
payment_amount_centicents: int
payment_amount_fp: DollarDecimal | None = None
# Required key; null for splits.
payment_time: AwareDatetime | None
settlement_execution_time: AwareDatetime
Expand All @@ -214,7 +228,7 @@ class CorporateActionPaymentDetail(BaseModel):
post_split_price_centicents: int | None = None
exchange_ts: AwareDatetime | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class ObligationEntry(BaseModel):
Expand All @@ -225,7 +239,8 @@ class ObligationEntry(BaseModel):
model (the spec only ever returns the composed shape, never bare
``ObligationInfo``). ``amount_centicents`` is the net settlement amount:
negative means the SCM pays Kalshi Klear, positive means Kalshi Klear pays
the SCM. All ``_centicents`` fields are plain ``int``.
the SCM. Fields with a ``*_fp`` wire counterpart are :data:`DollarDecimal`;
remaining ``_centicents`` fields are plain ``int``.

Inline detail arrays are capped at 1000 rows; when truncated the matching
``*_truncated`` flag is set and the full set is available via the paged
Expand All @@ -236,9 +251,11 @@ class ObligationEntry(BaseModel):
id: str
user_id: str
amount_centicents: int
amount_fp: DollarDecimal | None = None
fees_centicents: int
maintenance_margin_centicents: int
pnl_centicents: int
pnl_fp: DollarDecimal | None = None
execution_time: AwareDatetime
last_updated_ts: AwareDatetime
# Spec (perps SCM) added a required ``asset_class`` on ``ObligationInfo``.
Expand All @@ -254,7 +271,7 @@ class ObligationEntry(BaseModel):
funding_payments_truncated: bool | None = None
corporate_action_payments_truncated: bool | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class GetObligationHistoryResponse(BaseModel):
Expand Down Expand Up @@ -331,25 +348,30 @@ class MarketSettlementEstimate(BaseModel):

quantity_centicount: int
variation_margin_centicents: int
variation_margin_fp: DollarDecimal | None = None
notional_value_centicents: int
notional_value_fp: DollarDecimal | None = None
# Optional fixed-point dollar string; omitted when position quantity is zero.
session_avg_price_fp: DollarDecimal | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class SettlementEstimate(BaseModel):
"""Spec ``SettlementEstimate`` — estimated next-settlement amounts (centicents)."""

variation_margin_centicents: int

variation_margin_fp: DollarDecimal | None = None
total_fees_centicents: int
maintenance_margin_delta_centicents: int
maintenance_margin_required_centicents: int
total_amount_centicents: int
total_amount_fp: DollarDecimal | None = None
# Spec v3.22.0: per-market breakdown map (market ticker -> estimate); optional.
positions: dict[str, MarketSettlementEstimate] | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


# ── Spec sync 3.24.0 additions (perps SCM) ──────────────────────────────────
Expand Down Expand Up @@ -401,8 +423,9 @@ class GetSettlementEstimateByAssetClassResponse(BaseModel):

estimates: dict[str, AssetClassSettlementEstimate]
settlement_balance_centicents: int
settlement_balance_fp: DollarDecimal | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class GetSettlementBalanceResponse(BaseModel):
Expand All @@ -413,9 +436,11 @@ class GetSettlementBalanceResponse(BaseModel):

user_id: str
balance_available_centicents: int
balance_available_fp: DollarDecimal | None = None
locked_balance_centicents: int | None = None
locked_balance_fp: DollarDecimal | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class GetGuarantyFundBalanceResponse(BaseModel):
Expand All @@ -438,12 +463,15 @@ class WithdrawSettlementBalanceRequest(BaseModel):
string** (``"500.00"``), NOT centicents — wire name equals the Python name, so
no ``serialization_alias`` is needed. It is validated **positive at construction**
(this is the single real-money write; a negative/zero amount must never reach
the endpoint). Uses ``extra="forbid"`` so a typo'd field fails at construction;
the endpoint). ``mfa_code`` is optional in the SDK (the spec marks it
required) so existing callers stay valid; omit it and the key is not sent.
Uses ``extra="forbid"`` so a typo'd field fails at construction;
serializes via ``model.model_dump(exclude_none=True, by_alias=True, mode="json")``
to ``{"amount": "500.00"}``.
"""

amount: WithdrawalAmount
mfa_code: str | None = None

model_config = {"extra": "forbid"}

Expand Down Expand Up @@ -474,12 +502,15 @@ class SettlementBalanceHistoryEntry(BaseModel):
"""Spec ``SettlementBalanceHistoryEntry`` — one settlement-balance change."""

balance_delta_centicents: int

balance_delta_fp: DollarDecimal | None = None
locked_balance_delta_centicents: int
locked_balance_delta_fp: DollarDecimal | None = None
reason: str
business_transaction_id: str
created_ts: AwareDatetime

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class GetSettlementBalanceHistoryResponse(BaseModel):
Expand Down Expand Up @@ -720,17 +751,20 @@ class MarketFundingEstimate(BaseModel):

quantity_centicount: int
funding_amount_centicents: int
funding_amount_fp: DollarDecimal | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class FundingEstimate(BaseModel):
"""Funding amount plus optional per-market position breakdown."""

funding_amount_centicents: int

funding_amount_fp: DollarDecimal | None = None
positions: dict[str, MarketFundingEstimate] | None = None

model_config = {"extra": "allow"}
model_config = {"extra": "allow", "populate_by_name": True}


class AssetClassFundingEstimate(BaseModel):
Expand Down
Loading
Loading