PearPear

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 keyValue
type"MARKET", the only value.
legsThe 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:

trackFires when
PEAKThe tracked value rises to threshold.
TROUGHThe tracked value falls to threshold.

Conditions

typedataFires on
ratiopriceSource, symbol_a, symbol_b, track, thresholdThe price ratio of two instruments.
pricepriceSource, symbol, track, thresholdOne instrument's price.
weighted_ratiopriceSource, basket, track, thresholdA whole basket's weighted ratio.
funding_rateconnector, symbol, track, thresholdAn instrument's funding rate.
btc_dominancesource: "coingecko", track, thresholdBTC dominance, in percent.
prediction_marketsource: "kalshi" | "polymarket", marketKey, track, thresholdA 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.

FormtypedataMeasures
StaticnotionalpriceSource, thresholdPosition notional in USD. threshold must be greater than 0.
StaticupnlpriceSource, thresholdUnrealized PnL in USD. threshold must not be 0.
Staticupnl_bpspriceSource, thresholdUnrealized PnL in basis points. threshold must not be 0.
Trailingnotional_trailingpriceSourcePosition notional, trailing its best value.
Trailingupnl_trailingpriceSourceUnrealized PnL in USD, trailing its best value.
Trailingupnl_bps_trailingpriceSourceUnrealized 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

LimitWhere it is enforced
At most 5 entries in attachTriggersOn the request. A 6th entry is a 400.
At most one trailing entryOn the request. A second is a 400.
A position holds at most 5 active triggers, at most one of them trailingWhen 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:

FieldValue
intent"ADJUST".
type"MARKET".
positionIdThe position UUID.
conditionAny of the six open conditions above.
payload{ "type": "MARKET", "legs": [...] }, as on an OPEN trigger.
clientIdOptional 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"
  }
}
FieldMeaning
idTrigger UUID. Cancel the trigger with it.
statusACTIVE on creation.
bracketTypeNONE on an open trigger. Brackets belong to close triggers.
context.legsThe payload.legs you sent.
context.attachedTriggersOne 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

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.

On this page