PearPear
API IntegrationImportant Terms

Synthetic Position

Why Pear's positions, entry prices, and PnL differ from the exchange's own view, and the fields that tell you when the basket view is incomplete.

Pear Protocol constructs synthetic positions on top of the raw positions an exchange holds. Each basket trade gets its own position with independent sizing, entry prices, PnL, and TP/SL, even when the underlying exchange account holds a single aggregated position per asset.

This is how Pear models every venue it connects to: hyperliquid, binance, bybit, okx, and lighter. The examples below use Hyperliquid, but the model and the arithmetic are the same on all five. Every position names its own connector, and a position never spans two of them.

Synthetic Positions vs the Exchange's Own Positions

An exchange tracks one position per asset per account. Pear splits that into baskets.

Example: One Underlying, Three Baskets

TradeLongShortBTC Size
Trade 1BTCETH0.5
Trade 2BTCALT0.3
Trade 3BTCSTOCK0.2

The exchange sees: 1.0 BTC long (single position, no basket concept)

Pear sees: 3 independent positions, each with own entry price, PnL, and TP/SL

Users can track performance per basket, set risk per basket, and close baskets independently.

How Fills Become Positions

After execution, each fill is processed against the existing open positions, oldest first: first closing an exact-match inverse position, then reducing any other position that partially holds that leg, then merging the leftover into a position already holding those legs or opening a new one. A single fill can be split across several positions when it closes more than one.

Entry prices are rebuilt from the fills ledger, and the rule is not a plain weighted average:

The fillWhat happens to the entry price
Opens a leg from zeroResets to the fill's price.
Increases a leg in the same directionBlends: the weighted average of the old basis and the new fill.
Reduces a legUnchanged. A reduction books PnL; it does not move the basis.
Flips a leg's directionResets to the flipping fill's price.

Position origin

Every position carries an origin, derived from where its fills came from. GET /positions returns it on each position:

originMeaning
PEAROnly Pear fills built this position
EXTERNALOnly outside fills did: direct exchange trades, or other frontends
PARTIALA mix of Pear and external fills touched this position

EXTERNAL and PARTIAL exist only because a client posted those outside fills to Pear. Pear does not poll the venue for them. See Syncing Venue Activity.

Individual fills carry the same distinction as isExternal, readable through GET /fills/{positionId}. That call takes the position id in the path and no query parameters at all, unlike GET /fills.

origin is not the only provenance Pear records. A position also carries a creation source internally, ENGINE for a row Pear's own order placement created and IMPORT for one the exchange sync reconstructed from venue history. That field is not on the wire, so do not go looking for it on a position. Its one visible effect: the statistics leaderboard ranks only ENGINE positions, so imported history never scores.

Closed positions say why

A closed position carries closedReason:

closedReasonMeaning
USERYou closed it.
LIQUIDATEDThe venue liquidated it.
NETTEDAn opposing basket cancelled it out inside Pear.

NETTED has no counterpart at the exchange. It is a Pear-side event: two of your baskets held offsetting exposure, so the ledger closed one against the other. Nothing was sent to the venue.

Synthetic fills

synthetic on a fill is a different thing from the synthetic positions this page is about. The two share a word and nothing else.

A synthetic fill is a bridge fill Pear writes when reconciling its ledger against the exchange snapshot: exposure the venue reports that Pear's fill history does not explain, or exposure Pear holds that the venue no longer does. It is a bookkeeping entry, not a trade you placed.

  • Read it as the synthetic boolean on each fill from GET /fills.
  • A synthetic fill that closes exposure at an unknowable price carries "0" for both price and usd. "0" is a sentinel here, not a real price. Those fills are excluded from PnL, average entry, fees, and volume.
  • Display them distinctly. A "0" price shown as a price misreports the trade, and a "0" notional summed into a total understates it without warning.

On a closed position, the same situation appears per step as a leg of kind REDUCE_UNPRICED: quantity closed, but no price existed to value it, so the step books no PnL and no entry basis.

PnL Discrepancy: Pear vs the Exchange

Pear calculates PnL at the basket level. The exchange calculates at the asset level. Different entry price tracking → different displayed PnL until all related positions close.

Pear: each basket maintains its own entry price per asset.

realized pnl   = direction × (exit_price − basket_entry_price) × size − fees
unrealized pnl = signed_size × (current_price − basket_entry_price)

Note the asymmetry: unrealized PnL charges no fees. It is a mark-to-market number. The fees a position will pay to close are not deducted until they are actually charged, so realized PnL on close is lower than the unrealized PnL you were watching.

The exchange: global weighted average entry across all trades of the same asset. Any close uses this global average.

Example

User opens same asset long in four baskets:

PairEntry Price
ASSET / SHORT-A33.500
ASSET / SHORT-B33.700
ASSET / SHORT-C33.879
ASSET / SHORT-D33.880

Exchange global average entry: 33.74

User closes only ASSET / SHORT-C at 34.298:

Entry UsedPnL per Unit
Pear33.879 (basket-specific)+0.419
The exchange33.74 (global average)+0.558

Neither is wrong, total PnL converges once all positions sharing that asset are fully closed.

Reading it from the API

A Pear position is basket-level, and the API exposes it that way. GET /positions returns one entry per basket, with entryPrices and totalFundingPayment keyed by instrument ID:

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "tradeAccountId": "9c1f0f2e-2b1a-4c33-9f0e-7d5b6a2c8e41",
  "connector": "hyperliquid",
  "status": "OPEN",
  "origin": "PEAR",
  "exposure": { "0": "+0.0052", "1": "-0.1620" },
  "initialExposureUSD": { "0": "+500", "1": "-500" },
  "entryPrices": { "0": "96150.5", "1": "3086.2" },
  "totalFundingPayment": { "0": "-1.25", "1": "+0.84" },
  "unstableHistoricalData": false,
  "createdAt": "2026-01-02T10:15:00.000Z",
  "updatedAt": "2026-01-02T14:40:00.000Z"
}

Every signed map uses an explicit sign: exposure, initialExposureUSD, and totalFundingPayment all carry a leading + or -. entryPrices is unsigned.

entryPrices is this basket's own weighted-average entry, derived from its fills ledger. That is the number that makes Pear's PnL differ from the exchange's, which uses one global average per asset. See Open Position.

entryPrices can omit an instrument that exposure holds. A leg with no cost basis to derive — no priced fill history — is left out rather than reported as zero. Read entryPrices with a presence check, and skip the leg when it is absent instead of substituting 0.

Two fields tell you when the basket view is not the whole story:

  • origin is PARTIAL or EXTERNAL, so fills placed outside Pear have moved this position.
  • unstableHistoricalData is true, so a backfilled fill invalidated metrics derived from this position.

Closing one basket reduces each leg by that basket's share, and leaves other baskets holding the same asset untouched. See Close Position.

On this page