@pear-protocol/utils
Advanced tools
| import type { InstrumentId } from '@pear-protocol/types'; | ||
| /** | ||
| * The canonical basket key: sha256 hex over the instrument ids a basket holds long | ||
| * and short, each side sorted, spelled as {"long":[...],"short":[...]}. | ||
| * | ||
| * This is the `basketKey` the backend reports on /markets/baskets rows and accepts | ||
| * in /markets/baskets/positions, derived byte-for-byte the way the backend derives | ||
| * it (deriveBasketKeyFromParts). The format is FROZEN: persisted rows are stored | ||
| * under it, so neither the sort, the payload spelling, nor the hash may change. | ||
| */ | ||
| export declare function deriveBasketKey(longInstrumentIds: InstrumentId[], shortInstrumentIds: InstrumentId[]): string; |
| export {}; |
@@ -10,5 +10,26 @@ import type { InstrumentId } from '@pear-protocol/types'; | ||
| }; | ||
| export type AssetRealization = { | ||
| /** | ||
| * One reduction of the position, attributed to the fill that made it. | ||
| * | ||
| * A discriminated union rather than an optional gross, because whether a close could | ||
| * be valued is a property of the replay, not of the fill: a caller cannot tell a | ||
| * priced close from an unpriceable one by looking at the fill alone. `reduceOnly` is | ||
| * an order intent and says nothing about what actually closed, and a 0-price synthetic | ||
| * can equally well OPEN a position — so the walk is the only thing that knows, and it | ||
| * says so here instead of leaving the answer to be guessed downstream. | ||
| * | ||
| * The walk is time-agnostic (a Fill carries no timestamp), so callers that need to | ||
| * filter by time resolve fillId against their own fill records. | ||
| */ | ||
| export type AssetClosure = { | ||
| kind: 'PRICED'; | ||
| fillId: string; | ||
| gross: BigNumber; | ||
| entryPrice: BigNumber; | ||
| exitPrice: BigNumber; | ||
| closedQuantity: BigNumber; | ||
| } | { | ||
| kind: 'UNPRICED'; | ||
| fillId: string; | ||
| closedQuantity: BigNumber; | ||
| }; | ||
@@ -20,6 +41,9 @@ export type AssetRealizedPnL = { | ||
| undeterminedSize: BigNumber; | ||
| exitQuantity: BigNumber; | ||
| exitNotional: BigNumber; | ||
| exitVwap: BigNumber | null; | ||
| totalFees: BigNumber; | ||
| tradeFees: BigNumber; | ||
| pearFees: BigNumber; | ||
| realizations: AssetRealization[]; | ||
| closures: AssetClosure[]; | ||
| }; |
@@ -1,2 +0,2 @@ | ||
| export type { AssetRealization, AssetRealizedPnL, AssetUnrealizedPnL } from './asset.types'; | ||
| export type { AssetClosure, AssetRealizedPnL, AssetUnrealizedPnL } from './asset.types'; | ||
| export * from './compute'; |
| export * from './compute'; | ||
| export * from './derive-basket-key'; |
+42
-7
@@ -189,4 +189,5 @@ import BigNumber9 from 'bignumber.js'; | ||
| const totalFees = tradeFees.plus(pearFees); | ||
| const { gross: grossPnl, undeterminedSize, realizations } = walkAssetFills(assetFills); | ||
| const { gross: grossPnl, undeterminedSize, closures, exitQuantity, exitNotional } = walkAssetFills(assetFills); | ||
| const netPnl = grossPnl.minus(totalFees); | ||
| const exitVwap = isZero(exitQuantity) ? null : divide(exitNotional, exitQuantity); | ||
| result.push({ | ||
@@ -197,6 +198,9 @@ id, | ||
| undeterminedSize, | ||
| exitQuantity, | ||
| exitNotional, | ||
| exitVwap, | ||
| totalFees, | ||
| tradeFees, | ||
| pearFees, | ||
| realizations | ||
| closures | ||
| }); | ||
@@ -212,3 +216,5 @@ } | ||
| let undeterminedSize = ZERO; | ||
| const realizations = []; | ||
| const closures = []; | ||
| let exitQuantity = ZERO; | ||
| let exitNotional = ZERO; | ||
| for (const fill of fills) { | ||
@@ -223,6 +229,20 @@ const price = parse(fill.price); | ||
| undeterminedSize = add(undeterminedSize, closedQty); | ||
| closures.push({ | ||
| kind: "UNPRICED", | ||
| fillId: fill.id, | ||
| closedQuantity: closedQty | ||
| }); | ||
| } else { | ||
| const realized = isPositive(positionSize) ? multiply(closedQty, subtract(price, entryPrice)) : multiply(closedQty, subtract(entryPrice, price)); | ||
| gross = add(gross, realized); | ||
| realizations.push({ fillId: fill.id, gross: realized }); | ||
| closures.push({ | ||
| kind: "PRICED", | ||
| fillId: fill.id, | ||
| gross: realized, | ||
| entryPrice, | ||
| exitPrice: price, | ||
| closedQuantity: closedQty | ||
| }); | ||
| exitQuantity = add(exitQuantity, closedQty); | ||
| exitNotional = add(exitNotional, multiply(closedQty, price)); | ||
| } | ||
@@ -241,3 +261,9 @@ } | ||
| } | ||
| return { gross, undeterminedSize, realizations }; | ||
| return { | ||
| gross, | ||
| undeterminedSize, | ||
| closures, | ||
| exitQuantity, | ||
| exitNotional | ||
| }; | ||
| } | ||
@@ -260,2 +286,9 @@ function computeBasketWeightedRatioV1(basket, priceMap) { | ||
| } | ||
| function deriveBasketKey(longInstrumentIds, shortInstrumentIds) { | ||
| const payload = JSON.stringify({ | ||
| long: [...longInstrumentIds].sort((a, b) => a.localeCompare(b)), | ||
| short: [...shortInstrumentIds].sort((a, b) => a.localeCompare(b)) | ||
| }); | ||
| return bytesToHex(sha256(new TextEncoder().encode(payload))); | ||
| } | ||
| function contractsToBase(contracts, contractSize) { | ||
@@ -340,3 +373,5 @@ return new BigNumber9(contracts).times(contractSize); | ||
| function computeRealizedPositionState(fills) { | ||
| const assetPnLs = computeAssetRealizedPnLs(fills); | ||
| return aggregateRealizedPositionState(computeAssetRealizedPnLs(fills)); | ||
| } | ||
| function aggregateRealizedPositionState(assetPnLs) { | ||
| const grossPnl = computePositionGrossRealizedPnL(assetPnLs); | ||
@@ -1766,2 +1801,2 @@ const netPnl = computePositionNetRealizedPnL(assetPnLs); | ||
| 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 }; | ||
| 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 }; |
| import type { InstrumentId } from '@pear-protocol/types'; | ||
| import type { AssetRealizedPnL } from '../asset/asset.types'; | ||
| import type { Fill } from '../fills/fill.types'; | ||
@@ -23,2 +24,14 @@ import type { RealizedPositionState, UnrealizedPositionState } from './position.types'; | ||
| /** | ||
| * Roll already-walked per-asset rows up into the position total. | ||
| * | ||
| * Separated from computeRealizedPositionState so a caller that already holds the | ||
| * per-asset rows can total them without walking the ledger a second time. The | ||
| * position total and the per-asset detail then come from one execution of the walk, | ||
| * which is the guarantee that makes them impossible to disagree. | ||
| * | ||
| * @param assetPnLs - Per-asset realized rows from computeAssetRealizedPnLs. | ||
| * @returns The realized position state those rows add up to. | ||
| */ | ||
| export declare function aggregateRealizedPositionState(assetPnLs: AssetRealizedPnL[]): RealizedPositionState; | ||
| /** | ||
| * Weighted-average entry (cost-basis) price per instrument in a position, derived purely from | ||
@@ -25,0 +38,0 @@ * fills — no mark price needed. This is the authoritative source for a position's entry price; |
+2
-2
| { | ||
| "name": "@pear-protocol/utils", | ||
| "version": "0.4.2", | ||
| "version": "0.5.0", | ||
| "description": "Pear Protocol Utility functions", | ||
@@ -30,3 +30,3 @@ "private": false, | ||
| "@noble/hashes": "^1.3.2", | ||
| "@pear-protocol/types": "^1.16.0", | ||
| "@pear-protocol/types": "^1.17.0", | ||
| "bignumber.js": "9.3.1" | ||
@@ -33,0 +33,0 @@ }, |
102622
3.54%55
3.77%2678
3.28%Updated