From 910f2f8a51b1e6eefcbc6763c4db0efbee70cada Mon Sep 17 00:00:00 2001 From: Jeff West Date: Sun, 14 Jun 2026 21:28:11 -0500 Subject: [PATCH 1/2] docs: 100% accuracy pass for v4.1.0 Document the 4.1.0 additions and fix a stale verb: - communications: block_trade_proposals sub-resource (quick-ref rows, a worked example section, mkdocstrings directives) + quotes min_ts/max_ts; fix quotes.accept/confirm verb (PUT, not POST) in the quick-ref table - account: volume_progress() (quick-ref row + section) - events: tickers filter (example + prose) - perps: MarginMarket mark-price fields + new TickerPrice model; required MarginPosition.subaccount - reference / request-models: new request models in the curated lists Verified: mkdocs build --strict passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/perps.md | 10 +++++ docs/reference.md | 4 ++ docs/request-models.md | 2 + docs/resources/account.md | 22 ++++++++++ docs/resources/communications.md | 73 +++++++++++++++++++++++++++++++- docs/resources/events.md | 6 +++ 6 files changed, 115 insertions(+), 2 deletions(-) diff --git a/docs/perps.md b/docs/perps.md index c69080fa..adc22bd9 100644 --- a/docs/perps.md +++ b/docs/perps.md @@ -89,6 +89,16 @@ Orders create/cancel/decrease/amend are POSTs/DELETEs and are **never retried**. record). `MarginMarket.leverage_estimates` maps notional position sizes (`"1000"`, `"10000"`, …) to `MultiplierDecimal` leverage, or `None` when margin config / price data is unavailable. +- **Mark prices** (since the v3.21.0 spec sync) — `MarginMarket` carries three + optional reference-price objects: `settlement_mark_price`, + `liquidation_mark_price`, and `reference_price`, each a nested `TickerPrice` + (`TickerPrice | None`). `TickerPrice` is a new model with `price` + (`DollarDecimal`, a `FixedPointDollars` USD string) and `ts_ms` (the source + timestamp in epoch **milliseconds**, `int`). All three are absent from the spec + `required` list, so they are `None` when the upstream price is unavailable. +- **Subaccounts** (since the v3.21.0 spec sync) — `MarginPosition` (the + `portfolio.positions()` rows) carries a **required** `subaccount` (`int`) + identifying which subaccount holds the position. ## Funding mechanics diff --git a/docs/reference.md b/docs/reference.md index d916f578..2c6fafca 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -56,6 +56,10 @@ every exception class. ::: kalshi.models.communications.AcceptQuoteRequest +::: kalshi.models.communications.ProposeBlockTradeRequest + +::: kalshi.models.communications.AcceptBlockTradeProposalRequest + ::: kalshi.models.multivariate.CreateMarketInMultivariateEventCollectionRequest ::: kalshi.models.multivariate.LookupTickersForMarketInMultivariateEventCollectionRequest diff --git a/docs/request-models.md b/docs/request-models.md index e03109f4..c38b52fc 100644 --- a/docs/request-models.md +++ b/docs/request-models.md @@ -74,6 +74,8 @@ OpenAPI spec. | `client.communications.create_rfq` | `CreateRFQRequest` | | `client.communications.create_quote` | `CreateQuoteRequest` | | `client.communications.accept_quote` | `AcceptQuoteRequest` | +| `client.communications.block_trade_proposals.create` | `ProposeBlockTradeRequest` | +| `client.communications.block_trade_proposals.accept` | `AcceptBlockTradeProposalRequest` | | `client.multivariate_collections.create_market` | `CreateMarketInMultivariateEventCollectionRequest` | | `client.multivariate_collections.lookup_tickers` | `LookupTickersForMarketInMultivariateEventCollectionRequest` | | `client.order_groups.create` | `CreateOrderGroupRequest` | diff --git a/docs/resources/account.md b/docs/resources/account.md index 7faaead5..a1539d2c 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` | +| `volume_progress()` | `GET /account/api_usage_level/volume_progress` | | `upgrade()` | `POST /account/api_usage_level/upgrade` | ## Read tier limits @@ -49,6 +50,27 @@ 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). +## Volume progress + +New in v4.1.0. `volume_progress()` (`GET /account/api_usage_level/volume_progress`) +returns the latest cron-computed trading-volume snapshots toward the +volume-based API usage tiers for the predictions (`event_contract`) lane. + +```python +progress = client.account.volume_progress() +for snapshot in progress.volume_progress: + print(snapshot.computed_ts, snapshot.trailing_30d_volume) + for goal in snapshot.goals: + # earn = volume needed to earn the level; keep = volume to retain it. + print(goal.level, goal.earn_volume_goal, goal.keep_volume_goal) +``` + +`AccountVolumeProgress.volume_progress` is a list of +`AccountApiUsageLevelVolumeProgress` snapshots. `computed_ts` is the Unix +second at which the snapshot was computed; `trailing_30d_volume` and each +goal's `earn_volume_goal` / `keep_volume_goal` are fixed-point contract counts +(`Decimal`). + ## API usage-level grants New in v4.0.0. `AccountApiLimits.grants` is a list of `ApiUsageLevelGrant` diff --git a/docs/resources/communications.md b/docs/resources/communications.md index 27a06809..515c3243 100644 --- a/docs/resources/communications.md +++ b/docs/resources/communications.md @@ -27,8 +27,11 @@ Auth required throughout. | `quotes.get(quote_id)` | `GET /communications/quotes/{quote_id}` | | `quotes.create(...)` | `POST /communications/quotes` | | `quotes.delete(quote_id)` | `DELETE /communications/quotes/{quote_id}` | -| `quotes.accept(quote_id, *, accepted_side)` | `POST /communications/quotes/{quote_id}/accept` | -| `quotes.confirm(quote_id)` | `POST /communications/quotes/{quote_id}/confirm` | +| `quotes.accept(quote_id, *, accepted_side)` | `PUT /communications/quotes/{quote_id}/accept` | +| `quotes.confirm(quote_id)` | `PUT /communications/quotes/{quote_id}/confirm` | +| `block_trade_proposals.list(...)` / `block_trade_proposals.list_all(...)` | `GET /communications/block-trade-proposals` | +| `block_trade_proposals.create(...)` | `POST /communications/block-trade-proposals` | +| `block_trade_proposals.accept(block_trade_proposal_id, *, subtrader_id=None, subaccount=None)` | `POST /communications/block-trade-proposals/{id}/accept` | `get_id()` returns your `participant_id` — the value you'll pass as `quote_creator_user_id` / `rfq_creator_user_id` when filtering lists. It stays @@ -114,6 +117,26 @@ for rfq in client.communications.rfqs.list_all(user_filter="self"): server-side shorthands like `"organization"` in the future without an SDK upgrade. +## Filtering quotes by time (v4.1.0) + +`quotes.list()` / `quotes.list_all()` accept `min_ts` and `max_ts` (Unix +seconds, `int`) to bound results by the quote's last-updated timestamp. They +compose with the required user-id filter: + +```python +import time + +# Quotes you made that were updated in the last hour. +for q in client.communications.quotes.list_all( + user_filter="self", + min_ts=int(time.time()) - 3600, +): + print(q.quote_id, q.updated_ts) +``` + +`min_ts` / `max_ts` are also accepted by the deprecated `list_quotes` / +`list_all_quotes` forwarders. + ## Post-only quotes `quotes.create()` accepts `post_only=True` (added in v2.1.0) to ensure your @@ -148,6 +171,44 @@ client.communications.quotes.create( `open`, `accepted`, `confirmed`, `canceled`. Status filtering accepts these literal strings. +## Block trade proposals (v4.1.0) + +`client.communications.block_trade_proposals` backs the +`/communications/block-trade-proposals` API (OpenAPI 3.21.0) — a bilateral +negotiated trade that both the buyer and seller must accept before it settles. + +```python +# Propose a block trade. The proposer names both sides explicitly. +resp = client.communications.block_trade_proposals.create( + buyer_user_id="user_abc", + seller_user_id="user_xyz", + market_ticker="KXPRES-24-DJT", + price_centi_cents=5600, # plain int, centi-cents (NOT a _dollars price) + centicount=50_000, # plain int, centicounts (NOT a _fp count) + maker_side="yes", + expiration_ts="2026-07-01T00:00:00Z", +) +proposal_id = resp.block_trade_proposal_id + +# List open proposals on a market. +for proposal in client.communications.block_trade_proposals.list_all( + market_ticker="KXPRES-24-DJT", status="open" +): + print(proposal.id, proposal.buyer_accepted, proposal.seller_accepted) + +# Accept (the counterparty side). The body is optional — pass subtrader_id +# or subaccount only if you trade through a sub-trader / subaccount. +client.communications.block_trade_proposals.accept(proposal_id) +``` + +!!! info "Prices are centi-cents, counts are centicounts — plain integers" + Unlike the rest of the SDK, `BlockTradeProposal.price_centi_cents` and + `.centicount` (and the matching `create()` kwargs) are **plain `int`s** in + centi-cents and centicounts respectively — they are *not* + `FixedPointDollars` / `_fp` wire fields, so no `Decimal` conversion or + `_dollars` / `_fp` aliasing applies. `price_centi_cents` and `centicount` + must each be `>= 1`. + ## Migrating from v2.x | v2.x (deprecated) | v3.0.0 (canonical) | @@ -181,6 +242,10 @@ literal strings. options: heading_level: 3 +::: kalshi.resources.communications.BlockTradeProposalsResource + options: + heading_level: 3 + ::: kalshi.resources.communications.AsyncCommunicationsResource options: heading_level: 3 @@ -192,3 +257,7 @@ literal strings. ::: kalshi.resources.communications.AsyncQuotesResource options: heading_level: 3 + +::: kalshi.resources.communications.AsyncBlockTradeProposalsResource + options: + heading_level: 3 diff --git a/docs/resources/events.md b/docs/resources/events.md index ffb59ced..a5ffdd79 100644 --- a/docs/resources/events.md +++ b/docs/resources/events.md @@ -22,6 +22,7 @@ more markets that share a resolution. page = client.events.list( status="open", # EventStatusLiteral series_ticker="KXPRES", + tickers=["KXPRES-24", "KXSENATE-24"], # filter to specific event tickers (v4.1.0) with_nested_markets=False, with_milestones=False, min_close_ts=1_700_000_000, @@ -36,6 +37,11 @@ for event in page: `EventStatusLiteral` has values `"unopened" | "open" | "closed" | "settled"`. Unlike [`MarketStatusLiteral`](../types.md), there is no `"paused"`. +`tickers` (added in v4.1.0) restricts the result set to specific event +tickers. Pass a `list[str]` (joined comma-separated on the wire) or a +pre-joined comma-separated string; list elements must be non-empty and must +not themselves contain commas. + ## Multivariate events `list_multivariate(...)` returns only events that participate in a From 35d3bf8fe21596a45773c4050eb711447ca18d00 Mon Sep 17 00:00:00 2001 From: Jeff West Date: Sun, 14 Jun 2026 21:36:48 -0500 Subject: [PATCH 2/2] docs: address review nits - communications: accept quick-ref row uses the named path param {block_trade_proposal_id} (matches the spec + the other rows), not {id} - events: trim wire-format jargon on the tickers filter, keep the comma constraint Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/resources/communications.md | 2 +- docs/resources/events.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/resources/communications.md b/docs/resources/communications.md index 515c3243..0f57ee65 100644 --- a/docs/resources/communications.md +++ b/docs/resources/communications.md @@ -31,7 +31,7 @@ Auth required throughout. | `quotes.confirm(quote_id)` | `PUT /communications/quotes/{quote_id}/confirm` | | `block_trade_proposals.list(...)` / `block_trade_proposals.list_all(...)` | `GET /communications/block-trade-proposals` | | `block_trade_proposals.create(...)` | `POST /communications/block-trade-proposals` | -| `block_trade_proposals.accept(block_trade_proposal_id, *, subtrader_id=None, subaccount=None)` | `POST /communications/block-trade-proposals/{id}/accept` | +| `block_trade_proposals.accept(block_trade_proposal_id, *, subtrader_id=None, subaccount=None)` | `POST /communications/block-trade-proposals/{block_trade_proposal_id}/accept` | `get_id()` returns your `participant_id` — the value you'll pass as `quote_creator_user_id` / `rfq_creator_user_id` when filtering lists. It stays diff --git a/docs/resources/events.md b/docs/resources/events.md index a5ffdd79..c9c540e2 100644 --- a/docs/resources/events.md +++ b/docs/resources/events.md @@ -38,9 +38,9 @@ for event in page: Unlike [`MarketStatusLiteral`](../types.md), there is no `"paused"`. `tickers` (added in v4.1.0) restricts the result set to specific event -tickers. Pass a `list[str]` (joined comma-separated on the wire) or a -pre-joined comma-separated string; list elements must be non-empty and must -not themselves contain commas. +tickers. Pass a `list[str]` or a single comma-separated string; individual +tickers must be non-empty and must not contain commas (the list is sent as +one comma-separated value). ## Multivariate events