For the complete documentation index, see llms.txt. This page is also available as Markdown.

Trigger Orders (SL/TP)

STOP_LOSS and TAKE_PROFIT are whole-position protective stops on perp markets. Arming and firing are live. Accepted triggers survive service restarts and are evaluated against the market's mark price. While armed it adds no order-book liquidity; when it fires it becomes an order with the time-in-force you chose at creation.

The whole-position model

A trigger create or modify omits JSON qty. The signature carries a full-position sentinel, so you do not re-sign when your position size changes. At firing, the engine sizes the child from the live position still reducible in the signed direction.

Position protected

isBuy

Stop-loss condition

Take-profit condition

Long

false (sell)

Mark price ≤ triggerPx

Mark price ≥ triggerPx

Short

true (buy)

Mark price ≥ triggerPx

Mark price ≤ triggerPx

At most one stop-loss and one take-profit can be armed per account per market. They form an OCO (one-cancels-the-other) pair. Cancel or re-price an existing same-type trigger before arming another. Triggers are perp-only; spot triggers are rejected.

The request contract documented here follows API 3.6.2. Trigger create and modify requests must omit both Boolean fields, including false. See Quickstart for the pinned Python SDK and Changelog for release history.

Request contract

Use REST POST /v2/createOrder or the ws-exec createOrder message:

Field
Trigger create rule

orderType

STOP_LOSS or TAKE_PROFIT

qty

Omit; sign the full-position sentinel below

triggerPx

Required, positive firing threshold

limitPx

Required, positive worst acceptable execution price; subject to the trigger band below

timeInForce

Required: IOC, GTC, or GTT; selects the fired child's behaviour

expiresAfter

Required for GTT: a future Unix-second timestamp strictly greater than deadline; omit for GTC/IOC

reduceOnly

Omit, including false; sign false. The resulting order can only reduce the protected position

postOnly

Omit, including false; sign false

For example, this stop-loss protects a long ETH position by selling at no worse than 2,490 when the mark reaches 2,500. The fired child is IOC. Replace the account, signer, nonce, signature and illustrative deadline with current values before submitting.

A successful create acknowledges arming with an orderId and status: OPEN; it is not a fill confirmation. A trigger already satisfying its firing condition can fire on the next evaluation — do not assume it will remain armed after the acknowledgement.

Choosing the fired child's time-in-force

TIF
Behaviour after firing

IOC

Fill available liquidity within limitPx, then cancel the remainder. A partial fill can leave a residual position without protection

GTC

Fill available liquidity within limitPx, then rest any remainder until filled or cancelled

GTT

Fill and rest like GTC, but stop at the expiry boundary below

A GTT trigger has one absolute expiresAfter shared by the armed trigger and fired child. Time spent armed reduces the child's remaining lifetime. Both phases expire before that timestamp to allow time for settlement, with cancelReason: GTT_EXPIRED. An expiry that leaves too little time is rejected with ORDER_EXPIRES_TOO_SOON_ERROR. Allow a buffer beyond your intended protection window; the required buffer is not configurable by clients or returned by the API. On-chain settlement still rejects a fill that lands after the signed expiry.

Trigger-price band

limitPx must be within the market's permitted distance from triggerPx on both create and modify. The API does not return that distance. Keep the two prices close; TRIGGER_LIMIT_OUTSIDE_BAND_ERROR means either the limit is too far from the trigger or stops are unavailable on that market. Read the message: move the limit closer only for a price-band rejection, and contact Reya if stops are unavailable.

Signing the full-position sentinel

Use the same Order / OrderDetails envelope as a limit order; see Signatures & Nonces.

  • quantity is ±(2²⁵⁵ − 1) (type(int256).max), a raw integer, not E18-scaled and not zero. Buy signs the positive sentinel; sell signs the negative sentinel. Its sign must match isBuy.

  • timeInForce signs your selected TIF: 0 for GTC, 1 for IOC, 2 for GTT. Send the matching JSON field; omission is invalid.

  • expiresAfter signs the GTT timestamp, or 0 for GTC/IOC while omitting the JSON field.

  • Prices are E18-scaled in the signature. Sign reduceOnly = false and postOnly = false.

The EIP-712 type shape and sentinel are unchanged by firing support; the chosen TIF and expiry are now part of the trigger's intended state. Do not reuse older examples that omit timeInForce or always sign GTC. The pinned Python SDK derives the sentinel internally from is_buy; omit qty in the request.

Firing, OCO and self-trades

