@pear-protocol/utils 0.4.1 → 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.
- package/dist/asset/asset.types.d.ts +26 -2
- package/dist/asset/index.d.ts +1 -1
- package/dist/basket/derive-basket-key.d.ts +11 -0
- package/dist/basket/derive-basket-key.test.d.ts +1 -0
- package/dist/basket/index.d.ts +1 -0
- package/dist/index.js +47 -8
- package/dist/position/compute.d.ts +13 -0
- package/dist/sync/compute.d.ts +2 -2
- package/package.json +2 -2
|
@@ -7,17 +7,41 @@ export type AssetUnrealizedPnL = {
|
|
|
7
7
|
currentPrice: BigNumber;
|
|
8
8
|
upnl: BigNumber;
|
|
9
9
|
};
|
|
10
|
-
|
|
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
|
-
|
|
46
|
+
closures: AssetClosure[];
|
|
23
47
|
};
|
package/dist/asset/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
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 {};
|
package/dist/basket/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import BigNumber9 from 'bignumber.js';
|
|
2
2
|
import { sha256 } from '@noble/hashes/sha256';
|
|
3
3
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
4
|
+
import { WATERMARK_FUTURE_SKEW_MS } from '@pear-protocol/types';
|
|
4
5
|
|
|
5
6
|
// src/asset/compute.ts
|
|
6
7
|
function parse(value) {
|
|
@@ -185,17 +186,21 @@ function computeAssetRealizedPnLs(fills) {
|
|
|
185
186
|
const tradeFees = assetFills.reduce((acc, fill) => acc.plus(new BigNumber9(fill.tradeFee ?? "0")), ZERO);
|
|
186
187
|
const pearFees = assetFills.reduce((acc, fill) => acc.plus(new BigNumber9(fill.pearFee ?? "0")), ZERO);
|
|
187
188
|
const totalFees = tradeFees.plus(pearFees);
|
|
188
|
-
const { gross: grossPnl, undeterminedSize,
|
|
189
|
+
const { gross: grossPnl, undeterminedSize, closures, exitQuantity, exitNotional } = walkAssetFills(assetFills);
|
|
189
190
|
const netPnl = grossPnl.minus(totalFees);
|
|
191
|
+
const exitVwap = isZero(exitQuantity) ? null : divide(exitNotional, exitQuantity);
|
|
190
192
|
result.push({
|
|
191
193
|
id,
|
|
192
194
|
grossPnl,
|
|
193
195
|
netPnl,
|
|
194
196
|
undeterminedSize,
|
|
197
|
+
exitQuantity,
|
|
198
|
+
exitNotional,
|
|
199
|
+
exitVwap,
|
|
195
200
|
totalFees,
|
|
196
201
|
tradeFees,
|
|
197
202
|
pearFees,
|
|
198
|
-
|
|
203
|
+
closures
|
|
199
204
|
});
|
|
200
205
|
}
|
|
201
206
|
return result;
|
|
@@ -206,7 +211,9 @@ function walkAssetFills(fills) {
|
|
|
206
211
|
let basisUnpriced = false;
|
|
207
212
|
let gross = ZERO;
|
|
208
213
|
let undeterminedSize = ZERO;
|
|
209
|
-
const
|
|
214
|
+
const closures = [];
|
|
215
|
+
let exitQuantity = ZERO;
|
|
216
|
+
let exitNotional = ZERO;
|
|
210
217
|
for (const fill of fills) {
|
|
211
218
|
const price = parse(fill.price);
|
|
212
219
|
const quantity = parse(fill.quantity);
|
|
@@ -216,10 +223,24 @@ function walkAssetFills(fills) {
|
|
|
216
223
|
const closedQty = min(positionSize.abs(), signedQuantity.abs());
|
|
217
224
|
if (basisUnpriced || isZero(price)) {
|
|
218
225
|
undeterminedSize = add(undeterminedSize, closedQty);
|
|
226
|
+
closures.push({
|
|
227
|
+
kind: "UNPRICED",
|
|
228
|
+
fillId: fill.id,
|
|
229
|
+
closedQuantity: closedQty
|
|
230
|
+
});
|
|
219
231
|
} else {
|
|
220
232
|
const realized = isPositive(positionSize) ? multiply(closedQty, subtract(price, entryPrice)) : multiply(closedQty, subtract(entryPrice, price));
|
|
221
233
|
gross = add(gross, realized);
|
|
222
|
-
|
|
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));
|
|
223
244
|
}
|
|
224
245
|
}
|
|
225
246
|
const isSameDirection = !isZero(positionSize) && isPositive(positionSize) === isPositive(signedQuantity);
|
|
@@ -233,7 +254,13 @@ function walkAssetFills(fills) {
|
|
|
233
254
|
}
|
|
234
255
|
positionSize = newPositionSize;
|
|
235
256
|
}
|
|
236
|
-
return {
|
|
257
|
+
return {
|
|
258
|
+
gross,
|
|
259
|
+
undeterminedSize,
|
|
260
|
+
closures,
|
|
261
|
+
exitQuantity,
|
|
262
|
+
exitNotional
|
|
263
|
+
};
|
|
237
264
|
}
|
|
238
265
|
function computeBasketWeightedRatioV1(basket, priceMap) {
|
|
239
266
|
let logSum = 0;
|
|
@@ -251,6 +278,13 @@ function computeBasketWeightedRatioV1(basket, priceMap) {
|
|
|
251
278
|
}
|
|
252
279
|
return exponentiate(logSum);
|
|
253
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
|
+
}
|
|
254
288
|
function contractsToBase(contracts, contractSize) {
|
|
255
289
|
return new BigNumber9(contracts).times(contractSize);
|
|
256
290
|
}
|
|
@@ -330,7 +364,9 @@ function computeUnrealizedPositionState(exposureMap, priceMap, fills) {
|
|
|
330
364
|
};
|
|
331
365
|
}
|
|
332
366
|
function computeRealizedPositionState(fills) {
|
|
333
|
-
|
|
367
|
+
return aggregateRealizedPositionState(computeAssetRealizedPnLs(fills));
|
|
368
|
+
}
|
|
369
|
+
function aggregateRealizedPositionState(assetPnLs) {
|
|
334
370
|
const grossPnl = computePositionGrossRealizedPnL(assetPnLs);
|
|
335
371
|
const netPnl = computePositionNetRealizedPnL(assetPnLs);
|
|
336
372
|
const tradeFees = assetPnLs.reduce((acc, asset) => acc.plus(asset.tradeFees), ZERO);
|
|
@@ -1663,7 +1699,10 @@ function _isValidExternalQuantity(quantity) {
|
|
|
1663
1699
|
return isGreaterThan(quantity, ZERO);
|
|
1664
1700
|
}
|
|
1665
1701
|
function _isValidExternalTimestamp(timestamp) {
|
|
1666
|
-
|
|
1702
|
+
if (!Number.isFinite(timestamp) || timestamp < 0) {
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
return timestamp <= Date.now() + WATERMARK_FUTURE_SKEW_MS;
|
|
1667
1706
|
}
|
|
1668
1707
|
function groupConfirmablesByPosition(confirmableFills) {
|
|
1669
1708
|
return confirmableFills.reduce((acc, fill) => {
|
|
@@ -1752,4 +1791,4 @@ function validateQuantity(quantity, price, precision) {
|
|
|
1752
1791
|
return true;
|
|
1753
1792
|
}
|
|
1754
1793
|
|
|
1755
|
-
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/dist/sync/compute.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HttpTypes } from '@pear-protocol/types';
|
|
2
2
|
import type { ComputeSyncPayloadArgs, ComputeSyncPayloadResult } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Derive the full sync payload from raw exchange data and local state.
|
|
@@ -29,5 +29,5 @@ export declare function _normalizeCloid(cloid?: string | null): string | undefin
|
|
|
29
29
|
export declare function _normalizeExchangeFillId(exchangeFillId?: string | null): string | undefined;
|
|
30
30
|
/** Rejects scientific-notation quantities (e.g. 1e-8) that cannot be safely parsed as fills. */
|
|
31
31
|
export declare function _isValidExternalQuantity(quantity: string): boolean;
|
|
32
|
-
/** Returns true only when the timestamp is a non-negative finite number. */
|
|
32
|
+
/** Returns true only when the timestamp is a non-negative finite number no further than clock skew ahead of now. */
|
|
33
33
|
export declare function _isValidExternalTimestamp(timestamp: number): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/utils",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
30
|
+
"@pear-protocol/types": "^1.17.0",
|
|
31
31
|
"bignumber.js": "9.3.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|