From 7959fd8da77190cbad9bb0f2c10f1f6c017d5b4f Mon Sep 17 00:00:00 2001 From: Jeff West Date: Thu, 6 Aug 2026 05:30:57 -0500 Subject: [PATCH] Reconcile OpenAPI/AsyncAPI/perps spec drift (v10.0.0) Closes #496 and #497. Syncs vendored specs and removes upstream-deleted multivariate lookup (REST PUT + WS channel), adds portfolio transfer-history GETs, and maps optional exchange_index / side-specific leverage fields. --- CHANGELOG.md | 41 ++ CLAUDE.md | 2 +- README.md | 7 +- ROADMAP.md | 7 + docs/index.md | 7 +- docs/migration.md | 53 +- docs/reference.md | 2 - docs/request-models.md | 1 - docs/resources/multivariate.md | 33 +- docs/resources/portfolio.md | 25 + docs/websockets.md | 7 +- kalshi/__init__.py | 12 +- kalshi/_contract_map.py | 19 +- kalshi/models/__init__.py | 10 +- kalshi/models/multivariate.py | 33 +- kalshi/models/portfolio.py | 29 + kalshi/perps/models/markets.py | 3 + kalshi/resources/multivariate.py | 106 +--- kalshi/resources/portfolio.py | 117 ++++ kalshi/ws/channels.py | 1 - kalshi/ws/client.py | 10 +- kalshi/ws/dispatch.py | 3 +- kalshi/ws/models/__init__.py | 6 - kalshi/ws/models/multivariate.py | 37 +- pyproject.toml | 10 +- specs/asyncapi.yaml | 102 +--- specs/openapi.yaml | 272 ++++++--- specs/perps_openapi.yaml | 772 +++++++++++++++++-------- tests/_contract_support.py | 32 +- tests/_model_fixtures.py | 11 - tests/integration/test_multivariate.py | 96 --- tests/integration/test_websocket.py | 18 - tests/perps/test_markets.py | 10 + tests/perps/ws/test_perps_ws_models.py | 3 +- tests/test_client.py | 9 - tests/test_contracts.py | 3 - tests/test_models.py | 29 - tests/test_multivariate.py | 111 ---- tests/test_multivariate_models.py | 10 - tests/test_portfolio.py | 102 ++++ tests/ws/test_dispatch.py | 39 +- tests/ws/test_models.py | 56 -- 42 files changed, 1174 insertions(+), 1082 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6124762f..bcbb92b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,47 @@ All notable changes to kalshi-sdk will be documented in this file. +## 10.0.0 — 2026-08-06 + +Reconciles upstream OpenAPI **3.27.0** content drift plus AsyncAPI / perps +OpenAPI changes (Closes #496, #497). **Breaking** for callers of multivariate +lookup (REST + WS). + +### Removed (breaking) + +- **`multivariate_collections.lookup_tickers()`** (sync + async) and models + `LookupTickersForMarketInMultivariateEventCollectionRequest` / + `LookupTickersResponse`. Upstream deleted + `PUT /multivariate_event_collections/{collection_ticker}/lookup` and the + matching schemas. `create_market()` remains (still deprecated). +- **WS `subscribe_multivariate()`** plus `MultivariateMessage` / + `MultivariatePayload` / `SelectedMarket`. AsyncAPI removed the + `multivariate` channel and `multivariateLookupPayload` schema. + `subscribe_multivariate_lifecycle()` is unchanged. + +### Added + +- **`portfolio.intra_exchange_transfers()`** / + **`intra_exchange_transfers_all()`** / + **`get_intra_exchange_transfer(transfer_id)`** (sync + async) — + `GET /portfolio/intra_exchange_instance_transfers` and + `GET /portfolio/intra_exchange_instance_transfers/{transfer_id}`. + Model: `IntraExchangeInstanceTransfer` (`amount` is fixed-point dollars). + Complements `PerpsClient.transfers.transfer_instance()` (POST create). +- **`MultivariateEventCollection.exchange_index`** (`int | None`) — optional + exchange shard inherited from the collection's series. +- **Perps** `MarginMarket.long_leverage_estimates` / + `short_leverage_estimates` (`dict[str, MultiplierDecimal] | None`). + +### Spec notes + +- Core OpenAPI `info.version` still **3.27.0** (paths 92→92; 103→104 operations; + 103 mapped). Still unimplemented on the core client: + `POST /portfolio/intra_exchange_instance_transfer` (use + `PerpsClient.transfers.transfer_instance()`). +- AsyncAPI: channels 15→14 (`multivariate` removed). +- Perps OpenAPI: additive optional leverage side maps on `MarginMarket`. + ## 9.0.0 — 2026-07-31 Syncs upstream core OpenAPI **3.26.0 → 3.27.0** (paths stay 92; 103 operations / diff --git a/CLAUDE.md b/CLAUDE.md index 5bfaff92..df909b68 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -122,7 +122,7 @@ tests/ ## API Reference -- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.27.0, 103 operations; 102 mapped in the core SDK — `POST /portfolio/intra_exchange_instance_transfer` is currently not available upstream) +- OpenAPI spec: https://docs.kalshi.com/openapi.yaml (v3.27.0, 104 operations; 103 mapped in the core SDK — `POST /portfolio/intra_exchange_instance_transfer` is implemented on `PerpsClient.transfers.transfer_instance` and left unimplemented on the core client) - AsyncAPI spec: https://docs.kalshi.com/asyncapi.yaml (13 WebSocket channels) - Base URL: https://api.elections.kalshi.com/trade-api/v2 - Demo URL: https://demo-api.kalshi.co/trade-api/v2 diff --git a/README.md b/README.md index 135d77b2..fa95324f 100644 --- a/README.md +++ b/README.md @@ -171,13 +171,12 @@ async def main() -> None: asyncio.run(main()) ``` -Available channels (12 typed + 2 escape-hatch). Twelve have dedicated +Available channels (11 typed + 2 escape-hatch). Eleven have dedicated `subscribe_*` methods — `subscribe_ticker`, `subscribe_trade`, `subscribe_orderbook_delta`, `subscribe_fill`, `subscribe_market_positions`, `subscribe_user_orders`, `subscribe_order_group`, -`subscribe_market_lifecycle`, `subscribe_multivariate`, -`subscribe_multivariate_lifecycle`, `subscribe_communications`, -`subscribe_cfbenchmarks_value`. The +`subscribe_market_lifecycle`, `subscribe_multivariate_lifecycle`, +`subscribe_communications`, `subscribe_cfbenchmarks_value`. The AsyncAPI-declared `control_frames` and `root` channels are reachable through the generic `subscribe(channel, ...)` escape hatch. See [docs/websockets.md](docs/websockets.md#the-12-channels) for the full diff --git a/ROADMAP.md b/ROADMAP.md index 485c0112..53b8f888 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,6 +2,13 @@ ## Shipped +- **v10.0.0 (2026-08-06)** — Spec-drift reconcile (#496 / #497). **Breaking:** + removed `multivariate_collections.lookup_tickers()` and WS + `subscribe_multivariate()` after upstream deleted the REST lookup endpoint + and AsyncAPI `multivariate` channel. Additive: + `portfolio.intra_exchange_transfers()` / `get_intra_exchange_transfer()`, + `MultivariateEventCollection.exchange_index`, perps + `MarginMarket.long_leverage_estimates` / `short_leverage_estimates`. - **v9.0.0 (2026-07-31)** — OpenAPI sync 3.26.0 → 3.27.0 (#492). **Breaking:** removed `subaccounts.transfer_position()` after upstream deleted position transfers. Additive: `live_data.get_event()`, diff --git a/docs/index.md b/docs/index.md index 0d0347e8..77bc9244 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,14 +3,15 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) prediction markets API. -- **Full REST coverage** — 102 operations across 19 resources (OpenAPI v3.27.0), +- **Full REST coverage** — 103 operations across 19 resources (OpenAPI v3.27.0), every kwarg drift-tested against the spec. - **V2 event-market orders** — new `create_v2` / `amend_v2` / `decrease_v2` / `cancel_v2` family on `/portfolio/events/orders/*`. Legacy `/portfolio/orders` keeps working; deprecation no earlier than May 6, 2026. - **Funding + cost introspection** — `portfolio.deposits()`, - `portfolio.withdrawals()`, `account.endpoint_costs()`. -- **Full WebSocket coverage** — 12 channels with sequence-gap detection, automatic + `portfolio.withdrawals()`, `portfolio.intra_exchange_transfers()`, + `account.endpoint_costs()`. +- **Full WebSocket coverage** — 11 channels with sequence-gap detection, automatic reconnection (with resubscribe-window frame stashing for high-volume channels), backpressure strategies, and an in-memory orderbook builder. Async-only — access via `AsyncKalshiClient.ws`. diff --git a/docs/migration.md b/docs/migration.md index 731eff49..71e90f54 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1,5 +1,45 @@ # Migration +## v9.0 → v10.0.0 + +Reconciles upstream OpenAPI **3.27.0** content (paths still 92; +2 GET +transfer-history ops), AsyncAPI removal of the `multivariate` channel, and +perps `MarginMarket` leverage side maps (Closes #496, #497). **Breaking** for +callers of multivariate lookup (REST + WS). + +### Removed + +- **`multivariate_collections.lookup_tickers()`** (sync + async) and + `LookupTickersForMarketInMultivariateEventCollectionRequest` / + `LookupTickersResponse`. Upstream deleted + `PUT /multivariate_event_collections/{collection_ticker}/lookup`. +- **WS `subscribe_multivariate()`** and `MultivariateMessage` / + `MultivariatePayload`. Use RFQs for new multivariate integrations; lifecycle + remains on `subscribe_multivariate_lifecycle()`. + +```python +# No longer available — the upstream REST endpoint and WS channel are gone: +# client.multivariate_collections.lookup_tickers("MVC-1", selected_markets=[...]) +# await ws.subscribe_multivariate() + +# create_market is still present (deprecated; predates RFQs): +# client.multivariate_collections.create_market(...) +``` + +### Added (non-breaking) + +- **`portfolio.intra_exchange_transfers()`** / + **`intra_exchange_transfers_all()`** / + **`get_intra_exchange_transfer(transfer_id)`** — history/detail for + event↔margined fund moves. Create transfers still via + `PerpsClient.transfers.transfer_instance()`. +- **`MultivariateEventCollection.exchange_index`** (optional). +- **Perps** `MarginMarket.long_leverage_estimates` / + `short_leverage_estimates` (optional). + +See the [changelog](https://github.com/TexasCoding/kalshi-python-sdk/blob/main/CHANGELOG.md) +for the full list. + ## v8.0 → v9.0.0 Syncs the SDK to core OpenAPI **3.27.0** (and the matching perps / SCM @@ -713,14 +753,13 @@ for position in client.portfolio.positions_all(): ### Multivariate endpoints emit `DeprecationWarning` -Per #269, `multivariate.lookup_tickers` and `multivariate.create_market` -(sync + async) carry `@typing_extensions.deprecated` decorators citing -the spec's "should not be used for new integrations" guidance. Use RFQs -instead. The endpoints still work; calls just emit a `DeprecationWarning` -on first use. +Per #269, `multivariate.create_market` (sync + async) carries a +`@typing_extensions.deprecated` decorator citing the spec's "should not be +used for new integrations" guidance. Use RFQs instead. The endpoint still +works; calls just emit a `DeprecationWarning` on first use. -(`multivariate.lookup_history`, also deprecated here in #269, was removed -entirely in 6.0.0 — see the [v5 → v6.0.0](#v5-v600) section above.) +(`multivariate.lookup_history` was removed in 6.0.0; +`multivariate.lookup_tickers` was removed in 10.0.0 — see the sections above.) ### `orders.list(event_ticker=...)` accepts lists diff --git a/docs/reference.md b/docs/reference.md index 32be5c85..715ac693 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -62,8 +62,6 @@ every exception class. ::: kalshi.models.multivariate.CreateMarketInMultivariateEventCollectionRequest -::: kalshi.models.multivariate.LookupTickersForMarketInMultivariateEventCollectionRequest - ::: kalshi.models.order_groups.CreateOrderGroupRequest ::: kalshi.models.order_groups.UpdateOrderGroupLimitRequest diff --git a/docs/request-models.md b/docs/request-models.md index 9451f3f5..d6344e7f 100644 --- a/docs/request-models.md +++ b/docs/request-models.md @@ -64,7 +64,6 @@ exposed by each resource method stays in lockstep with the OpenAPI spec. | `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` | | `client.order_groups.update_limit` | `UpdateOrderGroupLimitRequest` | | `client.subaccounts.transfer` | `ApplySubaccountTransferRequest` | diff --git a/docs/resources/multivariate.md b/docs/resources/multivariate.md index d4182bfe..f0bf743f 100644 --- a/docs/resources/multivariate.md +++ b/docs/resources/multivariate.md @@ -9,11 +9,18 @@ Public listing, auth-required minting. Attribute name on the client: `multivariate_collections`. !!! warning "Deprecated methods" - `create_market()` and `lookup_tickers()` are deprecated — "This endpoint - predates RFQs and should not be used for new integrations." Calling them - emits a `DeprecationWarning`. Use the - [Communications (RFQ/Quote)](communications.md) surface instead. `list()` / - `list_all()` / `get()` remain supported. + `create_market()` is deprecated — "This endpoint predates RFQs and should + not be used for new integrations." Calling it emits a `DeprecationWarning`. + Use the [Communications (RFQ/Quote)](communications.md) surface instead. + `list()` / `list_all()` / `get()` remain supported. + +!!! danger "Removed in 10.0.0" + `lookup_tickers()` and the + `LookupTickersForMarketInMultivariateEventCollectionRequest` / + `LookupTickersResponse` models were removed — Kalshi deleted + `PUT /multivariate_event_collections/{ticker}/lookup` from the OpenAPI + spec, and the AsyncAPI `multivariate` / `multivariate_lookup` channel + with it. !!! danger "Removed in 6.0.0" `lookup_history()` and the `LookupPoint` model were removed — Kalshi deleted @@ -27,7 +34,6 @@ Public listing, auth-required minting. Attribute name on the client: | `list(...)` / `list_all(...)` | `GET /multivariate_event_collections` | no | | `get(collection_ticker)` | `GET /multivariate_event_collections/{ticker}` | no | | `create_market(collection_ticker, *, selected_markets, with_market_payload=False)` | `POST /multivariate_event_collections/{ticker}` | yes | -| `lookup_tickers(collection_ticker, *, selected_markets)` | `PUT /multivariate_event_collections/{ticker}/lookup` | yes | ## List collections @@ -38,7 +44,7 @@ page = client.multivariate_collections.list( limit=100, ) for c in page: - print(c.collection_ticker, c.title) + print(c.collection_ticker, c.title, c.exchange_index) ``` ## Select legs @@ -55,19 +61,6 @@ legs = [ ] ``` -## Lookup the auto-generated ticker (no mint) - -```python -resp = client.multivariate_collections.lookup_tickers( - "KXWEATHER-SPORTS-COMBO", - selected_markets=legs, -) -print(resp.market_ticker, resp.event_ticker) -``` - -Wire-level note: this endpoint is a `PUT` — unusual for a read operation, but -matches the OpenAPI spec. - ## Mint a combo market ```python diff --git a/docs/resources/portfolio.md b/docs/resources/portfolio.md index 30eeaa47..62054704 100644 --- a/docs/resources/portfolio.md +++ b/docs/resources/portfolio.md @@ -14,6 +14,8 @@ Auth required throughout. | `total_resting_order_value()` | `GET /portfolio/summary/total_resting_order_value` (FCM only) | | `deposits(*, limit, cursor)` / `deposits_all(*, limit, max_pages)` | `GET /portfolio/deposits` | | `withdrawals(*, limit, cursor)` / `withdrawals_all(*, limit, max_pages)` | `GET /portfolio/withdrawals` | +| `intra_exchange_transfers(...)` / `intra_exchange_transfers_all(...)` | `GET /portfolio/intra_exchange_instance_transfers` | +| `get_intra_exchange_transfer(transfer_id)` | `GET /portfolio/intra_exchange_instance_transfers/{transfer_id}` | `balance()`, `positions()` / `positions_all()`, `settlements()` / `settlements_all()`, and `fills()` / `fills_all()` all take an optional @@ -186,6 +188,29 @@ which is `None` until the transfer settles. Both `*_all` variants accept `max_pages=N` to bound iteration. +## Intra-exchange instance transfers + +New in v10.0.0. History and detail for fund moves between the +`event_contract` and `margined` exchange instances. **Creating** a transfer +is still on the perps surface +(`PerpsClient.transfers.transfer_instance()`); these GETs are on the core +portfolio API. + +```python +page = client.portfolio.intra_exchange_transfers(limit=50) +for t in page: + print(t.transfer_id, t.source, t.destination, t.amount, t.status) + +for t in client.portfolio.intra_exchange_transfers_all(): + ... + +t = client.portfolio.get_intra_exchange_transfer("xfer-...") +print(t.status, t.created_ts) +``` + +`IntraExchangeInstanceTransfer.amount` is a fixed-point dollar +`DollarDecimal` (not the integer centicents used on the POST create body). + ## Position fields `MarketPosition` and `EventPosition` use the standard `_dollars` / `_fp` diff --git a/docs/websockets.md b/docs/websockets.md index 5f1156aa..b7bd2967 100644 --- a/docs/websockets.md +++ b/docs/websockets.md @@ -29,7 +29,7 @@ SDK's perspective on it. on every delta. - `on_state_change=` and `on_error=` hooks on the constructor for observability. -## The 12 channels +## The 11 channels | SDK method | Wire channel | Message `type` field | Message class | Auth | |---|---|---|---|---| @@ -37,7 +37,6 @@ SDK's perspective on it. | `subscribe_trade` | `trade` | `trade` | `TradeMessage` | public | | `subscribe_orderbook_delta` | `orderbook_delta` | `orderbook_snapshot` → `orderbook_delta` | `OrderbookSnapshotMessage` / `OrderbookDeltaMessage` | public | | `subscribe_market_lifecycle` | `market_lifecycle_v2` | `market_lifecycle_v2` / `event_fee_update` | `MarketLifecycleMessage` / `EventFeeUpdateMessage` | public | -| `subscribe_multivariate` | `multivariate` | `multivariate_lookup` | `MultivariateMessage` | public | | `subscribe_multivariate_lifecycle` | `multivariate_market_lifecycle` | `multivariate_market_lifecycle` | `MultivariateLifecycleMessage` | public | | `subscribe_fill` | `fill` | `fill` | `FillMessage` | private | | `subscribe_user_orders` | `user_orders` | `user_order` (singular) | `UserOrdersMessage` | private | @@ -47,8 +46,8 @@ SDK's perspective on it. | `subscribe_cfbenchmarks_value` | `cfbenchmarks_value` | `cfbenchmarks_value` / `cfbenchmarks_value_indexlist` | `CFBenchmarksValueMessage` / `CFBenchmarksIndexListMessage` | private | The `type` column matters when filtering raw logs — note the singular forms -for `user_order`, `market_position`, and the `multivariate_lookup` / -`multivariate` mismatch. +for `user_order` and `market_position`. The standalone `multivariate` / +`multivariate_lookup` channel was removed from AsyncAPI (SDK v10.0.0). !!! warning "Migration (v3.1.0): `event_fee_update` rides `market_lifecycle_v2`" Since the v3.20.0 spec sync (SDK v3.1.0) the `market_lifecycle_v2` channel diff --git a/kalshi/__init__.py b/kalshi/__init__.py index 177e5992..4c61e481 100644 --- a/kalshi/__init__.py +++ b/kalshi/__init__.py @@ -80,6 +80,7 @@ EventPosition, EventStatusLiteral, ExchangeIndexStatus, + ExchangeInstanceLiteral, ExchangeStatus, Fill, ForecastPercentilesPoint, @@ -111,9 +112,9 @@ IncentiveProgramStatusLiteral, IncentiveProgramTypeLiteral, IndexedBalance, + IntraExchangeInstanceTransfer, + IntraExchangeInstanceTransferStatusLiteral, LiveData, - LookupTickersForMarketInMultivariateEventCollectionRequest, - LookupTickersResponse, MaintenanceWindow, Market, MarketCandlesticks, @@ -253,6 +254,7 @@ "EventPosition", "EventStatusLiteral", "ExchangeIndexStatus", + "ExchangeInstanceLiteral", "ExchangeStatus", "Fill", "FixClient", @@ -288,6 +290,8 @@ "IncentiveProgramStatusLiteral", "IncentiveProgramTypeLiteral", "IndexedBalance", + "IntraExchangeInstanceTransfer", + "IntraExchangeInstanceTransferStatusLiteral", "KalshiAuth", "KalshiAuthError", "KalshiBackpressureError", @@ -311,8 +315,6 @@ "KlearClient", "KlearConfig", "LiveData", - "LookupTickersForMarketInMultivariateEventCollectionRequest", - "LookupTickersResponse", "MaintenanceWindow", "MarginFixClient", "Market", @@ -379,4 +381,4 @@ "Withdrawal", ] -__version__ = "9.0.0" +__version__ = "10.0.0" diff --git a/kalshi/_contract_map.py b/kalshi/_contract_map.py index eaffbe77..b65ba3b1 100644 --- a/kalshi/_contract_map.py +++ b/kalshi/_contract_map.py @@ -482,13 +482,9 @@ class ContractEntry: notes="Long-form CreateMarketInMultivariateEventCollectionResponse", ), ContractEntry( - sdk_model="kalshi.models.multivariate.LookupTickersForMarketInMultivariateEventCollectionRequest", - spec_schema="LookupTickersForMarketInMultivariateEventCollectionRequest", - ), - ContractEntry( - sdk_model="kalshi.models.multivariate.LookupTickersResponse", - spec_schema="LookupTickersForMarketInMultivariateEventCollectionResponse", - notes="Spec name is the long-form ...Response; SDK shortens", + sdk_model="kalshi.models.portfolio.IntraExchangeInstanceTransfer", + spec_schema="IntraExchangeInstanceTransfer", + notes="GET transfer history/detail response item (#496/#497)", ), ] @@ -565,15 +561,6 @@ class ContractEntry: "user_order drift. No direct demo capture (demo account idle for " "positions during capture window).", ), - ContractEntry( - sdk_model="kalshi.ws.models.multivariate.MultivariatePayload", - spec_schema="multivariateLookupPayload", - notes="Aligned to spec v0.14.0 (2026-04-19): envelope type is " - "'multivariate_lookup' on the wire; channel name stays 'multivariate'. " - "MultivariateLifecycleMessage (type 'multivariate_market_lifecycle') " - "is unaffected -- separate spec-aligned sibling. No direct demo " - "capture (no active collections emitting).", - ), ContractEntry( sdk_model="kalshi.ws.models.order_group.OrderGroupPayload", spec_schema="orderGroupUpdatesPayload", diff --git a/kalshi/models/__init__.py b/kalshi/models/__init__.py index dc19917e..aa23b655 100644 --- a/kalshi/models/__init__.py +++ b/kalshi/models/__init__.py @@ -97,8 +97,6 @@ AssociatedEvent, CreateMarketInMultivariateEventCollectionRequest, CreateMarketResponse, - LookupTickersForMarketInMultivariateEventCollectionRequest, - LookupTickersResponse, MultivariateCollectionStatusLiteral, MultivariateEventCollection, TickerPair, @@ -138,7 +136,10 @@ Balance, Deposit, EventPosition, + ExchangeInstanceLiteral, IndexedBalance, + IntraExchangeInstanceTransfer, + IntraExchangeInstanceTransferStatusLiteral, MarketPosition, PaymentStatusLiteral, PaymentTypeLiteral, @@ -234,6 +235,7 @@ "EventPosition", "EventStatusLiteral", "ExchangeIndexStatus", + "ExchangeInstanceLiteral", "ExchangeStatus", "Fill", "ForecastPercentilesPoint", @@ -265,9 +267,9 @@ "IncentiveProgramStatusLiteral", "IncentiveProgramTypeLiteral", "IndexedBalance", + "IntraExchangeInstanceTransfer", + "IntraExchangeInstanceTransferStatusLiteral", "LiveData", - "LookupTickersForMarketInMultivariateEventCollectionRequest", - "LookupTickersResponse", "MaintenanceWindow", "Market", "MarketCandlesticks", diff --git a/kalshi/models/multivariate.py b/kalshi/models/multivariate.py index 789de40c..8722f633 100644 --- a/kalshi/models/multivariate.py +++ b/kalshi/models/multivariate.py @@ -51,6 +51,8 @@ class MultivariateEventCollection(BaseModel): size_min: int size_max: int functional_description: str + # Optional: exchange shard inherited from the collection's series. + exchange_index: int | None = None model_config = {"extra": "allow", "populate_by_name": True} @@ -95,28 +97,6 @@ class CreateMarketInMultivariateEventCollectionRequest(BaseModel): model_config = {"extra": "forbid"} -class LookupTickersForMarketInMultivariateEventCollectionRequest(BaseModel): - """Parameters for ``PUT /multivariate_event_collections/{collection_ticker}/lookup``. - - Matches spec - ``components.schemas.LookupTickersForMarketInMultivariateEventCollectionRequest``. - Only ``selected_markets``, required. - - Carve-out: ``extra="forbid"`` on this model rejects unknown top-level - keys but NOT unknown keys inside each ``TickerPair`` in - ``selected_markets`` — ``TickerPair`` itself is ``extra="allow"`` (see - its docstring for why). Phantom keys nested inside a ``TickerPair`` - currently pass through to the wire. Tracked as a v0.9 follow-up. - - See ``kalshi.resources.multivariate.MultivariateCollectionsResource.lookup_tickers`` - — v0.8.0 builds this model internally; method signature unchanged. - """ - - selected_markets: list[TickerPair] - - model_config = {"extra": "forbid"} - - class CreateMarketResponse(BaseModel): """Response from creating a market in a multivariate collection.""" @@ -125,12 +105,3 @@ class CreateMarketResponse(BaseModel): market: Market | None = None model_config = {"extra": "allow"} - - -class LookupTickersResponse(BaseModel): - """Response from looking up tickers in a multivariate collection.""" - - event_ticker: str - market_ticker: str - - model_config = {"extra": "allow"} diff --git a/kalshi/models/portfolio.py b/kalshi/models/portfolio.py index 2a6e0918..15a66410 100644 --- a/kalshi/models/portfolio.py +++ b/kalshi/models/portfolio.py @@ -203,3 +203,32 @@ class Settlement(BaseModel): value: int | None = None model_config = {"extra": "allow", "populate_by_name": True} + + +ExchangeInstanceLiteral = Literal["event_contract", "margined"] +"""Exchange instance for intra-exchange fund movement (event vs margined).""" + +IntraExchangeInstanceTransferStatusLiteral = Literal["pending", "complete"] +"""Status of an intra-exchange instance transfer.""" + + +class IntraExchangeInstanceTransfer(BaseModel): + """A single intra-exchange instance transfer history entry. + + Spec ``IntraExchangeInstanceTransfer``. ``amount`` is a fixed-point + dollar string (``FixedPointDollars``), not centicents — unlike the + POST request body's integer ``amount`` on + :class:`~kalshi.perps.models.transfers.IntraExchangeInstanceTransferRequest`. + """ + + transfer_id: str + source: ExchangeInstanceLiteral + destination: ExchangeInstanceLiteral + source_exchange_shard: int + destination_exchange_shard: int + amount: DollarDecimal + status: IntraExchangeInstanceTransferStatusLiteral + created_ts: int + + model_config = {"extra": "allow"} + diff --git a/kalshi/perps/models/markets.py b/kalshi/perps/models/markets.py index b10ee219..817b2b76 100644 --- a/kalshi/perps/models/markets.py +++ b/kalshi/perps/models/markets.py @@ -90,6 +90,9 @@ class MarginMarket(BaseModel): # Leverage (1 / margin_rate) keyed by notional position size in dollars # ("1000", "10000", ...). Null when margin config or price data is missing. leverage_estimates: dict[str, MultiplierDecimal] | None = None + # Side-specific leverage maps (same notional keys as leverage_estimates). + long_leverage_estimates: dict[str, MultiplierDecimal] | None = None + short_leverage_estimates: dict[str, MultiplierDecimal] | None = None price: DollarDecimal | None = None bid: DollarDecimal | None = None ask: DollarDecimal | None = None diff --git a/kalshi/resources/multivariate.py b/kalshi/resources/multivariate.py index 465a7233..dfcfaef2 100644 --- a/kalshi/resources/multivariate.py +++ b/kalshi/resources/multivariate.py @@ -1,4 +1,4 @@ -"""Multivariate event collections resource — list, get, create, lookup.""" +"""Multivariate event collections resource — list, get, create market.""" from __future__ import annotations @@ -12,8 +12,6 @@ from kalshi.models.multivariate import ( CreateMarketInMultivariateEventCollectionRequest, CreateMarketResponse, - LookupTickersForMarketInMultivariateEventCollectionRequest, - LookupTickersResponse, MultivariateCollectionStatusLiteral, MultivariateEventCollection, TickerPair, @@ -76,34 +74,6 @@ def _build_create_market_body( return request.model_dump(exclude_none=True, by_alias=True, mode="json") -def _build_lookup_tickers_body( - request: LookupTickersForMarketInMultivariateEventCollectionRequest | None, - *, - selected_markets: builtins.list[TickerPair] | None, -) -> dict[str, Any]: - _check_request_exclusive(request, selected_markets=selected_markets) - if request is None: - if selected_markets is None: - raise TypeError("lookup_tickers() requires `selected_markets` (or pass `request=...`)") - request = LookupTickersForMarketInMultivariateEventCollectionRequest( - selected_markets=list(selected_markets), - ) - return request.model_dump(exclude_none=True, by_alias=True, mode="json") - - -def _parse_lookup_tickers_response( - data: dict[str, Any] | None, -) -> LookupTickersResponse: - # Spec: this endpoint always returns 200 with body; guard against a - # future server regression to 204 giving opaque Pydantic errors. - # (use an explicit check, not assert — asserts are stripped under -O) - if data is None: - raise RuntimeError( - "lookup: expected 200 with body, got 204 (spec drift)", - ) - return LookupTickersResponse.model_validate(data) - - class MultivariateCollectionsResource(SyncResource): """Sync multivariate event collections API.""" @@ -208,43 +178,6 @@ def create_market( ) return CreateMarketResponse.model_validate(data) - @overload - def lookup_tickers( - self, - collection_ticker: str, - *, - request: LookupTickersForMarketInMultivariateEventCollectionRequest, - extra_headers: dict[str, str] | None = None, - ) -> LookupTickersResponse: ... - @overload - def lookup_tickers( - self, - collection_ticker: str, - *, - selected_markets: builtins.list[TickerPair], - extra_headers: dict[str, str] | None = None, - ) -> LookupTickersResponse: ... - @deprecated(_DEPRECATION_MSG) - def lookup_tickers( - self, - collection_ticker: str, - *, - request: (LookupTickersForMarketInMultivariateEventCollectionRequest | None) = None, - selected_markets: builtins.list[TickerPair] | None = None, - extra_headers: dict[str, str] | None = None, - ) -> LookupTickersResponse: - self._require_auth() - body = _build_lookup_tickers_body( - request, - selected_markets=selected_markets, - ) - data = self._put( - f"/multivariate_event_collections/{_seg(collection_ticker, name='collection_ticker')}/lookup", # noqa: E501 - json=body, - extra_headers=extra_headers, - ) - return _parse_lookup_tickers_response(data) - class AsyncMultivariateCollectionsResource(AsyncResource): """Async multivariate event collections API.""" @@ -349,40 +282,3 @@ async def create_market( extra_headers=extra_headers, ) return CreateMarketResponse.model_validate(data) - - @overload - async def lookup_tickers( - self, - collection_ticker: str, - *, - request: LookupTickersForMarketInMultivariateEventCollectionRequest, - extra_headers: dict[str, str] | None = None, - ) -> LookupTickersResponse: ... - @overload - async def lookup_tickers( - self, - collection_ticker: str, - *, - selected_markets: builtins.list[TickerPair], - extra_headers: dict[str, str] | None = None, - ) -> LookupTickersResponse: ... - @deprecated(_DEPRECATION_MSG) - async def lookup_tickers( - self, - collection_ticker: str, - *, - request: (LookupTickersForMarketInMultivariateEventCollectionRequest | None) = None, - selected_markets: builtins.list[TickerPair] | None = None, - extra_headers: dict[str, str] | None = None, - ) -> LookupTickersResponse: - self._require_auth() - body = _build_lookup_tickers_body( - request, - selected_markets=selected_markets, - ) - data = await self._put( - f"/multivariate_event_collections/{_seg(collection_ticker, name='collection_ticker')}/lookup", # noqa: E501 - json=body, - extra_headers=extra_headers, - ) - return _parse_lookup_tickers_response(data) diff --git a/kalshi/resources/portfolio.py b/kalshi/resources/portfolio.py index 86ce7d29..decc5e62 100644 --- a/kalshi/resources/portfolio.py +++ b/kalshi/resources/portfolio.py @@ -10,6 +10,7 @@ from kalshi.models.portfolio import ( Balance, Deposit, + IntraExchangeInstanceTransfer, MarketPosition, PositionsResponse, Settlement, @@ -21,6 +22,7 @@ SyncResource, _fills_params, _params, + _seg, _validate_limit, _validate_max_pages, ) @@ -361,6 +363,66 @@ def withdrawals_all( extra_headers=extra_headers, ) + def intra_exchange_transfers( + self, + *, + limit: int | None = None, + cursor: str | None = None, + extra_headers: dict[str, str] | None = None, + ) -> Page[IntraExchangeInstanceTransfer]: + """List intra-exchange instance transfer history. + + ``GET /portfolio/intra_exchange_instance_transfers``. Complements + :meth:`~kalshi.perps.resources.transfers.TransfersResource.transfer_instance` + (POST create on the margin product). + """ + self._require_auth() + _validate_limit(limit, hi=500) + params = _params(limit=limit, cursor=cursor) + return self._list( + "/portfolio/intra_exchange_instance_transfers", + IntraExchangeInstanceTransfer, + "transfers", + params=params, + extra_headers=extra_headers, + ) + + def intra_exchange_transfers_all( + self, + *, + limit: int | None = None, + max_pages: int | None = None, + extra_headers: dict[str, str] | None = None, + ) -> Iterator[IntraExchangeInstanceTransfer]: + """Auto-paginate intra-exchange instance transfers.""" + self._require_auth() + _validate_max_pages(max_pages) + _validate_limit(limit, hi=500) + params = _params(limit=limit) + return self._list_all( + "/portfolio/intra_exchange_instance_transfers", + IntraExchangeInstanceTransfer, + "transfers", + params=params, + max_pages=max_pages, + extra_headers=extra_headers, + ) + + def get_intra_exchange_transfer( + self, + transfer_id: str, + *, + extra_headers: dict[str, str] | None = None, + ) -> IntraExchangeInstanceTransfer: + """Get a single intra-exchange instance transfer by id.""" + self._require_auth() + data = self._get( + f"/portfolio/intra_exchange_instance_transfers/" + f"{_seg(transfer_id, name='transfer_id')}", + extra_headers=extra_headers, + ) + return IntraExchangeInstanceTransfer.model_validate(data.get("transfer", data)) + class AsyncPortfolioResource(AsyncResource): """Async portfolio API.""" @@ -657,3 +719,58 @@ def withdrawals_all( max_pages=max_pages, extra_headers=extra_headers, ) + + async def intra_exchange_transfers( + self, + *, + limit: int | None = None, + cursor: str | None = None, + extra_headers: dict[str, str] | None = None, + ) -> Page[IntraExchangeInstanceTransfer]: + """List intra-exchange instance transfer history (async).""" + self._require_auth() + _validate_limit(limit, hi=500) + params = _params(limit=limit, cursor=cursor) + return await self._list( + "/portfolio/intra_exchange_instance_transfers", + IntraExchangeInstanceTransfer, + "transfers", + params=params, + extra_headers=extra_headers, + ) + + def intra_exchange_transfers_all( + self, + *, + limit: int | None = None, + max_pages: int | None = None, + extra_headers: dict[str, str] | None = None, + ) -> AsyncIterator[IntraExchangeInstanceTransfer]: + """Auto-paginate intra-exchange instance transfers (async).""" + self._require_auth() + _validate_max_pages(max_pages) + _validate_limit(limit, hi=500) + params = _params(limit=limit) + return self._list_all( + "/portfolio/intra_exchange_instance_transfers", + IntraExchangeInstanceTransfer, + "transfers", + params=params, + max_pages=max_pages, + extra_headers=extra_headers, + ) + + async def get_intra_exchange_transfer( + self, + transfer_id: str, + *, + extra_headers: dict[str, str] | None = None, + ) -> IntraExchangeInstanceTransfer: + """Get a single intra-exchange instance transfer by id (async).""" + self._require_auth() + data = await self._get( + f"/portfolio/intra_exchange_instance_transfers/" + f"{_seg(transfer_id, name='transfer_id')}", + extra_headers=extra_headers, + ) + return IntraExchangeInstanceTransfer.model_validate(data.get("transfer", data)) diff --git a/kalshi/ws/channels.py b/kalshi/ws/channels.py index 7fbd0f78..e950f646 100644 --- a/kalshi/ws/channels.py +++ b/kalshi/ws/channels.py @@ -56,7 +56,6 @@ "market_lifecycle_v2": frozenset({ "market_ticker", "market_tickers", "market_id", "market_ids", }), - "multivariate": frozenset(), "multivariate_market_lifecycle": frozenset(), "communications": frozenset({"shard_factor", "shard_key"}), # CF Benchmarks index value feed: seeded with index_ids only — market_* diff --git a/kalshi/ws/client.py b/kalshi/ws/client.py index 85a31f52..7bcf59f1 100644 --- a/kalshi/ws/client.py +++ b/kalshi/ws/client.py @@ -36,7 +36,7 @@ from kalshi.ws.models.fill import FillMessage from kalshi.ws.models.market_lifecycle import MarketLifecycleMessage from kalshi.ws.models.market_positions import MarketPositionsMessage -from kalshi.ws.models.multivariate import MultivariateLifecycleMessage, MultivariateMessage +from kalshi.ws.models.multivariate import MultivariateLifecycleMessage from kalshi.ws.models.order_group import OrderGroupMessage from kalshi.ws.models.orderbook_delta import OrderbookDeltaMessage, OrderbookSnapshotMessage from kalshi.ws.models.ticker import TickerMessage @@ -855,14 +855,6 @@ async def subscribe_market_lifecycle( overflow=OverflowStrategy.DROP_OLDEST, maxsize=maxsize, ) - async def subscribe_multivariate( - self, *, maxsize: int = 1000, - ) -> AsyncIterator[MultivariateMessage]: - return await self._do_subscribe( - "multivariate", - overflow=OverflowStrategy.DROP_OLDEST, maxsize=maxsize, - ) - async def subscribe_multivariate_lifecycle( self, *, maxsize: int = 1000, ) -> AsyncIterator[MultivariateLifecycleMessage]: diff --git a/kalshi/ws/dispatch.py b/kalshi/ws/dispatch.py index a7ed805b..7b634d5c 100644 --- a/kalshi/ws/dispatch.py +++ b/kalshi/ws/dispatch.py @@ -18,7 +18,7 @@ from kalshi.ws.models.fill import FillMessage from kalshi.ws.models.market_lifecycle import MarketLifecycleMessage from kalshi.ws.models.market_positions import MarketPositionsMessage -from kalshi.ws.models.multivariate import MultivariateLifecycleMessage, MultivariateMessage +from kalshi.ws.models.multivariate import MultivariateLifecycleMessage from kalshi.ws.models.order_group import OrderGroupMessage from kalshi.ws.models.orderbook_delta import OrderbookDeltaMessage, OrderbookSnapshotMessage from kalshi.ws.models.ticker import TickerMessage @@ -41,7 +41,6 @@ "order_group_updates": OrderGroupMessage, "market_lifecycle_v2": MarketLifecycleMessage, "event_fee_update": EventFeeUpdateMessage, - "multivariate_lookup": MultivariateMessage, "multivariate_market_lifecycle": MultivariateLifecycleMessage, "communications": CommunicationsMessage, "cfbenchmarks_value": CFBenchmarksValueMessage, diff --git a/kalshi/ws/models/__init__.py b/kalshi/ws/models/__init__.py index d72c36e1..7c357893 100644 --- a/kalshi/ws/models/__init__.py +++ b/kalshi/ws/models/__init__.py @@ -38,9 +38,6 @@ ) from kalshi.ws.models.multivariate import ( MultivariateLifecycleMessage, - MultivariateMessage, - MultivariatePayload, - SelectedMarket, ) from kalshi.ws.models.order_group import ( OrderGroupMessage, @@ -83,8 +80,6 @@ "MarketPositionsPayload", # Multivariate "MultivariateLifecycleMessage", - "MultivariateMessage", - "MultivariatePayload", "OkMessage", # Order group "OrderGroupMessage", @@ -99,7 +94,6 @@ "QuoteExecutedPayload", "RfqCreatedPayload", "RfqDeletedPayload", - "SelectedMarket", "SubscribedMessage", "SubscriptionInfo", # Ticker diff --git a/kalshi/ws/models/multivariate.py b/kalshi/ws/models/multivariate.py index da69ec6d..35d66be1 100644 --- a/kalshi/ws/models/multivariate.py +++ b/kalshi/ws/models/multivariate.py @@ -1,41 +1,16 @@ -"""Multivariate and multivariate market lifecycle channel message models.""" +"""Multivariate market lifecycle channel message models. + +The standalone ``multivariate`` / ``multivariate_lookup`` channel and payload +were removed from AsyncAPI (spec drift 2026-08); only +``multivariate_market_lifecycle`` remains. +""" from __future__ import annotations from pydantic import BaseModel -from kalshi.types import NullableList from kalshi.ws.models.market_lifecycle import MarketLifecyclePayload -class SelectedMarket(BaseModel): - """A selected market within a multivariate collection.""" - - event_ticker: str | None = None - market_ticker: str | None = None - side: str | None = None - model_config = {"extra": "allow", "populate_by_name": True} - - -class MultivariatePayload(BaseModel): - """Payload for multivariate messages (public channel).""" - - collection_ticker: str - event_ticker: str - market_ticker: str - selected_markets: NullableList[SelectedMarket] - model_config = {"extra": "allow", "populate_by_name": True} - - -class MultivariateMessage(BaseModel): - """Multivariate update message. NO required seq.""" - - type: str = "multivariate_lookup" - sid: int - seq: int | None = None - msg: MultivariatePayload - model_config = {"extra": "allow", "populate_by_name": True} - - class MultivariateLifecycleMessage(BaseModel): """Multivariate market lifecycle message. Same payload as MarketLifecycleMessage.""" diff --git a/pyproject.toml b/pyproject.toml index b3a24d1e..64c6589f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kalshi-sdk" -version = "9.0.0" +version = "10.0.0" description = "A professional Python SDK for the Kalshi prediction markets and Perps (margin) APIs" readme = "README.md" license = { text = "MIT" } @@ -79,10 +79,10 @@ markers = [ "integration_real_api_only: endpoints the demo server cannot service (auth-gated roles, demo-broken). Skipped under the default integration run; enable explicitly with -m integration_real_api_only against a prod-like account.", ] filterwarnings = [ - # `kalshi.resources.multivariate.{lookup_tickers,lookup_history,create_market}` - # are flagged `@deprecated` per spec (the endpoints predate RFQs). Tests - # that exercise them on purpose would drown CI in noise; user callers - # still see the warning in their own code. + # `kalshi.resources.multivariate.create_market` is flagged `@deprecated` + # per spec (the endpoint predates RFQs). Tests that exercise it on purpose + # would drown CI in noise; user callers still see the warning in their own + # code. "ignore:This endpoint predates RFQs.*:DeprecationWarning", ] diff --git a/specs/asyncapi.yaml b/specs/asyncapi.yaml index 13704eac..0b5aceb7 100644 --- a/specs/asyncapi.yaml +++ b/specs/asyncapi.yaml @@ -380,30 +380,6 @@ channels: $ref: '#/components/messages/multivariateMarketLifecycle' eventLifecycle: $ref: '#/components/messages/eventLifecycle' - multivariate: - address: multivariate - title: Multivariate Lookups (Deprecated) - description: > - Deprecated: this channel predates RFQs and should not be used for new - integrations. - - - Multivariate collection lookup notifications. - - - **Requirements:** - - - No additional channel-level authentication beyond the authenticated - WebSocket connection - - - No filtering parameters; subscription is global - - - **Use case:** Tracking multivariate lookup interest for legacy - integrations - messages: - multivariateLookup: - $ref: '#/components/messages/multivariateLookup' communications: address: communications title: Communications @@ -913,16 +889,6 @@ operations: - $ref: '#/channels/multivariate_market_lifecycle/messages/eventLifecycle' tags: - name: market-data - receiveMultivariateLookup: - action: send - title: Multivariate Lookup - summary: Receive multivariate collection lookup notifications - channel: - $ref: '#/channels/multivariate' - messages: - - $ref: '#/channels/multivariate/messages/multivariateLookup' - tags: - - name: market-data receiveRFQCreated: action: send title: RFQ Created @@ -1960,31 +1926,6 @@ components: event_ticker: KXBTCD-26MAY2018 fee_type_override: null fee_multiplier_override: null - multivariateLookup: - name: multivariate_lookup - title: Multivariate Lookup (Deprecated) - summary: Deprecated multivariate collection lookup notification - description: This message predates RFQs and should not be used for new integrations. - contentType: application/json - payload: - $ref: '#/components/schemas/multivariateLookupPayload' - examples: - - name: lookupRecorded - summary: Multivariate lookup recorded - payload: - type: multivariate_lookup - sid: 13 - msg: - collection_ticker: KXOSCARWINNERS-25 - event_ticker: KXOSCARWINNERS-25C0CE5 - market_ticker: KXOSCARWINNERS-25C0CE5-36353 - selected_markets: - - event_ticker: KXOSCARACTO-25 - market_ticker: KXOSCARACTO-25-AB - side: 'yes' - - event_ticker: KXOSCARACTR-25 - market_ticker: KXOSCARACTR-25-DM - side: 'yes' marketPosition: name: market_position title: Market Position Update @@ -2273,7 +2214,6 @@ components: - market_positions - market_lifecycle_v2 - multivariate_market_lifecycle - - multivariate - communications - order_group_updates - user_orders @@ -3482,6 +3422,7 @@ components: - center_half_edge_deci_cent - center_quint_edge_quint_cent - center_quint_edge_deci_cent + - center_centi_edge_centi_cent price_ranges: type: array description: >- @@ -3669,47 +3610,6 @@ components: description: >- Event fee multiplier override. `null` when the override has been cleared. - multivariateLookupPayload: - type: object - required: - - type - - sid - - msg - properties: - type: - type: string - const: multivariate_lookup - sid: - $ref: '#/components/schemas/subscriptionId' - msg: - type: object - required: - - collection_ticker - - event_ticker - - market_ticker - - selected_markets - properties: - collection_ticker: - type: string - event_ticker: - type: string - market_ticker: - type: string - selected_markets: - type: array - items: - type: object - required: - - event_ticker - - market_ticker - - side - properties: - event_ticker: - type: string - market_ticker: - type: string - side: - $ref: '#/components/schemas/marketSide' marketPositionPayload: type: object required: diff --git a/specs/openapi.yaml b/specs/openapi.yaml index 67dfa3ae..85f4b607 100644 --- a/specs/openapi.yaml +++ b/specs/openapi.yaml @@ -1606,9 +1606,12 @@ paths: operationId: GetBalance summary: Get Balance description: >- - Endpoint for getting the balance and portfolio value of a member. Both - values are returned in cents. This endpoint also accepts API keys with - the 'read::portfolio_balance' scope. + Endpoint for getting the balance and portfolio value of a member. By + default the returned balance is the primary account's available balance. + Pass a non-zero `subaccount` to fetch that subaccount's balance on a + specific exchange index instead (`exchange_index`, defaulting to 0). + When `subaccount` is omitted or 0, `exchange_index` has no effect. This + endpoint also accepts API keys with the 'read::portfolio_balance' scope. tags: - portfolio security: @@ -1617,7 +1620,15 @@ paths: kalshiAccessTimestamp: [] parameters: - $ref: '#/components/parameters/SubaccountQueryDefaultPrimary' - - $ref: '#/components/parameters/ExchangeIndexQuery' + - name: exchange_index + in: query + schema: + $ref: '#/components/schemas/ExchangeIndex' + x-go-type-skip-optional-pointer: true + description: >- + Exchange index to read the subaccount balance from, paired with a + non-zero `subaccount`. Defaults to 0. Ignored when `subaccount` is + omitted or 0. responses: '200': description: Balance retrieved successfully @@ -1661,6 +1672,64 @@ paths: $ref: '#/components/responses/ForbiddenError' '500': $ref: '#/components/responses/InternalServerError' + /portfolio/intra_exchange_instance_transfers: + get: + operationId: GetIntraExchangeInstanceTransfers + summary: Get Intra Account Transfers + description: Endpoint for fetching intra-exchange account transfer history. + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - $ref: '#/components/parameters/TransfersLimitQuery' + - $ref: '#/components/parameters/CursorQuery' + responses: + '200': + description: Transfers retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetIntraExchangeInstanceTransfersResponse' + '400': + $ref: '#/components/responses/BadRequestError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '500': + $ref: '#/components/responses/InternalServerError' + /portfolio/intra_exchange_instance_transfers/{transfer_id}: + get: + operationId: GetIntraExchangeInstanceTransfer + summary: Get Intra Account Transfer + description: Endpoint for getting a single intra-account transfer by id. + tags: + - portfolio + security: + - kalshiAccessKey: [] + kalshiAccessSignature: [] + kalshiAccessTimestamp: [] + parameters: + - name: transfer_id + in: path + required: true + description: Transfer id returned by creation endpoint + schema: + type: string + responses: + '200': + description: The requested transfer + content: + application/json: + schema: + $ref: '#/components/schemas/GetIntraExchangeInstanceTransferResponse' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': + $ref: '#/components/responses/InternalServerError' /portfolio/subaccounts: post: operationId: CreateSubaccount @@ -3513,70 +3582,6 @@ paths: $ref: '#/components/responses/BadRequestError' '500': $ref: '#/components/responses/InternalServerError' - /multivariate_event_collections/{collection_ticker}/lookup: - put: - operationId: LookupTickersForMarketInMultivariateEventCollection - summary: Lookup Tickers For Market In Multivariate Event Collection - deprecated: true - description: >- - DEPRECATED: This endpoint predates RFQs and should not be used for new - integrations. Endpoint for looking up an individual market in a - multivariate event collection. If - CreateMarketInMultivariateEventCollection has never been hit with that - variable combination before, this will return a 404. - x-mint: - content: > - - - This endpoint is deprecated and predates RFQs. Do not use it for new - integrations. - - - - - - - **Rate limit:** 2 tokens per request. See `GET - /trade-api/v2/account/endpoint_costs` for current non-default endpoint - costs. - - - tags: - - multivariate - security: - - kalshiAccessKey: [] - kalshiAccessSignature: [] - kalshiAccessTimestamp: [] - parameters: - - name: collection_ticker - in: path - required: true - description: Collection ticker - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: >- - #/components/schemas/LookupTickersForMarketInMultivariateEventCollectionRequest - responses: - '200': - description: Market looked up successfully - content: - application/json: - schema: - $ref: >- - #/components/schemas/LookupTickersForMarketInMultivariateEventCollectionResponse - '400': - $ref: '#/components/responses/BadRequestError' - '401': - $ref: '#/components/responses/UnauthorizedError' - '404': - $ref: '#/components/responses/NotFoundError' - '500': - $ref: '#/components/responses/InternalServerError' /incentive_programs: get: operationId: GetIncentivePrograms @@ -4152,6 +4157,19 @@ components: default: 100 x-oapi-codegen-extra-tags: validate: omitempty,min=1,max=500 + TransfersLimitQuery: + name: limit + in: query + description: Number of results per page. Defaults to 100. Maximum value is 500. + schema: + type: integer + format: int64 + minimum: 1 + maximum: 500 + default: 100 + x-go-type-skip-optional-pointer: true + x-oapi-codegen-extra-tags: + validate: omitempty,min=1,max=500 MarketLimitQuery: name: limit in: query @@ -5554,7 +5572,9 @@ components: type: array items: $ref: '#/components/schemas/IndexedBalance' - description: Balance broken down per exchange index. + description: >- + User balance breakdown per exchange instance, omitted only when + using a subaccount-restricted API key. CreateSubaccountRequest: type: object properties: @@ -5833,6 +5853,11 @@ components: - applied - failed - returned + x-enum-varnames: + - DepositStatusPending + - DepositStatusApplied + - DepositStatusFailed + - DepositStatusReturned description: >- Current status of the deposit. 'applied' means funds are reflected in balance. @@ -5895,6 +5920,11 @@ components: - applied - failed - returned + x-enum-varnames: + - WithdrawalStatusPending + - WithdrawalStatusApplied + - WithdrawalStatusFailed + - WithdrawalStatusReturned description: >- Current status of the withdrawal. 'applied' means funds have been deducted from balance. @@ -6693,14 +6723,22 @@ components: description: The amount to transfer in centicents source_exchange_shard: type: integer + minimum: 0 + maximum: 100 default: 0 x-go-type-skip-optional-pointer: true description: Source exchange shard index (default 0) + x-oapi-codegen-extra-tags: + validate: gte=0,lte=100 destination_exchange_shard: type: integer + minimum: 0 + maximum: 100 default: 0 x-go-type-skip-optional-pointer: true description: Destination exchange shard index (default 0) + x-oapi-codegen-extra-tags: + validate: gte=0,lte=100 IntraExchangeInstanceTransferResponse: type: object required: @@ -6709,6 +6747,72 @@ components: transfer_id: type: string description: The ID of the transfer that was created + IntraExchangeInstanceTransferStatus: + type: string + enum: + - pending + - complete + x-enum-varnames: + - IntraExchangeInstanceTransferStatusPending + - IntraExchangeInstanceTransferStatusComplete + description: Transfer status. + IntraExchangeInstanceTransfer: + type: object + required: + - transfer_id + - source + - destination + - source_exchange_shard + - destination_exchange_shard + - amount + - status + - created_ts + properties: + transfer_id: + type: string + description: Unique transfer id + source: + $ref: '#/components/schemas/ExchangeInstance' + description: Source exchange instance + destination: + $ref: '#/components/schemas/ExchangeInstance' + description: Destination exchange instance + source_exchange_shard: + type: integer + description: Source exchange shard index + destination_exchange_shard: + type: integer + description: Destination exchange shard index + amount: + $ref: '#/components/schemas/FixedPointDollars' + description: Transfer amount in dollars + status: + $ref: '#/components/schemas/IntraExchangeInstanceTransferStatus' + created_ts: + type: integer + format: int64 + description: Unix timestamp when the transfer was created + GetIntraExchangeInstanceTransfersResponse: + type: object + required: + - transfers + properties: + transfers: + type: array + items: + $ref: '#/components/schemas/IntraExchangeInstanceTransfer' + cursor: + type: string + description: >- + Cursor for the next page of results. Omitted when there are no + further pages. + GetIntraExchangeInstanceTransferResponse: + type: object + required: + - transfer + properties: + transfer: + $ref: '#/components/schemas/IntraExchangeInstanceTransfer' OrderGroup: type: object required: @@ -8378,6 +8482,12 @@ components: description: >- Series associated with the collection. Events produced in the collection will be associated with this series. + exchange_index: + allOf: + - $ref: '#/components/schemas/ExchangeIndex' + description: Exchange index inherited from the collection's series. + x-go-type-skip-optional-pointer: true + x-omitempty: false title: type: string description: Title of the collection. @@ -8491,30 +8601,6 @@ components: description: Side of the market (yes or no). x-oapi-codegen-extra-tags: validate: required,oneof=yes no - LookupTickersForMarketInMultivariateEventCollectionRequest: - type: object - required: - - selected_markets - properties: - selected_markets: - type: array - items: - $ref: '#/components/schemas/TickerPair' - description: >- - List of selected markets that act as parameters to determine which - market is produced. - LookupTickersForMarketInMultivariateEventCollectionResponse: - type: object - required: - - event_ticker - - market_ticker - properties: - event_ticker: - type: string - description: Event ticker for the looked up market. - market_ticker: - type: string - description: Market ticker for the looked up market. CreateMarketInMultivariateEventCollectionRequest: type: object required: diff --git a/specs/perps_openapi.yaml b/specs/perps_openapi.yaml index 7a47cf72..2a4af76e 100644 --- a/specs/perps_openapi.yaml +++ b/specs/perps_openapi.yaml @@ -2,14 +2,14 @@ openapi: 3.0.0 info: title: Kalshi Trade API Manual Endpoints version: 0.0.1 - description: Manually defined OpenAPI spec for endpoints being migrated to spec-first approach - + description: >- + Manually defined OpenAPI spec for endpoints being migrated to spec-first + approach servers: - url: https://external-api.kalshi.com/trade-api/v2 description: Production perps REST API server - url: https://external-api.demo.kalshi.co/trade-api/v2 description: Demo perps REST API server - paths: /margin/fcm/subtraders: post: @@ -45,7 +45,6 @@ paths: $ref: '#/components/responses/ConflictError' '500': $ref: '#/components/responses/InternalServerError' - /account/limits/perps: get: operationId: GetPerpsAccountApiLimits @@ -68,12 +67,11 @@ paths: description: Unauthorized '500': description: Internal server error - /margin/exchange/status: get: operationId: GetMarginExchangeStatus summary: Get Exchange Status - description: 'Endpoint for getting the margin exchange status.' + description: Endpoint for getting the margin exchange status. tags: - exchange responses: @@ -101,12 +99,13 @@ paths: application/json: schema: $ref: '#/components/schemas/ExchangeStatus' - /margin/risk_parameters: get: operationId: GetMarginRiskParameters summary: Get Risk Parameters - description: 'Returns system-wide margin risk parameters including liquidation thresholds and per-market initial margin multipliers.' + description: >- + Returns system-wide margin risk parameters including liquidation + thresholds and per-market initial margin multipliers. tags: - risk responses: @@ -116,12 +115,11 @@ paths: application/json: schema: $ref: '#/components/schemas/GetMarginRiskParametersResponse' - /margin/orders: get: operationId: GetMarginOrders summary: Get Orders - description: 'Endpoint for listing margin orders with optional filtering.' + description: Endpoint for listing margin orders with optional filtering. tags: - orders security: @@ -182,7 +180,6 @@ paths: $ref: '#/components/responses/RateLimitError' '500': $ref: '#/components/responses/InternalServerError' - /margin/orders/{order_id}: get: operationId: GetMarginOrder @@ -209,11 +206,12 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - delete: operationId: CancelMarginOrder summary: Cancel Order - description: Endpoint for canceling an order. Cancels all remaining resting contracts and returns the canceled order details. + description: >- + Endpoint for canceling an order. Cancels all remaining resting contracts + and returns the canceled order details. tags: - orders security: @@ -236,12 +234,14 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - /margin/orders/{order_id}/decrease: post: operationId: DecreaseMarginOrder summary: Decrease Order - description: Endpoint for decreasing the number of contracts in an existing order. Exactly one of `reduce_by` or `reduce_to` must be provided. Canceling an order is equivalent to decreasing to zero. + description: >- + Endpoint for decreasing the number of contracts in an existing order. + Exactly one of `reduce_by` or `reduce_to` must be provided. Canceling an + order is equivalent to decreasing to zero. tags: - orders security: @@ -272,16 +272,22 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - /margin/orders/{order_id}/amend: post: operationId: AmendMarginOrder summary: Amend Order - description: Endpoint for amending the price and/or max number of fillable contracts in an existing margin order. + description: >- + Endpoint for amending the price and/or max number of fillable contracts + in an existing margin order. x-mint: - content: | + content: > - Amending a resting order preserves queue position only when the amendment decreases size. All other amendments — like increasing size or changing price forfeit queue position and place the order at the back of the queue. + + Amending a resting order preserves queue position only when the + amendment decreases size. All other amendments — like increasing size + or changing price forfeit queue position and place the order at the + back of the queue. + tags: - orders @@ -313,7 +319,6 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - /margin/markets: get: operationId: GetMarginMarkets @@ -342,12 +347,13 @@ paths: $ref: '#/components/responses/RateLimitError' '500': $ref: '#/components/responses/InternalServerError' - /margin/markets/{ticker}: get: operationId: GetMarginMarket summary: Get Market - description: Endpoint for fetching a margin market with trading stats (price, volume, open interest). + description: >- + Endpoint for fetching a margin market with trading stats (price, volume, + open interest). tags: - market parameters: @@ -372,7 +378,6 @@ paths: $ref: '#/components/responses/RateLimitError' '500': $ref: '#/components/responses/InternalServerError' - /margin/markets/{ticker}/orderbook: get: operationId: GetMarginMarketOrderbook @@ -397,7 +402,9 @@ paths: default: 0 - name: aggregation_tick_size in: query - description: Tick size in dollars for aggregating price levels (e.g., 0.10 for 10 cent buckets) + description: >- + Tick size in dollars for aggregating price levels (e.g., 0.10 for 10 + cent buckets) required: false schema: type: string @@ -416,7 +423,6 @@ paths: $ref: '#/components/responses/RateLimitError' '500': $ref: '#/components/responses/InternalServerError' - /margin/markets/{ticker}/candlesticks: get: operationId: GetMarginMarketCandlesticks @@ -434,34 +440,49 @@ paths: - name: start_ts in: query required: true - description: Start timestamp (Unix timestamp). Candlesticks will include those ending on or after this time. + description: >- + Start timestamp (Unix timestamp). Candlesticks will include those + ending on or after this time. schema: type: integer format: int64 - name: end_ts in: query required: true - description: End timestamp (Unix timestamp). Candlesticks will include those ending on or before this time. + description: >- + End timestamp (Unix timestamp). Candlesticks will include those + ending on or before this time. schema: type: integer format: int64 - name: period_interval in: query required: true - description: Time period length of each candlestick in minutes. Valid values are 1 (1 minute), 60 (1 hour), or 1440 (1 day). + description: >- + Time period length of each candlestick in minutes. Valid values are + 1 (1 minute), 60 (1 hour), or 1440 (1 day). schema: type: integer - enum: [1, 60, 1440] + enum: + - 1 + - 60 + - 1440 x-oapi-codegen-extra-tags: - validate: "required,oneof=1 60 1440" + validate: required,oneof=1 60 1440 - name: include_latest_before_start in: query required: false - description: | - If true, prepends the latest candlestick available before the start_ts. This synthetic candlestick is created by: + description: > + If true, prepends the latest candlestick available before the + start_ts. This synthetic candlestick is created by: + 1. Finding the most recent real candlestick before start_ts - 2. Projecting it forward to the first period boundary (calculated as the next period interval after start_ts) - 3. Setting all OHLC prices to null, and `price.previous` to the close price from the real candlestick + + 2. Projecting it forward to the first period boundary (calculated as + the next period interval after start_ts) + + 3. Setting all OHLC prices to null, and `price.previous` to the + close price from the real candlestick schema: type: boolean default: false @@ -478,7 +499,6 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - /margin/fills: get: operationId: GetMarginFills @@ -542,12 +562,11 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/positions: get: operationId: GetMarginPositions summary: Get Positions - description: 'Endpoint for retrieving the authenticated user''s margin positions.' + description: Endpoint for retrieving the authenticated user's margin positions. tags: - portfolio security: @@ -581,12 +600,14 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/trades: get: operationId: GetMarginTrades summary: Get Trades - description: 'Endpoint for retrieving public margin trades for a given 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 for a given market ticker. + Returns a paginated response. Use the cursor value from the previous + response to get the next page. tags: - market parameters: @@ -638,12 +659,13 @@ paths: $ref: '#/components/responses/BadRequestError' '500': $ref: '#/components/responses/InternalServerError' - /margin/enabled: get: operationId: GetMarginEnabled summary: Get Enabled Status - description: Endpoint for checking if margin trading is enabled for the authenticated user. + description: >- + Endpoint for checking if margin trading is enabled for the authenticated + user. tags: - exchange security: @@ -661,12 +683,13 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/notional_risk_limit: get: operationId: GetMarginNotionalRiskLimit summary: Get Notional Risk Limit - description: 'Endpoint for retrieving the notional value risk limit for the authenticated margin user.' + description: >- + Endpoint for retrieving the notional value risk limit for the + authenticated margin user. tags: - risk security: @@ -684,16 +707,24 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/balance: get: operationId: GetMarginBalance summary: Get Balance - description: 'Endpoint for retrieving the balance breakdown for the authenticated direct margin user. Returns cash balance (aggregate and per-subaccount), position value, total balance, and maintenance margin requirement.' + description: >- + Endpoint for retrieving the balance breakdown for the authenticated + direct margin user. Returns cash balance (aggregate and per-subaccount), + position value, total balance, and maintenance margin requirement. x-mint: - content: | + content: > - **Rate limit:** 5 tokens per request, or 50 tokens when `compute_available_balance=true` (the available-balance computation scans all resting orders). See `GET /trade-api/v2/account/endpoint_costs` for current non-default endpoint costs. + + **Rate limit:** 5 tokens per request, or 50 tokens when + `compute_available_balance=true` (the available-balance computation + scans all resting orders). See `GET + /trade-api/v2/account/endpoint_costs` for current non-default endpoint + costs. + tags: - portfolio @@ -709,7 +740,10 @@ paths: type: boolean default: false x-go-type-skip-optional-pointer: true - description: 'When true, computes available_balance per subaccount at an increased rate limit cost. Available balance is 0 when the flag is false or omitted.' + description: >- + When true, computes available_balance per subaccount at an increased + rate limit cost. Available balance is 0 when the flag is false or + omitted. responses: '200': description: Margin balance retrieved successfully @@ -725,12 +759,15 @@ paths: $ref: '#/components/responses/RateLimitError' '500': $ref: '#/components/responses/InternalServerError' - /margin/risk: get: operationId: GetMarginRisk summary: Get Risk - description: 'Endpoint for retrieving leverage and liquidation price data for the authenticated direct margin user. Returns account-level leverage plus per-position leverage and liquidation prices, grouped by subaccount and market.' + description: >- + Endpoint for retrieving leverage and liquidation price data for the + authenticated direct margin user. Returns account-level leverage plus + per-position leverage and liquidation prices, grouped by subaccount and + market. tags: - risk security: @@ -750,12 +787,14 @@ paths: $ref: '#/components/responses/ForbiddenError' '500': $ref: '#/components/responses/InternalServerError' - /margin/fee_tiers: get: operationId: GetMarginFeeTiers summary: Get Fee Tiers - description: 'Endpoint for retrieving the margin fee tiers for the authenticated direct margin user. Returns a map of margin market tickers to their fee tier strings.' + description: >- + Endpoint for retrieving the margin fee tiers for the authenticated + direct margin user. Returns a map of margin market tickers to their fee + tier strings. tags: - fees security: @@ -773,12 +812,15 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/funding_history: get: operationId: GetMarginFundingHistory summary: Get Funding History - description: 'Endpoint for retrieving the authenticated user''s historical margin funding payments joined with funding rates for a specific market, or across all markets when ticker is empty, over an inclusive UTC date range.' + description: >- + Endpoint for retrieving the authenticated user's historical margin + funding payments joined with funding rates for a specific market, or + across all markets when ticker is empty, over an inclusive UTC date + range. tags: - funding security: @@ -789,14 +831,18 @@ paths: - name: ticker in: query required: false - description: Market ticker for funding history. Leave empty to query across all markets. + description: >- + Market ticker for funding history. Leave empty to query across all + markets. schema: type: string x-go-type-skip-optional-pointer: true - name: start_date in: query required: true - description: Inclusive UTC start date for funding history range (YYYY-MM-DD format) + description: >- + Inclusive UTC start date for funding history range (YYYY-MM-DD + format) schema: type: string format: date @@ -829,12 +875,13 @@ paths: $ref: '#/components/responses/ForbiddenError' '500': $ref: '#/components/responses/InternalServerError' - /margin/funding_rates/historical: get: operationId: GetMarginHistoricalFundingRates summary: Get Historical Funding Rates - description: Endpoint for retrieving historical margin funding rates for a market, or across all markets when ticker is empty. + description: >- + Endpoint for retrieving historical margin funding rates for a market, or + across all markets when ticker is empty. tags: - funding parameters: @@ -848,14 +895,18 @@ paths: - name: start_ts in: query required: false - description: Start timestamp (Unix timestamp in seconds). If omitted, defaults to the earliest available data. + description: >- + Start timestamp (Unix timestamp in seconds). If omitted, defaults to + the earliest available data. schema: type: integer format: int64 - name: end_ts in: query required: false - description: End timestamp (Unix timestamp in seconds). If omitted, defaults to the current time. + description: >- + End timestamp (Unix timestamp in seconds). If omitted, defaults to + the current time. schema: type: integer format: int64 @@ -870,13 +921,16 @@ paths: $ref: '#/components/responses/BadRequestError' '500': $ref: '#/components/responses/InternalServerError' - /margin/funding_rates/estimate: get: operationId: GetMarginFundingRateEstimate summary: Get Funding Rate Estimate - description: | - Returns the estimated funding rate for the current, in-progress funding period. The value is a time-weighted average of the premium index computed over `[last_funding_time, now)`, so it continues to move as new data accumulates through the window and is only finalized at `next_funding_time`. + description: > + Returns the estimated funding rate for the current, in-progress funding + period. The value is a time-weighted average of the premium index + computed over `[last_funding_time, now)`, so it continues to move as new + data accumulates through the window and is only finalized at + `next_funding_time`. tags: - funding parameters: @@ -900,12 +954,11 @@ paths: $ref: '#/components/responses/BadRequestError' '500': $ref: '#/components/responses/InternalServerError' - /portfolio/intra_exchange_instance_transfer: post: operationId: IntraExchangeInstanceTransfer summary: Intra Account Transfer - description: 'Endpoint for transferring funds within the same account.' + description: Endpoint for transferring funds within the same account. tags: - portfolio security: @@ -933,12 +986,14 @@ paths: $ref: '#/components/responses/ForbiddenError' '500': $ref: '#/components/responses/InternalServerError' - /portfolio/margin/subaccounts: post: operationId: CreateMarginSubaccount summary: Create Subaccount - description: 'Creates a new subaccount for the authenticated user in the margin exchange. Subaccounts are numbered sequentially starting from 1. Maximum 63 numbered subaccounts per user (64 including the primary account).' + description: >- + Creates a new subaccount for the authenticated user in the margin + exchange. Subaccounts are numbered sequentially starting from 1. Maximum + 63 numbered subaccounts per user (64 including the primary account). tags: - portfolio security: @@ -960,12 +1015,13 @@ paths: $ref: '#/components/responses/ForbiddenError' '500': $ref: '#/components/responses/InternalServerError' - /portfolio/margin/subaccounts/transfer: post: operationId: ApplyMarginSubaccountTransfer summary: Transfer Between Subaccounts - description: 'Transfers funds between the authenticated user''s margin subaccounts. Use 0 for the primary account, or 1-63 for numbered subaccounts.' + description: >- + Transfers funds between the authenticated user's margin subaccounts. Use + 0 for the primary account, or 1-63 for numbered subaccounts. tags: - portfolio security: @@ -991,12 +1047,13 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/order_groups: get: operationId: GetMarginOrderGroups summary: Get Order Groups - description: 'Retrieves all order groups for the authenticated user on the margin exchange.' + description: >- + Retrieves all order groups for the authenticated user on the margin + exchange. tags: - order-groups security: @@ -1018,12 +1075,14 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/order_groups/create: post: operationId: CreateMarginOrderGroup summary: Create Order Group - description: 'Creates a new order group on the margin exchange with a contracts limit measured over a rolling window. When the limit is hit, all orders in the group are cancelled and no new orders can be placed until reset.' + description: >- + Creates a new order group on the margin exchange with a contracts limit + measured over a rolling window. When the limit is hit, all orders in the + group are cancelled and no new orders can be placed until reset. tags: - order-groups security: @@ -1049,12 +1108,13 @@ paths: $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalServerError' - /margin/order_groups/{order_group_id}: get: operationId: GetMarginOrderGroup summary: Get Order Group - description: 'Retrieves details for a single order group on the margin exchange including all order IDs and auto-cancel status.' + description: >- + Retrieves details for a single order group on the margin exchange + including all order IDs and auto-cancel status. tags: - order-groups security: @@ -1080,7 +1140,9 @@ paths: delete: operationId: DeleteMarginOrderGroup summary: Delete Order Group - description: 'Deletes an order group on the margin exchange and cancels all orders within it.' + description: >- + Deletes an order group on the margin exchange and cancels all orders + within it. tags: - order-groups security: @@ -1103,12 +1165,14 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - /margin/order_groups/{order_group_id}/reset: put: operationId: ResetMarginOrderGroup summary: Reset Order Group - description: 'Resets the order group matched contracts counter to zero on the margin exchange, allowing new orders to be placed again after the limit was hit.' + description: >- + Resets the order group matched contracts counter to zero on the margin + exchange, allowing new orders to be placed again after the limit was + hit. tags: - order-groups security: @@ -1137,12 +1201,13 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - /margin/order_groups/{order_group_id}/trigger: put: operationId: TriggerMarginOrderGroup summary: Trigger Order Group - description: 'Triggers the order group on the margin exchange, canceling all orders in the group and preventing new orders until the group is reset.' + description: >- + Triggers the order group on the margin exchange, canceling all orders in + the group and preventing new orders until the group is reset. tags: - order-groups security: @@ -1171,12 +1236,14 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - /margin/order_groups/{order_group_id}/limit: put: operationId: UpdateMarginOrderGroupLimit summary: Update Order Group Limit - description: 'Updates the order group contracts limit on the margin exchange. If the updated limit would immediately trigger the group, all orders in the group are canceled and the group is triggered.' + description: >- + Updates the order group contracts limit on the margin exchange. If the + updated limit would immediately trigger the group, all orders in the + group are canceled and the group is triggered. tags: - order-groups security: @@ -1207,7 +1274,6 @@ paths: $ref: '#/components/responses/NotFoundError' '500': $ref: '#/components/responses/InternalServerError' - components: securitySchemes: kalshiAccessKey: @@ -1257,7 +1323,9 @@ components: schema: $ref: '#/components/schemas/ErrorResponse' RateLimitError: - description: 'Rate limit exceeded. The default cost is 10 tokens per request. Use GET /trade-api/v2/account/endpoint_costs to list non-default endpoint costs.' + description: >- + Rate limit exceeded. The default cost is 10 tokens per request. Use GET + /trade-api/v2/account/endpoint_costs to list non-default endpoint costs. content: application/json: schema: @@ -1276,9 +1344,10 @@ components: properties: subtrader_suffix: type: string - pattern: '^[a-z0-9]{1,16}$' - description: Suffix for the new subtrader. The full subtrader id is composed server-side as {user_id}_{subtrader_suffix}. - + pattern: ^[a-z0-9]{1,16}$ + description: >- + Suffix for the new subtrader. The full subtrader id is composed + server-side as {user_id}_{subtrader_suffix}. CreateMarginFCMSubtraderResponse: type: object required: @@ -1286,8 +1355,9 @@ components: properties: subtrader_id: type: string - description: The full id of the created subtrader, in the form {user_id}_{subtrader_suffix}. - + description: >- + The full id of the created subtrader, in the form + {user_id}_{subtrader_suffix}. ApplySubaccountTransferRequest: type: object required: @@ -1301,13 +1371,17 @@ components: format: uuid description: Unique client-provided transfer ID for idempotency. x-oapi-codegen-extra-tags: - validate: "required" + validate: required from_subaccount: type: integer - description: Source subaccount number (0 for primary, 1-63 for numbered subaccounts). + description: >- + Source subaccount number (0 for primary, 1-63 for numbered + subaccounts). to_subaccount: type: integer - description: Destination subaccount number (0 for primary, 1-63 for numbered subaccounts). + description: >- + Destination subaccount number (0 for primary, 1-63 for numbered + subaccounts). amount_cents: type: integer format: int64 @@ -1321,19 +1395,30 @@ components: subaccount: type: integer minimum: 0 - description: Optional subaccount number to use for this order group (0 for primary, 1-63 for subaccounts). Subaccount-restricted API keys must omit this field or pass their locked subaccount. + description: >- + Optional subaccount number to use for this order group (0 for + primary, 1-63 for subaccounts). Subaccount-restricted API keys must + omit this field or pass their locked subaccount. contracts_limit: type: integer format: int64 minimum: 1 - description: Specifies the maximum number of contracts that can be matched within this group over a rolling 15-second window. Whole contracts only. Provide contracts_limit or contracts_limit_fp; if both provided they must match. + description: >- + Specifies the maximum number of contracts that can be matched within + this group over a rolling 15-second window. Whole contracts only. + Provide contracts_limit or contracts_limit_fp; if both provided they + must match. x-go-type-skip-optional-pointer: true x-oapi-codegen-extra-tags: validate: omitempty,gte=1 contracts_limit_fp: $ref: '#/components/schemas/FixedPointCount' nullable: true - description: String representation of the maximum number of contracts that can be matched within this group over a rolling 15-second window. Provide contracts_limit or contracts_limit_fp; if both provided they must match. + description: >- + String representation of the maximum number of contracts that can be + matched within this group over a rolling 15-second window. Provide + contracts_limit or contracts_limit_fp; if both provided they must + match. exchange_index: allOf: - $ref: '#/components/schemas/ExchangeIndex' @@ -1351,7 +1436,9 @@ components: subaccount: type: integer minimum: 0 - description: Subaccount number that owns the created order group (0 for primary, 1-63 for subaccounts). + description: >- + Subaccount number that owns the created order group (0 for primary, + 1-63 for subaccounts). x-go-type-skip-optional-pointer: true exchange_index: allOf: @@ -1365,7 +1452,6 @@ components: subaccount_number: type: integer description: The sequential number assigned to this subaccount (1-63). - # Order Group schemas EmptyResponse: type: object description: An empty response body @@ -1383,11 +1469,15 @@ components: description: Additional details about the error, if available ExchangeIndex: type: integer - description: "Identifier for an exchange shard. Defaults to 0 if unspecified. Note: currently only 0 supported." + description: >- + Identifier for an exchange shard. Defaults to 0 if unspecified. Note: + currently only 0 supported. example: 0 ExchangeInstance: type: string - enum: ['event_contract', 'margined'] + enum: + - event_contract + - margined description: The exchange instance type BucketLimit: type: object @@ -1429,7 +1519,10 @@ components: $ref: '#/components/schemas/BucketLimit' grants: type: array - description: The caller's active API usage level grants across exchange lanes, where each grant applies to its exchange_instance and usage_tier reflects the effective tier for the lane reported by this endpoint. + description: >- + The caller's active API usage level grants across exchange lanes, + where each grant applies to its exchange_instance and usage_tier + reflects the effective tier for the lane reported by this endpoint. items: $ref: '#/components/schemas/ApiUsageLevelGrant' ApiUsageLevelGrant: @@ -1448,19 +1541,31 @@ components: type: integer format: int64 nullable: true - description: Unix timestamp (seconds) when the grant expires. Absent for permanent grants. + description: >- + Unix timestamp (seconds) when the grant expires. Absent for + permanent grants. source: type: string - description: 'How the grant was created: "volume" (earned from trading volume) or "manual" (assigned by Kalshi).' + description: >- + How the grant was created: "volume" (earned from trading volume) or + "manual" (assigned by Kalshi). FixedPointCount: type: string - description: Fixed-point contract count string (2 decimals, e.g., "10.00"; referred to as "fp" in field names). Requests accept 0-2 decimal places (e.g., "10", "10.0", "10.00"); responses always emit 2 decimals. Fractional contract values (e.g., "2.50") are supported; the minimum granularity is 0.01 contracts. - example: "10.00" - # Common schemas + description: >- + Fixed-point contract count string (2 decimals, e.g., "10.00"; referred + to as "fp" in field names). Requests accept 0-2 decimal places (e.g., + "10", "10.0", "10.00"); responses always emit 2 decimals. Fractional + contract values (e.g., "2.50") are supported; the minimum granularity is + 0.01 contracts. + example: '10.00' FixedPointDollars: type: string - description: US dollar amount as a fixed-point decimal string with up to 6 decimal places of precision. This is the maximum supported precision; valid quote intervals for a given market are constrained by that market's price level structure. - example: "0.5600" + description: >- + US dollar amount as a fixed-point decimal string with up to 6 decimal + places of precision. This is the maximum supported precision; valid + quote intervals for a given market are constrained by that market's + price level structure. + example: '0.5600' GetOrderGroupResponse: type: object required: @@ -1472,7 +1577,9 @@ components: description: Whether auto-cancel is enabled for this order group contracts_limit_fp: $ref: '#/components/schemas/FixedPointCount' - description: String representation of the current maximum contracts allowed over a rolling 15-second window. + description: >- + String representation of the current maximum contracts allowed over + a rolling 15-second window. x-go-type-skip-optional-pointer: true orders: type: array @@ -1511,14 +1618,22 @@ components: description: The amount to transfer in centicents source_exchange_shard: type: integer + minimum: 0 + maximum: 100 default: 0 x-go-type-skip-optional-pointer: true description: Source exchange shard index (default 0) + x-oapi-codegen-extra-tags: + validate: gte=0,lte=100 destination_exchange_shard: type: integer + minimum: 0 + maximum: 100 default: 0 x-go-type-skip-optional-pointer: true description: Destination exchange shard index (default 0) + x-oapi-codegen-extra-tags: + validate: gte=0,lte=100 IntraExchangeInstanceTransferResponse: type: object required: @@ -1539,7 +1654,9 @@ components: x-go-type-skip-optional-pointer: true contracts_limit_fp: $ref: '#/components/schemas/FixedPointCount' - description: String representation of the current maximum contracts allowed over a rolling 15-second window. + description: >- + String representation of the current maximum contracts allowed over + a rolling 15-second window. x-go-type-skip-optional-pointer: true is_auto_cancel_enabled: type: boolean @@ -1549,20 +1666,31 @@ components: allOf: - $ref: '#/components/schemas/ExchangeIndex' x-go-type-skip-optional-pointer: true - # Market Orderbook schemas PriceLevelDollarsCountFp: type: array minItems: 2 maxItems: 2 - example: ["0.1500", "100.00"] + example: + - '0.1500' + - '100.00' items: type: string - description: Price level in dollars represented as [dollars_string, fp] where dollars_string is like "0.1500" and fp is a FixedPointCount string (fixed-point contract count). The second element is the contract quantity (not price). + description: >- + Price level in dollars represented as [dollars_string, fp] where + dollars_string is like "0.1500" and fp is a FixedPointCount string + (fixed-point contract count). The second element is the contract + quantity (not price). SelfTradePreventionType: type: string - enum: ['taker_at_cross', 'maker'] - description: | - The self-trade prevention type for orders. `taker_at_cross` cancels the taker order when it would trade against another order from the same user; execution stops and any partial fills already matched are executed. `maker` cancels the resting maker order and continues matching. + enum: + - taker_at_cross + - maker + description: > + The self-trade prevention type for orders. `taker_at_cross` cancels the + taker order when it would trade against another order from the same + user; execution stops and any partial fills already matched are + executed. `maker` cancels the resting maker order and continues + matching. UpdateOrderGroupLimitRequest: type: object properties: @@ -1570,14 +1698,22 @@ components: type: integer format: int64 minimum: 1 - description: New maximum number of contracts that can be matched within this group over a rolling 15-second window. Whole contracts only. Provide contracts_limit or contracts_limit_fp; if both provided they must match. + description: >- + New maximum number of contracts that can be matched within this + group over a rolling 15-second window. Whole contracts only. Provide + contracts_limit or contracts_limit_fp; if both provided they must + match. x-go-type-skip-optional-pointer: true x-oapi-codegen-extra-tags: validate: omitempty,gte=1 contracts_limit_fp: $ref: '#/components/schemas/FixedPointCount' nullable: true - description: String representation of the new maximum number of contracts that can be matched within this group over a rolling 15-second window. Provide contracts_limit or contracts_limit_fp; if both provided they must match. + description: >- + String representation of the new maximum number of contracts that + can be matched within this group over a rolling 15-second window. + Provide contracts_limit or contracts_limit_fp; if both provided they + must match. ExchangeStatus: type: object required: @@ -1586,11 +1722,14 @@ components: properties: exchange_active: type: boolean - description: False if the exchange is no longer taking any state changes at all. True unless under maintenance. + description: >- + False if the exchange is no longer taking any state changes at all. + True unless under maintenance. trading_active: type: boolean - description: True if trading is currently permitted on the exchange. False outside exchange hours or during pauses. - + description: >- + True if trading is currently permitted on the exchange. False + outside exchange hours or during pauses. GetMarginRiskParametersResponse: type: object required: @@ -1611,7 +1750,10 @@ components: additionalProperties: type: number format: double - description: Map of market ticker to initial margin multiplier. The initial margin requirement is the maintenance margin multiplied by this value. + description: >- + Map of market ticker to initial margin multiplier. The initial + margin requirement is the maintenance margin multiplied by this + value. CreateMarginOrderRequest: type: object required: @@ -1646,7 +1788,10 @@ components: format: int64 time_in_force: type: string - enum: ['fill_or_kill', 'good_till_canceled', 'immediate_or_cancel'] + enum: + - fill_or_kill + - good_till_canceled + - immediate_or_cancel x-oapi-codegen-extra-tags: validate: required,oneof=fill_or_kill good_till_canceled immediate_or_cancel x-go-type-skip-optional-pointer: true @@ -1660,21 +1805,28 @@ components: x-go-type-skip-optional-pointer: true cancel_order_on_pause: type: boolean - description: If this flag is set to true, the order will be canceled if the order is open and trading on the exchange is paused for any reason. + description: >- + If this flag is set to true, the order will be canceled if the order + is open and trading on the exchange is paused for any reason. reduce_only: type: boolean - description: Specifies whether the order place count should be capped by the member's current position. Orders with reduce_only set to true will be rejected unless time_in_force is immediate_or_cancel or fill_or_kill. + description: >- + Specifies whether the order place count should be capped by the + member's current position. Orders with reduce_only set to true will + be rejected unless time_in_force is immediate_or_cancel or + fill_or_kill. subaccount: type: integer minimum: 0 default: 0 - description: The subaccount number to use for this margin order. 0 is the primary subaccount. + description: >- + The subaccount number to use for this margin order. 0 is the primary + subaccount. x-go-type-skip-optional-pointer: true order_group_id: type: string description: The order group this order is part of x-go-type-skip-optional-pointer: true - CreateMarginOrderResponse: type: object required: @@ -1691,14 +1843,19 @@ components: description: Number of contracts filled immediately upon placement. remaining_count: $ref: '#/components/schemas/FixedPointCount' - description: Number of contracts remaining after placement. For IOC orders, this reflects the final state after unfilled contracts are canceled. + description: >- + Number of contracts remaining after placement. For IOC orders, this + reflects the final state after unfilled contracts are canceled. average_fill_price: $ref: '#/components/schemas/FixedPointDollars' - description: Volume-weighted average fill price. Only present when fill_count > 0. + description: >- + Volume-weighted average fill price. Only present when fill_count > + 0. average_fee_paid: $ref: '#/components/schemas/FixedPointDollars' - description: Volume-weighted average fee paid per contract for fills resulting from this request. Only present when fill_count > 0. - + description: >- + Volume-weighted average fee paid per contract for fills resulting + from this request. Only present when fill_count > 0. GetMarginOrderResponse: type: object required: @@ -1706,7 +1863,6 @@ components: properties: order: $ref: '#/components/schemas/MarginOrder' - GetMarginOrdersResponse: type: object required: @@ -1719,7 +1875,6 @@ components: $ref: '#/components/schemas/MarginOrder' cursor: type: string - MarginOrder: type: object required: @@ -1776,17 +1931,20 @@ components: x-omitempty: false cancel_order_on_pause: type: boolean - description: If this flag is set to true, the order will be canceled if the order is open and trading on the exchange is paused for any reason. + description: >- + If this flag is set to true, the order will be canceled if the order + is open and trading on the exchange is paused for any reason. order_group_id: type: string description: The order group this order is part of order_source: $ref: '#/components/schemas/OrderSource' - description: The source of the order. Indicates whether the order was placed by the user or by the system on behalf of the user. + description: >- + The source of the order. Indicates whether the order was placed by + the user or by the system on behalf of the user. order_reason: $ref: '#/components/schemas/OrderReason' description: The reason for a system-generated order, when applicable. - CancelMarginOrderResponse: type: object required: @@ -1799,20 +1957,24 @@ components: type: string reduced_by: $ref: '#/components/schemas/FixedPointCount' - description: Number of contracts that were canceled (i.e. the remaining count at time of cancellation). - + description: >- + Number of contracts that were canceled (i.e. the remaining count at + time of cancellation). DecreaseMarginOrderRequest: type: object properties: reduce_by: $ref: '#/components/schemas/FixedPointCount' nullable: true - description: String representation of the number of contracts to reduce by. Exactly one of `reduce_by` or `reduce_to` must be provided. + description: >- + String representation of the number of contracts to reduce by. + Exactly one of `reduce_by` or `reduce_to` must be provided. reduce_to: $ref: '#/components/schemas/FixedPointCount' nullable: true - description: String representation of the number of contracts to reduce to. Exactly one of `reduce_by` or `reduce_to` must be provided. - + description: >- + String representation of the number of contracts to reduce to. + Exactly one of `reduce_by` or `reduce_to` must be provided. DecreaseMarginOrderResponse: type: object required: @@ -1826,7 +1988,6 @@ components: remaining_count: $ref: '#/components/schemas/FixedPointCount' description: Number of contracts remaining after the decrease. - AmendMarginOrderRequest: type: object required: @@ -1861,7 +2022,6 @@ components: type: string description: The new client-specified order ID after amendment x-go-type-skip-optional-pointer: true - AmendMarginOrderResponse: type: object required: @@ -1875,23 +2035,30 @@ components: $ref: '#/components/schemas/FixedPointCount' nullable: true x-omitempty: false - description: Number of contracts remaining after the amend. Only present when the amend caused a fill or changed the resting size. + description: >- + Number of contracts remaining after the amend. Only present when the + amend caused a fill or changed the resting size. fill_count: $ref: '#/components/schemas/FixedPointCount' nullable: true x-omitempty: false - description: Number of contracts filled as a result of the amend crossing the book. Only present when fills occurred or remaining size changed. + description: >- + Number of contracts filled as a result of the amend crossing the + book. Only present when fills occurred or remaining size changed. average_fill_price: $ref: '#/components/schemas/FixedPointDollars' nullable: true x-omitempty: false - description: Volume-weighted average fill price for fills resulting from the amend. Only present when fills occurred. + description: >- + Volume-weighted average fill price for fills resulting from the + amend. Only present when fills occurred. average_fee_paid: $ref: '#/components/schemas/FixedPointDollars' nullable: true x-omitempty: false - description: Volume-weighted average fee paid per contract for fills resulting from the amend. Only present when fills occurred. - + description: >- + Volume-weighted average fee paid per contract for fills resulting + from the amend. Only present when fills occurred. MarginOrderbookCount: type: object required: @@ -1900,15 +2067,18 @@ components: properties: bids: type: array - description: Bid price levels, ordered from best bid downward. Each level is [price, quantity]. + description: >- + Bid price levels, ordered from best bid downward. Each level is + [price, quantity]. items: $ref: '#/components/schemas/PriceLevelDollarsCountFp' asks: type: array - description: Ask price levels, ordered from best ask upward. Each level is [price, quantity]. + description: >- + Ask price levels, ordered from best ask upward. Each level is + [price, quantity]. items: $ref: '#/components/schemas/PriceLevelDollarsCountFp' - MarginOrderbookResponse: type: object required: @@ -1916,7 +2086,6 @@ components: properties: orderbook: $ref: '#/components/schemas/MarginOrderbookCount' - MarginMarket: type: object required: @@ -1946,8 +2115,9 @@ components: type: number format: double description: > - Leverage estimate (1 / margin_rate) evaluated at a small retail-sized notional position. - Actual leverage may be lower for larger positions as the liquidation margin rate grows with size. + Leverage estimate (1 / margin_rate) evaluated at a small + retail-sized notional position. Actual leverage may be lower for + larger positions as the liquidation margin rate grows with size. Null when margin config or price data is unavailable. leverage_estimates: type: object @@ -1955,10 +2125,26 @@ components: type: number format: double description: > - Leverage estimates (1 / margin_rate) keyed by notional position size in dollars - ("1000", "10000", "100000", "1000000"). Leverage decreases at larger notionals as - the liquidation margin rate grows with size. - Null when margin config or price data is unavailable. + Leverage estimates (1 / margin_rate) keyed by notional position size + in dollars ("1000", "10000", "100000", "1000000"). Leverage + decreases at larger notionals as the liquidation margin rate grows + with size. Null when margin config or price data is unavailable. + long_leverage_estimates: + type: object + additionalProperties: + type: number + format: double + description: > + Leverage estimates for a long position, keyed by the same notional + position sizes as leverage_estimates. + short_leverage_estimates: + type: object + additionalProperties: + type: number + format: double + description: > + Leverage estimates for a short position, keyed by the same notional + position sizes as long_leverage_estimates. price: $ref: '#/components/schemas/FixedPointDollars' description: Last trade price in dollars. @@ -1979,7 +2165,9 @@ components: description: One sided trade volume in the last 24 hours. volume_24h_notional_value_dollars: $ref: '#/components/schemas/FixedPointDollars' - description: Total notional value of one sided trade volume in the last 24 hours in dollars. + description: >- + Total notional value of one sided trade volume in the last 24 hours + in dollars. bid: $ref: '#/components/schemas/FixedPointDollars' description: Best bid price in dollars. @@ -1997,7 +2185,6 @@ components: description: Underlying reference price, scaled per contract. schedule: $ref: '#/components/schemas/MarginMarketSchedule' - MarginMarketSchedule: type: object nullable: true @@ -2014,13 +2201,16 @@ components: type: integer format: int64 nullable: true - description: Unix timestamp in seconds for the next scheduled close. Null while closed. + description: >- + Unix timestamp in seconds for the next scheduled close. Null while + closed. next_open_ts: type: integer format: int64 nullable: true - description: Unix timestamp in seconds for the next scheduled open. Null while open. - + description: >- + Unix timestamp in seconds for the next scheduled open. Null while + open. TickerPrice: type: object required: @@ -2034,26 +2224,40 @@ components: type: integer format: int64 description: Source timestamp in epoch milliseconds. - MarginMarketStatus: type: string - enum: [inactive, active, closed] + enum: + - inactive + - active + - closed description: The status of a margin market - OrderSource: type: string - enum: ['user', 'system'] - description: The source of the order. 'user' indicates a user-placed order, 'system' indicates a system-generated order. - + enum: + - user + - system + description: >- + The source of the order. 'user' indicates a user-placed order, 'system' + indicates a system-generated order. OrderReason: type: string - enum: ['liquidation', 'take_profit_stop_loss'] - description: The reason for a system-generated order. Present for liquidation and TP/SL orders. - + enum: + - liquidation + - take_profit_stop_loss + description: >- + The reason for a system-generated order. Present for liquidation and + TP/SL orders. LastUpdateReason: type: string - enum: ['', 'Decrease', 'Amend', 'MarginCancel', 'SelfTradeCancel', 'ExpiryCancel', 'Trade', 'PostOnlyCrossCancel'] - + enum: + - '' + - Decrease + - Amend + - MarginCancel + - SelfTradeCancel + - ExpiryCancel + - Trade + - PostOnlyCrossCancel MarginMarketResponse: type: object required: @@ -2061,7 +2265,6 @@ components: properties: market: $ref: '#/components/schemas/MarginMarket' - GetMarginMarketsResponse: type: object required: @@ -2071,7 +2274,6 @@ components: type: array items: $ref: '#/components/schemas/MarginMarket' - GetMarginFillsResponse: type: object required: @@ -2084,7 +2286,6 @@ components: $ref: '#/components/schemas/MarginFill' cursor: type: string - MarginFill: type: object required: @@ -2125,16 +2326,19 @@ components: description: Fill price in fixed-point dollars entry_price: type: string - description: Position entry price used to compute incremental realized PnL for this fill + description: >- + Position entry price used to compute incremental realized PnL for + this fill fees: type: string description: Fees paid on filled contracts, in dollars realized_pnl: type: string - description: Incremental realized PnL contributed by this fill, in fixed-point dollars + description: >- + Incremental realized PnL contributed by this fill, in fixed-point + dollars order_source: $ref: '#/components/schemas/OrderSource' - GetMarginPositionsResponse: type: object required: @@ -2144,7 +2348,6 @@ components: type: array items: $ref: '#/components/schemas/MarginPosition' - MarginPosition: type: object required: @@ -2158,13 +2361,17 @@ components: properties: subaccount: type: integer - description: The subaccount number that holds this position (0 for primary, 1-63 for subaccounts) + description: >- + The subaccount number that holds this position (0 for primary, 1-63 + for subaccounts) market_ticker: type: string description: Market ticker symbol position: $ref: '#/components/schemas/FixedPointCount' - description: Position size as a fixed-point count string (positive = long, negative = short) + description: >- + Position size as a fixed-point count string (positive = long, + negative = short) entry_price: $ref: '#/components/schemas/FixedPointDollars' description: Weighted average entry price of the open position @@ -2174,19 +2381,30 @@ components: margin_used: $ref: '#/components/schemas/FixedPointDollars' nullable: true - description: Maintenance-margin-based capital usage for the open position. Null when the position shares its asset class with other portfolio-margin positions in the subaccount, since margin is then computed jointly for the group and cannot be attributed to a single market. + description: >- + Maintenance-margin-based capital usage for the open position. Null + when the position shares its asset class with other portfolio-margin + positions in the subaccount, since margin is then computed jointly + for the group and cannot be attributed to a single market. fees: $ref: '#/components/schemas/FixedPointDollars' - description: Total fees accumulated over the lifetime of the current open position, resets when position is fully closed + description: >- + Total fees accumulated over the lifetime of the current open + position, resets when position is fully closed roe: type: number format: double nullable: true - description: Return on equity as a percentage (unrealized_pnl / margin_used * 100). Null when margin_used is zero or not attributable to this market. + description: >- + Return on equity as a percentage (unrealized_pnl / margin_used * + 100). Null when margin_used is zero or not attributable to this + market. is_portfolio: type: boolean - description: 'True when this position is hedged within a portfolio, so margin_used and roe cannot be attributed to it individually and are not reported.' - + description: >- + True when this position is hedged within a portfolio, so margin_used + and roe cannot be attributed to it individually and are not + reported. GetMarginTradesResponse: type: object required: @@ -2199,7 +2417,6 @@ components: $ref: '#/components/schemas/MarginTrade' cursor: type: string - MarginTrade: type: object required: @@ -2231,9 +2448,10 @@ components: description: Side of the taker in this trade BookSide: type: string - enum: ['bid', 'ask'] + enum: + - bid + - ask description: The side of an order or trade (bid or ask) - MarginEnabledResponse: type: object required: @@ -2242,7 +2460,6 @@ components: enabled: type: boolean description: Indicates whether margin trading is enabled for the user - NotionalRiskLimitResponse: type: object required: @@ -2251,16 +2468,21 @@ components: properties: default_notional_value_risk_limit: type: string - description: The notional value risk limit for the user as a fixed-point dollar string with 4 decimal places (e.g., "5000.0000") - example: "5000.0000" + description: >- + The notional value risk limit for the user as a fixed-point dollar + string with 4 decimal places (e.g., "5000.0000") + example: '5000.0000' notional_value_risk_limits_by_market_ticker: type: object additionalProperties: type: string - description: Map of market_ticker to notional value risk limit as a fixed-point dollar string with 4 decimal places (e.g., "5000.0000"). If present, the market-level risk limit overrides the default notional value risk limit. + description: >- + Map of market_ticker to notional value risk limit as a fixed-point + dollar string with 4 decimal places (e.g., "5000.0000"). If present, + the market-level risk limit overrides the default notional value + risk limit. example: - "market-abc-123": "5000.0000" - + market-abc-123: '5000.0000' MarginSubaccountBalance: type: object required: @@ -2277,23 +2499,34 @@ components: description: The subaccount number (0 for primary, 1-63 for subaccounts) position_value: $ref: '#/components/schemas/FixedPointDollars' - description: Mark-to-market value of open positions for this subaccount in fixed-point dollars + description: >- + Mark-to-market value of open positions for this subaccount in + fixed-point dollars account_equity: $ref: '#/components/schemas/FixedPointDollars' - description: Account equity for this subaccount in fixed-point dollars. 0 for self clearing members. + description: >- + Account equity for this subaccount in fixed-point dollars. 0 for + self clearing members. maintenance_margin: $ref: '#/components/schemas/FixedPointDollars' - description: Maintenance margin requirement for this subaccount in fixed-point dollars + description: >- + Maintenance margin requirement for this subaccount in fixed-point + dollars initial_margin: $ref: '#/components/schemas/FixedPointDollars' - description: Initial margin requirement for this subaccount in fixed-point dollars. 0 for self clearing members. + description: >- + Initial margin requirement for this subaccount in fixed-point + dollars. 0 for self clearing members. resting_orders_margin: $ref: '#/components/schemas/FixedPointDollars' - description: Margin locked by resting orders for this subaccount in fixed-point dollars. 0 unless compute_available_balance is passed. + description: >- + Margin locked by resting orders for this subaccount in fixed-point + dollars. 0 unless compute_available_balance is passed. available_balance: $ref: '#/components/schemas/FixedPointDollars' - description: Available balance for this subaccount in fixed-point dollars. 0 for institutional users or if compute_available_balance was not passed. - + description: >- + Available balance for this subaccount in fixed-point dollars. 0 for + institutional users or if compute_available_balance was not passed. GetMarginBalanceResponse: type: object required: @@ -2308,7 +2541,6 @@ components: settled_funds: $ref: '#/components/schemas/FixedPointDollars' description: Total settled funds across all subaccounts in fixed-point dollars - MarginRiskPosition: type: object required: @@ -2333,24 +2565,36 @@ components: description: Current mark price for the market in fixed-point dollars position_notional: $ref: '#/components/schemas/FixedPointDollars' - description: Absolute notional value of the position (|qty| * mark_price) in fixed-point dollars + description: >- + Absolute notional value of the position (|qty| * mark_price) in + fixed-point dollars maintenance_margin_required: $ref: '#/components/schemas/FixedPointDollars' - description: Maintenance margin requirement for this position in fixed-point dollars. Null if margin config is missing. + description: >- + Maintenance margin requirement for this position in fixed-point + dollars. Null if margin config is missing. nullable: true position_leverage: type: number format: double - description: 'Position leverage ratio (position_notional / maintenance_margin_required). Null when maintenance margin is zero or config is missing.' + description: >- + Position leverage ratio (position_notional / + maintenance_margin_required). Null when maintenance margin is zero + or config is missing. nullable: true estimated_liquidation_price: $ref: '#/components/schemas/FixedPointDollars' - description: 'Estimated portfolio-aware liquidation price for this position within the subaccount. Null when no valid liquidation price exists.' + description: >- + Estimated portfolio-aware liquidation price for this position within + the subaccount. Null when no valid liquidation price exists. nullable: true is_portfolio: type: boolean - description: 'True when this position is hedged within a portfolio, so maintenance_margin_required, position_leverage, and estimated_liquidation_price cannot be attributed to it individually and are not reported.' - + description: >- + True when this position is hedged within a portfolio, so + maintenance_margin_required, position_leverage, and + estimated_liquidation_price cannot be attributed to it individually + and are not reported. GetMarginRiskResponse: type: object required: @@ -2361,20 +2605,26 @@ components: account_leverage: type: number format: double - description: 'Account-level leverage (total_position_notional / total_maintenance_margin). Null when total maintenance margin is zero.' + description: >- + Account-level leverage (total_position_notional / + total_maintenance_margin). Null when total maintenance margin is + zero. nullable: true total_position_notional: $ref: '#/components/schemas/FixedPointDollars' - description: Sum of absolute position notional values across all positions in fixed-point dollars + description: >- + Sum of absolute position notional values across all positions in + fixed-point dollars total_maintenance_margin: $ref: '#/components/schemas/FixedPointDollars' - description: Sum of maintenance margin requirements across all positions in fixed-point dollars + description: >- + Sum of maintenance margin requirements across all positions in + fixed-point dollars positions: type: array items: $ref: '#/components/schemas/MarginRiskPosition' description: Per-position risk breakdown grouped by subaccount and market - GetMarginFeeTiersResponse: type: object required: @@ -2386,14 +2636,19 @@ components: additionalProperties: type: number format: double - description: A map of margin market ticker to the maker-side fee rate as a decimal fraction of notional (e.g. 0.0005 = 0.05% = 5 bps). Multiply notional by this value to compute the fee. + description: >- + A map of margin market ticker to the maker-side fee rate as a + decimal fraction of notional (e.g. 0.0005 = 0.05% = 5 bps). Multiply + notional by this value to compute the fee. taker_fee_rates: type: object additionalProperties: type: number format: double - description: A map of margin market ticker to the taker-side fee rate as a decimal fraction of notional (e.g. 0.0012 = 0.12% = 12 bps). Multiply notional by this value to compute the fee. - + description: >- + A map of margin market ticker to the taker-side fee rate as a + decimal fraction of notional (e.g. 0.0012 = 0.12% = 12 bps). + Multiply notional by this value to compute the fee. MarginFundingHistoryEntry: type: object required: @@ -2421,7 +2676,9 @@ components: description: Mark price at the time of funding funding_amount: $ref: '#/components/schemas/FixedPointDollars' - description: Dollar amount of the funding payment (positive = received, negative = paid) + description: >- + Dollar amount of the funding payment (positive = received, negative + = paid) quantity: $ref: '#/components/schemas/FixedPointCount' description: Position size at time of funding as a fixed-point count string @@ -2429,7 +2686,6 @@ components: type: integer nullable: true description: Subaccount number (0 for primary) - GetMarginFundingHistoryResponse: type: object required: @@ -2440,7 +2696,6 @@ components: items: $ref: '#/components/schemas/MarginFundingHistoryEntry' description: Array of historical funding payment entries - MarginFundingRate: type: object required: @@ -2463,7 +2718,6 @@ components: mark_price: $ref: '#/components/schemas/FixedPointDollars' description: Mark price at the time of funding - GetMarginHistoricalFundingRatesResponse: type: object required: @@ -2474,7 +2728,6 @@ components: items: $ref: '#/components/schemas/MarginFundingRate' description: Array of historical funding rate entries - GetMarginFundingRateEstimateResponse: type: object required: @@ -2498,7 +2751,6 @@ components: type: string format: date-time description: Timestamp of the next scheduled funding event - GetMarginMarketCandlesticksResponse: type: object required: @@ -2513,7 +2765,6 @@ components: description: Array of candlestick data points for the specified time range. items: $ref: '#/components/schemas/MarginMarketCandlestick' - MarginMarketCandlestick: type: object required: @@ -2532,26 +2783,39 @@ components: description: Unix timestamp for the inclusive end of the candlestick period. bid: $ref: '#/components/schemas/BidAskDistributionHistorical' - description: Open, high, low, close (OHLC) data for buy offers on the market during the candlestick period. + description: >- + Open, high, low, close (OHLC) data for buy offers on the market + during the candlestick period. ask: $ref: '#/components/schemas/BidAskDistributionHistorical' - description: Open, high, low, close (OHLC) data for sell offers on the market during the candlestick period. + description: >- + Open, high, low, close (OHLC) data for sell offers on the market + during the candlestick period. price: $ref: '#/components/schemas/PriceDistributionHistorical' - description: Open, high, low, close (OHLC) and more data for trade prices on the market during the candlestick period. + description: >- + Open, high, low, close (OHLC) and more data for trade prices on the + market during the candlestick period. volume: $ref: '#/components/schemas/FixedPointCount' - description: Number of contracts traded on the market during the candlestick period. + description: >- + Number of contracts traded on the market during the candlestick + period. volume_notional_value_dollars: $ref: '#/components/schemas/FixedPointDollars' - description: Notional value of contracts traded on the market during the candlestick period. + description: >- + Notional value of contracts traded on the market during the + candlestick period. open_interest: $ref: '#/components/schemas/FixedPointCount' - description: Number of contracts held on the market by end of the candlestick period (end_period_ts). + description: >- + Number of contracts held on the market by end of the candlestick + period (end_period_ts). open_interest_notional_value_dollars: $ref: '#/components/schemas/FixedPointDollars' - description: Notional value of contracts held on the market by end of the candlestick period (end_period_ts). - + description: >- + Notional value of contracts held on the market by end of the + candlestick period (end_period_ts). BidAskDistributionHistorical: type: object required: @@ -2559,7 +2823,10 @@ components: - low - high - close - description: OHLC data for quoted prices on one side of the orderbook during the candlestick period. These values reflect bid or ask quotes, not executed trade prices. + description: >- + OHLC data for quoted prices on one side of the orderbook during the + candlestick period. These values reflect bid or ask quotes, not executed + trade prices. properties: open: $ref: '#/components/schemas/FixedPointDollars' @@ -2573,7 +2840,6 @@ components: close: $ref: '#/components/schemas/FixedPointDollars' description: Quoted price at the end of the candlestick period (in dollars). - PriceDistributionHistorical: type: object required: @@ -2588,38 +2854,52 @@ components: allOf: - $ref: '#/components/schemas/FixedPointDollars' nullable: true - description: Price of the first trade during the candlestick period (in dollars). Null if no trades occurred. + description: >- + Price of the first trade during the candlestick period (in dollars). + Null if no trades occurred. low: allOf: - $ref: '#/components/schemas/FixedPointDollars' nullable: true - description: Lowest trade price during the candlestick period (in dollars). Null if no trades occurred. + description: >- + Lowest trade price during the candlestick period (in dollars). Null + if no trades occurred. high: allOf: - $ref: '#/components/schemas/FixedPointDollars' nullable: true - description: Highest trade price during the candlestick period (in dollars). Null if no trades occurred. + description: >- + Highest trade price during the candlestick period (in dollars). Null + if no trades occurred. close: allOf: - $ref: '#/components/schemas/FixedPointDollars' nullable: true - description: Price of the last trade during the candlestick period (in dollars). Null if no trades occurred. + description: >- + Price of the last trade during the candlestick period (in dollars). + Null if no trades occurred. mean: allOf: - $ref: '#/components/schemas/FixedPointDollars' nullable: true - description: Volume-weighted average price during the candlestick period (in dollars). Null if no trades occurred. + description: >- + Volume-weighted average price during the candlestick period (in + dollars). Null if no trades occurred. previous: allOf: - $ref: '#/components/schemas/FixedPointDollars' nullable: true - description: Close price from the previous candlestick period (in dollars). Null if this is the first candlestick or no prior trade exists. - + description: >- + Close price from the previous candlestick period (in dollars). Null + if this is the first candlestick or no prior trade exists. parameters: CursorQuery: name: cursor in: query - description: Pagination cursor. Use the cursor value returned from the previous response to get the next page of results. Leave empty for the first page. + description: >- + Pagination cursor. Use the cursor value returned from the previous + response to get the next page of results. Leave empty for the first + page. schema: type: string x-go-type-skip-optional-pointer: true @@ -2634,7 +2914,7 @@ components: maximum: 1000 default: 100 x-oapi-codegen-extra-tags: - validate: "omitempty,min=1,max=1000" + validate: omitempty,min=1,max=1000 MarginOrdersLimitQuery: name: limit in: query @@ -2646,7 +2926,7 @@ components: maximum: 10000 default: 10000 x-oapi-codegen-extra-tags: - validate: "omitempty,min=1,max=10000" + validate: omitempty,min=1,max=10000 MaxTsQuery: name: max_ts in: query @@ -2692,7 +2972,9 @@ components: name: subaccount in: query required: false - description: Subaccount number (0 for primary, 1-63 for subaccounts). If omitted, defaults to all subaccounts. + description: >- + Subaccount number (0 for primary, 1-63 for subaccounts). If omitted, + defaults to all subaccounts. schema: type: integer minimum: 0 diff --git a/tests/_contract_support.py b/tests/_contract_support.py index 90d9b1e0..2402ded1 100644 --- a/tests/_contract_support.py +++ b/tests/_contract_support.py @@ -759,6 +759,23 @@ class Exclusion: http_method="GET", path_template="/portfolio/fills", ), + MethodEndpointEntry( + sdk_method="kalshi.resources.portfolio.PortfolioResource.intra_exchange_transfers", + http_method="GET", + path_template="/portfolio/intra_exchange_instance_transfers", + ), + MethodEndpointEntry( + sdk_method=( + "kalshi.resources.portfolio.PortfolioResource.intra_exchange_transfers_all" + ), + http_method="GET", + path_template="/portfolio/intra_exchange_instance_transfers", + ), + MethodEndpointEntry( + sdk_method="kalshi.resources.portfolio.PortfolioResource.get_intra_exchange_transfer", + http_method="GET", + path_template="/portfolio/intra_exchange_instance_transfers/{transfer_id}", + ), # ── series ────────────────────────────────────────────────────────────── MethodEndpointEntry( sdk_method="kalshi.resources.series.SeriesResource.list", @@ -866,12 +883,6 @@ class Exclusion: path_template="/multivariate_event_collections/{collection_ticker}", request_body_schema="#/components/schemas/CreateMarketInMultivariateEventCollectionRequest", ), - MethodEndpointEntry( - sdk_method=("kalshi.resources.multivariate.MultivariateCollectionsResource.lookup_tickers"), - http_method="PUT", - path_template="/multivariate_event_collections/{collection_ticker}/lookup", - request_body_schema="#/components/schemas/LookupTickersForMarketInMultivariateEventCollectionRequest", - ), ] @@ -969,6 +980,13 @@ class Exclusion: reason="paginator-handled; not a caller-facing kwarg on list_all", kind="paginator_handled", ), + ( + "kalshi.resources.portfolio.PortfolioResource.intra_exchange_transfers_all", + "cursor", + ): Exclusion( + reason="paginator-handled; not a caller-facing kwarg on list_all", + kind="paginator_handled", + ), # --- model-first overload kwarg (#56) --- # batch_cancel_v2 (DELETE /portfolio/events/orders/batched with a requestBody) # is covered by TestRequestParamDrift. The `request` kwarg is an SDK-side @@ -1258,6 +1276,7 @@ class Exclusion: "kalshi.resources.portfolio.PortfolioResource.withdrawals_all", "kalshi.resources.portfolio.PortfolioResource.positions_all", "kalshi.resources.portfolio.PortfolioResource.fills_all", + "kalshi.resources.portfolio.PortfolioResource.intra_exchange_transfers_all", "kalshi.resources.fcm.FcmResource.orders_all", "kalshi.resources.fcm.FcmResource.positions_all", "kalshi.resources.incentive_programs.IncentiveProgramsResource.list_all", @@ -1289,6 +1308,7 @@ class Exclusion: "kalshi.resources.portfolio.AsyncPortfolioResource.withdrawals_all", "kalshi.resources.portfolio.AsyncPortfolioResource.positions_all", "kalshi.resources.portfolio.AsyncPortfolioResource.fills_all", + "kalshi.resources.portfolio.AsyncPortfolioResource.intra_exchange_transfers_all", "kalshi.resources.fcm.AsyncFcmResource.orders_all", "kalshi.resources.fcm.AsyncFcmResource.positions_all", "kalshi.resources.incentive_programs.AsyncIncentiveProgramsResource.list_all", diff --git a/tests/_model_fixtures.py b/tests/_model_fixtures.py index 5e7227dd..d436ff17 100644 --- a/tests/_model_fixtures.py +++ b/tests/_model_fixtures.py @@ -532,17 +532,6 @@ def quote_executed_payload_dict(**overrides: Any) -> dict[str, Any]: return base -def multivariate_payload_dict(**overrides: Any) -> dict[str, Any]: - """Spec-shaped MultivariatePayload msg dict.""" - base: dict[str, Any] = { - "collection_ticker": "COLL-A", - "selected_markets": [], - "market_ticker": "MKT-A", - "event_ticker": "EVT-A", - } - base.update(overrides) - return base - def market_lifecycle_payload_dict(**overrides: Any) -> dict[str, Any]: """Spec-shaped MarketLifecyclePayload msg dict. diff --git a/tests/integration/test_multivariate.py b/tests/integration/test_multivariate.py index bd919dca..29ab3071 100644 --- a/tests/integration/test_multivariate.py +++ b/tests/integration/test_multivariate.py @@ -4,7 +4,6 @@ import pytest -from kalshi.async_client import AsyncKalshiClient from kalshi.client import KalshiClient from kalshi.errors import ( KalshiNotFoundError, @@ -13,7 +12,6 @@ from kalshi.models.common import Page from kalshi.models.multivariate import ( CreateMarketResponse, - LookupTickersResponse, MultivariateEventCollection, TickerPair, ) @@ -27,7 +25,6 @@ "list_all", "get", "create_market", - "lookup_tickers", ], ) @@ -123,96 +120,3 @@ def test_create_market( assert resp.event_ticker assert resp.market_ticker - def test_lookup_tickers( - self, - sync_client: KalshiClient, - demo_collection: MultivariateEventCollection, - ) -> None: - """PUT lookup — resolves a TickerPair set to a canonical combo ticker.""" - pairs = _build_ticker_pairs(demo_collection, sync_client) - if not pairs: - pytest.skip("Demo collection has no associated events with markets") - try: - resp = sync_client.multivariate_collections.lookup_tickers( - demo_collection.collection_ticker, - selected_markets=pairs, - ) - except (KalshiValidationError, KalshiNotFoundError) as e: - pytest.skip(f"Demo rejected lookup_tickers for this collection: {e}") - assert isinstance(resp, LookupTickersResponse) - assert resp.event_ticker - assert resp.market_ticker - - -@pytest.mark.integration -class TestMultivariateAsync: - async def test_list(self, async_client: AsyncKalshiClient) -> None: - page = await async_client.multivariate_collections.list(limit=5) - assert isinstance(page, Page) - assert isinstance(page.items, list) - if page.items: - assert isinstance(page.items[0], MultivariateEventCollection) - assert_model_fields(page.items[0]) - - async def test_list_all(self, async_client: AsyncKalshiClient) -> None: - count = 0 - async for collection in async_client.multivariate_collections.list_all(limit=2): - assert isinstance(collection, MultivariateEventCollection) - assert_model_fields(collection) - count += 1 - if count >= 2: - break - - async def test_get( - self, async_client: AsyncKalshiClient, demo_collection_ticker: str - ) -> None: - collection = await async_client.multivariate_collections.get( - demo_collection_ticker - ) - assert isinstance(collection, MultivariateEventCollection) - assert_model_fields(collection) - assert collection.collection_ticker == demo_collection_ticker - - async def test_create_market( - self, - async_client: AsyncKalshiClient, - sync_client: KalshiClient, - demo_collection: MultivariateEventCollection, - ) -> None: - # sync_client is used only to build the fixture pairs — the - # endpoint-under-test is called via async_client below. - pairs = _build_ticker_pairs(demo_collection, sync_client) - if not pairs: - pytest.skip("Demo collection has no associated events with markets") - try: - resp = await async_client.multivariate_collections.create_market( - demo_collection.collection_ticker, - selected_markets=pairs, - ) - except (KalshiValidationError, KalshiNotFoundError) as e: - pytest.skip(f"Demo rejected create_market for this collection: {e}") - assert isinstance(resp, CreateMarketResponse) - assert resp.event_ticker - assert resp.market_ticker - - async def test_lookup_tickers( - self, - async_client: AsyncKalshiClient, - sync_client: KalshiClient, - demo_collection: MultivariateEventCollection, - ) -> None: - # sync_client is used only to build the fixture pairs — the - # endpoint-under-test is called via async_client below. - pairs = _build_ticker_pairs(demo_collection, sync_client) - if not pairs: - pytest.skip("Demo collection has no associated events with markets") - try: - resp = await async_client.multivariate_collections.lookup_tickers( - demo_collection.collection_ticker, - selected_markets=pairs, - ) - except (KalshiValidationError, KalshiNotFoundError) as e: - pytest.skip(f"Demo rejected lookup_tickers for this collection: {e}") - assert isinstance(resp, LookupTickersResponse) - assert resp.event_ticker - assert resp.market_ticker diff --git a/tests/integration/test_websocket.py b/tests/integration/test_websocket.py index f312d795..82583f93 100644 --- a/tests/integration/test_websocket.py +++ b/tests/integration/test_websocket.py @@ -178,24 +178,6 @@ async def test_ws_subscribe_communications( assert isinstance(msg, CommunicationsMessage) assert msg.type == "communications" - @retry_transient(max_retries=2, delay=1.0) - async def test_ws_subscribe_multivariate( - self, - ws_session: KalshiWebSocket, - ) -> None: - """Subscribe to multivariate channel. Skip if demo has no active collections.""" - from kalshi.ws.models.multivariate import MultivariateMessage - stream = await ws_session.subscribe_multivariate() - try: - msg = await asyncio.wait_for(stream.__anext__(), timeout=15.0) - except TimeoutError: - pytest.skip( - "No multivariate frame within 15s — demo likely has no active " - "collections, and 'multivariate_lookup' envelope key is " - "spec-inferred (no live capture during v0.14.0 work)" - ) - assert isinstance(msg, MultivariateMessage) - assert msg.type == "multivariate_lookup" @retry_transient(max_retries=2, delay=1.0) async def test_ws_subscribe_multivariate_lifecycle( diff --git a/tests/perps/test_markets.py b/tests/perps/test_markets.py index e90d0615..a70f4726 100644 --- a/tests/perps/test_markets.py +++ b/tests/perps/test_markets.py @@ -45,6 +45,8 @@ def _market_dict(**overrides: object) -> dict[str, object]: }, "leverage_estimate": 2.5, "leverage_estimates": {"1000": 2.5, "10000": 2.0, "100000": 1.5}, + "long_leverage_estimates": {"1000": 2.4, "10000": 1.9}, + "short_leverage_estimates": {"1000": 2.6, "10000": 2.1}, "price": "0.5600", "bid": "0.5500", "ask": "0.5700", @@ -108,6 +110,14 @@ def test_happy(self, perps_client: PerpsClient) -> None: "10000": Decimal("2.0"), "100000": Decimal("1.5"), } + assert m.long_leverage_estimates == { + "1000": Decimal("2.4"), + "10000": Decimal("1.9"), + } + assert m.short_leverage_estimates == { + "1000": Decimal("2.6"), + "10000": Decimal("2.1"), + } assert m.volume == Decimal("1000.00") assert m.volume_notional_value == Decimal("560.0000") assert m.volume_24h_notional_value == Decimal("140.0000") diff --git a/tests/perps/ws/test_perps_ws_models.py b/tests/perps/ws/test_perps_ws_models.py index e0749f69..803d31a0 100644 --- a/tests/perps/ws/test_perps_ws_models.py +++ b/tests/perps/ws/test_perps_ws_models.py @@ -627,8 +627,7 @@ def test_out_of_scope_channels_have_no_helpers(self) -> None: for name in ( "subscribe_market_positions", - "subscribe_multivariate", - "subscribe_multivariate_lifecycle", + "subscribe_multivariate_lifecycle", "subscribe_communications", "subscribe_market_lifecycle", ): diff --git a/tests/test_client.py b/tests/test_client.py index b043524e..f3dcab43 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1048,15 +1048,6 @@ def test_multivariate_create_market_raises_auth_required(self) -> None: client.multivariate_collections.create_market("MVC-1", selected_markets=[]) client.close() - def test_multivariate_lookup_tickers_raises_auth_required(self) -> None: - config = KalshiConfig( - base_url="https://test.kalshi.com/trade-api/v2", - timeout=5.0, - ) - client = KalshiClient(config=config, demo=True) - with pytest.raises(AuthRequiredError): - client.multivariate_collections.lookup_tickers("MVC-1", selected_markets=[]) - client.close() @respx.mock def test_markets_list_does_not_raise_auth_required(self) -> None: diff --git a/tests/test_contracts.py b/tests/test_contracts.py index c8719027..d24dd7b1 100644 --- a/tests/test_contracts.py +++ b/tests/test_contracts.py @@ -1422,9 +1422,6 @@ def _assert_params_match( "#/components/schemas/CreateMarketInMultivariateEventCollectionRequest": ( "kalshi.models.multivariate.CreateMarketInMultivariateEventCollectionRequest" ), - "#/components/schemas/LookupTickersForMarketInMultivariateEventCollectionRequest": ( - "kalshi.models.multivariate.LookupTickersForMarketInMultivariateEventCollectionRequest" - ), "#/components/schemas/CreateOrderGroupRequest": ( "kalshi.models.order_groups.CreateOrderGroupRequest" ), diff --git a/tests/test_models.py b/tests/test_models.py index 9da96861..b587ddf6 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -798,35 +798,6 @@ def test_forbid_extra(self) -> None: ) -class TestLookupTickersRequest: - def test_requires_selected_markets(self) -> None: - from pydantic import ValidationError - - from kalshi.models.multivariate import ( - LookupTickersForMarketInMultivariateEventCollectionRequest, - ) - - with pytest.raises(ValidationError): - LookupTickersForMarketInMultivariateEventCollectionRequest() # type: ignore[call-arg] - - def test_forbid_extra(self) -> None: - from pydantic import ValidationError - - from kalshi.models.multivariate import ( - LookupTickersForMarketInMultivariateEventCollectionRequest, - TickerPair, - ) - - pair = TickerPair(event_ticker="E1", market_ticker="M1", side="yes") - with pytest.raises(ValidationError): - LookupTickersForMarketInMultivariateEventCollectionRequest( - selected_markets=[pair], - bogus=1, # type: ignore[call-arg] - ) - - -# ---------- P2#4: AwareDatetime on REST response models ---------- - class TestAwareDatetimeRejectsNaive: """REST models reject naive datetimes at construction (#221 P2.4).""" diff --git a/tests/test_multivariate.py b/tests/test_multivariate.py index 3188f354..4ba0cf0e 100644 --- a/tests/test_multivariate.py +++ b/tests/test_multivariate.py @@ -139,53 +139,8 @@ def test_create_market_auth_guard(self, unauth_mv: MultivariateCollectionsResour unauth_mv.create_market("MVC-1", selected_markets=[]) -class TestMultivariateLookupTickers: - @respx.mock - def test_lookup_tickers(self, mv: MultivariateCollectionsResource) -> None: - respx.put(f"{BASE}/multivariate_event_collections/MVC-1/lookup").mock( - return_value=httpx.Response( - 200, - json={ - "event_ticker": "EVT-1", - "market_ticker": "MKT-1", - }, - ) - ) - pairs = [TickerPair(market_ticker="M-A", event_ticker="E-A", side="yes")] - result = mv.lookup_tickers("MVC-1", selected_markets=pairs) - assert result.event_ticker == "EVT-1" - - def test_lookup_tickers_auth_guard(self, unauth_mv: MultivariateCollectionsResource) -> None: - with pytest.raises(AuthRequiredError): - unauth_mv.lookup_tickers("MVC-1", selected_markets=[]) - - @respx.mock - def test_lookup_tickers_raises_on_204_spec_drift( - self, - mv: MultivariateCollectionsResource, - ) -> None: - # Spec says this endpoint returns 200 with a body. If it ever - # regresses to 204, we want a clear RuntimeError, not an opaque - # Pydantic validation error on `model_validate(None)`. Issue #72. - respx.put(f"{BASE}/multivariate_event_collections/MVC-1/lookup").mock( - return_value=httpx.Response(204), - ) - with pytest.raises(RuntimeError, match="spec drift"): - mv.lookup_tickers("MVC-1", selected_markets=[]) - class TestMultivariateDeprecationWarnings: - def test_lookup_tickers_emits_deprecation_warning( - self, mv: MultivariateCollectionsResource - ) -> None: - with respx.mock(base_url=BASE) as router: - router.put("/multivariate_event_collections/MVC-1/lookup").mock( - return_value=httpx.Response( - 200, json={"event_ticker": "EVT-1", "market_ticker": "MKT-1"} - ) - ) - with pytest.warns(DeprecationWarning, match=r"predates RFQs"): - mv.lookup_tickers("MVC-1", selected_markets=[]) def test_create_market_emits_deprecation_warning( self, mv: MultivariateCollectionsResource @@ -257,35 +212,8 @@ async def test_create_market_auth_guard( with pytest.raises(AuthRequiredError): await unauth_async_mv.create_market("MVC-1", selected_markets=[]) - @respx.mock - @pytest.mark.asyncio - async def test_lookup_tickers(self, async_mv: AsyncMultivariateCollectionsResource) -> None: - respx.put(f"{BASE}/multivariate_event_collections/MVC-1/lookup").mock( - return_value=httpx.Response(200, json={"event_ticker": "E", "market_ticker": "M"}) - ) - result = await async_mv.lookup_tickers("MVC-1", selected_markets=[]) - assert result.event_ticker == "E" - @pytest.mark.asyncio - async def test_lookup_tickers_auth_guard( - self, - unauth_async_mv: AsyncMultivariateCollectionsResource, - ) -> None: - with pytest.raises(AuthRequiredError): - await unauth_async_mv.lookup_tickers("MVC-1", selected_markets=[]) - @respx.mock - @pytest.mark.asyncio - async def test_lookup_tickers_raises_on_204_spec_drift( - self, - async_mv: AsyncMultivariateCollectionsResource, - ) -> None: - # Async sibling of the sync spec-drift guard. Issue #72. - respx.put(f"{BASE}/multivariate_event_collections/MVC-1/lookup").mock( - return_value=httpx.Response(204), - ) - with pytest.raises(RuntimeError, match="spec drift"): - await async_mv.lookup_tickers("MVC-1", selected_markets=[]) class TestCreateMarketWireShape: @@ -395,42 +323,3 @@ def test_ticker_pair_phantom_key_flows_to_wire( ) -class TestLookupTickersWireShape: - """v0.8.0: lookup_tickers() builds LookupTickersForMarketInMultivariateEventCollectionRequest - internally and serializes via model_dump.""" - - @respx.mock - def test_only_selected_markets_in_body(self, mv: MultivariateCollectionsResource) -> None: - import json - - route = respx.put(f"{BASE}/multivariate_event_collections/MVC-1/lookup").mock( - return_value=httpx.Response(200, json={"event_ticker": "E", "market_ticker": "M"}) - ) - pairs = [TickerPair(market_ticker="M-A", event_ticker="E-A", side="yes")] - mv.lookup_tickers("MVC-1", selected_markets=pairs) - - body = json.loads(route.calls[0].request.content) - assert set(body.keys()) == {"selected_markets"} - assert len(body["selected_markets"]) == 1 - - @respx.mock - @pytest.mark.asyncio - async def test_async_only_selected_markets_in_body( - self, - test_auth: KalshiAuth, - config: KalshiConfig, - ) -> None: - import json - - from kalshi._base_client import AsyncTransport - - async_mv = AsyncMultivariateCollectionsResource(AsyncTransport(test_auth, config)) - route = respx.put(f"{BASE}/multivariate_event_collections/MVC-1/lookup").mock( - return_value=httpx.Response(200, json={"event_ticker": "E", "market_ticker": "M"}) - ) - pairs = [TickerPair(market_ticker="M-A", event_ticker="E-A", side="yes")] - await async_mv.lookup_tickers("MVC-1", selected_markets=pairs) - - body = json.loads(route.calls[0].request.content) - assert set(body.keys()) == {"selected_markets"} - assert len(body["selected_markets"]) == 1 diff --git a/tests/test_multivariate_models.py b/tests/test_multivariate_models.py index 9c90a282..d6c136fa 100644 --- a/tests/test_multivariate_models.py +++ b/tests/test_multivariate_models.py @@ -4,7 +4,6 @@ from kalshi.models.multivariate import ( CreateMarketResponse, - LookupTickersResponse, MultivariateEventCollection, TickerPair, ) @@ -117,12 +116,3 @@ def test_without_market(self) -> None: assert r.market is None -class TestLookupTickersResponseModel: - def test_parse(self) -> None: - r = LookupTickersResponse.model_validate( - { - "event_ticker": "EVT-1", - "market_ticker": "MKT-1", - } - ) - assert r.event_ticker == "EVT-1" diff --git a/tests/test_portfolio.py b/tests/test_portfolio.py index 052a74a2..d7382b36 100644 --- a/tests/test_portfolio.py +++ b/tests/test_portfolio.py @@ -1312,3 +1312,105 @@ async def test_fills_all_requires_auth( with pytest.raises(AuthRequiredError): async for _ in unauth_async_portfolio.fills_all(): pass + + +# ── Intra-exchange instance transfers (#496/#497) ─────────────────────── + + +_TRANSFER = { + "transfer_id": "xfer-1", + "source": "event_contract", + "destination": "margined", + "source_exchange_shard": 0, + "destination_exchange_shard": 0, + "amount": "25.5000", + "status": "complete", + "created_ts": 1_700_000_000, +} + + +class TestPortfolioIntraExchangeTransfers: + @respx.mock + def test_returns_page(self, portfolio: PortfolioResource) -> None: + respx.get( + "https://test.kalshi.com/trade-api/v2/portfolio/intra_exchange_instance_transfers" + ).mock( + return_value=httpx.Response( + 200, json={"transfers": [_TRANSFER], "cursor": "next"} + ) + ) + page = portfolio.intra_exchange_transfers(limit=10) + assert len(page.items) == 1 + t = page.items[0] + assert t.transfer_id == "xfer-1" + assert t.source == "event_contract" + assert t.destination == "margined" + assert t.amount == Decimal("25.5000") + assert t.status == "complete" + assert page.cursor == "next" + + @respx.mock + def test_all_paginates(self, portfolio: PortfolioResource) -> None: + respx.get( + "https://test.kalshi.com/trade-api/v2/portfolio/intra_exchange_instance_transfers" + ).mock( + side_effect=[ + httpx.Response( + 200, json={"transfers": [{**_TRANSFER, "transfer_id": "a"}], "cursor": "c1"} + ), + httpx.Response( + 200, json={"transfers": [{**_TRANSFER, "transfer_id": "b"}], "cursor": ""} + ), + ] + ) + ids = [t.transfer_id for t in portfolio.intra_exchange_transfers_all()] + assert ids == ["a", "b"] + + @respx.mock + def test_get_by_id(self, portfolio: PortfolioResource) -> None: + respx.get( + "https://test.kalshi.com/trade-api/v2/portfolio/" + "intra_exchange_instance_transfers/xfer-1" + ).mock(return_value=httpx.Response(200, json={"transfer": _TRANSFER})) + t = portfolio.get_intra_exchange_transfer("xfer-1") + assert t.transfer_id == "xfer-1" + assert t.amount == Decimal("25.5000") + + def test_requires_auth(self, unauth_portfolio: PortfolioResource) -> None: + with pytest.raises(AuthRequiredError): + unauth_portfolio.intra_exchange_transfers() + with pytest.raises(AuthRequiredError): + unauth_portfolio.get_intra_exchange_transfer("xfer-1") + + +class TestAsyncPortfolioIntraExchangeTransfers: + @respx.mock + @pytest.mark.asyncio + async def test_returns_page(self, async_portfolio: AsyncPortfolioResource) -> None: + respx.get( + "https://test.kalshi.com/trade-api/v2/portfolio/intra_exchange_instance_transfers" + ).mock( + return_value=httpx.Response( + 200, json={"transfers": [_TRANSFER], "cursor": ""} + ) + ) + page = await async_portfolio.intra_exchange_transfers() + assert len(page.items) == 1 + assert page.items[0].transfer_id == "xfer-1" + + @respx.mock + @pytest.mark.asyncio + async def test_get_by_id(self, async_portfolio: AsyncPortfolioResource) -> None: + respx.get( + "https://test.kalshi.com/trade-api/v2/portfolio/" + "intra_exchange_instance_transfers/xfer-1" + ).mock(return_value=httpx.Response(200, json={"transfer": _TRANSFER})) + t = await async_portfolio.get_intra_exchange_transfer("xfer-1") + assert t.status == "complete" + + @pytest.mark.asyncio + async def test_requires_auth( + self, unauth_async_portfolio: AsyncPortfolioResource + ) -> None: + with pytest.raises(AuthRequiredError): + await unauth_async_portfolio.intra_exchange_transfers() diff --git a/tests/ws/test_dispatch.py b/tests/ws/test_dispatch.py index 27e8e442..ef42794a 100644 --- a/tests/ws/test_dispatch.py +++ b/tests/ws/test_dispatch.py @@ -13,7 +13,6 @@ from kalshi.ws.dispatch import CONTROL_TYPES, MESSAGE_MODELS, MessageDispatcher from kalshi.ws.models.event_fee import EventFeeUpdateMessage from kalshi.ws.models.market_positions import MarketPositionsMessage -from kalshi.ws.models.multivariate import MultivariateMessage from kalshi.ws.models.user_orders import UserOrdersMessage from kalshi.ws.sequence import SequenceTracker from tests._model_fixtures import ( @@ -390,7 +389,6 @@ async def test_all_channel_types_have_models(self) -> None: "order_group_updates", "market_lifecycle_v2", "event_fee_update", - "multivariate_lookup", "multivariate_market_lifecycle", "communications", "cfbenchmarks_value", @@ -687,41 +685,12 @@ def test_message_models_market_position_key_is_singular() -> None: assert "market_positions" not in MESSAGE_MODELS -@pytest.mark.asyncio -async def test_dispatch_routes_multivariate_lookup() -> None: - """Spec emits `type: multivariate_lookup` on the multivariate channel. - - Regression guard. No direct live capture on demo (no active - collections emitting); aligns to spec matching the user_orders - pattern. - """ - mgr = FakeSubManager() - sub = mgr.add(17, "multivariate") - dispatcher = MessageDispatcher(sub_mgr=mgr) # type: ignore[arg-type] - raw = ( - '{"type":"multivariate_lookup","sid":17,"msg":' - + json.dumps( - { - "collection_ticker": "C1", - "selected_markets": [], - "market_ticker": "M1", - "event_ticker": "E1", - } - ) - + "}" - ) - await dispatcher.dispatch(json.loads(raw)) - msg = await asyncio.wait_for(sub.queue.get(), timeout=1.0) - assert isinstance(msg, MultivariateMessage) - - -def test_message_models_multivariate_lookup_key() -> None: - """MESSAGE_MODELS must key on the spec-correct singular type string.""" - assert "multivariate_lookup" in MESSAGE_MODELS - # multivariate_market_lifecycle is sibling (different message type) -- must stay +def test_message_models_multivariate_lifecycle_key() -> None: + """MESSAGE_MODELS keeps multivariate_market_lifecycle; lookup channel removed.""" assert "multivariate_market_lifecycle" in MESSAGE_MODELS - assert "multivariate" not in MESSAGE_MODELS # the original short form, now replaced + assert "multivariate_lookup" not in MESSAGE_MODELS + assert "multivariate" not in MESSAGE_MODELS @pytest.mark.asyncio diff --git a/tests/ws/test_models.py b/tests/ws/test_models.py index 18b12d52..4f943b02 100644 --- a/tests/ws/test_models.py +++ b/tests/ws/test_models.py @@ -32,7 +32,6 @@ from kalshi.ws.models.market_positions import MarketPositionsMessage, MarketPositionsPayload from kalshi.ws.models.multivariate import ( MultivariateLifecycleMessage, - MultivariateMessage, ) from kalshi.ws.models.order_group import OrderGroupMessage, OrderGroupPayload from kalshi.ws.models.orderbook_delta import ( @@ -687,61 +686,6 @@ def test_market_lifecycle_settled(self) -> None: class TestMultivariateModel: - def test_parse_multivariate(self) -> None: - raw = { - "type": "multivariate", - "sid": 8, - "msg": { - "collection_ticker": "COL-1", - "event_ticker": "EVT-1", - "market_ticker": "MKT-A", - "selected_markets": [ - { - "event_ticker": "EVT-1", - "market_ticker": "MKT-A", - "side": "yes", - }, - { - "event_ticker": "EVT-1", - "market_ticker": "MKT-B", - "side": "no", - }, - ], - }, - } - msg = MultivariateMessage.model_validate(raw) - assert msg.type == "multivariate" - assert msg.msg.collection_ticker == "COL-1" - assert len(msg.msg.selected_markets) == 2 - assert msg.msg.selected_markets[0].market_ticker == "MKT-A" - assert msg.msg.selected_markets[1].side == "no" - - def test_multivariate_no_seq(self) -> None: - """`seq` is optional on this channel — full payload must still parse without it.""" - raw = { - "type": "multivariate", - "sid": 8, - "msg": { - "collection_ticker": "COL-1", - "selected_markets": [], - "market_ticker": "MKT-A", - "event_ticker": "EVT-1", - }, - } - msg = MultivariateMessage.model_validate(raw) - assert msg.seq is None - - def test_multivariate_missing_required_raises(self) -> None: - """Post-#172: MultivariatePayload requires market_ticker / event_ticker. - Omitting them must raise instead of leaving them None.""" - raw = { - "type": "multivariate", - "sid": 8, - "msg": {"collection_ticker": "COL-1", "selected_markets": []}, - } - with pytest.raises(ValidationError): - MultivariateMessage.model_validate(raw) - def test_multivariate_lifecycle(self) -> None: raw = { "type": "multivariate_market_lifecycle",