Self-trade prevention (STP) stops an incoming order from executing against a resting order of the same Main Account. The incoming order selects which order Polyester expires when their prices would otherwise match.
STP covers a Main Account and all of its Subaccounts as one scope. Orders from the Main Account never match orders from its Subaccounts, and orders from two Subaccounts of the same Main Account never match each other. STP does not compare client order IDs, API keys, or strategy names. STP groups are not currently supported.
Modes
| Value | Behavior at a self-trade |
|---|---|
EXPIRE_MAKER | Cancels the resting maker order; the incoming order can continue |
EXPIRE_TAKER | Cancels the remaining incoming order; the resting maker remains |
EXPIRE_BOTH | Cancels the resting maker and the remaining incoming order |
The mode on the incoming order controls the outcome. The resting order's original STP mode does not override it.
self_trade_prevention_mode is omitted or set to SELF_TRADE_PREVENTION_MODE_UNSPECIFIED, Polyester applies EXPIRE_MAKER. Read APIs report the
effective mode as EXPIRE_MAKER.Matching behavior
STP is evaluated when the incoming order reaches a maker of the same Main Account in price-time order. Executions against other accounts that occurred earlier remain valid and are not rolled back.
EXPIRE_MAKERremoves the maker's entire remaining quantity, not only the overlapping quantity. The incoming order can then continue through later eligible liquidity.EXPIRE_TAKERpreserves the maker and cancels the incoming order's unfilled remainder.EXPIRE_BOTHremoves the maker's entire remaining quantity and cancels the incoming order's unfilled remainder.
No execution occurs between the two orders of the same Main Account.
Set the mode
OrderIntent.self_trade_prevention_mode carries the mode for single and batch order creation, on CreateOrder and BatchCreateOrders. REST and ConnectRPC ProtoJSON
use selfTradePreventionMode. A REST example:
{
"order": {
"symbol": "BTC-USDT",
"side": "BUY",
"baseQty": "0.01",
"selfTradePreventionMode": "EXPIRE_TAKER",
"limitGtc": {
"price": "50000.00"
}
}
}For protobuf requests, set one of the defined orders.v1.SelfTradePreventionMode values. Generated SDKs expose the same three effective modes
using language-specific names and casing.
EXPIRE_MAKER, sending the intended mode makes order intent,
logs, and client-side reconciliation easier to audit.Post-only and fill-or-kill
Post-only validation takes precedence over STP. A post-only order that would cross the book is
rejected with ERROR_CODE_POST_ONLY_CROSS; STP does not cancel either order.
For fill-or-kill (FOK), makers of the same Main Account are not executable liquidity:
- With
EXPIRE_MAKER, Polyester ignores makers of the same Main Account when checking whether enough eligible liquidity exists. If the FOK order can fill completely from other accounts, the makers of the same Main Account that it reaches expire and matching continues. - With
EXPIRE_TAKERorEXPIRE_BOTH, reaching a maker of the same Main Account prevents the FOK order from completing.
An FOK order that cannot complete is rejected with ERROR_CODE_FOK_INSUFFICIENT_LIQUIDITY. The
failed FOK attempt does not partially fill or cancel a resting maker.
Trigger child orders
Standalone trigger intents, created with CreateTrigger, use the same enum in TriggerIntent.self_trade_prevention_mode. The selected mode is applied to each child order when it
reaches the order book. If the field is omitted, child orders default to EXPIRE_MAKER.
When STP cancels a child order of a ladder, only that child order ends canceled, with the STP cancel
reason. The ladder itself does not end canceled: once all of its children have ended, it ends COMPLETED, keeping the fills its children made.
An attached take-profit, stop-loss, or trailing stop never trades with the order it protects. If the
order still rests when a leg fires, Polyester first cancels its unfilled remainder, with cancel
reason ATTACHED_EXIT_FIRED, then places the exit. This happens whatever the STP mode, and the
fills the order made before the cancel stand.
When EXPIRE_TAKER or EXPIRE_BOTH expires an attached exit order because it would trade with
another of your resting orders, the leg does not end: it stays armed. The expired exit ends CANCELED with its STP cancel reason, and the leg's trigger records one EVENT_FIRE_REFUSED with fire_refusal_reason TRIGGER_FAILURE_REASON_SELF_TRADE_PREVENTED (REST SELF_TRADE_PREVENTED),
whose child_seq names that exit.
While the blocking order is still first in line at the best price, the leg does not fire again, whatever price it watches: new trades and index price updates do not re-fire it. Once that order is gone, filled or canceled, or no longer first at the best price, the leg fires once if its trigger condition still holds; otherwise it waits for the next price that meets it. See Attached take-profit and stop-loss.
Observe the outcome
STP can end an order after its create request was accepted. Use order reads and private realtime
updates to observe the resulting cancellation or remaining working order. Persist the effective selfTradePreventionMode returned with each order so reconciliation reflects the instruction that
Polyester applied.