Closed Position
Read closed-trade history, with realized PnL, fees, and funding per leg.
GET /positions/closed (SDK: sdk.core.positions.closed) returns closed trades, newest close first. Pear composes each one server-side into the trade a trader recognises: what it earned, what it cost, and what each leg did.
curl "https://pro-gateway.pearprotocol.io/positions/closed?limit=50" \
-H "x-api-key: $PEAR_API_KEY" \
-H "x-trade-account-id: $TRADE_ACCOUNT_ID"Response
{
"limit": 50,
"nextCursor": "eyJjbG9zZWRBdCI6…",
"trades": [
{
"positionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"connector": "hyperliquid",
"openedAt": "2026-01-01T00:00:00.000Z",
"closedAt": "2026-01-03T11:42:07.000Z",
"closedReason": "USER",
"origin": "PEAR",
"grossPnl": "+42.50",
"netPnl": "+41.72",
"totalFees": "+0.78",
"tradeFees": "+0.61",
"pearFees": "+0.17",
"totalFunding": "-0.41",
"entryNotional": "1000.00",
"exitNotional": "1042.50",
"exitNotionalComplete": true,
"hasSyntheticFills": false,
"unstableHistoricalData": false,
"longAssets": ["0"],
"shortAssets": ["1"],
"legs": [
{
"asset": "0",
"side": "LONG",
"entryVwap": "96150.5",
"exitVwap": "98420.0",
"grossPnl": "+118.00",
"netPnl": "+117.55",
"totalFees": "+0.45",
"tradeFees": "+0.35",
"pearFees": "+0.10",
"funding": "-1.25",
"entryNotional": "500.00",
"exitQuantity": "0.0052",
"undeterminedSize": "0"
},
{
"asset": "1",
"side": "SHORT",
"entryVwap": "3086.2",
"exitVwap": "3552.0",
"grossPnl": "-75.50",
"netPnl": "-75.83",
"totalFees": "+0.33",
"tradeFees": "+0.26",
"pearFees": "+0.07",
"funding": "+0.84",
"entryNotional": "500.00",
"exitQuantity": "0.1620",
"undeterminedSize": "0"
}
]
}
]
}longAssets, shortAssets, and legs[].asset hold instrument IDs, so "0" is BTC and "1" is ETH on Hyperliquid. See Instrument ID.
PnL, fees, and funding
| Field | Meaning |
|---|---|
grossPnl | Realized PnL before fees. |
netPnl | Realized PnL after fees. |
totalFees | Signed, so a venue maker rebate makes it negative. Split into tradeFees (the exchange) and pearFees. Each leg carries the same three fields. |
totalFunding | Funding over the position's life. Negative means you paid. |
totalFunding is not folded into netPnl. netPnl is a fills-only number, and funding is a separate cash flow. Add the two for the trade's all-in result:
all-in = netPnl + totalFunding = 41.72 + (-0.41) = 41.31totalFunding can also exceed the sum of legs[].funding. Funding follows exposure, and an asset that carried exposure but produced no confirmed fill earns no leg. Summing the legs would understate what the trade paid.
When a number is missing or partial
Closed trades are derived from a fills ledger, so a few fields tell you how much to trust the rest:
| Field | Read it as |
|---|---|
entryVwap, exitVwap | Absent, not "0", when no priced entry or exit exists. "0" is a real price, so absence is the honest answer. |
undeterminedSize | Size that closed with no derivable PnL. It contributes nothing to grossPnl or exitVwap. The leg's total closed size is exitQuantity + undeterminedSize. |
exitNotionalComplete | false when part of the position closed unpriceably, so exitNotional understates the proceeds. |
hasSyntheticFills | true when a synthetic bridge fill reconstructed a gap in the ledger. This is not the opposite of exitNotionalComplete: a synthetic fill can carry a real price and stay complete. |
unstableHistoricalData | true when a backfilled fill made this trade's metrics unreliable. |
closedReason | USER for a close you asked for, NETTED when an opposing trade cancelled it out, LIQUIDATED for a venue liquidation. The key is omitted, not null, when no reason was recorded. |
Step history
Add includeSteps=true for what happened to the position over its life, oldest first: the open, every add, every reduction, and the close.
{
"steps": [
{
"executionId": "8d2e…",
"at": "2026-01-01T00:00:00.000Z",
"grossPnl": "+0",
"legs": [
{ "kind": "INCREASE", "asset": "0", "side": "BUY", "quantity": "0.0052", "price": "96150.5", "tradeFee": "+0.22", "pearFee": "+0.05" }
]
}
]
}legs says how each asset finished; steps says how it got there. A step leg is INCREASE, REDUCE, or REDUCE_UNPRICED. Only reductions book PnL, so grossPnl is "+0" on a pure increase.
A REDUCE leg also carries entryBasis: the running cost basis that reduction closed against. Its grossPnl is (price - entryBasis) * quantity, signed by direction.
Ask for steps only when you need them. The array is unbounded, one entry per recorded action, so a heavily traded position produces a long one.
Paginating
Filter by startDate and endDate, and page with limit and cursor.
| Parameter | Values |
|---|---|
startDate, endDate | Inclusive bounds on the close time. Absent means unbounded. |
limit | Default 20, maximum 200. This is deliberately below the 2000 other lists allow, because every trade drags its fills along. limit=500 is a 400. |
cursor | The nextCursor of the previous page. |
includeSteps | true adds the step history. Unlike the flags on GET /executions, includeSteps=false does work. |
This endpoint takes no connector, no status, and no orderBy. Ordering is fixed at newest-closed-first.
Page until
nextCursoris absent, never until a page comes back short. A page can hold fewer thanlimittrades and still have more behind it, because closed positions with no confirmed fills are skipped.
Live positions are at Open Position. The fills behind a closed trade are at Fills. The same trades aggregated across the whole account are at Portfolio.