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.
Request contract
Use REST POST /v2/createOrder or the ws-exec createOrder message:
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
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.
quantityis±(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 matchisBuy.timeInForcesigns your selected TIF:0for GTC,1for IOC,2for GTT. Send the matching JSON field; omission is invalid.expiresAftersigns the GTT timestamp, or0for GTC/IOC while omitting the JSON field.Prices are E18-scaled in the signature. Sign
reduceOnly = falseandpostOnly = 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;qtyis 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 withPOSITION_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
orderIdorclientOrderId. Cancellation is bound to the signer wallet that armed the trigger. Manually cancelling one armed leg leaves its sibling armed.User
cancelAllclears 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
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