diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index e37caf60..00000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Claude Code Review - -on: - pull_request: - types: [opened, synchronize] - # Optional: Only run on specific file changes - # paths: - # - "src/**/*.ts" - # - "src/**/*.tsx" - # - "src/**/*.js" - # - "src/**/*.jsx" - -jobs: - claude-review: - # Skip Dependabot PRs — GitHub does not expose repo secrets (including - # CLAUDE_CODE_OAUTH_TOKEN) to dependabot-triggered runs, so the action - # exits with an empty-credential failure on every dep bump. The diff is - # also low-signal for review (version bumps, not behavior changes). - if: github.actor != 'dependabot[bot]' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - - steps: - # Third-party actions pinned to SHA (not tag) because this workflow holds - # id-token:write and consumes CLAUDE_CODE_OAUTH_TOKEN. A compromised - # mutable tag could exfiltrate the OIDC/OAuth token. Matches the - # precedent in spec-sync.yml. - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-review - uses: anthropics/claude-code-action@af0559ee4f514d1ef21826982bed13f7edc3c35e # v1.0.123 (v1) - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - prompt: | - REPO: ${{ github.repository }} - PR NUMBER: ${{ github.event.pull_request.number }} - - Please review this pull request and provide feedback on: - - Code quality and best practices - - Potential bugs or issues - - Performance considerations - - Security concerns - - Test coverage - - Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback. - - Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR. - - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://docs.claude.com/en/docs/claude-code/sdk#command-line for available options - claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' - diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index d6af9844..00000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - issues: write - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - # Third-party actions pinned to SHA (not tag) because this workflow holds - # contents:write + pull-requests:write + issues:write + id-token:write and - # consumes CLAUDE_CODE_OAUTH_TOKEN. A compromised mutable tag would - # weaponize the workflow. Matches the precedent in spec-sync.yml. - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@af0559ee4f514d1ef21826982bed13f7edc3c35e # v1.0.123 (v1) - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' - diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a16703c..afe78ff9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,45 @@ All notable changes to kalshi-sdk will be documented in this file. +## 7.2.0 — 2026-07-22 + +Reconciles upstream OpenAPI/perps OpenAPI drift under version string **3.25.0** +(Closes #481, #482). Soft-deprecation + defensive model relaxations only — no +breaking public-API removals. + +### Deprecated + +- **`PerpsClient.orders.list_fcm()` / `list_all_fcm()`** (sync + async) now emit + a `DeprecationWarning`. Kalshi removed `GET /margin/fcm/orders` from the + perps OpenAPI, so the live endpoint may 404. Methods are **retained** + (soft-deprecated) pending confirmation the removal is permanent — same + pattern as `exchange.announcements()` after 3.24.0. + +### Changed + +- **`SubaccountTransfer` is cash-only on the wire.** Upstream dropped + `transfer_type` (was required) and the position-only fields + (`market_ticker`, `side`, `count`, `price`) from + `GET /portfolio/subaccounts/transfers`. Position moves remain on + `POST /portfolio/subaccounts/positions/transfer` + (`transfer_position()`). Dropped fields are **defensively optional** on the + SDK model so lagging payloads still parse; new responses omit them. + `transfer_type` is now `Literal["cash", "position"] | None` (default + `None`). + +### Spec notes + +- Core OpenAPI `info.version` still `3.25.0` (content-only change; paths 91→91, + 102 operations / 101 mapped). +- Perps OpenAPI: removed `GET /margin/fcm/orders` and the `fcm` tag (paths + 32→31, operations 35→34). Perps AsyncAPI / SCM specs unchanged. + +### Infra + +- Removed Claude Code Action workflows (`claude-code-review.yml`, + `claude.yml`) — the automated review check was failing CI without adding + required signal. + ## 7.1.0 — 2026-07-17 Syncs the upstream OpenAPI/AsyncAPI specs **3.24.0 → 3.25.0** (Closes #475). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b898ac74..0b8fe9c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -79,13 +79,8 @@ If your change adds a new endpoint, register it in ## Review cycle -Most PRs receive an automated code review from Claude (the -`claude-review` check). This is **advisory, not required** — it fails -by design on Dependabot PRs and on PRs that modify the review workflow -itself. When it does run, please address the substantive feedback or -reply with a brief justification for skipping it. Maintainers -squash-merge after the required CI checks are green and review is -addressed. +Maintainers squash-merge after the required CI checks are green and any +human review feedback is addressed. ## Reporting bugs / requesting features diff --git a/README.md b/README.md index 352e2652..d41f505a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A professional, spec-first Python SDK for the [Kalshi](https://kalshi.com) predi [![Type checked: mypy strict](https://img.shields.io/badge/mypy-strict-blue.svg)](https://mypy.readthedocs.io/) - **Full coverage** of the Kalshi REST API (101 operations across 19 resources, OpenAPI v3.25.0) and WebSocket API (12 typed `subscribe_*` channels + 2 escape-hatch). -- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (35 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (11 operations). See [Perps (margin) trading](#perps-margin-trading). +- **Perps (margin) API**: standalone `PerpsClient` / `AsyncPerpsClient` + `PerpsWebSocket` for the perpetual-futures exchange (34 REST operations, 6 WS channels), plus a `KlearClient` for the Self-Clearing-Member "Klear" settlement API (11 operations). See [Perps (margin) trading](#perps-margin-trading). - **FIX protocol**: an async-first FIX engine (FIXT.1.1 / FIX50SP2) for both products — order-entry, drop-copy, market-data, post-trade (prediction), and RFQ (prediction) sessions (plus order-group management over the order-entry session) with typed message models, sequence recovery, and order-book / settlement reassembly. `from kalshi import FixClient` / `MarginFixClient`. See [FIX protocol](#fix-protocol-low-latency-trading). - **V2 event-market orders**: `create_v2` / `amend_v2` / `decrease_v2` / `cancel_v2` plus batched variants on `/portfolio/events/orders/*` — the only order-write surface. - **Funding & cost introspection**: `portfolio.deposits()`, `portfolio.withdrawals()`, `account.endpoint_costs()`. diff --git a/ROADMAP.md b/ROADMAP.md index d4ef1b85..26f17d46 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,6 +2,11 @@ ## Shipped +- **v7.2.0 (2026-07-22)** — Spec-drift reconcile under OpenAPI 3.25.0 + (#481 / #482 / #483). Soft-deprecate perps `orders.list_fcm` / + `list_all_fcm` (upstream removed `GET /margin/fcm/orders`). + `SubaccountTransfer` cash-only on the wire (`transfer_type` + position + fields defensive-optional). Removed Claude Code Action CI workflows. - **v7.1.0 (2026-07-17)** — OpenAPI sync v3.24.0 → v3.25.0 (#475 / #476). Additive: `MarginMarket.schedule` + nested `MarginMarketSchedule` (null for 24/7 markets). Core `POST /portfolio/intra_exchange_instance_transfer` diff --git a/SECURITY.md b/SECURITY.md index 8022594f..dfc94166 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -68,7 +68,7 @@ Out of scope: - Spec-sync workflow runs with `contents: read` only; it cannot push, open PRs, or execute upstream-derived Python. - Third-party Actions SHA-pinned in workflows that hold elevated - permissions (release, spec-sync, claude review). + permissions (release, spec-sync). - RSA-PSS request signing with timestamp; signatures rejected if clock-skew exceeds the API's tolerance. diff --git a/docs/index.md b/docs/index.md index da4348d1..e65a6827 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,7 +15,7 @@ markets API. backpressure strategies, and an in-memory orderbook builder. Async-only — access via `AsyncKalshiClient.ws`. - **Perps (margin) API** — standalone `PerpsClient` / `AsyncPerpsClient` + - `PerpsWebSocket` for the perpetual-futures exchange (35 REST operations, 6 WS + `PerpsWebSocket` for the perpetual-futures exchange (34 REST operations, 6 WS channels), and a `KlearClient` for the Self-Clearing-Member settlement API (11 operations, Bearer token auth). See [Perps](perps.md). - **FIX protocol** — a hand-rolled, async-first FIX engine (FIXT.1.1 / FIX50SP2) diff --git a/docs/migration.md b/docs/migration.md index d2cc37bb..29f5c803 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1,5 +1,57 @@ # Migration +## v7.1 → v7.2.0 + +Reconciles in-place OpenAPI/perps OpenAPI edits under version string **3.25.0** +(no `info.version` bump upstream). **No breaking public-API removals** — +one soft-deprecation and a defensive model relaxation. + +### Deprecated: `PerpsClient.orders.list_fcm()` / `list_all_fcm()` + +Kalshi removed `GET /margin/fcm/orders` from the perps OpenAPI, so the live +endpoint may 404. The methods (sync + async) are **retained** and emit a +`DeprecationWarning` on each call — same soft-deprecation pattern as +`exchange.announcements()` after 3.24.0. A future major release removes them +once the removal is confirmed permanent. + +```python +# Still callable, but warns and may 404: +# perps.orders.list_fcm(subtrader_id="...") +# list(perps.orders.list_all_fcm(subtrader_id="...")) +``` + +Prediction-API FCM routes (`client.fcm.orders` / `client.fcm.positions` on +`/fcm/*`) are **unchanged**. + +### Changed: `SubaccountTransfer` is cash-only on the wire + +`GET /portfolio/subaccounts/transfers` no longer returns position-transfer +rows. Upstream dropped `transfer_type` and the position-only fields +(`market_ticker`, `side`, `count`, `price`) from the schema. + +- New responses omit those fields; `t.transfer_type` is `None` unless a lagging + server still sends it. +- Position moves continue via `subaccounts.transfer_position()` (and its + request/response models) — not via the transfers list. +- The SDK keeps the dropped fields as **optional** so older payloads still + parse (defensive optional-ization). + +```python +page = client.subaccounts.list_transfers() +for t in page: + # cash row only + print(t.transfer_id, t.amount_cents, t.from_subaccount, t.to_subaccount) + # t.transfer_type may be None (no longer required on the wire) +``` + +### Infra + +Claude Code Action workflows (`claude-review` / `@claude`) were removed from +CI; they are not part of the public SDK surface. + +See the [changelog](https://github.com/TexasCoding/kalshi-python-sdk/blob/main/CHANGELOG.md) +for the full list. + ## v7.0 → v7.1.0 Syncs the SDK to OpenAPI/AsyncAPI spec **3.25.0**. **No breaking changes.** diff --git a/docs/perps.md b/docs/perps.md index f65b6465..923afdf0 100644 --- a/docs/perps.md +++ b/docs/perps.md @@ -60,7 +60,7 @@ async with AsyncPerpsClient.from_env(demo=True) as perps: |---|---| | `exchange` | `status()`, `enabled()` (per-member access gate), `risk_parameters()` | | `markets` | `list()`, `get()`, `orderbook()`, `candlesticks()` | -| `orders` | `create()`, `get()`, `list()` / `list_all()`, `cancel()`, `decrease()`, `amend()`, `list_fcm()` / `list_all_fcm()` | +| `orders` | `create()`, `get()`, `list()` / `list_all()`, `cancel()`, `decrease()`, `amend()` | | `order_groups` | `list()`, `get()`, `create()`, `delete()`, `reset()`, `trigger()`, `update_limit()` | | `portfolio` | `positions()`, `fills()` / `fills_all()`, `trades()` / `trades_all()` | | `margin` | `balance()`, `risk()`, `notional_risk_limit()`, `fee_tiers()`, `api_limits()` | @@ -70,6 +70,13 @@ async with AsyncPerpsClient.from_env(demo=True) as perps: The margin order side is `bid` / `ask` (not the prediction API's `yes` / `no`). Orders create/cancel/decrease/amend are POSTs/DELETEs and are **never retried**. +!!! warning "Deprecated in v7.2.0 — `list_fcm` / `list_all_fcm`" + Kalshi removed `GET /margin/fcm/orders` from the perps OpenAPI. The SDK + methods still exist (sync + async) but emit a `DeprecationWarning` and may + 404 against the live API. They will be removed in a future major release + once the removal is confirmed permanent. Prediction-API FCM + (`client.fcm.*` on `/fcm/*`) is unchanged. + ## Value types & timestamps - Prices are `DollarDecimal` — `FixedPointDollars` strings with up to 6 decimal diff --git a/docs/resources/subaccounts.md b/docs/resources/subaccounts.md index 445037ea..979c2c68 100644 --- a/docs/resources/subaccounts.md +++ b/docs/resources/subaccounts.md @@ -95,7 +95,8 @@ for t in client.subaccounts.list_all_transfers(): ``` Standard `Page[SubaccountTransfer]` pagination. `t.created_ts` is Unix -seconds. +seconds. Rows are **cash transfers only** — position moves use +`transfer_position()` and are not listed here. ## Netting diff --git a/kalshi/__init__.py b/kalshi/__init__.py index 10a9418e..5032456e 100644 --- a/kalshi/__init__.py +++ b/kalshi/__init__.py @@ -379,4 +379,4 @@ "Withdrawal", ] -__version__ = "7.1.0" +__version__ = "7.2.0" diff --git a/kalshi/models/subaccounts.py b/kalshi/models/subaccounts.py index 46cd22ec..f42eeffe 100644 --- a/kalshi/models/subaccounts.py +++ b/kalshi/models/subaccounts.py @@ -125,18 +125,22 @@ class GetSubaccountBalancesResponse(BaseModel): class SubaccountTransfer(BaseModel): - """A past transfer between subaccounts. + """A past **cash** transfer between subaccounts. ``created_ts`` is a Unix seconds integer per spec (``format: int64``), matching ``SubaccountBalance.updated_ts``. This is intentionally different from RFQ/Quote timestamps, which are ISO datetime strings. - Spec v3.23.0 split transfers into two kinds via ``transfer_type``: ``cash`` - (money moved; ``amount_cents`` set) and ``position`` (contracts moved). The - ``market_ticker`` / ``side`` / ``count`` / ``price`` fields are populated only - for ``position`` transfers, so they are optional here. Spec v3.24.0 renamed the - per-contract ``price_cents`` (integer cents) → ``price`` - (``FixedPointDollars``), surfacing as a ``Decimal`` in dollars. + Spec sync (in-place edit under OpenAPI 3.25.0, 2026-07-20/21) narrowed + ``GET /portfolio/subaccounts/transfers`` to cash rows only. Upstream + dropped ``transfer_type`` and the position-only fields + (``market_ticker`` / ``side`` / ``count`` / ``price``) from this schema. + Position moves use :class:`ApplySubaccountPositionTransferRequest` / + :class:`ApplySubaccountPositionTransferResponse` on + ``POST /portfolio/subaccounts/positions/transfer`` — they are not listed + here. Fields removed from the wire schema are retained as optional + (defensive optional-ization) so payloads from lagging servers still parse; + new responses omit them. """ transfer_id: str @@ -144,10 +148,9 @@ class SubaccountTransfer(BaseModel): to_subaccount: int amount_cents: int created_ts: int - # Spec v3.23.0 required additions. exchange_index: int - transfer_type: Literal["cash", "position"] - # Position-transfer-only fields (absent on cash transfers). + # Soft-kept after upstream removal from SubaccountTransfer (cash-only list). + transfer_type: Literal["cash", "position"] | None = None market_ticker: str | None = None side: Literal["yes", "no"] | None = None count: int | None = None diff --git a/kalshi/perps/models/orders.py b/kalshi/perps/models/orders.py index ff15f06a..33c36886 100644 --- a/kalshi/perps/models/orders.py +++ b/kalshi/perps/models/orders.py @@ -198,7 +198,7 @@ class GetMarginOrderResponse(BaseModel): class GetMarginOrdersResponse(BaseModel): - """Paginated envelope for ``GET /margin/orders`` and ``GET /margin/fcm/orders``. + """Paginated envelope for ``GET /margin/orders`` (and soft-deprecated FCM list). Spec ``GetMarginOrdersResponse`` (line 1759). ``cursor`` drives ``list_all()`` / ``list_all_fcm()``. diff --git a/kalshi/perps/resources/orders.py b/kalshi/perps/resources/orders.py index 4db94134..da9e39e8 100644 --- a/kalshi/perps/resources/orders.py +++ b/kalshi/perps/resources/orders.py @@ -7,16 +7,19 @@ ``_require_auth()`` first — an unauthenticated caller gets ``AuthRequiredError`` before any HTTP request. ``create``, ``cancel``, ``decrease``, and ``amend`` are POST/DELETE, which the transport never retries (duplicate-order / cancel- -idempotency risk); only the GET ``get``/``list``/``list_fcm`` retry. +idempotency risk); only the GET ``get``/``list`` (and soft-deprecated +``list_fcm``) retry. ``subaccount`` routing follows the spec split: ``create`` carries it in the request *body* (``CreateMarginOrderRequest.subaccount``), while -cancel/decrease/amend route it as a *query* param. ``list_fcm`` has no -``subaccount`` at all — it filters by the required ``subtrader_id``. +cancel/decrease/amend route it as a *query* param. Soft-deprecated +``list_fcm`` has no ``subaccount`` at all — it filters by the required +``subtrader_id``. """ from __future__ import annotations +import warnings from collections.abc import AsyncIterator, Iterator from typing import Any, overload @@ -47,6 +50,19 @@ ) from kalshi.types import to_decimal +# Soft-deprecation (spec sync 2026-07-21): Kalshi removed GET /margin/fcm/orders +# from perps OpenAPI. Methods are RETAINED (not deleted) pending confirmation the +# removal is permanent — upstream has transiently dropped endpoints as publishing +# glitches before (see exchange.announcements / #452). Emit DeprecationWarning; +# live API may 404 until/unless the path returns. Future major release removes +# them once confirmed. +_LIST_FCM_DEPRECATED = ( + "orders.list_fcm() / list_all_fcm() are deprecated: Kalshi removed " + "GET /margin/fcm/orders from the perps OpenAPI spec, so the live endpoint " + "may return 404. The methods are retained pending confirmation the removal " + "is permanent and will be removed in a future major release." +) + # --------------------------------------------------------------------------- # Shared request-body builders (pure: kwargs in, dict[str, Any] out). Both the # sync and async resource methods call the same builder so model construction @@ -497,8 +513,13 @@ def list_fcm( ) -> GetMarginOrdersResponse: """List margin orders for an FCM subtrader, single page (GET /margin/fcm/orders). + .. deprecated:: + Soft-deprecated: Kalshi removed ``GET /margin/fcm/orders`` from the + perps OpenAPI. Retained pending confirmation; may 404 live. + ``subtrader_id`` is required. No ``subaccount`` param. """ + warnings.warn(_LIST_FCM_DEPRECATED, DeprecationWarning, stacklevel=2) self._require_auth() params = _list_fcm_params( subtrader_id=subtrader_id, @@ -524,7 +545,13 @@ def list_all_fcm( max_pages: int | None = None, extra_headers: dict[str, str] | None = None, ) -> Iterator[MarginOrder]: - """Auto-paginate FCM subtrader orders (GET /margin/fcm/orders).""" + """Auto-paginate FCM subtrader orders (GET /margin/fcm/orders). + + .. deprecated:: + Soft-deprecated: Kalshi removed ``GET /margin/fcm/orders`` from the + perps OpenAPI. Retained pending confirmation; may 404 live. + """ + warnings.warn(_LIST_FCM_DEPRECATED, DeprecationWarning, stacklevel=2) self._require_auth() _validate_max_pages(max_pages) params = _list_fcm_params( @@ -811,6 +838,7 @@ async def list_fcm( extra_headers: dict[str, str] | None = None, ) -> GetMarginOrdersResponse: """List FCM subtrader orders, single page. See :meth:`MarginOrdersResource.list_fcm`.""" + warnings.warn(_LIST_FCM_DEPRECATED, DeprecationWarning, stacklevel=2) self._require_auth() params = _list_fcm_params( subtrader_id=subtrader_id, @@ -836,7 +864,13 @@ def list_all_fcm( max_pages: int | None = None, extra_headers: dict[str, str] | None = None, ) -> AsyncIterator[MarginOrder]: - """Auto-paginate FCM subtrader orders. Returns an async iterator — use ``async for``.""" + """Auto-paginate FCM subtrader orders. Returns an async iterator — use ``async for``. + + .. deprecated:: + Soft-deprecated: Kalshi removed ``GET /margin/fcm/orders`` from the + perps OpenAPI. Retained pending confirmation; may 404 live. + """ + warnings.warn(_LIST_FCM_DEPRECATED, DeprecationWarning, stacklevel=2) self._require_auth() _validate_max_pages(max_pages) params = _list_fcm_params( diff --git a/pyproject.toml b/pyproject.toml index ddca4927..59bf9281 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kalshi-sdk" -version = "7.1.0" +version = "7.2.0" description = "A professional Python SDK for the Kalshi prediction markets and Perps (margin) APIs" readme = "README.md" license = { text = "MIT" } diff --git a/specs/openapi.yaml b/specs/openapi.yaml index 47a6cf05..1e644395 100644 --- a/specs/openapi.yaml +++ b/specs/openapi.yaml @@ -5214,7 +5214,6 @@ components: - amount_cents - created_ts - exchange_index - - transfer_type properties: transfer_id: type: string @@ -5228,7 +5227,7 @@ components: amount_cents: type: integer format: int64 - description: Cash transfer amount in cents. Zero for position transfers. + description: Cash transfer amount in cents. created_ts: type: integer format: int64 @@ -5236,24 +5235,6 @@ components: exchange_index: type: integer description: Exchange index the transfer was applied on. - transfer_type: - type: string - enum: [cash, position] - x-enum-varnames: [TransferTypeCash, TransferTypePosition] - description: Whether this row is a cash or position transfer. - market_ticker: - type: string - description: Market ticker (position transfers only). - side: - type: string - enum: ["yes", "no"] - description: Position side (position transfers only). - count: - type: integer - description: Number of contracts moved (position transfers only). - price: - $ref: '#/components/schemas/FixedPointDollars' - description: Per-contract transfer price in fixed-point dollars, always the YES-side price (position transfers only). UpdateSubaccountNettingRequest: type: object diff --git a/specs/perps_openapi.yaml b/specs/perps_openapi.yaml index 584012c2..4ab23d15 100644 --- a/specs/perps_openapi.yaml +++ b/specs/perps_openapi.yaml @@ -146,49 +146,6 @@ paths: $ref: '#/components/responses/RateLimitError' '500': $ref: '#/components/responses/InternalServerError' - /margin/fcm/orders: - get: - operationId: GetMarginFCMOrders - summary: Get FCM Orders - description: > - Endpoint for FCM members to get margin orders filtered by subtrader ID. - - This endpoint requires FCM member access level and allows filtering - margin orders by subtrader ID. - tags: - - fcm - security: - - kalshiAccessKey: [] - kalshiAccessSignature: [] - kalshiAccessTimestamp: [] - parameters: - - name: subtrader_id - in: query - required: true - description: >- - Restricts the response to margin orders for a specific subtrader - (FCM members only) - schema: - type: string - - $ref: '#/components/parameters/TickerQuery' - - $ref: '#/components/parameters/MinTsQuery' - - $ref: '#/components/parameters/MaxTsQuery' - - $ref: '#/components/parameters/StatusQuery' - - $ref: '#/components/parameters/MarginOrdersLimitQuery' - - $ref: '#/components/parameters/CursorQuery' - responses: - '200': - description: Margin orders retrieved successfully - content: - application/json: - schema: - $ref: '#/components/schemas/GetMarginOrdersResponse' - '400': - $ref: '#/components/responses/BadRequestError' - '401': - $ref: '#/components/responses/UnauthorizedError' - '500': - $ref: '#/components/responses/InternalServerError' /margin/orders/{order_id}: get: operationId: GetMarginOrder @@ -2968,8 +2925,6 @@ tags: description: Order group management endpoints - name: portfolio description: Portfolio and balance information endpoints - - name: fcm - description: FCM member specific endpoints - name: risk description: Margin risk metrics, parameters, and limits - name: funding diff --git a/tests/_contract_support.py b/tests/_contract_support.py index c4ce9f3d..12d8e652 100644 --- a/tests/_contract_support.py +++ b/tests/_contract_support.py @@ -1380,16 +1380,10 @@ class Exclusion: path_template="/margin/orders/{order_id}/amend", request_body_schema="#/components/schemas/AmendMarginOrderRequest", ), - MethodEndpointEntry( - sdk_method="kalshi.perps.resources.orders.MarginOrdersResource.list_fcm", - http_method="GET", - path_template="/margin/fcm/orders", - ), - MethodEndpointEntry( - sdk_method="kalshi.perps.resources.orders.MarginOrdersResource.list_all_fcm", - http_method="GET", - path_template="/margin/fcm/orders", - ), + # list_fcm / list_all_fcm intentionally unmapped: upstream removed + # GET /margin/fcm/orders from perps OpenAPI (spec-drift #482). Methods are + # soft-deprecated (retained + DeprecationWarning) pending confirmation — + # same pattern as exchange.announcements after 3.24.0. # ── perps order_groups (#392) ────────────────────────────────────── MethodEndpointEntry( sdk_method="kalshi.perps.resources.order_groups.OrderGroupsResource.list", @@ -1603,18 +1597,10 @@ class Exclusion: reason="paginator-handled; not a caller-facing kwarg on list_all", kind="paginator_handled", ), - ("kalshi.perps.resources.orders.MarginOrdersResource.list_all_fcm", "cursor"): Exclusion( - reason="paginator-handled; not a caller-facing kwarg on list_all_fcm", - kind="paginator_handled", - ), ("kalshi.perps.resources.orders.MarginOrdersResource.list_all", "max_pages"): Exclusion( reason="client-side paginator safety cap (#98); not a wire param", kind="client_only", ), - ("kalshi.perps.resources.orders.MarginOrdersResource.list_all_fcm", "max_pages"): Exclusion( - reason="client-side paginator safety cap (#98); not a wire param", - kind="client_only", - ), ("kalshi.perps.models.orders.GetMarginOrdersResponse", "cursor"): Exclusion( reason=( "spec marks cursor required, but Kalshi omits the key on the final " diff --git a/tests/perps/test_orders.py b/tests/perps/test_orders.py index 6d4ab284..7e0eac30 100644 --- a/tests/perps/test_orders.py +++ b/tests/perps/test_orders.py @@ -546,7 +546,7 @@ async def test_async(self, async_perps_client: AsyncPerpsClient) -> None: await async_perps_client.close() -# ── list_fcm / list_all_fcm ────────────────────────────────────────────────── +# ── list_fcm / list_all_fcm (soft-deprecated; path removed from perps OpenAPI) ── class TestListFcm: @@ -555,7 +555,8 @@ def test_happy_subtrader_in_query(self, perps_client: PerpsClient) -> None: route = respx.get(f"{BASE}/margin/fcm/orders").mock( return_value=httpx.Response(200, json={"orders": [_order_dict()], "cursor": ""}) ) - resp = perps_client.orders.list_fcm(subtrader_id="sub-7", ticker="BTC-PERP") + with pytest.warns(DeprecationWarning, match=r"list_fcm"): + resp = perps_client.orders.list_fcm(subtrader_id="sub-7", ticker="BTC-PERP") assert isinstance(resp, GetMarginOrdersResponse) q = dict(route.calls[0].request.url.params) assert q["subtrader_id"] == "sub-7" @@ -577,7 +578,8 @@ def test_list_all_fcm_walks_pages(self, perps_client: PerpsClient) -> None: ), ] ) - orders = list(perps_client.orders.list_all_fcm(subtrader_id="sub-7")) + with pytest.warns(DeprecationWarning, match=r"list_all_fcm|list_fcm"): + orders = list(perps_client.orders.list_all_fcm(subtrader_id="sub-7")) assert [o.order_id for o in orders] == ["ord-1", "ord-2"] for call in route.calls: assert "subaccount" not in dict(call.request.url.params) @@ -588,7 +590,10 @@ def test_unauthenticated_raises_before_http(self) -> None: return_value=httpx.Response(200, json={"orders": [], "cursor": ""}) ) client = PerpsClient(config=PerpsConfig.demo()) - with pytest.raises(AuthRequiredError): + with ( + pytest.warns(DeprecationWarning, match=r"list_fcm"), + pytest.raises(AuthRequiredError), + ): client.orders.list_fcm(subtrader_id="sub-7") assert not route.called client.close() @@ -598,9 +603,10 @@ async def test_async_list_all_fcm(self, async_perps_client: AsyncPerpsClient) -> respx.get(f"{BASE}/margin/fcm/orders").mock( return_value=httpx.Response(200, json={"orders": [_order_dict()], "cursor": ""}) ) - seen = [ - o.order_id - async for o in async_perps_client.orders.list_all_fcm(subtrader_id="sub-7") - ] + with pytest.warns(DeprecationWarning, match=r"list_all_fcm|list_fcm"): + seen = [ + o.order_id + async for o in async_perps_client.orders.list_all_fcm(subtrader_id="sub-7") + ] assert seen == ["ord-1"] await async_perps_client.close() diff --git a/tests/test_subaccounts.py b/tests/test_subaccounts.py index b5fd33c9..d9f1a14d 100644 --- a/tests/test_subaccounts.py +++ b/tests/test_subaccounts.py @@ -92,6 +92,8 @@ def test_subaccount_balance_parses_dollar_decimal(self) -> None: assert bal.updated_ts == 1_700_000_000 def test_subaccount_transfer_parses(self) -> None: + # Spec sync (2026-07-20/21): GET transfers is cash-only; transfer_type + # and position fields are no longer on the wire schema. t = SubaccountTransfer.model_validate( { "transfer_id": "xfer-1", @@ -100,20 +102,18 @@ def test_subaccount_transfer_parses(self) -> None: "amount_cents": 500, "created_ts": 1_700_000_000, "exchange_index": 0, - "transfer_type": "cash", } ) assert t.transfer_id == "xfer-1" assert t.amount_cents == 500 assert t.exchange_index == 0 - assert t.transfer_type == "cash" - # Position-only fields are absent on a cash transfer. + assert t.transfer_type is None assert t.market_ticker is None assert t.side is None - def test_subaccount_position_transfer_parses(self) -> None: - # v3.24.0: position transfers carry market_ticker/side/count/price (the - # per-contract price is now FixedPointDollars, renamed from price_cents). + def test_subaccount_transfer_soft_keeps_legacy_position_fields(self) -> None: + # Lagging servers / cached payloads with the pre-removal shape still + # parse: transfer_type and position-only fields are optional. t = SubaccountTransfer.model_validate( { "transfer_id": "xfer-2", @@ -135,11 +135,9 @@ def test_subaccount_position_transfer_parses(self) -> None: assert t.count == 10 assert t.price == Decimal("0.55") - def test_subaccount_transfer_requires_v3_23_fields(self) -> None: - # exchange_index and transfer_type are spec-required (v3.23.0). A - # 3.23.0 server always sends them; omitting them is a hard error rather - # than silently defaulting — matches the drift suite's spec-required => - # SDK-required enforcement. + def test_subaccount_transfer_requires_exchange_index(self) -> None: + # exchange_index remains spec-required. transfer_type was removed from + # the wire schema and is optional on the SDK (defensive optional-ization). with pytest.raises(ValidationError): SubaccountTransfer.model_validate( { @@ -148,7 +146,7 @@ def test_subaccount_transfer_requires_v3_23_fields(self) -> None: "to_subaccount": 1, "amount_cents": 100, "created_ts": 1_700_000_200, - # exchange_index + transfer_type intentionally omitted + # exchange_index intentionally omitted } ) @@ -611,7 +609,6 @@ def test_returns_paginated_transfers( "amount_cents": 100, "created_ts": 1, "exchange_index": 0, - "transfer_type": "cash", }, ], "cursor": "next", @@ -641,7 +638,6 @@ def test_list_all_auto_paginates( "amount_cents": 100, "created_ts": 1, "exchange_index": 0, - "transfer_type": "cash", }, ], "cursor": "p2", @@ -658,7 +654,6 @@ def test_list_all_auto_paginates( "amount_cents": 50, "created_ts": 2, "exchange_index": 0, - "transfer_type": "cash", }, ], }, @@ -833,7 +828,6 @@ async def test_list_all_transfers( "amount_cents": 100, "created_ts": 1, "exchange_index": 0, - "transfer_type": "cash", }, ], "cursor": "p2",