When a trigger fires, it becomes the selected order and cancels the other armed leg with OCO_SIBLING_FIRED. This also happens when the child fills nothing. A fired IOC can therefore consume both protective legs without closing the whole position. Re-arming protection is your responsibility; the exchange does not automatically retry or re-arm the pair.

If the firing stop would trade against the account's own resting makers, those maker orders are cancelled with PROTECTIVE_SELF_TRADE_SWEEP so the protective child can execute. The reason appears on the maker's cancellation. Ordinary SELF_TRADE_PREVENTION instead cancels the incoming taker and leaves the maker in place.

A fire can fail before a child exists:

  • POSITION_CLOSED — the position has closed, flipped, or fallen below one tradable lot.

  • RISK_REJECTED — pre-trade risk refuses the proposed close.

Both legs then become terminal with that refusal reason and triggered: false; neither leg remains as protection. cancelReasonMessage is a fixed explanation of the reason, not the individual risk check that failed.

A matching pause, stale required mark, or temporarily unavailable risk checks defers firing and leaves the trigger armed; this differs from a terminal RISK_REJECTED cancellation. GTT expiry still applies while firing is deferred. Once inputs recover, the engine evaluates qualifying triggers again.

Position updates can also retire armed protection with POSITION_CLOSED when the recorded position has closed, flipped, or become too small to trade, without waiting for a price crossing. Reconcile the position before re-arming.

Reading armed and fired orders

openOrders and the orderChanges stream expose triggered:

  • false: still armed, outside the book, modifiable and cancellable; qty is omitted.

  • true: fired child. A GTC/GTT remainder can rest in the book, but is cancel-only. Each fill is clamped to the position still reducible at that instant, so its remaining quantity is an upper bound, not a guaranteed fillable size. A clamp to zero cancels the remainder with POSITION_CLOSED.

A fired child is not proactively removed when another action closes the position. It can still block the account's own crossing orders through ordinary self-trade prevention; explicitly cancel it when it is no longer wanted.

orderHistory always omits triggered. Absence there means unknown, not false; use openOrders or live orderChanges when you need this distinction. On those two surfaces, absence indicates an older deployment predating firing support. LIMIT orders are never triggered: true.

Re-pricing (modify)

Only an armed trigger can be modified. modifyOrder preserves its orderId and clientOrderId and changes only limitPx and triggerPx. The new price pair must satisfy the same trigger-price band.

Restate the original timeInForce and, for GTT, the original expiresAfter. Omit reduceOnly, postOnly, and qty. Supplying either Boolean field, even false, is rejected. The SDK and API reconstruct both signed Boolean values as false, independently of stored order state. All identity fields and the sentinel's sign stay unchanged. Sign the complete post-modify state with a fresh nonce. Changing a trigger's TIF or expiry requires cancelling and creating a new trigger.

For the IOC stop above, a re-price request is:

Use current request-signing values as for create. An identical restate is rejected with EMPTY_MODIFY_ERROR. A fired child cannot be modified.

Cancelling protection

  • Single cancel works by orderId or clientOrderId. Cancellation is bound to the signer wallet that armed the trigger. Manually cancelling one armed leg leaves its sibling armed.

  • User cancelAll clears armed triggers and fired resting children within its scope, as well as ordinary orders.

  • Cancel-on-Disconnect preserves both armed triggers and fired protective children. A disconnect does not remove this exposure.

Errors

Code
Meaning and action

INPUT_VALIDATION_ERROR

Missing/invalid TIF, invalid TIF/expiry combination, supplied qty, reduceOnly, or postOnly on create or modify (including Boolean false), invalid trigger price, spot trigger, or an invalid modify restate. Correct the request and re-sign

TRIGGER_ALREADY_EXISTS_ERROR

Same-type armed trigger exists for this account/market; modify or cancel it first

TRIGGER_IOC_MUST_NOT_EXPIRE_ERROR

An IOC trigger carries an expiry. Omit expiresAfter and re-sign; this invalid combination can also return INPUT_VALIDATION_ERROR

TRIGGER_LIMIT_OUTSIDE_BAND_ERROR

Prices are too far apart, or stops are unavailable on the market; read the message before changing the price

ORDER_EXPIRES_TOO_SOON_ERROR

GTT expiry leaves too little settlement headroom; choose a later expiry

EMPTY_MODIFY_ERROR

No modifiable value changed

Other request rejections can use CREATE_ORDER_OTHER_ERROR / MODIFY_ORDER_OTHER_ERROR; read the returned message. TRIGGER_REQUIRES_GTC_ERROR belongs to the retired GTC-only trigger contract. New clients must support the chosen-TIF contract above.

Last updated