@pear-protocol/utils 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,17 +7,41 @@ export type AssetUnrealizedPnL = {
7
7
  currentPrice: BigNumber;
8
8
  upnl: BigNumber;
9
9
  };
10
- export type AssetRealization = {
10
+ /**
11
+ * One reduction of the position, attributed to the fill that made it.
12
+ *
13
+ * A discriminated union rather than an optional gross, because whether a close could
14
+ * be valued is a property of the replay, not of the fill: a caller cannot tell a
15
+ * priced close from an unpriceable one by looking at the fill alone. `reduceOnly` is
16
+ * an order intent and says nothing about what actually closed, and a 0-price synthetic
17
+ * can equally well OPEN a position — so the walk is the only thing that knows, and it
18
+ * says so here instead of leaving the answer to be guessed downstream.
19
+ *
20
+ * The walk is time-agnostic (a Fill carries no timestamp), so callers that need to
21
+ * filter by time resolve fillId against their own fill records.
22
+ */
23
+ export type AssetClosure = {
24
+ kind: 'PRICED';
11
25
  fillId: string;
12
26
  gross: BigNumber;
27
+ entryPrice: BigNumber;
28
+ exitPrice: BigNumber;
29
+ closedQuantity: BigNumber;
30
+ } | {
31
+ kind: 'UNPRICED';
32
+ fillId: string;
33
+ closedQuantity: BigNumber;
13
34
  };
14
35
  export type AssetRealizedPnL = {
15
36
  id: InstrumentId;
16
37
  grossPnl: BigNumber;
17
38
  netPnl: BigNumber;
18
39
  undeterminedSize: BigNumber;
40
+ exitQuantity: BigNumber;
41
+ exitNotional: BigNumber;
42
+ exitVwap: BigNumber | null;
19
43
  totalFees: BigNumber;
20
44
  tradeFees: BigNumber;
21
45
  pearFees: BigNumber;
22
- realizations: AssetRealization[];
46
+ closures: AssetClosure[];
23
47
  };
@@ -1,2 +1,2 @@
1
- export type { AssetRealization, AssetRealizedPnL, AssetUnrealizedPnL } from './asset.types';
1
+ export type { AssetClosure, AssetRealizedPnL, AssetUnrealizedPnL } from './asset.types';
2
2
  export * from './compute';
@@ -0,0 +1,11 @@
1
+ import type { InstrumentId } from '@pear-protocol/types';
2
+ /**
3
+ * The canonical basket key: sha256 hex over the instrument ids a basket holds long
4
+ * and short, each side sorted, spelled as {"long":[...],"short":[...]}.
5
+ *
6
+ * This is the `basketKey` the backend reports on /markets/baskets rows and accepts
7
+ * in /markets/baskets/positions, derived byte-for-byte the way the backend derives
8
+ * it (deriveBasketKeyFromParts). The format is FROZEN: persisted rows are stored
9
+ * under it, so neither the sort, the payload spelling, nor the hash may change.
10
+ */
11
+ export declare function deriveBasketKey(longInstrumentIds: InstrumentId[], shortInstrumentIds: InstrumentId[]): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1,2 @@
1
1
  export * from './compute';
2
+ export * from './derive-basket-key';
package/dist/index.js CHANGED
@@ -186,17 +186,21 @@ function computeAssetRealizedPnLs(fills) {
186
186
  const tradeFees = assetFills.reduce((acc, fill) => acc.plus(new BigNumber9(fill.tradeFee ?? "0")), ZERO);
187
187
  const pearFees = assetFills.reduce((acc, fill) => acc.plus(new BigNumber9(fill.pearFee ?? "0")), ZERO);
188
188
  const totalFees = tradeFees.plus(pearFees);
189
- const { gross: grossPnl, undeterminedSize, realizations } = walkAssetFills(assetFills);
189
+ const { gross: grossPnl, undeterminedSize, closures, exitQuantity, exitNotional } = walkAssetFills(assetFills);
190
190
  const netPnl = grossPnl.minus(totalFees);
191
+ const exitVwap = isZero(exitQuantity) ? null : divide(exitNotional, exitQuantity);
191
192
  result.push({
192
193
  id,
193
194
  grossPnl,
194
195
  netPnl,
195
196
  undeterminedSize,
197
+ exitQuantity,
198
+ exitNotional,
199
+ exitVwap,
196
200
  totalFees,
197
201
  tradeFees,
198
202
  pearFees,
199
- realizations
203
+ closures
200
204
  });
201
205
  }
202
206
  return result;
@@ -207,7 +211,9 @@ function walkAssetFills(fills) {
207
211
  let basisUnpriced = false;
208
212
  let gross = ZERO;
209
213
  let undeterminedSize = ZERO;
210
- const realizations = [];
214
+ const closures = [];
215
+ let exitQuantity = ZERO;
216
+ let exitNotional = ZERO;
211
217
  for (const fill of fills) {
212
218
  const price = parse(fill.price);
213
219
  const quantity = parse(fill.quantity);
@@ -217,10 +223,24 @@ function walkAssetFills(fills) {
217
223
  const closedQty = min(positionSize.abs(), signedQuantity.abs());
218
224
  if (basisUnpriced || isZero(price)) {
219
225
  undeterminedSize = add(undeterminedSize, closedQty);
226
+ closures.push({
227
+ kind: "UNPRICED",
228
+ fillId: fill.id,
229
+ closedQuantity: closedQty
230
+ });
220
231
  } else {
221
232
  const realized = isPositive(positionSize) ? multiply(closedQty, subtract(price, entryPrice)) : multiply(closedQty, subtract(entryPrice, price));
222
233
  gross = add(gross, realized);
223
- realizations.push({ fillId: fill.id, gross: realized });
234
+ closures.push({
235
+ kind: "PRICED",
236
+ fillId: fill.id,
237
+ gross: realized,
238
+ entryPrice,
239
+ exitPrice: price,
240
+ closedQuantity: closedQty
241
+ });
242
+ exitQuantity = add(exitQuantity, closedQty);
243
+ exitNotional = add(exitNotional, multiply(closedQty, price));
224
244
  }
225
245
  }
226
246
  const isSameDirection = !isZero(positionSize) && isPositive(positionSize) === isPositive(signedQuantity);
@@ -234,7 +254,13 @@ function walkAssetFills(fills) {
234
254
  }
235
255
  positionSize = newPositionSize;
236
256
  }
237
- return { gross, undeterminedSize, realizations };
257
+ return {
258
+ gross,
259
+ undeterminedSize,
260
+ closures,
261
+ exitQuantity,
262
+ exitNotional
263
+ };
238
264
  }
239
265
  function computeBasketWeightedRatioV1(basket, priceMap) {
240
266
  let logSum = 0;
@@ -252,6 +278,13 @@ function computeBasketWeightedRatioV1(basket, priceMap) {
252
278
  }
253
279
  return exponentiate(logSum);
254
280
  }
281
+ function deriveBasketKey(longInstrumentIds, shortInstrumentIds) {
282
+ const payload = JSON.stringify({
283
+ long: [...longInstrumentIds].sort((a, b) => a.localeCompare(b)),
284
+ short: [...shortInstrumentIds].sort((a, b) => a.localeCompare(b))
285
+ });
286
+ return bytesToHex(sha256(new TextEncoder().encode(payload)));
287
+ }
255
288
  function contractsToBase(contracts, contractSize) {
256
289
  return new BigNumber9(contracts).times(contractSize);
257
290
  }
@@ -331,7 +364,9 @@ function computeUnrealizedPositionState(exposureMap, priceMap, fills) {
331
364
  };
332
365
  }
