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
| Trade | Long | Short | BTC Size |
|---|---|---|---|
| Trade 1 | BTC | ETH | 0.5 |
| Trade 2 | BTC | ALT | 0.3 |
| Trade 3 | BTC | STOCK | 0.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 fill | What happens to the entry price |
|---|---|
| Opens a leg from zero | Resets to the fill's price. |
| Increases a leg in the same direction | Blends: the weighted average of the old basis and the new fill. |
| Reduces a leg | Unchanged. A reduction books PnL; it does not move the basis. |
| Flips a leg's direction | Resets 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:
origin | Meaning |
|---|---|
PEAR | Only Pear fills built this position |
EXTERNAL | Only outside fills did: direct exchange trades, or other frontends |
PARTIAL | A 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:
closedReason | Meaning |
|---|---|
USER | You closed it. |
LIQUIDATED | The venue liquidated it. |
NETTED | An 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
syntheticboolean on each fill fromGET /fills. - A synthetic fill that closes exposure at an unknowable price carries
"0"for bothpriceandusd."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:
| Pair | Entry Price |
|---|---|
| ASSET / SHORT-A | 33.500 |
| ASSET / SHORT-B | 33.700 |
| ASSET / SHORT-C | 33.879 |
| ASSET / SHORT-D | 33.880 |
Exchange global average entry: 33.74
User closes only ASSET / SHORT-C at 34.298:
| Entry Used | PnL per Unit | |
|---|---|---|
| Pear | 33.879 (basket-specific) | +0.419 |
| The exchange | 33.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:
originisPARTIALorEXTERNAL, so fills placed outside Pear have moved this position.unstableHistoricalDataistrue, 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.