PearPear

Take Profit / Stop Loss

Attach take-profit and stop-loss triggers to an open basket.

Arm a close trigger on an open basket with POST /triggers/close (SDK: sdk.core.triggers.close). It rests off-book and closes the whole position when the condition is met.

Request

Take profit at 5% unrealized PnL, measured in basis points:

{
  "type": "MARKET",
  "intent": "CLOSE",
  "positionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "bracketType": "TAKE_PROFIT",
  "condition": {
    "type": "upnl_bps",
    "data": { "priceSource": "mark", "threshold": 500 }
  },
  "clientId": "your-client-id"
}

Stop loss at 300 USD of unrealized loss:

{
  "type": "MARKET",
  "intent": "CLOSE",
  "positionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "bracketType": "STOP_LOSS",
  "condition": {
    "type": "upnl",
    "data": { "priceSource": "mark", "threshold": -300 }
  }
}

Take profit and stop loss are separate triggers. Arm both to bracket a position.

The call answers 200 with the resting trigger. Nothing trades until it fires.

How many a position can hold

LimitValue
Active triggers per position5
Trailing triggers per position1

A sixth is rejected with You cannot have more than 5 active triggers for a position. A second trailing is rejected with Trailing stop loss trigger already exists for this position. Ladder rungs do not count against the 5.

bracketType

bracketType is required on a static close trigger. It sets which way the measured value must cross threshold, which is why a close condition carries no track field. That is the difference from an open trigger, which does.

bracketTypeFires when the value
TAKE_PROFITreaches or passes threshold from below
STOP_LOSSdrops below threshold
NONEreaches or passes threshold from below, with no profit or loss label. Same crossing as TAKE_PROFIT.

A trailing close omits bracketType.

On a price condition the labels invert for a short leg

For every condition type except price, bracketType is taken as your intent and stored as you sent it.

price is different. It watches one instrument's price, and a rising price is profit only if you are long that instrument. bracketType stays a crossing direction, so on the short leg of a pair the profit and loss labels swap:

Watched leg"TAKE_PROFIT""STOP_LOSS"
LongFires above threshold — your profit.Fires below threshold — your loss.
ShortFires above threshold — your loss.Fires below threshold — your profit.

To arm a price stop loss on the short leg, send bracketType: "TAKE_PROFIT". Send "STOP_LOSS" and the trigger sits on the wrong side of the market.

semanticBracketType

semanticBracketType is an optional field that records what you meant: TAKE_PROFIT or STOP_LOSS. It does not change which way the trigger fires. Pear derives your intent from bracketType and the watched leg's side, then checks your value against that derivation, so it is a guard against encoding the crossing backwards:

CaseResult
price condition, watched leg is longMust equal bracketType.
price condition, watched leg is shortMust be the opposite of bracketType.
Any other condition typeMust equal bracketType.
bracketType: "NONE"Must be omitted.
The condition's symbol is not in the position, or that leg is flatMust be omitted. The side cannot be proved, so nothing can be validated.

A value that disagrees is rejected with semanticBracketType contradicts the bracketType encoding. Omitting the field is always valid.

Conditions

typedataCloses on
upnlpriceSource, thresholdUnrealized PnL in USD.
upnl_bpspriceSource, thresholdUnrealized PnL in basis points of the position.
notionalpriceSource, thresholdThe position's notional value.
pricepriceSource, symbol, thresholdOne instrument's price.
ratiopriceSource, symbol_a, symbol_b, thresholdThe price ratio of two instruments.
weighted_ratiopriceSource, basket, thresholdThe basket's weighted ratio.
prediction_marketsource, marketKey, thresholdA prediction-market outcome price.

priceSource is mid or mark. source is kalshi or polymarket. Every symbol is an instrument ID, and ratio needs two distinct ones.

weighted_ratio takes a basket of at least two entries, each { "symbol", "side", "weight" }. Symbols are distinct, side is BUY or SELL, and the weights sum to exactly 1.

threshold

Conditionthreshold
upnl, upnl_bpsSigned. Negative marks a loss. Must not be 0.
notional, price, ratio, weighted_ratio, prediction_marketMust be greater than 0.

Trailing stops

Every condition above except prediction_market has a _trailing twin, which follows the position's best value instead of resting at a fixed level. A trailing close omits bracketType and adds a trailing object:

{
  "type": "MARKET",
  "intent": "CLOSE",
  "positionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "condition": {
    "type": "upnl_bps_trailing",
    "data": { "priceSource": "mark" }
  },
  "trailing": {
    "mode": "RELATIVE_BPS",
    "deltaValue": 200,
    "activationValue": 500
  }
}

That arms once the position is 500 bps up, then closes it if it gives back 200 bps from its peak.

FieldMeaning
modeRELATIVE_BPS reads deltaValue as basis points. ABSOLUTE_POINTS reads it in the condition's own units.
deltaValueHow far the value may retrace before the trigger closes the position.
activationValueOptional. The trigger stays dormant until the value reaches this level.

The trailing condition has no threshold, because the level moves.

deltaValue rules:

RuleApplies to
Greater than 0Every mode
A whole numberRELATIVE_BPS only
Between 1 and 5000RELATIVE_BPS only
Less than activationValuenotional_trailing in ABSOLUTE_POINTS mode, when activationValue is present

activationValue rules:

RuleApplies to
Must be greater than 0notional_trailing
Must not be 0upnl_trailing, upnl_bps_trailing

A trailing close is always recorded as a stop loss, whatever it tracks.

Arm at open time

To make sure a basket is never unprotected, attach the close trigger to the opening call instead. POST /trade/open takes a trigger, and POST /triggers/open and POST /ladders take attachTriggers. Attached triggers accept the notional, upnl, and upnl_bps conditions and their trailing twins. See Basket Trade.

What a fired trigger leaves behind

A trigger is spent the moment it fires, whatever the close it launched does next. Its status becomes TRIGGERED and it can never fire again — including when that close failed outright, or filled only part of the basket.

When the close does not empty the position:

  • The position stays OPEN, holding whatever the close left.
  • No position.closed arrives.
  • The spent trigger cannot be cancelled. PATCH /triggers/{triggerId}/cancel on it returns 409.
  • Your other triggers and schedules on that position stay armed. Pear cancels them only when the close actually closed the position.

Watch for a trigger.triggered that is not followed by position.closed. Read the leftover exposure with GET /positions and decide what to do with it.

What else cancels a close trigger

EventcancelledReason
The position closesPOSITION_CLOSED
The position is reversed, on any real fill, including a partial that did not flip itPOSITION_REVERSED
The position is netted away by another tradePOSITION_NETTED
You cancel itUSER

Manage close triggers

Cancel a close trigger like any other, with PATCH /triggers/{triggerId}/cancel. Cancelling a trigger that is not ACTIVE returns 409. List them with GET /triggers. Fires stream over the WebSocket as trigger.triggered, then position.closed.

A condition that names an instrument the price oracles cannot resolve is rejected with 422. Use the ids from GET /instruments.

On this page