client.triggers is the standalone automation surface. When a trigger's condition fires, the venue
places a child order for you. Methods are authenticated and account-scoped (account / sub_account_id).
Unlike attached risk on client.orders, a trigger created here
has no parent order: it stands alone against live market data.
Trigger types (trigger_type): stop_loss, take_profit, trailing_stop, twap, ladder.
post_only is only valid for limit GTC. Market / IOC / FOK
children raise PolyesterValidationError.Methods
| Method | Summary |
|---|---|
create | Create a trigger of any of the five types. |
get | Fetch one trigger by id, or None. |
list | List triggers (symbol / status filters). |
modify | Patch live price, distance, or slippage fields. |
cancel | Cancel a trigger by id. |
pause / resume | Pause or resume a trigger. |
list_events | List paginated lifecycle events. |
subscribe | Stream live trigger state (private). |
subscribe_events | Stream trigger lifecycle events (private). |
Create
result = await client.triggers.create(
symbol="BTC-USDT",
trigger_type="stop_loss",
side="sell",
qty="0.25",
order_type="market",
tif="ioc", # market children execute as market-IOC; tif is unused for market
trigger_price="60000",
client_trigger_id="sl-001",
)
print(result.trigger_id, result.status) # status == "accepted"A trailing stop may sell or buy. A buy trailing stop requires limit_price, the most its child pays
per unit:
await client.triggers.create(
symbol="BTC-USDT",
trigger_type="trailing_stop",
side="sell",
qty="0.25",
trailing_distance_ticks=500_000_000, # distance in 1e9 price ticks
activation_price="70000",
max_slippage_bps=50,
)
await client.triggers.create(
symbol="BTC-USDT",
trigger_type="trailing_stop",
side="buy",
qty="0.25",
trailing_distance_bps=100,
limit_price="85000", # maximum price, fixed at create
)Returns TriggerMutationResult (trigger_id, status). Create responses synthesize status="accepted" (admission only, the wire create response no longer carries a lifecycle
enum). Pause / resume / cancel / modify return lifecycle labels such as armed, paused, cancelled.
Common child-order fields
symbol(required): e.g."BTC-USDT"side(required):"buy"|"sell"qty(required): decimal /Quantityorder_type(optional): default"market"tif(optional): default"gtc"limit_price(with a limit child)post_only(optional): limit GTC onlyfee_asset(optional):"quote"|"base"(baseis BUY-only)self_trade_prevention_mode(optional):expire_taker/expire_maker/expire_bothclient_trigger_id(optional): Idempotency keyaccount/sub_account_id(optional): Scope
Per-type fields
stop_loss/take_profit:trigger_price.trigger_price_sourceis"last"(default) or"index"; spot markets have no mark price, so"mark"raisesPolyesterValidationError. A market child takes an optional cap,max_slippage_ticksormax_slippage_bps(1 to 10000 to buy, 1 to 9999 to sell), at most one; a limit child takes none, since its limit price bounds it.trailing_stop:sideis"sell"or"buy". Supplytrailing_distance_ticksortrailing_distance_bps, optionalactivation_price,max_slippage_ticks/max_slippage_bps. A buy requireslimit_price, its maximum price, at leastactivation_pricewhen that is set; a sell takes none.order_type,tif, andpost_onlydo not alter this strategy.max_slippage_bpsis 1 to 9999: the SDK raisesPolyesterValidationErrorfor 10,000, and the API rejects it withMAX_SLIPPAGE_INVALID. Amax_slippage_ticksat or above the price at which a sell stop fires is not rejected; the stop then sells at the lowest valid price. An activation price the market has already reached ends the triggerfailedwith reasonTRIGGER_FAILURE_REASON_TRIGGER_WOULD_TRIGGER_IMMEDIATELY; to start trailing from the market, leave it out.twap:twap_duration_ms,twap_slice_interval_ms.ladder:ladder_price_min,ladder_price_max,ladder_levels,ladder_distribution("linear").
Strategy capability matrix
stop_loss/take_profit: buy or sell. Child is market-IOC or limit (with TIF). Requirestrigger_price. Pause, resume, cancel, and modify are supported.trailing_stop: buy or sell. Requires a trailing distance in ticks or bps, andlimit_priceon a buy. Pause, resume, and cancel are supported; modify patches distance, activation, and slippage.twap: buy or sell, sliced market child. Requirestwap_duration_msandtwap_slice_interval_ms. The server may reject lifecycle operations that do not apply mid-run.ladder: buy or sell, multi-level limit child. Requires ladder min, max, and levels, with distribution"linear". The server may reject lifecycle operations that do not apply mid-run.
Ladder entry quantity is not guaranteed to equal the sum of child limit sizes after step / fee rounding. Child aggregate qty may be lower than the requested entry. TWAP / ladder creates are not guaranteed bulk atomic placement of every child; if a mid-run lifecycle op is rejected or children land partially, fall back to single-order placement and explicit cleanup of residual children / open orders. Pause may be rejected for TWAP even when pause appears in generic lifecycle helpers.
The SDK exposes pause/resume/cancel/modify for any trigger ID. Strategy-specific restrictions are enforced by the API; treat unexpected rejections as unsupported for that strategy state.
Limits per Pair
Each trading account (Main Account or Subaccount) can hold a limited number of triggers that have not ended on each Pair. Other accounts and other Pairs are unaffected. A create past a limit is rejected and nothing is created; retry once one of the account's triggers on that Pair ends.
- 100: All strategies, attached take-profit, stop-loss, and trailing legs included. Rejected with
MARKET_TRIGGER_LIMIT_EXCEEDED(293). - 20: TWAP. Rejected with
MARKET_TWAP_LIMIT_EXCEEDED(294). - 5: Ladder. Rejected with
MARKET_LADDER_LIMIT_EXCEEDED(295).
TWAPs and ladders also count toward the 100-trigger limit, which is checked first, so a TWAP or
ladder create past it is rejected with MARKET_TRIGGER_LIMIT_EXCEEDED. Each TWAP or ladder counts
once, however many slices or levels it has. An order whose attached legs would pass the 100-trigger
limit is rejected with MARKET_TRIGGER_LIMIT_EXCEEDED, and neither the order nor its legs is
placed.
Public IDs
Non-zero trigger_id values returned by create, list, and subscriptions are base58 encodings of the underlying uint64; wire zero is represented as the sentinel string "0".
Pass returned IDs back as strings to get / pause / resume / cancel / modify / list_events. Helpers polyester.codecs.scalars.id_to_int / format_id convert when you need the integer
form. Some canonical base58 encodings contain only digits (for example "2"), so digit-only does
not necessarily mean decimal. The parser prefers a canonical base58 round-trip before the decimal
fallback. See Public IDs.
Manage triggers
trigger = await client.triggers.get(trigger_id=result.trigger_id)
if trigger:
print(trigger.status, trigger.trigger_type, trigger.details)
page_token = None
while True:
listed = await client.triggers.list(
symbol="BTC-USDT",
status=["created", "armed", "running"], # or a single str
limit=50,
page_token=page_token,
)
# Unknown status labels raise PolyesterValidationError; they do not silently return empty.
for trigger in listed.triggers:
print(trigger.status, trigger.trigger_id)
page_token = listed.next_page_token or None
if not page_token:
break
await client.triggers.modify(trigger_id=result.trigger_id, trigger_price="59500")
await client.triggers.pause(trigger_id=result.trigger_id)
await client.triggers.resume(trigger_id=result.trigger_id)
await client.triggers.cancel(trigger_id=result.trigger_id)Status vocabulary: created, armed, running, completed, cancelled, failed, paused.
Canceled and failed triggers expose cancel_reason / failure_reason labels (empty when unset).
modify and resume do not need a symbol: without one they target the trigger's own market. An API
key limited to specific markets must give it. On modify, omit activation_price, max_slippage_ticks, and max_slippage_bps to preserve the current values, or send an explicit zero
to clear them. A child's limit price is fixed at create: cancel the trigger and create a new one
instead. A modify that would make a trailing stop act at once is refused with ERROR_CODE_TRIGGER_WOULD_TRIGGER_IMMEDIATELY, leaving the existing state unchanged.
trigger_price and activation_price on modify take a decimal string, Decimal, or Price.
When you give symbol or symbol_id, a Price bound to another market raises PolyesterValidationError, as it does on create, and so does a bound Price when the catalogs
cannot check its market. Without a symbol the price is sent as given, so pass one for the
trigger's market or an unbound one.
get and list show a trigger as the latest answered modify, pause, or resume left it, or
answer unavailable until they can. Both resend the request on unavailable for about 2 seconds,
then raise PolyesterServerError. Trigger.version is 1 for a trigger's first state and higher for
every later state change; keep the copy with the highest version.
List events
page_token = None
while True:
events = await client.triggers.list_events(
trigger_id=result.trigger_id,
limit=20,
event_type="fired", # optional; see the event types below
page_token=page_token,
)
for event in events.events:
print(
event.event_type,
event.child_order_id,
event.fire_price,
event.cancel_reason,
event.failure_reason,
event.fire_refusal_reason,
event.ts_ns,
)
page_token = events.next_page_token or None
if not page_token:
breakChild orders are not on the trigger snapshot. Use list_events(..., event_type="fired") and read child_order_id / child_seq from each event, or orders.list_open(trigger_id=...) / orders.list_history(trigger_id=...) for the child orders themselves.
A fire_refused event records a fire refused while the trigger stayed live; fire_refusal_reason says why. It is not terminal. is_terminal_trigger_event(event_type) is true only for canceled and failed; read the trigger's status rather than inferring it from its last event.
Subscribe
states = await client.triggers.subscribe(account_id=account_id)
async with states:
async for trigger in states:
print(trigger.status, trigger.trigger_id)
break
ev_sub = await client.triggers.subscribe_events(account_id=account_id)
async with ev_sub:
async for event in ev_sub:
print(event.event_type, event.ts_ns)
breakThe Trigger shape
Selected fields: trigger_id, subaccount_id, symbol_id, symbol, trigger_type, status, parent_order_id, side, order_type, time_in_force, qty, limit_price, fee_asset, self_trade_prevention_mode, post_only, trigger_price, client_trigger_id, trigger_price_source, max_slippage / max_slippage_bps, created_at / updated_at / armed_at / completed_at, cancel_reason, failure_reason, version, details.
details.case is one of stop, trailing, twap, ladder, with the matching nested payload.
For TWAP, details.twap.executed_qty populates as slices fire; right after create it is typically
unset until the first child order is placed. Child-order history comes from list_events.
Decoded TriggerEvent exposes trigger_id, subaccount_id, symbol_id, trigger_type, event_type (fired, canceled, updated, failed, activated, armed, or fire_refused), ts_ns, child_seq, child_order_id, fire_price, cancel_reason, failure_reason, and fire_refusal_reason.