client.triggers is the standalone automation surface: create a trigger, and when its condition
fires it places a child order for you. Every method is authenticated and account-scoped, so each
input accepts an optional account field ("main", "active", or { subaccountId }). See Account scoping for how the default resolves.
Triggers come in five types, selected by triggerType: stop_loss, take_profit, trailing_stop, twap, and ladder. Each strategy has an explicit configuration and child
execution. Prices and distances are decimal strings, validated against the pair's scale before
the request leaves your process. A decimal above the pair's protobuf wire-format ceiling throws CatalogConversionError before network I/O. The ceiling is not an exchange limit.
Unlike the take-profit, stop-loss, and trailing legs you can attach to an order through client.orders, a trigger created here has no parent order:
it stands on its own and fires against live market data.
Methods
| Method | Summary |
|---|---|
create | Create a trigger of any of the five types. |
get | Fetch one trigger by id, or null. |
list | List triggers with symbol ID, status, type, and parent-order filters. |
modify | Patch a live trigger's price, distance, or slippage fields. |
cancel | Cancel a trigger by id. |
pause | Pause a trigger without discarding its configuration. |
resume | Resume a paused trigger. |
listEvents | Read a trigger's lifecycle events, newest first. |
subscribe | Stream live trigger state over a private channel. |
subscribeEvents | Stream trigger lifecycle events over a private channel. |
create(input, options?)
Creates a standalone trigger and returns a CreateTriggerResult. The input is a triggerType variant, so the fields you supply depend on the type. When you omit clientTriggerId, the SDK
generates one for you and returns it in the result.
await client.catalog.ensureReady();
const symbolId = client.catalog.market.requireSymbolIdByPairSymbol("BTC-USDT");
// Stop-loss: submit a market IOC sell when the condition fires
const result = await client.triggers.create({
triggerType: "stop_loss",
symbolId,
side: "sell",
qty: "0.25",
triggerPrice: "60000",
execution: { type: "market_ioc" },
});
console.log(result.triggerId, result.clientTriggerId, result.acceptedAt);// Trailing stop: follow the price up, sell if it drops 500 back
await client.triggers.create({
triggerType: "trailing_stop",
symbolId,
qty: "0.25",
trailingDistance: { kind: "distance", distance: "500" },
activationPrice: "70000",
maxSlippage: { kind: "bps", bps: 50 },
});
// Buy trailing stop: follow the price down, buy if it rises 500 back, paying at most 66000
await client.triggers.create({
triggerType: "trailing_stop",
symbolId,
side: "buy",
qty: "0.25",
trailingDistance: { kind: "distance", distance: "500" },
activationPrice: "65000",
limitPrice: "66000",
});interface CreateTriggerResult {
triggerId: string;
clientTriggerId: string;
acceptedAt: number; // epoch ms
acceptedAtNs: string;
}This result acknowledges admission only. Read or subscribe to the trigger for its runtime status.
Common fields
| Field | Type | Required | Notes |
|---|---|---|---|
symbolId | number | yes | Stable market ID, integer 1 through 4,294,967,295. |
qty | decimal string | yes | Total base-asset quantity. Strict precision. |
feeAsset | "quote" | "base" | no | Defaults to "quote". |
selfTradePreventionMode | "expire_taker" | "expire_maker" | "expire_both" | no | Defaults to "expire_maker". |
clientTriggerId | string | no | Idempotency key. Auto-generated when omitted. |
account | AccountScope | no | Scope override. |
Per-type fields
stop_loss/take_profit:side,triggerPrice,execution, and optionaltriggerPriceSource. Either side accepts{ type: "market_ioc", maxSlippage? }or a limit execution:{ type: "limit_gtc", price, postOnly? },{ type: "limit_ioc", price }, or{ type: "limit_fok", price }. Omit the market IOCmaxSlippageto use the pair's default market slippage; a BPS value is 1 through 10,000 for a buy and 1 through 9,999 for a sell.trailing_stop:trailingDistance(required, a tagged shape:{ kind: "distance", distance: "500" }or{ kind: "bps", bps: 50 }, BPS 1 through 10,000), plus optionalactivationPrice(decimal string),maxSlippage({ kind: "slippage", slippage: "0.25" },{ kind: "bps", bps: 50 }from 1 through 9,999, or{ kind: "none" }), andtriggerPriceSource. Its child executes as market IOC.sidedefaults to"sell", which rejectslimitPrice. Passside: "buy"with alimitPrice, the most the child pays per unit; it must be at leastactivationPricewhen that is set.twap:side,durationMs(at least 1000ms),sliceIntervalMs(at least 100ms and no longer than the duration; a longer interval throwsValidationErrorwithsliceIntervalMs cannot exceed durationMs), andexecution:{ type: "market_ioc", maxSlippage? }or{ type: "limit_gtc", price }. For market IOC,maxSlippageprotects each slice. Omit it to use the pair default, or set an absolute decimal price delta with{ kind: "slippage", slippage: "0.25" }or an integer BPS limit from 1 through 10,000 with{ kind: "bps", bps: 50 }.ladder:side,priceMin(belowpriceMax),priceMax,levels(2 to 100), and optionalpostOnly. Children are distributed linearly as limit GTC orders.
triggerPriceSource is "last" (the default when omitted) or "index". "index" watches the
market's index price and waits while it is stale. Spot markets do not support "mark", which the
SDK rejects with ValidationError.
An OutcomeUnknownError with TRIGGER_CREATE_OUTCOME_UNKNOWN means the trigger may already exist:
list triggers and match its clientTriggerId before creating it again. See Errors.
CatalogConversionError from @polyester/sdk/catalogs before network I/O.
The same applies when a value exceeds the corresponding SpotOrderConstraints wire
ceiling. The catalog validator checks parse, tick, step, and minimum rules, but not the wire
ceilings. See the catalog reference.get(input, options?)
Fetches one trigger by triggerId, or null when it is not found.
const trigger = await client.triggers.get({ triggerId: result.triggerId });
if (trigger) {
console.log(trigger.status, trigger.configuration, trigger.runtimeDetails);
}list(input?, options?)
Returns { triggers, nextPageToken }, newest first. Filter by symbolId, status (an array of
status labels), triggerType, and parentOrderId, and page with limit (default 50) and pageToken.
const { triggers } = await client.triggers.list({
symbolId,
status: ["created", "armed", "running"],
triggerType: "trailing_stop",
});status accepts "created", "armed", "running", "completed", "canceled", "failed", or "paused". A trigger on a symbol the catalog does not know fails the page instead of being
dropped from it: refresh the catalog with client.catalog.refresh() and read again.
modify(input, options?)
Patches a live trigger by triggerId. Supply at least one of triggerPrice, trailingDistance, activationPrice, or maxSlippage; supplying none throws. Omit activationPrice or maxSlippage to preserve it, or pass { kind: "none" } to clear either; a cleared maxSlippage on a market child falls back to the pair's default market slippage. Price patches must be
positive. trailingDistance accepts a decimal distance or BPS from 1 through 10,000; maxSlippage accepts a decimal slippage or BPS from 1 through 9,999.
A child's limit price is fixed at creation. To change it, cancel the trigger and create a new one.
symbolId is optional: omitted, it targets the trigger's own market; when set, it must be that
market. An API key limited to specific markets must still pass it.
await client.triggers.modify({
triggerId: trigger.triggerId,
triggerPrice: "59500",
});
// Widen a trailing stop and cap its slippage
await client.triggers.modify({
triggerId: trailing.triggerId,
symbolId,
trailingDistance: { kind: "distance", distance: "750" },
maxSlippage: { kind: "bps", bps: 40 },
});Returns { triggerId, status, ts, tsNs }, where ts is epoch milliseconds and tsNs is the
exact epoch-nanosecond decimal string.
cancel(input, options?)
Cancels a trigger by triggerId and returns a CancelTriggerResult ({ triggerId, status, ts, tsNs }).
await client.triggers.cancel({ triggerId: trigger.triggerId });A refused cancel fails with TRIGGER_STATUS_INVALID (its status does not allow it) or TRIGGER_NOT_MODIFIABLE (its type or state does not support it).
pause(input, options?) / resume(input, options?)
Pause preserves the trigger's immutable configuration. resume takes an optional symbolId with
the same rule as modify. Both methods return { triggerId, status, ts, tsNs }.
await client.triggers.pause({ triggerId: trigger.triggerId });
await client.triggers.resume({ triggerId: trigger.triggerId });For modify, cancel, pause, and resume, an OutcomeUnknownError with TRIGGER_CONTROL_OUTCOME_UNKNOWN means the change may already have applied: read the trigger with get before sending it again.
listEvents(input, options?)
Returns { events, nextPageToken } for one trigger's lifecycle, newest first. limit defaults to 50. Events cover fires, cancels, updates, failures, activations, arming, refused fires, and the child orders a trigger places.
const { events } = await client.triggers.listEvents({
triggerId: trigger.triggerId,
limit: 20,
});
for (const event of events) {
console.log(
event.eventType,
event.firePrice,
event.childOrderId,
event.cancelReason,
event.failureReason
);
}import type { TriggerEvent } from "@polyester/sdk";firePrice is optional. Time-scheduled events such as TWAP slices can legitimately have no fire
price; treat undefined as unavailable, not as zero. Each event carries ts (epoch milliseconds),
the exact tsNs, and childSeq. Pass any eventType label except "unspecified" as the
optional eventType input to filter the list.
eventType | Meaning |
|---|---|
fired | The trigger fired and may have created a child order. |
canceled | The trigger was canceled. |
updated | The trigger's configuration or lifecycle state was updated. |
failed | The trigger became terminal after its next child order could not be admitted. |
activated | A trailing stop's activation condition was met and tracking began, standalone or attached. |
armed | An attached take-profit, stop-loss, or trailing-stop leg started protecting its parent order, once, when the parent's first fill made it live (or when a leg paused before that fill resumes). Not terminal. |
fire_refused | A fire was refused and the trigger stayed live. Not terminal. fireRefusalReason says why, using the failure reason labels, and childSeq names the refused attempt. With self_trade_prevented, childSeq names the child order self-trade prevention expired instead. |
unspecified | The server omitted the type or sent one newer than this SDK build. |
cancelReason and failureReason are mutually exclusive typed terminal codes. Canceled records
set cancelReason; failed records set failureReason; other records leave both undefined. The
codes use stable snake-case labels.
cancelReason is one of user_request, oco, parent_canceled_no_fill, self_trade_prevention, account_repair, pair_drain_trigger, pair_drain_twap, pair_drain_ladder, pair_drain_attached_leg, market_halted, missing_reason_code, internal_error, or unspecified. Show the pair_drain_* reasons as "market disabled" and market_halted as "market halted", not as a user cancel. self_trade_prevention means
self-trade prevention canceled the trigger's child orders.
failureReason labels are the lowercased reason names, such as insufficient_funds, max_open_orders_per_pair, index_price_unavailable, trigger_would_trigger_immediately, child_order_inconsistent, account_repairing, duplicate_order_id, and market_quote_stale. self_trade_prevented appears only as a fireRefusalReason.
subscribe(input)
Streams live trigger state over a private channel. Takes an accountId plus the standard handler
fields, and returns an idempotent unsubscribe function. See the realtime client reference for the handler contract.
const unsubscribe = client.triggers.subscribe({
accountId,
onEvent: (trigger) => console.log(trigger.status, trigger.triggerId),
onError: (ctx) => console.error(ctx.channel, ctx.error),
});
// later
unsubscribe();Each trigger carries version: 1 for its first state and higher for every later change. Across get, list, and subscribe, keep the copy with the highest version and ignore any copy whose
version is not higher than the one you hold, since a reconnect can repeat an earlier state and
list results can lag.
subscribeEvents(input)
Streams trigger lifecycle events (fires, cancels, updates, failures, activations, arming, refused fires, child orders) over a private channel.
Same handler contract as subscribe, emitting TriggerEvent records. Each publication batches
events, which arrive one at a time through onEvent. (triggerId, eventType, childSeq, tsNs) identifies an event, and a repeated identity replaces the earlier copy. A malformed event reaches onError without dropping the rest of its batch.
const unsubscribe = client.triggers.subscribeEvents({
accountId,
onEvent: (event) => console.log(event.eventType, event.firePrice),
});Managing a trigger's lifecycle
The management methods compose into a full lifecycle: create, inspect, adjust, and cancel.
// Create
const { triggerId } = await client.triggers.create({
triggerType: "stop_loss",
symbolId,
side: "sell",
qty: "0.25",
triggerPrice: "60000",
execution: { type: "market_ioc" },
});
// Inspect what is live
const { triggers } = await client.triggers.list({ status: ["created", "armed"] });
// Move the stop up as the position runs
await client.triggers.modify({ triggerId, symbolId, triggerPrice: "62000" });
// Read what happened
const { events } = await client.triggers.listEvents({ triggerId });
// Tear it down
await client.triggers.cancel({ triggerId });The Trigger shape
get, list, and subscribe share one parsed shape. Each trigger carries symbolId, not a pair
symbol; use the catalog for its display label. Prices and quantities are decimal strings, and
timestamps are epoch milliseconds.
import type { Trigger } from "@polyester/sdk";configuration is the immutable strategy union:
{ type: "stop_loss" | "take_profit", side, triggerPrice, execution }, where a market IOC execution decodes itsmaxSlippageand alimit_gtcexecution itspostOnly{ type: "trailing_stop", side, trailingDistance, activationPrice?, maxSlippage, limitPrice? }, withlimitPriceset for a buy trailing stop{ type: "twap", side, durationMs, sliceIntervalMs, execution }, where market IOC execution can set per-slicemaxSlippageor omit it for the pair default{ type: "ladder", side, priceMin, priceMax, levels, postOnly }{ type: "unspecified" }
Executions use the same lowercase tags as create inputs. Parsed read models may use { type: "unspecified" } when the server omits a configuration branch. There is no childOrderIds field on Trigger. Terminal triggers also expose the same mutually exclusive cancelReason and failureReason fields as events. version orders a trigger's states (see subscribe).
runtimeDetails is separate from configuration and carries mutable strategy state:
{ case: "stop", triggerPrice, triggerPriceSource, triggerDirection }{ case: "trailing", trailingDistance?, trailingDistanceBps, activationPrice?, peakPrice?, troughPrice?, triggerPrice?, maxSlippage?, maxSlippageBps, triggerPriceSource, triggerDirection }{ case: "twap", twapDurationMs, twapSliceIntervalMs, sliceIdx, sliceCount, executedQty }{ case: "ladder", ladderPriceMin, ladderPriceMax, ladderLevels, ladderDistribution, executedQty, executedLevels }{ case: undefined }
For a trailing stop, triggerPrice is the current threshold. It is undefined until the trigger
is armed, then moves with the peak or trough.
executedQty is the cumulative filled child-order base quantity as a decimal string. For a ladder, executedLevels counts levels with at least one fill, including partially filled levels.
Related
- Trading guide for the task-oriented walkthrough.
- Orders for spot orders and attached (parent-bound) risk.
- Trades for the fills a trigger's child orders produce.
- Errors for validation and precision error types.