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

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

## 22.7.0 — 2026-11-03

Reconciles nightly spec drift (#567) against the latest perps OpenAPI /
AsyncAPI and Klear (SCM) specs. Additive only. No breaking changes.

### Added

- **`is_block_trade`** query param on perps `portfolio.trades` /
`trades_all` (sync and async). Omit for all trades; `True` filters to
block trades; `False` filters to non-block (`"false"` is sent, not
dropped). (Closes #567).
- Optional **`is_block_trade`** (`bool | None`) on `MarginTrade` and
`MarginTradePayload`. The spec marks it required; the SDK keeps it
optional (contract exclusion `server_omits_despite_required`) so
payloads omitting the key still parse.
- Klear `MarginReport.report_type` now accepts every spec report type: the
previously missing `position_transfer_audit`, `corporate_action_payments`,
`cash_balance_daily`, `cash_activity_daily`, `daily_financial_summary`,
`interest_on_collateral_monthly`, `daily_statement`, and the companion `*_fp` variants
(`trade_audit_fp`, `position_transfer_audit_fp`, `position_snapshot_fp`,
`funding_periods_fp`, `settlement_periods_fp`,
`corporate_action_payments_fp`, `cash_balance_daily_fp`,
`cash_activity_daily_fp`, `daily_financial_summary_fp`,
`interest_on_collateral_monthly_fp`).

## 22.6.0 — 2026-10-31

Reconciles nightly spec drift (#563) against the latest Klear (SCM) spec
Expand Down
6 changes: 5 additions & 1 deletion docs/perps.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ key is not sent.
rows across all subaccounts (subaccount-restricted API keys still only
see their locked subaccount). Pass `subaccount=0` for the primary.
`portfolio.trades()` / `trades_all()` take an optional `ticker`; omit it
to retrieve public trades across all margin markets.
to retrieve public trades across all margin markets. Optional
`is_block_trade` filters by block / non-block: omit for all trades,
`True` for block trades only, `False` for non-block only (`"false"` is
sent, not dropped). Each `MarginTrade` also carries optional
`is_block_trade` (`bool | None`; `None` when the server omits the key).

!!! warning "Deprecated in v7.2.0 — `list_fcm` / `list_all_fcm`"
Kalshi removed `GET /margin/fcm/orders` from the perps OpenAPI. The SDK
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.6.0"
__version__ = "22.7.0"
17 changes: 17 additions & 0 deletions kalshi/perps/klear/models/margin.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,29 @@ def _require_positive_withdrawal(value: Decimal) -> Decimal:
# the ``PaymentStatusLiteral`` style in kalshi/models/portfolio.py).
MarginReportTypeLiteral = Literal[
"trade_audit",
"trade_audit_fp",
"position_transfer_audit",
"position_transfer_audit_fp",
"position_snapshot",
"position_snapshot_fp",
"market_price_snapshot",
"funding_periods",
"funding_periods_fp",
"settlement_periods",
"settlement_periods_fp",
"corporate_action_payments",
"corporate_action_payments_fp",
"maintenance_margin",
"maintenance_margin_aggregate",
"cash_balance_daily",
"cash_balance_daily_fp",
"cash_activity_daily",
"cash_activity_daily_fp",
"daily_financial_summary",
"daily_financial_summary_fp",
"interest_on_collateral_monthly",
"interest_on_collateral_monthly_fp",
"daily_statement",
]
"""Spec ``MarginReport.report_type`` — the kind of margin report."""

Expand Down
4 changes: 4 additions & 0 deletions kalshi/perps/models/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ class MarginTrade(BaseModel):
price: DollarDecimal
created_time: AwareDatetime
taker_side: BookSideLiteral
# Spec marks is_block_trade required. Kept optional (None when the
# key is absent) so fixtures and payloads from before the field
# still validate. See PERPS_EXCLUSIONS.
is_block_trade: bool | None = None

model_config = {"extra": "allow"}

Expand Down
32 changes: 27 additions & 5 deletions kalshi/perps/resources/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from kalshi.resources._base import (
AsyncResource,
SyncResource,
_bool_param,
_params,
_seg,
_validate_limit,
Expand Down Expand Up @@ -71,11 +72,15 @@ def _margin_trades_params(
max_ts: int | None,
limit: int | None,
cursor: str | None,
is_block_trade: bool | None,
) -> dict[str, Any]:
"""Build query params for ``GET /margin/trades``.

``ticker`` is optional — omit (``None``) to retrieve trades across all
margin markets. ``_params`` drops ``None`` so the query key is not sent.
margin markets. ``is_block_trade`` is optional — omit for all trades,
``True`` for block trades only, ``False`` for non-block only (serialized
as ``"true"`` / ``"false"``; ``None`` is dropped). ``_params`` drops
``None`` so omitted keys are not sent.
"""
limit = _validate_limit(limit, hi=1000)
return _params(
Expand All @@ -84,6 +89,7 @@ def _margin_trades_params(
max_ts=max_ts,
limit=limit,
cursor=cursor,
is_block_trade=_bool_param(is_block_trade),
)


Expand Down Expand Up @@ -161,19 +167,23 @@ def trades(
max_ts: int | None = None,
limit: int | None = None,
cursor: str | None = None,
is_block_trade: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> Page[MarginTrade]:
"""Public margin trades (no auth required).

``ticker`` filters to one market. Omit to retrieve trades across all
margin markets.
margin markets. ``is_block_trade`` filters by block / non-block:
omit for all trades, ``True`` for block trades only, ``False`` for
non-block only.
"""
params = _margin_trades_params(
ticker=ticker,
min_ts=min_ts,
max_ts=max_ts,
limit=limit,
cursor=cursor,
is_block_trade=is_block_trade,
)
return self._list(
"/margin/trades", MarginTrade, "trades", params=params, extra_headers=extra_headers
Expand All @@ -186,13 +196,16 @@ def trades_all(
min_ts: int | None = None,
max_ts: int | None = None,
limit: int | None = None,
is_block_trade: bool | None = None,
max_pages: int | None = None,
extra_headers: dict[str, str] | None = None,
) -> Iterator[MarginTrade]:
"""Auto-paginate public margin trades (no auth required).

``ticker`` filters to one market. Omit to retrieve trades across all
margin markets.
margin markets. ``is_block_trade`` filters by block / non-block:
omit for all trades, ``True`` for block trades only, ``False`` for
non-block only.
"""
_validate_max_pages(max_pages)
params = _margin_trades_params(
Expand All @@ -201,6 +214,7 @@ def trades_all(
max_ts=max_ts,
limit=limit,
cursor=None,
is_block_trade=is_block_trade,
)
return self._list_all(
"/margin/trades",
Expand Down Expand Up @@ -438,19 +452,23 @@ async def trades(
max_ts: int | None = None,
limit: int | None = None,
cursor: str | None = None,
is_block_trade: bool | None = None,
extra_headers: dict[str, str] | None = None,
) -> Page[MarginTrade]:
"""Public margin trades (no auth required).

``ticker`` filters to one market. Omit to retrieve trades across all
margin markets.
margin markets. ``is_block_trade`` filters by block / non-block:
omit for all trades, ``True`` for block trades only, ``False`` for
non-block only.
"""
params = _margin_trades_params(
ticker=ticker,
min_ts=min_ts,
max_ts=max_ts,
limit=limit,
cursor=cursor,
is_block_trade=is_block_trade,
)
return await self._list(
"/margin/trades", MarginTrade, "trades", params=params, extra_headers=extra_headers
Expand All @@ -463,13 +481,16 @@ def trades_all(
min_ts: int | None = None,
max_ts: int | None = None,
limit: int | None = None,
is_block_trade: bool | None = None,
max_pages: int | None = None,
extra_headers: dict[str, str] | None = None,
) -> AsyncIterator[MarginTrade]:
"""Returns an async iterator over public margin trades — use ``async for`` (no auth).

``ticker`` filters to one market. Omit to retrieve trades across all
margin markets.
margin markets. ``is_block_trade`` filters by block / non-block:
omit for all trades, ``True`` for block trades only, ``False`` for
non-block only.
"""
_validate_max_pages(max_pages)
params = _margin_trades_params(
Expand All @@ -478,6 +499,7 @@ def trades_all(
max_ts=max_ts,
limit=limit,
cursor=None,
is_block_trade=is_block_trade,
)
return self._list_all(
"/margin/trades",
Expand Down
11 changes: 8 additions & 3 deletions kalshi/perps/ws/models/trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
class MarginTradePayload(BaseModel):
"""Payload for ``trade`` messages (``marginTradePayload.msg``).

All fields required per spec. ``trade_id`` is a UUID string; ``price`` is a
dollar-decimal string; ``count`` is a fixed-point count string;
``taker_side`` is ``bid``/``ask``; ``ts_ms`` is epoch milliseconds.
``trade_id`` is a UUID string; ``price`` is a dollar-decimal string;
``count`` is a fixed-point count string; ``taker_side`` is ``bid``/``ask``;
``ts_ms`` is epoch milliseconds. Spec marks ``is_block_trade`` required;
the SDK keeps it optional so payloads omitting the key still parse.
"""

trade_id: str
Expand All @@ -28,6 +29,10 @@ class MarginTradePayload(BaseModel):
count: FixedPointCount
taker_side: PerpsBookSide
ts_ms: int
# Spec marks is_block_trade required. Kept optional (None when the
# key is absent) so fixtures and payloads from before the field
# still validate. See PERPS_EXCLUSIONS.
is_block_trade: bool | None = None
model_config = {"extra": "allow", "populate_by_name": True}


Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kalshi-sdk"
version = "22.6.0"
version = "22.7.0"
description = "A professional Python SDK for the Kalshi prediction markets and Perps (margin) APIs"
readme = "README.md"
license = { text = "MIT" }
Expand Down
4 changes: 3 additions & 1 deletion specs/asyncapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2157,7 +2157,7 @@ components:
name: "MVE One"
title: "Market 1"
yes_sub_title: "YES 1"
no_sub_title: "NO 1"
no_sub_title: "YES 1"
rules_primary: "Rule 1"
rules_secondary: "Rule 2"
can_close_early: true
Expand Down Expand Up @@ -3978,6 +3978,8 @@ components:
type: string
no_sub_title:
type: string
deprecated: true
description: Deprecated. Use `yes_sub_title` instead. Equal to `yes_sub_title` on every market created since September 2024.
rules_primary:
type: string
rules_secondary:
Expand Down
4 changes: 3 additions & 1 deletion specs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11398,7 +11398,9 @@ components:
description: Shortened title for the yes side of this market
no_sub_title:
type: string
description: Shortened title for the no side of this market
deprecated: true
description: |
Deprecated. Use `yes_sub_title` instead. Equal to `yes_sub_title` on every market created since September 2024.
created_time:
type: string
format: date-time
Expand Down
5 changes: 4 additions & 1 deletion specs/perps_asyncapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1346,7 +1346,7 @@ components:
$ref: '#/components/schemas/sequenceNumber'
msg:
type: object
required: ["trade_id", "market_ticker", "price", "count", "taker_side", "ts_ms"]
required: ["trade_id", "market_ticker", "price", "count", "taker_side", "is_block_trade", "ts_ms"]
properties:
trade_id:
type: string
Expand All @@ -1363,6 +1363,9 @@ components:
x-rust-serialize-with: ws::dto::serde_money::serialize_amount_fixed_point_count
taker_side:
$ref: '#/components/schemas/bookSide'
is_block_trade:
type: boolean
description: True if the trade was matched off book as a block trade
ts_ms:
type: integer
format: int64
Expand Down
15 changes: 14 additions & 1 deletion specs/perps_openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ paths:
get:
operationId: GetMarginTrades
summary: Get Trades
description: 'Endpoint for retrieving public margin trades across all markets or filtered by market ticker. Returns a paginated response. Use the cursor value from the previous response to get the next page.'
description: 'Endpoint for retrieving public margin trades across all markets or filtered by market ticker. Block trades are included in the response by default and identified by the `is_block_trade` field; use the `is_block_trade` query parameter to filter by block / non-block. Returns a paginated response. Use the cursor value from the previous response to get the next page.'
tags:
- market
parameters:
Expand Down Expand Up @@ -995,6 +995,7 @@ paths:
type: integer
format: int64
description: Filter trades before this Unix timestamp
- $ref: '#/components/parameters/IsBlockTradeQuery'
responses:
'200':
description: Trades retrieved successfully
Expand Down Expand Up @@ -3184,6 +3185,7 @@ components:
$ref: '#/components/schemas/MarginMarketStatus'
fractional_trading_enabled:
type: boolean
description: Whether fractional trading is enabled for this market.
leverage_estimate:
type: number
format: double
Expand Down Expand Up @@ -3564,6 +3566,7 @@ components:
- price
- created_time
- taker_side
- is_block_trade
properties:
trade_id:
type: string
Expand All @@ -3584,6 +3587,9 @@ components:
taker_side:
$ref: '#/components/schemas/BookSide'
description: Side of the taker in this trade
is_block_trade:
type: boolean
description: True if this trade was matched off-book as a block trade (e.g. via RFQ / negotiated block proposal); false for trades that filled on the standard order book.
BookSide:
type: string
enum: ['bid', 'ask']
Expand Down Expand Up @@ -4213,6 +4219,13 @@ components:
schema:
type: string
x-go-type-skip-optional-pointer: true
IsBlockTradeQuery:
name: is_block_trade
in: query
description: |
Filter trades by whether they are block trades. Omit to return all trades. Set to `true` to return only block trades. Set to `false` to return only non-block trades.
schema:
type: boolean
LimitQuery:
name: limit
in: query
Expand Down
Loading
Loading