333
366
  function computeRealizedPositionState(fills) {
334
- const assetPnLs = computeAssetRealizedPnLs(fills);
367
+ return aggregateRealizedPositionState(computeAssetRealizedPnLs(fills));
368
+ }
369
+ function aggregateRealizedPositionState(assetPnLs) {
335
370
  const grossPnl = computePositionGrossRealizedPnL(assetPnLs);
336
371
  const netPnl = computePositionNetRealizedPnL(assetPnLs);
337
372
  const tradeFees = assetPnLs.reduce((acc, asset) => acc.plus(asset.tradeFees), ZERO);
@@ -1756,4 +1791,4 @@ function validateQuantity(quantity, price, precision) {
1756
1791
  return true;
1757
1792
  }
1758
1793
 
1759
- export { BIPS, ZERO, abs, add, baseToContracts, buildApplicables, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeEntryPriceForAsset, computePositionEntryPrices, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeSyncPayload, computeUnrealizedPositionState, computeUnrealizedPositionStateFromEntryPrices, configureProxy, contractsToBase, countDecimals, divide, exponentiate, generateDeterministicPositionKey, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, proxyFetch, sideSign, sign, signed, signedBySide, subtract, toDecimalString, validateLeverage, validateQuantity };
1794
+ export { BIPS, ZERO, abs, add, aggregateRealizedPositionState, baseToContracts, buildApplicables, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeEntryPriceForAsset, computePositionEntryPrices, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeSyncPayload, computeUnrealizedPositionState, computeUnrealizedPositionStateFromEntryPrices, configureProxy, contractsToBase, countDecimals, deriveBasketKey, divide, exponentiate, generateDeterministicPositionKey, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, proxyFetch, sideSign, sign, signed, signedBySide, subtract, toDecimalString, validateLeverage, validateQuantity };
@@ -1,4 +1,5 @@
1
1
  import type { InstrumentId } from '@pear-protocol/types';
2
+ import type { AssetRealizedPnL } from '../asset/asset.types';
2
3
  import type { Fill } from '../fills/fill.types';
3
4
  import type { RealizedPositionState, UnrealizedPositionState } from './position.types';
4
5
  type ExposureMap = Record<InstrumentId, string>;
@@ -19,6 +20,18 @@ export declare function computeUnrealizedPositionState(exposureMap: ExposureMap,
19
20
  * @returns The realized position state.
20
21
  */
21
22
  export declare function computeRealizedPositionState(fills: Fill[]): RealizedPositionState;
23
+ /**
24
+ * Roll already-walked per-asset rows up into the position total.
25
+ *
26
+ * Separated from computeRealizedPositionState so a caller that already holds the
27
+ * per-asset rows can total them without walking the ledger a second time. The
28
+ * position total and the per-asset detail then come from one execution of the walk,
29
+ * which is the guarantee that makes them impossible to disagree.
30
+ *
31
+ * @param assetPnLs - Per-asset realized rows from computeAssetRealizedPnLs.
32
+ * @returns The realized position state those rows add up to.
33
+ */
34
+ export declare function aggregateRealizedPositionState(assetPnLs: AssetRealizedPnL[]): RealizedPositionState;
22
35
  /**
23
36
  * Weighted-average entry (cost-basis) price per instrument in a position, derived purely from
24
37
  * fills — no mark price needed. This is the authoritative source for a position's entry price;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/utils",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Pear Protocol Utility functions",
5
5
  "private": false,
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@noble/hashes": "^1.3.2",
30
- "@pear-protocol/types": "^1.16.0",
30
+ "@pear-protocol/types": "^1.17.0",
31
31
  "bignumber.js": "9.3.1"
32
32
  },
33
33
  "devDependencies": {