Trigger Order
Open a basket when a market condition is met.
A trigger order rests off-book and opens the basket when a condition is met, evaluated against live oracle prices.
Create one with POST /triggers/open (SDK: sdk.core.triggers.open).
Request
This opens the long BTC / short ETH basket when the BTC/ETH price ratio falls to 24:
{
"type": "MARKET",
"intent": "OPEN",
"condition": {
"type": "ratio",
"data": {
"priceSource": "mid",
"symbol_a": "0",
"symbol_b": "1",
"track": "TROUGH",
"threshold": 24
}
},
"payload": {
"type": "MARKET",
"legs": [
{ "symbol": "0", "side": "BUY", "mode": "USD", "amount": "500" },
{ "symbol": "1", "side": "SELL", "mode": "USD", "amount": "500" }
]
},
"clientId": "your-client-id"
}The condition decides when. The payload is the basket to open.
payload is not the same shape as POST /trade/open. It holds exactly two keys:
payload key | Value |
|---|---|
type | "MARKET", the only value. |
legs | The basket legs, each symbol, side, mode, amount. At least one leg, each a distinct instrument. |
Any other key inside payload, or inside one of its legs, is a 400. clientId and trigger are legal on /trade/open and rejected here. Put clientId at the top level of the trigger body instead. There is no trigger field; use attachTriggers below.
The body itself rejects unknown top-level keys the same way, where /trade/open drops them without complaint.
Direction: track
Every open condition carries track, which sets the side the value has to come from:
track | Fires when |
|---|---|
PEAK | The tracked value rises to threshold. |
TROUGH | The tracked value falls to threshold. |
Conditions
type | data | Fires on |
|---|---|---|
ratio | priceSource, symbol_a, symbol_b, track, threshold | The price ratio of two instruments. |
price | priceSource, symbol, track, threshold | One instrument's price. |
weighted_ratio | priceSource, basket, track, threshold | A whole basket's weighted ratio. |
funding_rate | connector, symbol, track, threshold | An instrument's funding rate. |
btc_dominance | source: "coingecko", track, threshold | BTC dominance, in percent. |
prediction_market | source: "kalshi" | "polymarket", marketKey, track, threshold | A prediction-market outcome price. |
priceSource is mid or mark. For weighted_ratio, basket legs need at least two distinct instruments whose weights sum to exactly 1. For ratio, symbol_a and symbol_b must be different instruments.
threshold must be greater than 0 on every condition except funding_rate, which accepts any number. Funding rates go negative, so a negative or zero threshold is valid there and only there.
Every instrument ID inside a condition is checked against GET /instruments when you create the trigger. An unknown ID returns 422, not 400. See Error Handling.
A BTC-dominance trigger looks like this:
{
"condition": {
"type": "btc_dominance",
"data": { "source": "coingecko", "track": "PEAK", "threshold": 60 }
}
}Read the current value from GET /oracle/btcdom before you pick a threshold.
Attach a take profit or stop loss
attachTriggers arms close triggers on the position the moment the open trigger fires, so the basket is never unprotected:
{
"attachTriggers": [
{
"intent": "CLOSE",
"type": "MARKET",
"bracketType": "TAKE_PROFIT",
"condition": { "type": "upnl_bps", "data": { "priceSource": "mark", "threshold": 500 } }
}
]
}attachTriggers is accepted only on intent: "OPEN".
Conditions you can attach
An attachment accepts a narrower condition set than POST /triggers/close. Only these six types are legal. ratio, price, weighted_ratio, and prediction_market are rejected here.
| Form | type | data | Measures |
|---|---|---|---|
| Static | notional | priceSource, threshold | Position notional in USD. threshold must be greater than 0. |
| Static | upnl | priceSource, threshold | Unrealized PnL in USD. threshold must not be 0. |
| Static | upnl_bps | priceSource, threshold | Unrealized PnL in basis points. threshold must not be 0. |
| Trailing | notional_trailing | priceSource | Position notional, trailing its best value. |
| Trailing | upnl_trailing | priceSource | Unrealized PnL in USD, trailing its best value. |
| Trailing | upnl_bps_trailing | priceSource | Unrealized PnL in basis points, trailing its best value. |
A static attachment carries bracketType, which is TAKE_PROFIT or STOP_LOSS, and puts the level in condition.data.threshold.
A trailing attachment carries a trailing object instead, and carries no bracketType:
{
"attachTriggers": [
{
"intent": "CLOSE",
"type": "MARKET",
"condition": { "type": "upnl_bps_trailing", "data": { "priceSource": "mark" } },
"trailing": { "mode": "RELATIVE_BPS", "deltaValue": 300, "activationValue": 500 }
}
]
}mode is RELATIVE_BPS or ABSOLUTE_POINTS. deltaValue must be greater than 0, and in RELATIVE_BPS an integer from 1 to 5000. activationValue is optional; omit it to start trailing immediately.
Limits, and what happens when they are exceeded
| Limit | Where it is enforced |
|---|---|
At most 5 entries in attachTriggers | On the request. A 6th entry is a 400. |
| At most one trailing entry | On the request. A second is a 400. |
| A position holds at most 5 active triggers, at most one of them trailing | When the open trigger fires. |
The two are enforced differently, and the difference matters. Your request is checked against an empty position, because the trigger opens a new one. By the time the trigger fires, that position may already carry triggers you added another way.
At fire time, attachments are admitted in request order until a limit is reached. Attachments that would push the position past 5 active triggers, or add a second trailing trigger, are discarded. The open still succeeds. Pear records one notification listing every discarded attachment, which you can read with GET /notifications. Re-create the ones you still want with POST /triggers/close.
Adjust an existing position instead
Set intent: "ADJUST" and add positionId to grow an open position when the condition is met, rather than opening a new one.
An ADJUST body takes exactly these keys and nothing else:
| Field | Value |
|---|---|
intent | "ADJUST". |
type | "MARKET". |
positionId | The position UUID. |
condition | Any of the six open conditions above. |
payload | { "type": "MARKET", "legs": [...] }, as on an OPEN trigger. |
clientId | Optional attribution code. |
attachTriggers is not accepted on an ADJUST trigger. The body is strict, so carrying the field over from an OPEN example is a 400. The position already exists, so arm its brackets directly with POST /triggers/close.
Response
200 OK, not 202. The body carries the trigger that now rests:
{
"trigger": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"tradeAccountId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"connector": "hyperliquid",
"type": "MARKET",
"intent": "OPEN",
"status": "ACTIVE",
"bracketType": "NONE",
"condition": {
"type": "ratio",
"data": {
"priceSource": "mid",
"symbol_a": "0",
"symbol_b": "1",
"track": "TROUGH",
"threshold": 24
}
},
"context": {
"type": "MARKET",
"legs": [
{ "symbol": "0", "side": "BUY", "mode": "USD", "amount": "500" },
{ "symbol": "1", "side": "SELL", "mode": "USD", "amount": "500" }
],
"attachedTriggers": [{ "kind": "STATIC", "bracketType": "TAKE_PROFIT" }]
},
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
}| Field | Meaning |
|---|---|
id | Trigger UUID. Cancel the trigger with it. |
status | ACTIVE on creation. |
bracketType | NONE on an open trigger. Brackets belong to close triggers. |
context.legs | The payload.legs you sent. |
context.attachedTriggers | One summary per attachTriggers entry: kind is STATIC or TRAILING, with the bracketType it will be created as. A trailing attachment reports STOP_LOSS. Empty when you sent none. |
context.attachedTriggers is a summary, not a list of triggers. The close triggers do not exist until the open trigger fires.
Manage triggers
| Action | Endpoint |
|---|---|
| List | GET /triggers |
| Cancel one | PATCH /triggers/{triggerId}/cancel |
| Cancel all | PATCH /triggers/cancel-all |
Creating a trigger emits trigger.created over the WebSocket. When it fires it emits trigger.triggered, then position.created. Cancelling emits trigger.cancelled. If a position for the same instruments appears while an OPEN trigger is still resting, Pear rewrites that trigger into an ADJUST trigger bound to the new position and emits trigger.converted.