Skip to content

fix(@typedtrader/exchange): send client order ids and stop retrying order placement POSTs#1178

Open
bennycode wants to merge 1 commit into
mainfrom
fix/order-idempotency-retry-gating
Open

fix(@typedtrader/exchange): send client order ids and stop retrying order placement POSTs#1178
bennycode wants to merge 1 commit into
mainfrom
fix/order-idempotency-retry-gating

Conversation

@bennycode

Copy link
Copy Markdown
Owner

Summary

Eliminates a duplicate-order risk from blind retries of order placement.

The scenario: both broker API clients wire axios-retry with a method-blind retry condition (Alpaca: 20 retries; Trading212: retries: Infinity). When an order-placement POST times out or returns a 5xx, the order may still have been accepted server-side — the failure only means the client never saw the response. A blind retry then re-submits the same order and can silently open a duplicate real-money position. Order placement is not idempotent, so it must never be auto-retried.

Changes:

  • Alpaca (AlpacaBroker.placeOrder): every placement now generates a client_order_id via crypto.randomUUID() and sends it in the POST /v2/orders body. Alpaca deduplicates on this id, so a manual re-submission with the same id is safe, and operators can reconcile whether a failed request actually landed. (docs)
  • Alpaca (shouldRetryAlpacaRequest): POST /v2/orders is never retried. GETs/DELETEs (including reads of /v2/orders and order cancellations) keep the existing retry behavior, and the PDT (40310100) / no-short (40310000) business-error filtering is unchanged.
  • Trading212 (shouldRetryTrading212Request, extracted + exported): POSTs to /api/v0/equity/orders/market and /api/v0/equity/orders/limit are never retried — Trading212's public API has no idempotency-key support, so a duplicate would be unrecoverable. All other requests keep today's behavior, including the per-endpoint rate-limit delays in getRetryDelay.

A failed order POST now surfaces immediately as an error to the caller (TradingSession already converts it to an error event; that path is untouched). Public package types are unchanged — neither API class is exported from src/index.ts, so postOrder could gain a required client_order_id param to enforce the invariant at compile time.

Test plan

  • AlpacaAPI.test.ts: order POST not retried on 503/429/network errors (incl. uppercase method); GET/DELETE on the same endpoint still retried; 429/503/EAI_AGAIN on other requests still retried; PDT/no-short rejections still not retried
  • Trading212API.test.ts (new): market/limit order POSTs not retried on 503/network errors; GET/DELETE order endpoints and other requests keep retry behavior; HTTP 400 not retried
  • AlpacaBroker.test.ts: placeOrder passes a client_order_id matching the UUIDv4 shape on the mocked API call; unique across two placements
  • packages/exchange: npm test — 9 files, 103 tests passed
  • packages/exchange: npm run lint — clean
  • npx lerna run dist --scope trading-strategies --include-dependencies — 3 projects built

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces the risk of duplicate real-money positions by preventing automatic retries on non-idempotent order-placement POST requests, and (for Alpaca) adding a client_order_id to enable server-side deduplication and operator reconciliation.

Changes:

  • Trading212: extracted shouldRetryTrading212Request and disabled retries for market/limit order placement POSTs while keeping existing retry behavior for all other requests.
  • Alpaca: disabled retries for POST /v2/orders and required client_order_id in postOrder params.
  • AlpacaBroker: generates and sends a fresh UUIDv4 client_order_id for every order placement; added/updated tests to cover both retry behavior and UUID propagation.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/exchange/src/broker/trading212/api/Trading212API.ts Stops retries for order-placement POSTs; keeps existing retry logic for other endpoints.
packages/exchange/src/broker/trading212/api/Trading212API.test.ts Adds coverage ensuring order-placement POSTs are not retried while other requests remain retryable.
packages/exchange/src/broker/alpaca/api/AlpacaAPI.ts Disables retries for POST /v2/orders and makes client_order_id required for postOrder.
packages/exchange/src/broker/alpaca/api/AlpacaAPI.test.ts Adds tests validating retry suppression for order placement and retry retention for GET/DELETE.
packages/exchange/src/broker/alpaca/AlpacaBroker.ts Generates and sends client_order_id: randomUUID() on every order placement.
packages/exchange/src/broker/alpaca/AlpacaBroker.test.ts Validates UUID shape is passed to the API and is unique across placements.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants