> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xmonaco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Trading

> Place and manage orders

```typescript theme={null}
import { MonacoSDK } from "@0xmonaco/core";

// Initialize SDK with wallet client (see Initialization guide)

// Place limit order (spot)
await sdk.trading.placeLimitOrder(
  tradingPairId,  // UUID from sdk.market.getTradingPairBySymbol('BTC/USDC')
  side,           // "BUY" | "SELL"
  quantity,       // e.g., "0.001"
  price,          // e.g., "50000"
  options?        // { tradingMode?, useMasterBalance?, expirationDate?, timeInForce?,
                  //   postOnly?, leverage?, reduceOnly?, marginAccountId?,
                  //   clientOrderId?, idempotencyKey? }
);

// Place market order (spot)
await sdk.trading.placeMarketOrder(
  tradingPairId,  // UUID from sdk.market.getTradingPairBySymbol('BTC/USDC')
  side,           // "BUY" | "SELL"
  quantity,       // e.g., "0.001"
  options?        // { tradingMode?, slippageTolerance?, leverage?, reduceOnly?,
                  //   marginAccountId?, clientOrderId?, idempotencyKey? }
);

// Order management
await sdk.trading.cancelOrder(orderId);                                // full cancel
await sdk.trading.cancelOrder(orderId, { remainingQuantityTarget });   // partial: reduce in place, keep queue position
await sdk.trading.batchCancel([id1, id2, id3]);
await sdk.trading.batchCancelAll();
await sdk.trading.batchCancelAll(tradingPairId);
await sdk.trading.replaceOrder(orderId, updates);

// TWAP parent orders (sliced execution across a time window)
await sdk.trading.createTwapOrder(tradingPairId, side, quantity, options);
await sdk.trading.getTwapOrder(twapOrderId);
await sdk.trading.listTwapOrders(params?);
await sdk.trading.listTwapOrderSlices(twapOrderId, params?);
await sdk.trading.cancelTwapOrder(twapOrderId);
await sdk.trading.getTwapOrderTca(twapOrderId);

// Conditional orders (perp TP/SL)
await sdk.trading.getConditionalOrder(conditionalOrderId);
await sdk.trading.cancelConditionalOrder(conditionalOrderId);
await sdk.trading.listConditionalOrders(params?);

// Order queries
await sdk.trading.getPaginatedOrders(params?);
await sdk.trading.getOrder(orderId);

// Fee simulation (before placing orders)
await sdk.fees.simulateFees({
  tradingPairId: tradingPairId,
  side: "BUY",
  price: "50000",
  quantity: "0.001"
});
```

## Place Orders

<ResponseField name="placeLimitOrder" type="(tradingPairId: string, side: OrderSide, quantity: string, price: string, options?) => Promise<CreateOrderResponse>">
  Place a limit order with a specific price. Defaults to GTC (Good Till Canceled) time-in-force.

  **Parameters:**

  * `tradingPairId`: string - Trading pair UUID (get via `sdk.market.getTradingPairBySymbol('BTC/USDC')`)
  * `side`: "BUY" | "SELL" - Order side
  * `quantity`: string - Order quantity
  * `price`: string - Limit price
  * `options?`: Optional settings
    * `tradingMode?`: `"SPOT" | "MARGIN"` (default: `"SPOT"`)
    * `useMasterBalance?`: boolean - Use master account balance (for sub-accounts)
    * `expirationDate?`: string - ISO 8601 date for GTC orders
    * `timeInForce?`: `"GTC" | "IOC" | "FOK"` - Time in force (default: `"GTC"`)
      * **GTC** (Good Till Canceled): Order stays active until filled or canceled
      * **IOC** (Immediate or Cancel): Fill immediately, cancel unfilled portion
      * **FOK** (Fill or Kill): Fill completely or reject entirely
      * `GTD` is declared in the protos but **never implemented** — the server rejects it. For an order that expires at a set time, send `GTC` with `expirationDate`.
    * `postOnly?`: boolean — maker-only guarantee. The order is rejected instead of matching if it would cross the book at admission (`BUY` at/above the best ask, `SELL` at/below the best bid) — REST `400` / gRPC `InvalidArgument` carrying the structured error code `POST_ONLY_WOULD_CROSS` (REST error-envelope `code` field; gRPC `google.rpc.ErrorInfo` reason). Match on the code; the `"post-only order would cross"` message substring is a legacy fallback. `LIMIT` with `GTC` only — rejected on `MARKET` orders and when combined with `IOC`/`FOK`. Persisted with the order and echoed back as `postOnly: true` on order reads. See [Post-Only Orders](/developers/trade#post-only-orders)
    * `leverage?`: string — decimal string, e.g. `"10"`; required for normal margin orders, omit only for reduce-only
    * `reduceOnly?`: boolean — order can only shrink existing exposure
    * `marginAccountId?`: string — optional legacy override; omit for normal perp orders
    * `positionSide?`: `"LONG" | "SHORT" | "NONE"` — deprecated. `side` is the authoritative direction; if supplied, `positionSide` must agree (`BUY`↔`LONG`, `SELL`↔`SHORT`)
    * `clientOrderId?`: string — a correlation handle you choose (≤64 chars from `A-Za-z0-9._:-`, whitespace trimmed, blank treated as absent). Echoed back on order detail/list reads and on every `orders` WebSocket event. Uniqueness is enforced only among your **resting** orders (`409 CLIENT_ORDER_ID_CONFLICT`); an order that never rests — `MARKET`, `IOC`/`FOK`, or a `LIMIT` that fills on arrival — does not take the handle. It is **not** an idempotency key: only a retained explicit `idempotencyKey` makes the same create safe to replay within 24 hours. Without that key, or after it expires, resubmitting after a lost response can execute a second time — reconcile against the order stream (`sdk.ws.userOrders`), not the order list, which is replica-backed and can omit an order that did land. See [Client Order IDs](/developers/trade#client-order-ids)
    * `selfTradePreventionMode?`: `"CANCEL_MAKER" | "CANCEL_TAKER" | "CANCEL_BOTH" | "SKIP"` — how the engine resolves this order meeting one of your **own** resting orders (your whole wallet family, across applications and sub-accounts). Overrides the wallet default set via [`sdk.profile.setSelfTradePreventionDefault`](/sdk/typescript/profile); omit it to inherit that, or the platform default `CANCEL_MAKER`. The taker's mode governs the whole step — resting orders' modes are never consulted. Echoed back on order reads and `orders` WebSocket events as the value you **requested**, absent when you sent none. See [Self-Trade Prevention](/developers/trade#self-trade-prevention)
    * `idempotencyKey?`: string — durable submission key (1–64 characters from `A-Za-z0-9._:-`). The SDK generates one for this call when omitted. Retain and reuse an explicit key with the same payload to recover across calls or restarts within 24 hours; a new key can execute a new order. See [Safe Retries](/developers/trade#safe-retries-with-idempotency-keys)

  Perps are one-way — `side` sets the direction. Attach TP/SL at entry with `takeProfit`/`stopLoss` (margin entry orders only; legs activate when the order fills and cancel if it is cancelled or expires unfilled), or attach to the open position with [`sdk.positions.attachPositionTpSl`](/sdk/typescript/positions). A `trailingStop` leg (`ParentTrailingStopLegParams`: `trailBps`, `activationPrice?`, `quantity?` / `closePosition?`, `slippageToleranceBps?`, `expiresAt?`) attaches the same way but arms only when the order **fully** fills. `trailingStopOrderId` is present only when the trailing stop materializes: the order fully fills on arrival or leaves a resting remainder. A partial fill whose remainder is cancelled (an `IOC` or `MARKET` order) creates no trailing stop and omits the id. See [Trailing Stop](/developers/trade#trailing-stop).

  **Perp example:**

  ```typescript theme={null}
  // Open a 10x long with TP/SL attached at entry
  await sdk.trading.placeLimitOrder(tradingPairId, "BUY", "1.5", "2000", {
    tradingMode: "MARGIN",
    leverage: "10",
    takeProfit: { triggerPrice: "2200", orderType: "MARKET" },
    stopLoss: { triggerPrice: "1900", orderType: "LIMIT", limitPrice: "1895", timeInForce: "IOC" },
  });

  // Or attach / adjust TP/SL once the position is open
  await sdk.positions.attachPositionTpSl(positionId, {
    takeProfit: { triggerPrice: "2200", orderType: "MARKET" },
    stopLoss: { triggerPrice: "1900", orderType: "LIMIT", limitPrice: "1895", timeInForce: "IOC" },
  });
  ```
</ResponseField>

<ResponseField name="placeMarketOrder" type="(tradingPairId: string, side: OrderSide, quantity: string, options?) => Promise<CreateOrderResponse>">
  Place a market order. Fills execute inside a mandatory server-side price band, 1,000 bps (10%) by default (see [Market-order price protection](/trading-mechanics/spot#market-order-price-protection); a perp market can set its own, see [Perp price band](/trading-mechanics/perps#perp-price-band)): a market order can partially fill with the remainder cancelled, or be rejected when no liquidity sits inside the band. Triggered TP/SL market legs use a wider 1,200 bps (12%) band by default.

  **Parameters:**

  * `tradingPairId`: string - Trading pair UUID (get via `sdk.market.getTradingPairBySymbol('BTC/USDC')`)
  * `side`: "BUY" | "SELL" - Order side
  * `quantity`: string - Order quantity
  * `options?`: Optional settings
    * `tradingMode?`: `"SPOT" | "MARGIN"` (default: `"SPOT"`)
    * `slippageTolerance?`: number - tightens the market's price band (e.g. 0.01 for 1%); cannot widen it
    * `leverage?`: string — decimal string; required for normal margin orders, omit only for reduce-only
    * `reduceOnly?`: boolean — order can only shrink existing exposure
    * `marginAccountId?`: string — optional legacy override; omit for normal perp orders
    * `positionSide?`: `"LONG" | "SHORT" | "NONE"` — deprecated; `side` is authoritative
    * `clientOrderId?`: string — correlation handle echoed on order reads and `orders` WebSocket events (≤64 chars from `A-Za-z0-9._:-`); not an idempotency key. See [Client Order IDs](/developers/trade#client-order-ids)
    * `selfTradePreventionMode?`: `"CANCEL_MAKER" | "CANCEL_TAKER" | "CANCEL_BOTH" | "SKIP"` — how the engine resolves this order meeting one of your **own** resting orders (your whole wallet family, across applications and sub-accounts). Overrides the wallet default set via [`sdk.profile.setSelfTradePreventionDefault`](/sdk/typescript/profile); omit it to inherit that, or the platform default `CANCEL_MAKER`. The taker's mode governs the whole step — resting orders' modes are never consulted. Echoed back on order reads and `orders` WebSocket events as the value you **requested**, absent when you sent none. See [Self-Trade Prevention](/developers/trade#self-trade-prevention)
    * `idempotencyKey?`: string — durable submission key (1–64 characters from `A-Za-z0-9._:-`). The SDK generates one for this call when omitted. Retain and reuse an explicit key with the same payload to recover across calls or restarts within 24 hours. See [Safe Retries](/developers/trade#safe-retries-with-idempotency-keys)

  **Perp example:**

  ```typescript theme={null}
  // Reduce-only market close of a short
  await sdk.trading.placeMarketOrder(tradingPairId, "BUY", "0.5", {
    tradingMode: "MARGIN",
    reduceOnly: true,
  });
  ```
</ResponseField>

<ResponseField name="batchCreate" type="(orders: BatchCreateOrderParams[]) => Promise<BatchCreateOrdersResponse>">
  Create 1–100 orders. Every item is processed independently and keeps its input position in `results`; one failure does not roll back successful siblings.

  `BatchCreateOrderParams` includes `tradingPairId`, `orderType`, `side`, and `quantity`, plus the single-create margin and execution options except attached `takeProfit` / `stopLoss` / `trailingStop`, which batch items do not support. `price` is required for `LIMIT`; `timeInForce` is valid only for `LIMIT`; `postOnly` requires `LIMIT` + `GTC`. Each item also accepts `clientOrderId?`, `idempotencyKey?` and `selfTradePreventionMode?`. `selfTradePreventionMode` **is** covered by `BatchCreateOrderItemSchema`, unlike `clientOrderId`. `BatchCreateOrdersSchema` validates the whole array before anything is sent, so one item's unknown mode throws `ValidationError` and **no item executes** — the per-item `results[].error.code === "INVALID_SELF_TRADE_PREVENTION_MODE"` is a raw REST/gRPC caller's shape, never an SDK caller's. A `FOK` item under `CANCEL_BOTH` that meets one of your own orders does reach the engine and fails as that item with `SELF_TRADE_NOT_ALLOWED`; neither rolls back its siblings. The SDK generates a separate key for each item that omits one. Retain explicit per-item keys when an ambiguous result must be retried across calls; a key protects only its own item and does not make the batch atomic.

  **Returns:** `{ totalRequested, totalSucceeded, totalFailed, results }`. Each result contains `orderId`, optional `matchResult`, and optional `error: { code, message }`; absence of `error` means success.

  The TypeScript SDK validates the batch envelope and every field covered by `BatchCreateOrderItemSchema` before sending; it throws `ValidationError` (exported by `@0xmonaco/types`, not `@0xmonaco/core`) for those client-schema failures or more than 100 items. `clientOrderId` is typed but is not covered by that client schema, so an invalid handle reaches the server and returns as the item's `results[].error.code === "INVALID_CLIENT_ORDER_ID"`. Raw REST/gRPC callers receive REST `400` / gRPC `InvalidArgument` for whole-request errors; REST `401` / gRPC `Unauthenticated` when authentication is missing; and REST `500` / gRPC `Internal` for an internal failure. A raw batch with no item key can also return whole-request REST `503` / gRPC `Unavailable` after a deadline or transport failure; its outcome is unknown, and without retained keys it cannot be safely retried. Other item-level validation and placement failures remain in `results`. Because the TypeScript SDK keys every item, an internal deadline expiry instead returns overall REST `200` / gRPC `OK` with `results[].error.code === "SERVICE_UNAVAILABLE"` on that item. Inspect every result, reconcile its state, and reuse the same explicit item key before retrying.

  See [Batch Create](/developers/trade#batch-create) and [Safe Retries](/developers/trade#safe-retries-with-idempotency-keys).
</ResponseField>

## Order Management

<ResponseField name="cancelOrder" type="(orderId: string, options?: { remainingQuantityTarget?: string }) => Promise<CancelOrderResponse>">
  Cancel an order. Called with just an `orderId`, it fully cancels the order.

  Pass `options.remainingQuantityTarget` for a **partial cancel**: the resting order is reduced *in place* to that remaining quantity, keeping its queue position — unlike `replaceOrder`, which retires the order and rests a new one at the back of the book even for a pure size-down. The released delta unlocks exactly as a cancel does (spot unlock / margin reserve).

  The target is "leave this much resting", not an amount to remove, so a retried request converges instead of compounding. It must be a positive multiple of the market's quantity step and strictly below the order's current remaining quantity — emptying an order is a full cancel's job, and growing one is a `replaceOrder`. The reduction emits a non-terminal `OrderPartiallyCancelled` event on the `orders` WebSocket channel (the order is still resting); see [WebSockets](/sdk/typescript/websockets#orders-channel).

  ```typescript theme={null}
  // Full cancel
  await sdk.trading.cancelOrder(orderId);

  // Partial: leave 0.5 resting, keeping queue position
  await sdk.trading.cancelOrder(orderId, { remainingQuantityTarget: "0.5" });
  ```
</ResponseField>

<ResponseField name="batchCancel" type="(orderIds: string[]) => Promise<BatchCancelOrdersResponse>">
  Cancel multiple specific orders by their IDs.

  **Parameters:**

  * `orderIds`: string\[] - Array of order IDs to cancel

  **Returns:**

  * `totalRequested`: number - Total number of orders requested to cancel
  * `totalCancelled`: number - Count of successfully cancelled orders
  * `totalFailed`: number - Count of failed cancellations
  * `results`: `BatchCancelResult[]` — each with `orderId`, optional `cancelledAt`, optional `error: { code, message }`. `cancelledAt` is the matching engine's cancel stamp at millisecond precision — it matches, to the millisecond, the `cancelledAt` a later read of the order returns

  **Example:**

  ```typescript theme={null}
  const result = await sdk.trading.batchCancel([
    "order-id-1",
    "order-id-2",
    "order-id-3",
  ]);

  console.log(`Cancelled: ${result.totalCancelled}/${result.totalRequested}`);

  // Check individual results — `error` is set only when cancellation failed
  result.results.forEach((r) => {
    if (r.error) {
      console.log(`Order ${r.orderId} failed: ${r.error.message}`);
    } else {
      console.log(`Order ${r.orderId} cancelled at ${r.cancelledAt}`);
    }
  });
  ```

  **Note:** Some cancellations may fail while others succeed; failures don't roll back successful cancellations. Use `totalFailed === 0` for an overall-success check.

  A batch is capped at **100 order IDs** per request; a longer array is rejected with API `400` (`At most 100 orders per batch request`). `batchCancelAll` enumerates active orders server-side, with a separate cap of **20,000 matching active orders**.
</ResponseField>

<ResponseField name="batchCancelAll" type="(tradingPairId?: string) => Promise<BatchCancelOrdersResponse>">
  Cancel all active orders, optionally filtered by trading pair.

  The request is capped at **20,000 matching active orders**. Above that cap, it cancels nothing and returns REST `400`; narrow the request by trading pair or cancel explicit batches of at most 100 IDs. This all-or-nothing cap check happens before any cancellation.

  **Parameters:**

  * `tradingPairId` (optional): string - Trading pair UUID to filter cancellation. If omitted, cancels all active orders globally.

  **Returns:** Same shape as `batchCancel` (`totalRequested`, `totalCancelled`, `totalFailed`, `results: BatchCancelResult[]`)

  **Throws:**

  * REST `400` / gRPC `InvalidArgument` when the pair UUID is invalid or more than 20,000 matching active orders would be cancelled
  * REST `401` / gRPC `Unauthenticated` when authentication is missing or invalid
  * REST `403` / gRPC `PermissionDenied` when a delegated session omits the required pair scope or lacks permission
  * REST `429` / gRPC `ResourceExhausted` when the risk-reduction budget is spent — mass exit costs **one** item per call, however many orders it cancels, and the call must fit both your account's budget and your [account family's](/developers/rate-limits#account-families) shared one. Honor the interval the transport carries: REST puts it in the body's `details.retryAfter`, gRPC attaches a `google.rpc.RetryInfo` detail to the status
  * REST `500` / gRPC `Internal` for an internal matching-engine failure
  * REST `503` / gRPC `Unavailable` for a transient matching-engine or transport failure

  **Example:**

  ```typescript theme={null}
  // Cancel ALL active orders globally
  const allResult = await sdk.trading.batchCancelAll();
  console.log(`Emergency exit: cancelled ${allResult.totalCancelled} orders`);

  // Cancel all orders for a specific trading pair
  const pair = await sdk.market.getTradingPairBySymbol('ETH/USDC');
  const pairResult = await sdk.trading.batchCancelAll(pair.id);
  console.log(`Cancelled ${pairResult.totalCancelled} ETH/USDC orders`);
  ```
</ResponseField>

<ResponseField name="replaceOrder" type="(orderId: string, newOrder: { price?: string; quantity?: string; useMasterBalance?: boolean; postOnly?: boolean; clientOrderId?: string; selfTradePreventionMode?: SelfTradePreventionMode }) => Promise<ReplaceOrderResponse>">
  Modify an existing order's price or quantity. `quantity` is the order's new **total**, not a fresh size: on a partially filled order the replacement rests `total - filled` and locks collateral for that remainder only (FIX cancel/replace convention: `LeavesQty = OrderQty - CumQty`), so the total must exceed the filled amount — replacing a 96/100-filled order with `quantity: "200"` rests 104 and reads as 96/200 filled. Omitting `quantity` keeps the order's total. The replacement inherits the original's fill history — filled quantity, VWAP, and realized fee/payment aggregates carry onto the new order — so summing those aggregates across a replacement chain double-counts (per-order and trade-level reads are unaffected). Margin reduce-only orders follow the same rule; what the replacement rests must not exceed the live position. Pass `postOnly` to re-state maker-only intent for the replacement — it is never inherited from the original order. A single `replaceOrder` validates `postOnly` (and every other check) *before* cancelling the original, so a post-only crossing rejection leaves the original resting, untouched. See [Post-Only Orders](/developers/trade#post-only-orders). A replacement is a new order with a new id, so `clientOrderId` is also not inherited — restate it to keep the handle. `selfTradePreventionMode` behaves the same way: the replacement carries its own value and inherits nothing from the original, falling back to your wallet default when omitted. See [Self-Trade Prevention](/developers/trade#self-trade-prevention).

  **Two kinds of order cannot be replaced at all.** A TWAP slice child is refused — cancel the parent instead — and so is the `LIMIT` leg a take-profit or stop-loss fired into, any order carrying `conditionalOrderId`: a replacement would rest under a new id the conditional order does not point at, severing the link in both directions. Both are `INVALID_ORDER` rejections, the conditional one naming the conditional order, and in both cases the original stays resting. Cancelling a triggered leg is unaffected.

  An entry order's pending `takeProfit` / `stopLoss` legs carry onto the replacement and are re-checked against its new price as placement would check them: a fixed trigger left on the wrong side of the replacement's expected entry is an `INVALID_ORDER` rejection, and the original stays resting with its legs. A replacement that fills on arrival arms the carried legs — see [Replace Orders](/developers/trade#replace-orders).
</ResponseField>

<ResponseField name="batchReplace" type="(orders: BatchReplaceOrderParams[]) => Promise<BatchReplaceOrdersResponse>">
  Replace 1–100 resting orders in one request. **Cancel-first**: the matching engine cancels every item's original before placing any replacement, in request order, so a two-sided quote can shift across its own resting prices in one call — see [Batch Replace](/developers/trade#batch-replace) for the failure semantics that follow from that ordering.

  `BatchReplaceOrderParams` carries `orderId` plus the optional `price`, `quantity`, `useMasterBalance`, `postOnly`, `clientOrderId` and `selfTradePreventionMode`. Although each is optional at the type level, `BatchReplaceOrderItemSchema` requires **at least one of `price` or `quantity`** per item — an item that only restates `selfTradePreventionMode` or `postOnly` throws `ValidationError` before sending. `quantity` is the order's new **total**, exactly as in `replaceOrder`. A replacement is a new order with a new id, so `postOnly`, `clientOrderId` and `selfTradePreventionMode` are **never inherited** — restate each one you want to keep. There is no `timeInForce` input: the replacement **inherits** the original's, and since only an already-resting order is eligible that is always `GTC`/`GTD` — so a replacement is never `IOC`/`FOK` and the `FOK` + `CANCEL_BOTH` self-trade rejection cannot arise here. A successful item is not necessarily resting either: `CANCEL_TAKER`, `CANCEL_BOTH`, or a `SKIP` remainder crossing one of your surviving orders can cancel the replacement immediately, so read the order's status rather than inferring it from the result.

  **Returns:** `{ totalRequested, totalSucceeded, totalFailed, results }`. Each `BatchReplaceResult` carries the required `originalOrderId`, plus optional `newOrderId`, `updatedFields`, `matchResult` and `error: { code, message }` — note it is `originalOrderId`, not `orderId`, so a result is keyed by the order you replaced rather than the one you got back.

  As with `batchCreate`, the SDK validates the whole array against `BatchReplaceOrdersSchema` before sending, so an unknown `selfTradePreventionMode` throws `ValidationError` and no item executes; the per-item `INVALID_SELF_TRADE_PREVENTION_MODE` is a raw REST/gRPC caller's shape.

  The same two exclusions as `replaceOrder` apply per item: a TWAP slice child and a `LIMIT` leg carrying `conditionalOrderId` are both refused with `INVALID_ORDER`, and the refused item's original stays resting. So is an item whose carried entry-order TP/SL would sit on the wrong side of the replacement's new price.
</ResponseField>

## Conditional Orders (Perp TP/SL)

Conditional orders sit dormant until mark price crosses a trigger, then submit the underlying order. Create TP/SL and trailing stops by attaching legs at entry via `takeProfit`/`stopLoss`/`trailingStop` on `placeLimitOrder` / `placeMarketOrder`, or to an open position via [`sdk.positions.attachPositionTpSl`](/sdk/typescript/positions). There is no standalone create call; existing conditionals can be fetched by ID, listed, and cancelled.

<ResponseField name="getConditionalOrder" type="(conditionalOrderId: string) => Promise<ConditionalOrder>">
  Fetch a single conditional (TP/SL) order by its UUID. The `takeProfitOrderId`, `stopLossOrderId` and `trailingStopOrderId` fields returned by `attachPositionTpSl` (and on an entry order's `CreateOrderResponse`) are conditional order UUIDs queryable via this method. A trailing stop reads `conditionType: "TRAILING_STOP"` and additionally carries `trailBps`, `activationPrice?`, `watermarkPrice?` (best mark since arming, from which `triggerPrice` is derived; absent on a stop that armed immediately until the first mark after arming; an `activationPrice` stop is seeded at that level the moment it arms) and `trailArmedAt?` (absent while waiting for `activationPrice`); `state` stays `ACTIVE` both while waiting for activation and while tracking (an entry-attached one is `PENDING_PARENT` until its order fully fills).

  **Returns:** `ConditionalOrder` — the same type produced by `listConditionalOrders`

  Once `state` is `TRIGGERED`, the response carries `triggeredOrder?: TriggeredOrderSummary` — what the order this conditional fired into has done so far, read from that order's own row at request time: `orderId`, `status`, `filledQuantity`, and optional `averageFillPrice`, `totalTakerFees`, `filledAt` and `terminalReason`. It is absent while the conditional waits on its trigger, absent on a `FAILED` conditional, and absent on the `conditional_orders` WebSocket frames — snapshot and live alike. A `FAILED` conditional has no summary because it never recorded a `triggeredOrderId` to join on, and it may have no close row either: a close the engine **placed** and the book then rejected persists as an order row carrying `conditionalOrderId`, but one refused before placement — a validation failure such as a bad quantity step — produces no order at all. Read `failureReason` on the conditional rather than looking for a close.

  `status` is copied verbatim from the close's own row, so treat it as the full `OrderStatus` union and switch with a `default` branch rather than against a fixed list. What you will actually see: `SETTLED` once a fully filled leg has settled, and `FILLED` in the window before that — a filled order is a real persisted state on its way to `SETTLED`, not a value this field skips; `SUBMITTED` / `PARTIALLY_FILLED` for a `LIMIT` leg still resting; `CANCELLED` when the remainder was cut, which includes a `MARKET` or `IOC` leg that filled part of its quantity, so a `CANCELLED` close can carry a **nonzero** `filledQuantity` (read `terminalReason`); and `EXPIRED` for a `LIMIT` leg that lapsed.

  Compare `triggeredOrder.averageFillPrice` against `triggerPrice` rather than assuming they match. The trigger fires when the **mark** crosses the level and the leg then trades against the **book**, and the mark is a reference price rather than the book's midpoint, sampled on a worker tick — so the two prices are independent. A market close usually fills worse than the trigger, by the spread it crosses plus the depth its size consumes, but there is no guaranteed minimum gap and a fast move can leave it better. Values for the **same state** are numerically equal to what `getOrder` returns, but the two reads are not guaranteed to see the same state: this summary is a repository read of the order row, while `getOrder` is cache-first and the read cache is fed by the live engine stream, so it can be ahead of the database by the persistor's lag. A `filledQuantity`, `averageFillPrice` or fee total that disagrees between the two is usually that lag, not a bug — read `getOrder` when you need the close's very latest state. Even for the same state the printed scale can differ, so never compare the raw strings with `===`: compare as exact decimals, normalizing the scale or using a decimal library, rather than through `Number()`, which is inexact. See [Trigger Price Is Not Fill Price](/developers/trade#trigger-price-is-not-fill-price).

  **Example:**

  ```typescript theme={null}
  // Open a position, then attach a take-profit leg
  await sdk.trading.placeLimitOrder(pair.id, "BUY", "1.5", "2000", {
    tradingMode: "MARGIN",
    leverage: "10",
  });

  const { takeProfitOrderId } = await sdk.positions.attachPositionTpSl(positionId, {
    takeProfit: { triggerPrice: "2200", orderType: "MARKET" },
  });

  if (!takeProfitOrderId) throw new Error("Take-profit order was not created");
  const tp = await sdk.trading.getConditionalOrder(takeProfitOrderId);
  console.log(tp.state, tp.triggerPrice);
  ```
</ResponseField>

<ResponseField name="cancelConditionalOrder" type="(conditionalOrderId: string) => Promise<CancelConditionalOrderResponse>">
  Cancel an active conditional order.

  **Returns:** `{ conditionalOrderId, status, message }`
</ResponseField>

<ResponseField name="listConditionalOrders" type="(params?: ListConditionalOrdersParams) => Promise<ListConditionalOrdersResponse>">
  List conditional orders for the authenticated user. Cursor (`pageToken`) pagination is the only mode.

  **Parameters (all optional):**

  * `marginAccountId?`: string
  * `tradingPairId?`: string
  * `state?`: `ConditionalOrderState`
  * `pageToken?`: string — pagination cursor:
    * **omitted** or **`""`** — start a cursor walk from the newest conditional order
    * **a previous `nextPageToken`** — resume the walk from that cursor
  * `pageSize?`: number — up to `1000`

  **Returns `ListConditionalOrdersResponse`:**

  * `orders`: `ConditionalOrder[]`
  * `total`: number — matching conditional orders, counted up to a ceiling of 10,000 and saturating there, so a caller with more than that reads the cap rather than a lifetime total
  * `nextPageToken`: string — cursor for the next page; minted only while a further page exists, empty on the final page. Follow it until it comes back empty; build "load more" UIs on it rather than "page N of M".
  * `pageSize`: number

  **Errors:** `400` (invalid query parameters, a malformed `pageToken`, or a `pageToken` replayed against different filters than the ones that minted it), `401` (auth required), `429` (read rate limit — retry after `retryAfter`), `500`.

  Rows carry `triggeredOrder` on the same terms as `getConditionalOrder` above: present once `state` is `TRIGGERED` and the close can be read, absent otherwise.
</ResponseField>

Lifecycle updates stream over the [`conditional_order_update` websocket event](/sdk/typescript/websockets). Those frames never carry `triggeredOrder` — read the close's outcome from REST.

## TWAP Orders

A TWAP (time-weighted average price) order executes a large order as a schedule of market child orders spread across a time window, averaging price over time instead of paying market impact in a single print. You submit one **parent order** and the matching engine plans, places, and tracks the child slices. See [TWAP Orders](/trading-mechanics/twap-orders) for the execution model (slicing, randomization, catch-up, hard stop). All six methods are authenticated and reject delegated-agent sessions.

<ResponseField name="createTwapOrder" type="(tradingPairId: string, side: OrderSide, quantity: string, options: CreateTwapOrderOptions) => Promise<CreateTwapOrderResponse>">
  Create a TWAP parent order. `quantity` must be at least twice the pair's minimum order size so it can be sliced.

  **`options` (`CreateTwapOrderOptions`):**

  * **Window (required, exactly one):** `durationSeconds` (number, or an int64 string the SDK normalizes to a JSON number; server default bounds 60 s – 30 days) **or** `endTime` (RFC 3339 UTC with a `Z` suffix — a non-UTC offset is rejected, not converted). `startTime` (RFC 3339 UTC) is allowed only alongside `endTime`. Client-side Zod validation enforces the exactly-one-of rule before the request is sent.
  * `limitPrice?`: string — parent-level cap; BUY children never execute above it, SELL never below.
  * `slippageToleranceBps?`: number — per-slice protective band (default `100`, max `500`).
  * `randomize?`: boolean — seeded ±30% size / ±30% timing jitter (default `true`).
  * `executionStyle?`: `"TAKER" | "PASSIVE"` (default `"TAKER"`). `TAKER` fires a banded `MARKET` child at every slot. `PASSIVE` rests a post-only `LIMIT` `GTC` child at the same-side touch, chases the touch with throttled reprices, and sweeps only the remainder as a banded `MARKET` child at the slot deadline — maker fills pay the pair's maker fee with no application fee. Passive parents plan on a 60 s target slice interval instead of \~5 s, under the same 2,880-slice cap. See [Passive Execution](/trading-mechanics/twap-orders#passive-execution).
  * `reduceOnly?`: boolean — margin only; children inherit and clamp to the live position.
  * `tradingMode?`: `"SPOT" | "MARGIN"` (default `"SPOT"`). Margin parents also take `marginAccountId?`, `riskBucketId?`, `marginMode?`, and `leverage?` (required for normal margin TWAPs).
  * `useMasterBalance?`, `strategyKey?`: optional.
  * `clientTwapId?`: string — idempotency key, ≤36 chars, unique per user + application; a duplicate is rejected with `409`.
  * **Conditional activation (optional):** `triggerPrice?` (string) and `triggerDirection?` (`"ABOVE" | "BELOW"`) are both-or-neither and make the parent wait, `PENDING`, until the reference price reaches the level. A conditional parent takes `durationSeconds` only — `startTime`/`endTime` are rejected — because its window floats from the trigger instant. `conditionalExpiry?` (RFC 3339 UTC, `Z` suffix only) bounds the wait, is valid only alongside a trigger, must be in the future, and both defaults to and is capped at 30 days from creation. Client-side Zod enforces the both-or-neither, duration-only, expiry-requires-trigger, and expiry-range rules before the request is sent; the `Z`-suffix rule is enforced server-side. See [Conditional TWAP](/trading-mechanics/twap-orders#conditional-twap-price-triggered-activation).

  **Returns `CreateTwapOrderResponse`:** `twapOrderId`, `status`, `message`, `state` (always `PENDING` at create), resolved `marginAccountId` / `riskBucketId`, `plannedSlices`, `firstSliceAt`, `endTime`.

  **Errors:** `400` (invalid window bounds, quantity too small or too large to slice, margin field mismatch, per-user active-TWAP cap reached, a trigger already satisfied at creation, a conditional create on a margin market that has no oracle mark yet, a `reduceOnly` margin parent whose risk bucket does not exist or is closed — a missing bucket keeps the usual remedy, while a bucket the request named explicitly and that is closed answers `Risk bucket is closed; this TWAP order cannot reopen it — transfer collateral to the risk bucket first`. A non-reduce-only margin parent is not refused in either case: it provisions the missing bucket, or funds the named closed one, at creation — or a validation refusal from that first-use provisioning, returned as `First-use risk bucket provisioning failed: {detail}`), `401` (auth required or delegated-agent session), `409` (duplicate `clientTwapId`), `429` (a parent costs one order-creation item against your account budget and your [account family's](/developers/rate-limits#account-families) shared one; the retry interval arrives as `details.retryAfter` over REST and as a `google.rpc.RetryInfo` detail over gRPC), `500` (internal server or matching-engine failure, including an internal fault inside first-use bucket provisioning, sanitized to `First-use risk bucket provisioning failed` — that exact text is the gRPC status message, while REST wraps it as `Internal server error: First-use risk bucket provisioning failed`), and `503` / gRPC `Unavailable` (the matching engine is unreachable, or the create's reply was lost in transport).

  Treat that `503` as an **unknown** outcome rather than a failure: the parent may already have been accepted. Reconcile before retrying — list your TWAP parents, or resubmit with the same `clientTwapId`, which answers `409` if the original landed.

  **Example:**

  ```typescript theme={null}
  const pair = await sdk.market.getTradingPairBySymbol("BTC/USDC");

  // Spot TWAP: 0.5 BTC over 10 minutes
  const twap = await sdk.trading.createTwapOrder(pair.id, "BUY", "0.5", {
    durationSeconds: 600,
    limitPrice: "50000",
  });
  console.log(twap.twapOrderId, twap.state, twap.plannedSlices);
  ```
</ResponseField>

<ResponseField name="getTwapOrder" type="(twapOrderId: string) => Promise<TwapOrder>">
  Fetch the owner's TWAP parent read model with live progress: `executedQuantity`, `executedNotional`, `averageFillPrice`, `progress` (0–1), `plannedSlices` / `slicesPlaced` / `slicesSkipped`, `nextSliceAt`, `state` (`PENDING` / `ACTIVE` / `COMPLETED` / `CANCELLED`), and `terminalReason` (including end-of-window shortfall, and `trigger expired` for a conditional parent whose trigger never fired). Monitor the slice counters, not the state, for degraded execution. The read model also echoes `executionStyle` (`"TAKER" | "PASSIVE"`) and, for a conditional parent, `triggerPrice`, `triggerDirection`, `conditionalExpiry`, and `triggeredAt` — read the armed window off the parent's `startTime` / `endTime` rather than the create response, whose values are provisional until the trigger fires. (`startAt` / `endAt` are the `twap_orders` WebSocket payload's names for the same pair; the REST read model uses `startTime` / `endTime`.)
</ResponseField>

<ResponseField name="listTwapOrders" type="(params?: ListTwapOrdersParams) => Promise<ListTwapOrdersResponse>">
  List the caller's TWAP parents. Cursor (`pageToken`) pagination is the only mode.

  **Parameters (all optional):**

  * `state?`: `TwapOrderState`
  * `tradingPairId?`: string
  * `pageToken?`: string — pagination cursor:
    * **omitted** or **`""`** — start a cursor walk from the newest TWAP parent
    * **a previous `nextPageToken`** — resume the walk from that cursor
  * `pageSize?`: number — up to `1000`

  **Returns `ListTwapOrdersResponse`:**

  * `orders`: `TwapOrder[]`
  * `total`: number — matching TWAP orders, counted up to a ceiling of 10,000 and saturating there, so a caller with more than that reads the cap rather than a lifetime total
  * `nextPageToken`: string — cursor for the next page; minted only while a further page exists, empty on the final page. Follow it until it comes back empty; build "load more" UIs on it rather than "page N of M".
  * `pageSize`: number

  **Errors:** `400` (invalid query parameters, a malformed `pageToken`, or a `pageToken` replayed against different filters than the ones that minted it), `401` (auth required), `429` (read rate limit — retry after `retryAfter`), `500`.
</ResponseField>

<ResponseField name="listTwapOrderSlices" type="(twapOrderId: string, params?: ListTwapOrderSlicesParams) => Promise<ListTwapOrderSlicesResponse>">
  List one TWAP parent's slices: the child orders it placed, each an `Order` exactly as `getOrder` returns it, with `parentOrderId` set to the parent. Resting and finished slices are both listed. A `PASSIVE` parent's reprices and deadline sweeps are child orders too, so it can have more slices than `slicesPlaced`. Cursor (`pageToken`) pagination is the only mode.

  **Parameters (`params`, all optional):**

  * `orderBy?`: `"ASC" | "DESC"` — by `createdAt`; default `"ASC"`, the oldest slice first
  * `pageToken?`: string — pagination cursor:
    * **omitted** or **`""`** — start a cursor walk from the first slice in `orderBy` order
    * **a previous `nextPageToken`** — resume the walk from that cursor
  * `pageSize?`: number — up to `1000` (default `20`)

  **Returns `ListTwapOrderSlicesResponse`:**

  * `slices`: `Order[]`
  * `nextPageToken`: string — cursor for the next page; minted only while a further page exists, empty on the final page
  * `pageSize`: number

  **Errors:** `400` (invalid TWAP order ID format, invalid query parameters, a malformed `pageToken`, or a `pageToken` replayed against a different parent or `orderBy` than the one that minted it), `401` (auth required or delegated-agent session), `404` (unknown ID, or another user's parent — byte-identical responses), `429` (read rate limit — retry after `retryAfter`), `500`.

  ```typescript theme={null}
  let pageToken = "";
  do {
    const page = await sdk.trading.listTwapOrderSlices(twapOrderId, { pageSize: 100, pageToken });
    for (const slice of page.slices) console.log(slice.id, slice.status, slice.filledQuantity);
    pageToken = page.nextPageToken;
  } while (pageToken);
  ```
</ResponseField>

<ResponseField name="cancelTwapOrder" type="(twapOrderId: string) => Promise<CancelTwapOrderResponse>">
  Cancel a TWAP parent. Atomic and immediate — child orders are IOC and never rest, so there is nothing to unwind; quantity already executed stands.

  **Returns:** `{ twapOrderId, status, message }`
</ResponseField>

<ResponseField name="getTwapOrderTca" type="(twapOrderId: string) => Promise<GetTwapOrderTcaResponse>">
  Fetch the post-execution TCA (transaction cost analysis) report for a **terminal** TWAP parent — available once the parent is `COMPLETED` or `CANCELLED`. The report scores `realizedAvgPrice` (`executedNotional / executedQuantity`, **gross of fees**) against two references over the active window (`windowStartAt` to `windowTerminalAt`, the actual terminal time — never the planned end): the arrival price at the window start and the window-TWAP benchmark, both from the single series named by `benchmarkSource` (`risk_mark_1m` for marked pairs, `ohlcv_1m_close` fallback). `slippageVsArrivalBps` and `performanceVsBenchmarkBps` are signed bps at 4 decimal places — **positive always means the execution beat the reference** (BUY filled below it, SELL above it).

  **Returns `GetTwapOrderTcaResponse`:** always-present structural fields (`twapOrderId`, `tradingPairId`, `state`, `side`, `totalQuantity`, `executedQuantity`, `shortfallQuantity`, window bounds), plus six optional analytics fields that are **omitted — key absent, not `null` — when that field cannot be computed honestly**, each on its own condition: `realizedAvgPrice` comes straight from the fills and is absent only when nothing executed; `arrivalPrice`, `benchmarkTwapPrice`, and `benchmarkSource` are absent when no reference series qualified or the window sealed no minute bucket; `slippageVsArrivalBps` / `performanceVsBenchmarkBps` are absent when either of their inputs is. A parent that filled but had no qualifying reference series still reports `realizedAvgPrice`, just no scores — handle the absent-key case on every analytics field.

  **Errors:** `400` (invalid TWAP order ID format, or the parent has not reached a terminal state yet), `401` (auth required or delegated-agent session), `404` (unknown ID, or another user's parent — byte-identical responses), `500` (internal server error).
</ResponseField>

Live progress streams on the authenticated `twap_orders` channel via [`sdk.ws.twapOrders`](/sdk/typescript/websockets#twap-orders-channel).

## Order Queries

Order reads and order WebSocket events expose `version?: number`, an opaque revision for the same order only. Merge a REST or subscription snapshot when `snapshot.version >= local.version`; the snapshot wins ties because several live events can share one sequencer step. Gaps are normal, versions from different orders are not comparable, and an absent version means unknown rather than zero. See the [market-maker reconciliation recipe](/developers/market-maker-runbook#4-reconnect-and-resync).

Conditional-order reads, live conditional updates and subscription snapshots also expose `version?: number`. When both sides carry one, merge them for the same conditional order with `incoming.version >= local.version`; a versioned row supersedes an unversioned one, and two unversioned rows compare on `updatedAt`.

<ResponseField name="getPaginatedOrders" type="(params?: GetPaginatedOrdersParams) => Promise<GetPaginatedOrdersResponse>">
  Query orders with filters, cursor-paginated; there is no page-number (offset) mode.

  Returns the caller's **book orders** only — `LIMIT` and `MARKET` orders, resting and historical — newest first across full history. Filter `status` to `["SUBMITTED", "PARTIALLY_FILLED"]` for the resting working set. TWAP parents and conditional (TP/SL) orders are not rows here: list them with `listTwapOrders` and `listConditionalOrders`. Nor are a TWAP's slices — the child orders a TWAP places, which carry `parentOrderId` — resting or finished, and they are not counted in `total`: read them with `listTwapOrderSlices`. The close a take-profit or stop-loss fired into is a book order and is listed, as its own `MARKET` or `LIMIT` row carrying `conditionalOrderId` back to the conditional; what that close did is also summarized on the conditional order's `triggeredOrder`.

  **Parameters:** all optional

  * `status?`: `OrderStatusFilter` — a single `OrderStatus` or an array (e.g. `["SUBMITTED", "PARTIALLY_FILLED"]`), sent as one request
  * `tradingPairId?`: string
  * `tradingMode?`: `"SPOT" | "MARGIN"`
  * `marginAccountId?`: string
  * `pageSize?`: number — items per page; up to 1000
  * `pageToken?`: string — pagination cursor:
    * **omitted** or **`""` (empty string)** — start a cursor walk over full history (hot and archived rows merged), newest first
    * **a previous `nextPageToken`** — resume the walk from that cursor

  **Returns:** `GetPaginatedOrdersResponse`:

  * `orders`: `Order[]` — historical orders
  * `nextPageToken`: string — the cursor for the next page; non-empty whenever a page returns rows, empty when the walk is exhausted (a zero-row page)
  * `pageSize`: number — items per page
  * `total`, `totalPages`: number — the item count and page count matching the filter, exact up to the server ceiling (10,000) and saturating there. A lower bound when `totalCapped` is `true`.
  * `totalCapped`: boolean — `true` when **more than** the ceiling matched, so the count stopped there and `total`/`totalPages` are a lower bound ("10,000+"); `false` when `total` is exact (including exactly the ceiling).

  **Example (the resting working set):**

  ```typescript theme={null}
  const open = await sdk.trading.getPaginatedOrders({ status: ["SUBMITTED", "PARTIALLY_FILLED"] });
  for (const order of open.orders) {
    console.log("resting", order.quantity, "@", order.price);
  }
  // Parent orders live on their own listings:
  const parents = await sdk.trading.listTwapOrders({ state: "ACTIVE" });
  const triggers = await sdk.trading.listConditionalOrders({ state: "ACTIVE" });
  ```

  **Example (walk full order history):**

  ```typescript theme={null}
  let pageToken = ""; // empty string starts the cursor walk
  do {
    const res = await sdk.trading.getPaginatedOrders({ pageToken });
    for (const order of res.orders) {
      // process order
    }
    pageToken = res.nextPageToken;
  } while (pageToken !== "");
  ```
</ResponseField>

<ResponseField name="iterateOrders" type="(params?) => AsyncGenerator<Order>">
  Walk full order history with cursor pagination, yielding one order at a time. Accepts the `getPaginatedOrders` filters plus `pageSize`; the cursor is managed internally.

  ```typescript theme={null}
  for await (const order of sdk.trading.iterateOrders({ status: "FILLED" })) {
    // process order
  }
  ```
</ResponseField>

<ResponseField name="getOrder" type="(orderId: string) => Promise<GetOrderResponse>">
  Get specific order details.

  An order the engine **placed** for a take-profit or stop-loss trigger — including one the book then rejected — carries `conditionalOrderId`, the conditional that fired it, so a close in order history can be attributed without scanning the conditional list. It is absent on every order you placed yourself, on TWAP slice children (those carry `parentOrderId`), and on triggered closes written before the field existed: there is no backfill, so absence on an older close means unknown, not placed by hand. `getPaginatedOrders` carries it on the same terms; **no order WebSocket event or snapshot row does**, so a close first seen over the socket acquires the field on the next REST read.
</ResponseField>

## Fee Simulation

Simulate fees before placing an order to understand exact costs.

<ResponseField name="fees.simulateFees" type="(params: SimulateFeeParams) => Promise<SimulateFeeResponse>">
  Calculate exact fees for an order before placing it. Requires authentication.

  **Parameters:**

  * `tradingPairId`: string - Trading pair UUID
  * `side`: "BUY" | "SELL" - Order side
  * `price`: string - Price per unit
  * `quantity`: string - Quantity to trade
  * `orderType?`: `"LIMIT" | "MARKET"` - Order type (default: `"LIMIT"`). `MARKET` orders include a slippage buffer in the lock amount.
  * `slippageToleranceBps?`: number - Slippage tolerance in basis points (0–1000); only *tightens* the protective band, never widens it. **Only valid when `orderType` is `"MARKET"`** (rejected otherwise). Default: `1000` (10%), matching the market-order price band.

  **Returns:**

  * `notional`: string - Total order value (price × quantity)
  * `monacoTakerFee`: string - Monaco protocol taker fee
  * `monacoTakerFeeBpsExact`: string | null - Tier-resolved Monaco taker rate as a decimal bps string (e.g. `"6.5"`) — the exact rate the ledger charges this caller, equal to `monacoTakerFee / notional`. Prefer this over the integer field below
  * `monacoMakerRebateBpsExact`: string | null - Tier-resolved Monaco maker rebate as a decimal bps string (negative = rebate, e.g. `"-1"`) — the exact rate the ledger credits this caller
  * `monacoTakerFeeBps`: number - **Deprecated.** Whole-bps taker rate; an integer cannot carry fractional tiered rates (e.g. 6.5), so this echoes the flat trading-pair column, not the tier-resolved rate. Use `monacoTakerFeeBpsExact` or `fees.getMyFeeTier`
  * `monacoMakerRebate`: string - Monaco maker rebate (negative = rebate)
  * `monacoMakerRebateBps`: number - **Deprecated.** Whole-bps maker rebate; same integer limitation as above. Use `monacoMakerRebateBpsExact` or `fees.getMyFeeTier`
  * `applicationTakerFee`: string - Application taker fee
  * `applicationTakerFeeBps`: number - Application fee in basis points
  * `totalTakerFees`: string - Total fees for taker (monaco + application)
  * `takerTotalPayment`: string - Total amount taker must pay
  * `makerTotalReceipt`: string - Total amount maker receives
  * `buyOrderLockAmount`: string | null - Amount locked for BUY orders
  * `maxQuantity`: string | null - Maximum quantity affordable at the given price, accounting for fees (null if not authenticated or balance unavailable)
  * `maxQuantityRaw`: string | null - `maxQuantity` in RAW (smallest-unit) format
  * `slippageToleranceBps`: number | null - Slippage tolerance used in the calculation, echoed back for `MARKET` orders (null for `LIMIT` orders)

  **Example (LIMIT):**

  ```typescript theme={null}
  const pair = await sdk.market.getTradingPairBySymbol('BTC/USDC');

  const fees = await sdk.fees.simulateFees({
    tradingPairId: pair.id,
    side: "BUY",
    price: "50000.00",
    quantity: "0.1"
  });

  console.log(`Order value: $${fees.notional}`);
  console.log(`Total fees: $${fees.totalTakerFees}`);
  console.log(`You will pay: $${fees.takerTotalPayment}`);
  ```

  **Example (MARKET with slippage):**

  ```typescript theme={null}
  const fees = await sdk.fees.simulateFees({
    tradingPairId: pair.id,
    side: "BUY",
    price: "50000.00",
    quantity: "0.1",
    orderType: "MARKET",
    slippageToleranceBps: 1000 // 10% slippage buffer (matches the price band)
  });

  console.log(`Lock amount: $${fees.buyOrderLockAmount}`);
  console.log(`Max affordable quantity: ${fees.maxQuantity}`);
  console.log(`Slippage applied: ${fees.slippageToleranceBps} bps`);
  ```
</ResponseField>

## Fee Tier

Look up your current volume-based fee tier and a pair's full schedule.

<ResponseField name="fees.getMyFeeTier" type="(params: GetMyFeeTierParams) => Promise<GetMyFeeTierResponse>">
  Return the authenticated caller's current fee tier, rolling 14-day volumes, and the requested pair's six-row fee schedule. Requires authentication.

  Fee tiers are volume-based and recalculated daily from a trailing 14-day weighted volume (`14d perp + 2.5 × 14d spot`), summed across every application the wallet trades through, so the tier and the volumes below are the same from any application. See [Fees](/fees) for the tier tables.

  **Parameters:**

  * `tradingPairId`: string - Trading pair UUID whose fee schedule to return

  **Returns:**

  * `currentTierLevel`: number - Resolved tier, `1` (lowest volume) through `6` (highest), from the higher of `weightedVolume14d` and any volume floor set on the wallet (a floor that is set or changed counts once it applies to the wallet's fills, usually within a minute, and likewise for an account the wallet opens in a new application; it stops counting at once when removed or expired)
  * `weightedVolume14d`: string - Weighted 14-day volume (perp + 2.5× spot) the tier resolves from, unless a higher volume floor is set on the wallet, in whole USD (e.g. `"5000000"` = \$5 M)
  * `spotVolume14d`: string - Rolling 14-day spot volume, in whole USD
  * `perpVolume14d`: string - Rolling 14-day perp volume, in whole USD
  * `volumeToNextTier`: string | null - Additional weighted volume needed to reach the next tier, counted from the volume the current tier resolved from; omitted at tier 6
  * `nextTierLevel`: number | null - Next tier level; omitted at tier 6
  * `feeSchedule`: FeeTierScheduleRow\[] - The pair's six rows, ascending by tier. Each row: `tierLevel` (number), `minVolumeThreshold` (string — weighted 14-day volume floor in quote/USD units), `makerFeeBps` (string — human bps, negative = rebate), `takerFeeBps` (string — human bps)

  **Example:**

  ```typescript theme={null}
  const pair = await sdk.market.getTradingPairBySymbol('BTC/USDC');

  const tier = await sdk.fees.getMyFeeTier({ tradingPairId: pair.id });

  console.log(`Current tier: ${tier.currentTierLevel}`);
  console.log(`14d weighted volume: ${tier.weightedVolume14d}`);
  if (tier.nextTierLevel != null) {
    console.log(`${tier.volumeToNextTier} more volume to reach tier ${tier.nextTierLevel}`);
  }
  for (const row of tier.feeSchedule) {
    console.log(`Tier ${row.tierLevel}: maker ${row.makerFeeBps} bps, taker ${row.takerFeeBps} bps`);
  }
  ```
</ResponseField>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.