client.triggers manages standalone automations. Types: CreateTriggerType::{StopLoss, TakeProfit, TrailingStop, Twap, Ladder}.
post_only on conditional children is only valid for limit GTC.Methods
| Method | Summary |
|---|---|
create | CreateTriggerParams โ TriggerMutationResult |
get / get_by_id | Proto request or the string ID returned by the SDK |
list / list_with | Proto request or ListTriggersOpts |
modify | ModifyTriggerParams |
cancel / pause / resume | Proto request wrappers |
cancel_by_id / pause_by_id / resume_by_id | String-ID convenience helpers |
list_events | Paginated events via ListTriggerEventsRequest |
subscribe / subscribe_events | Private streams (recv) |
Create
use polyester::models::{
CreateOrderType, CreateSide, CreateTimeInForce, CreateTriggerParams, CreateTriggerType,
};
use polyester::{Price, Quantity};
let quantity_scale = client
.catalogs
.base_quantity_scale_for_symbol("BTC-USDT")
.ok_or_else(|| polyester::Error::validation("BTC-USDT quantity scale is unavailable"))?;
let result = client.triggers.create(CreateTriggerParams {
symbol: "BTC-USDT".into(),
trigger_type: CreateTriggerType::StopLoss,
side: CreateSide::Sell,
order_type: CreateOrderType::Market,
qty: Quantity::from_decimal_str("0.25", quantity_scale, Some("BTC-USDT".into()), None)?,
trigger_price: Some(Price::from_decimal_str("60000", Some("BTC-USDT".into()))?),
limit_price: None,
trigger_price_source: None, // unsupported values fail instead of being discarded
time_in_force: Some(CreateTimeInForce::Ioc), // market โ market-IOC; tif unused for market
subaccount_id: None,
client_trigger_id: "sl-001".into(),
post_only: false, // limit GTC children only
activation_price: None,
trailing_distance_ticks: None,
trailing_distance_bps: None,
max_slippage_ticks: None,
max_slippage_bps: None,
twap_duration_ms: None,
twap_slice_interval_ms: None,
ladder_price_min: None,
ladder_price_max: None,
ladder_levels: None,
ladder_distribution: None,
fee_asset: None,
self_trade_prevention_mode: None,
}).await?;Trailing stop requires trailing_distance_ticks or trailing_distance_bps. The wire strategy is
always SELL market-IOC, so the SDK requires side: CreateSide::Sell and rejects BUY rather than
silently changing intent. order_type, time_in_force, and post_only do not alter this strategy. client_trigger_id is required, and the SDK never generates it. Create and persist one stable ID
per logical trigger, then reuse it when reconciling or retrying an ambiguous result.
Create returns TriggerMutationResult with 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.
Strategy capability matrix
| Strategy | Side | Child execution | Required fields | Pause / resume / cancel / modify |
|---|---|---|---|---|
StopLoss / TakeProfit | buy or sell | market-IOC or limit (+ TIF) | trigger_price | Supported (*_by_id helpers) |
TrailingStop | sell only (BUY rejected) | always SELL market-IOC | trailing distance ticks or bps | Supported; modify patches distance / activation / slippage |
Twap | buy or sell | market-IOC or limit-GTC slices | twap_duration_ms, twap_slice_interval_ms | Supported; server may reject lifecycle ops that do not apply mid-run |
Ladder | buy or sell | multi-level limit | ladder min/max/levels; distribution linear | Supported; server may reject lifecycle ops that do not apply mid-run |
Ladder entry quantity is not guaranteed to equal the sum of child limit sizes after step / fee rounding, so 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.
Get by ID
// Canonical base58 string returned by create / list / subscribe.
let trigger_id = "3nK8xQmP2aBcDeFgHiJkLmNoPqRsTuVwXy";
let trigger = client.triggers.get_by_id(trigger_id, None).await?;The convenience helper accepts the base58 string returned by create, list, and subscriptions.
Canonical base58 can consist only of digits, so never decide that an ID is decimal from its
characters. Reuse IDs returned by the SDK. Use the lower-level get method only when you already
have a typed proto u64 ID.
List triggers
use polyester::models::ListTriggersOpts;
let mut page_token = None;
loop {
let listed = client.triggers.list_with(ListTriggersOpts {
symbol: Some("BTC-USDT".into()),
status: vec!["created".into(), "armed".into()],
limit: 50,
page_token,
..Default::default()
}).await?;
// Unknown status labels โ Error::Validation
for t in &listed.triggers {
println!("{} {}", t.status, t.trigger_id);
}
if listed.next_page_token.is_empty() {
break;
}
page_token = Some(listed.next_page_token);
}Modify
use polyester::models::ModifyTriggerParams;
use polyester::Price;
let trigger_id = "3nK8xQmP2aBcDeFgHiJkLmNoPqRsTuVwXy".to_owned();
client.triggers.modify(ModifyTriggerParams {
trigger_id,
subaccount_id: None,
trigger_price: Some(Price::from_decimal_str("59500", Some("BTC-USDT".into()))?),
limit_price: None,
activation_price: None,
trailing_distance_ticks: None,
trailing_distance_bps: None,
max_slippage_ticks: None,
max_slippage_bps: None,
}).await?;Pause, resume, and cancel
let trigger_id = "3nK8xQmP2aBcDeFgHiJkLmNoPqRsTuVwXy";
client.triggers.pause_by_id(trigger_id, None).await?;
client.triggers.resume_by_id(trigger_id, None).await?;
client.triggers.cancel_by_id(trigger_id, None).await?;List events
use polyester::proto::triggers::v1::{ListTriggerEventsRequest, TriggerEventType};
use polyester::codecs::scalars::id_to_u64;
let trigger_id = id_to_u64("3nK8xQmP2aBcDeFgHiJkLmNoPqRsTuVwXy", "trigger_id")?;
let mut page_token = String::new();
loop {
let events = client.triggers.list_events(ListTriggerEventsRequest {
trigger_id,
limit: 20,
// optional: EVENT_FIRED | EVENT_CANCELED | EVENT_UPDATED
event_type: TriggerEventType::EventFired.into(),
page_token: page_token.clone(),
..Default::default()
}).await?;
for ev in &events.events {
println!(
"{} {} {:?} {}",
ev.event_type, ev.child_order_id, ev.fire_price, ev.ts_ns
);
}
if events.next_page_token.is_empty() {
break;
}
page_token = events.next_page_token;
}Child orders are not on the trigger snapshot. Set event_type to EVENT_FIRED and read child_order_id / child_seq from each decoded event.
list_events is currently a proto-level method, so decode the SDK-returned canonical base58 ID
with id_to_u64 (returns polyester::Result, so ? maps decode failures into Error). Do not
parse an all-digit canonical base58 ID as decimal.
Subscribe
let account_id = client.default_account_id.as_deref();
let mut sub = client.triggers.subscribe(account_id).await?;
while let Some(t) = sub.recv_result().await? {
println!("{} {}", t.status, t.trigger_id);
break;
}
let mut ev = client.triggers.subscribe_events(account_id).await?;
while let Some(e) = ev.recv_result().await? {
println!("{} {}", e.event_type, e.ts_ns);
break;
}Decoded TriggerEvent exposes trigger_id, subaccount_id, symbol_id, trigger_type, event_type (fired / canceled / updated), ts_ns, child_seq, child_order_id, fire_price, and reason.
Decoded Trigger includes, for TWAP, details โ Twap โ executed_qty. That field 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.