Close Position
Close an open basket, at market or over time.
Close one basket with POST /trade/{positionId}/close (SDK: sdk.core.trade.close). The body carries no legs and no size, because a close always unwinds the whole basket:
{
"type": "MARKET",
"clientId": "your-client-id"
}curl -X POST "https://pro-gateway.pearprotocol.io/trade/$POSITION_ID/close" \
-H "x-api-key: $PEAR_API_KEY" \
-H "x-trade-account-id: $TRADE_ACCOUNT_ID" \
-H "Content-Type: application/json" \
-d '{ "type": "MARKET" }'clientId is optional. type accepts only "MARKET". The call answers 202 with an execution; the fill follows over the WebSocket. The position must be OPEN — closing anything else returns 409, not 404.
To take off part of a basket instead, see Partially Adjust Position.
What a close takes down with it
A close does not only trade. Once it has actually closed the position, Pear also:
- Cancels every trigger resting on it, with
cancelledReason: "POSITION_CLOSED". Your stop losses go with the position. - Cancels every schedule on it, with the same reason. The one exception is a close that is itself a slice of one of those schedules.
Separately, any close that fills at all pauses the position's auto-rebalance config, with pausedReason: "POSITION_CHANGED".
A close that only partially filled leaves the position open, so its triggers and schedules stay armed while the auto-rebalance config is already paused.
Close everything
POST /trade/close-all (SDK: sdk.core.trade.closeAll) closes every open basket on the trade account, with the same body:
{ "type": "MARKET" }It answers 202, but the call blocks until the whole run is over. Expect its latency to grow with the number of positions. The body is every execution the run created, each already in its final status:
{
"executions": [
{
"id": "6b1f0f1c-1f6e-4a3f-9f4b-6f1a2d3c4e50",
"connector": "hyperliquid",
"intent": "CLOSE",
"orderType": "MARKET",
"status": "COMPLETED",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:04.000Z",
"position": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "exposure": {} }
},
{
"id": "0c2a9d51-7a0e-4c1b-8a53-9d0b7e6f5a21",
"connector": "hyperliquid",
"intent": "CLOSE",
"orderType": "MARKET",
"status": "FAILED",
"error": "Order rejected by the exchange",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:06.000Z",
"position": { "id": "9f2c1a44-2b0d-4e7a-91c3-5d8e0a1b2c34", "exposure": { "0": "+0.0021", "1": "-0.5" } }
}
]
}Three things follow from that.
Some executions can fail while others succeed. A child that fails does not stop the run; it is recorded and the walk continues. Close-all can therefore return with positions still open. Check every status in the array, then read GET /positions to see what is left.
Positions close oldest first, one at a time. The whole run holds the trade account's lock, so nothing else on the account trades in between.
Some executions carry intent: "ADJUST", not "CLOSE". Two positions can block each other: when one holds a leg too small to meet the exchange's minimum order value, that order is only accepted if it fully closes the asset on the exchange — which it cannot while another position still holds the same asset. Where that happens, the run first closes the other position's leg on that asset as a reduce-only adjust, freeing the small leg to close whole. Those untangling adjusts appear in executions alongside the closes.
An account with no open positions returns 409 NOOP_ERROR. Check before calling, or treat the 409 as "nothing to do".
Close over time
POST /trade/{positionId}/close is always a market close. To spread the exit out, create a closing TWAP with POST /schedules instead:
{
"strategy": "TWAP",
"intent": "CLOSE",
"executionType": "MARKET",
"positionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"startAt": "2026-01-01T00:00:00.000Z",
"endAt": "2026-01-01T01:00:00.000Z",
"sliceIntervalSec": 300
}See Time-Weighted Average Price. To close on a market condition rather than on a clock, arm a close trigger, see Take Profit / Stop Loss.
What a close does to the exchange position
A close reduces each leg by the basket's share of that asset, not the asset's global total on the exchange. If two Pear baskets hold BTC, closing one leaves the other's BTC untouched. See Synthetic Position.
The result arrives over the WebSocket as position.closed, carrying closedReason: "USER". Closed positions stay readable through GET /positions/closed.