@pear-protocol/utils 0.5.2 → 0.6.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.
|
@@ -1,6 +1,56 @@
|
|
|
1
|
-
import type { InstrumentId, Side } from '@pear-protocol/types';
|
|
1
|
+
import type { HttpTypes, InstrumentId, Side } from '@pear-protocol/types';
|
|
2
2
|
export type BasketLeg = {
|
|
3
3
|
symbol: InstrumentId;
|
|
4
4
|
side: Side;
|
|
5
5
|
weight: number;
|
|
6
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors the shipped basket model (`BasketItem` in the frontend): unsigned `weight`
|
|
9
|
+
* with `side` alongside, weights summing to 100. Sizing ignores `side` — margin
|
|
10
|
+
* requirement does not depend on direction — and normalises by Σweight.
|
|
11
|
+
*/
|
|
12
|
+
export type MaxBasketSizeLeg = {
|
|
13
|
+
asset: InstrumentId;
|
|
14
|
+
side: Side;
|
|
15
|
+
/** Unsigned percentage. The set sums to 100. */
|
|
16
|
+
weight: number;
|
|
17
|
+
};
|
|
18
|
+
export type MaxBasketSizeInput = {
|
|
19
|
+
legs: MaxBasketSizeLeg[];
|
|
20
|
+
/** What the caller is requesting for the whole basket, before each leg's own venue max caps it. */
|
|
21
|
+
requestedLeverage: number;
|
|
22
|
+
};
|
|
23
|
+
export type MaxBasketSizeResult = {
|
|
24
|
+
/**
|
|
25
|
+
* The whole basket's max GROSS notional in USD — Σ leg notional, not per-side and not per-asset.
|
|
26
|
+
* The basket is atomic, so this is one ceiling for the entire structure, not a per-leg maximum.
|
|
27
|
+
* Null when the caller could not supply an available balance, or a leg names an asset the
|
|
28
|
+
* caller's max-leverage map does not list.
|
|
29
|
+
*/
|
|
30
|
+
maxNotional: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* The margin `maxNotional` spends — always the whole `budget`/`availableBalance` the caller
|
|
33
|
+
* supplied, since that is exactly the point at which the basket stops growing. Stated explicitly
|
|
34
|
+
* rather than left for the caller to infer, and `null` on the same terms as `maxNotional`.
|
|
35
|
+
*/
|
|
36
|
+
marginRequired: string | null;
|
|
37
|
+
};
|
|
38
|
+
/** One basket leg with its leverage already capped by the caller. */
|
|
39
|
+
export type CappedBasketLeg = {
|
|
40
|
+
/** Unsigned, in the caller's own scale — `computeMaxBasketSize` normalises by Σweight. */
|
|
41
|
+
weight: number;
|
|
42
|
+
effectiveLeverage: string;
|
|
43
|
+
};
|
|
44
|
+
/** The one margin budget every leg funds out of. */
|
|
45
|
+
export type MaxBasketSizeCalculation = {
|
|
46
|
+
/** USD-equivalent margin available. Every connector this sizes for funds a basket from one balance. */
|
|
47
|
+
budget: string;
|
|
48
|
+
legs: CappedBasketLeg[];
|
|
49
|
+
};
|
|
50
|
+
/** What `sizeSinglePoolBasket` needs beyond the legs: the account's balance and the leverage ceilings. */
|
|
51
|
+
export type SinglePoolSizingContext = {
|
|
52
|
+
/** The account's whole available margin, or `null` when the caller could not read one. */
|
|
53
|
+
availableBalance: string | null;
|
|
54
|
+
/** Off the core-sdk instrument catalog — only `id` and `leverage` are read. */
|
|
55
|
+
instruments: Pick<HttpTypes.Instrument, 'id' | 'leverage'>[];
|
|
56
|
+
};
|
package/dist/basket/index.d.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MaxBasketSizeCalculation, MaxBasketSizeInput, MaxBasketSizeResult, SinglePoolSizingContext } from './basket.types';
|
|
2
|
+
/**
|
|
3
|
+
* The largest gross notional this basket can reach while every leg stays funded at its weight.
|
|
4
|
+
*
|
|
5
|
+
* Takes the caller's figures as given — a malformed budget or leverage is the caller's own
|
|
6
|
+
* responsibility to keep clean (enforce it with a schema at the boundary if that matters to you),
|
|
7
|
+
* not something this function guards against.
|
|
8
|
+
*/
|
|
9
|
+
export declare function computeMaxBasketSize(input: MaxBasketSizeCalculation): MaxBasketSizeResult;
|
|
10
|
+
/**
|
|
11
|
+
* Sizes a basket that funds every leg out of one account-wide balance and caps leverage at nothing
|
|
12
|
+
* but the caller-supplied max: `effectiveLeverage = min(requested, catalogMax)`.
|
|
13
|
+
*
|
|
14
|
+
* A leg naming an asset the caller's catalog does not list leaves its ceiling unknown, and sizing
|
|
15
|
+
* against the request alone would overstate the basket — so one unknown leg (or no available
|
|
16
|
+
* balance at all) reports no size rather than a wrong one.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sizeSinglePoolBasket(input: MaxBasketSizeInput, context: SinglePoolSizingContext): MaxBasketSizeResult;
|
package/dist/index.js
CHANGED
|
@@ -328,6 +328,81 @@ function deriveBasketKey(longInstrumentIds, shortInstrumentIds) {
|
|
|
328
328
|
return bytesToHex(sha256(new TextEncoder().encode(payload)));
|
|
329
329
|
}
|
|
330
330
|
__name(deriveBasketKey, "deriveBasketKey");
|
|
331
|
+
|
|
332
|
+
// src/basket/max-size.ts
|
|
333
|
+
function computeMaxBasketSize(input) {
|
|
334
|
+
const weightSum = sumWeights(input.legs);
|
|
335
|
+
if (isZero(weightSum)) {
|
|
336
|
+
return unsizedResult();
|
|
337
|
+
}
|
|
338
|
+
const shares = input.legs.map((leg) => toLegShare(leg, weightSum));
|
|
339
|
+
const size = divide(input.budget, marginPerUnitNotional(shares));
|
|
340
|
+
return {
|
|
341
|
+
maxNotional: size.toFixed(),
|
|
342
|
+
marginRequired: input.budget
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
__name(computeMaxBasketSize, "computeMaxBasketSize");
|
|
346
|
+
function sizeSinglePoolBasket(input, context) {
|
|
347
|
+
if (context.availableBalance === null) {
|
|
348
|
+
return unsizedResult();
|
|
349
|
+
}
|
|
350
|
+
const maxLeverageById = new Map(context.instruments.map((instrument) => [
|
|
351
|
+
instrument.id,
|
|
352
|
+
instrument.leverage
|
|
353
|
+
]));
|
|
354
|
+
const capped = [];
|
|
355
|
+
for (const leg of input.legs) {
|
|
356
|
+
const maxLeverage = maxLeverageById.get(leg.asset);
|
|
357
|
+
if (maxLeverage === void 0) {
|
|
358
|
+
return unsizedResult();
|
|
359
|
+
}
|
|
360
|
+
capped.push({
|
|
361
|
+
weight: leg.weight,
|
|
362
|
+
effectiveLeverage: cappedLeverage(input.requestedLeverage, maxLeverage)
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return computeMaxBasketSize({
|
|
366
|
+
budget: context.availableBalance,
|
|
367
|
+
legs: capped
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
__name(sizeSinglePoolBasket, "sizeSinglePoolBasket");
|
|
371
|
+
function cappedLeverage(requested, maxLeverage) {
|
|
372
|
+
const granted = min(String(requested), String(maxLeverage));
|
|
373
|
+
return isFiniteDecimal(granted) && granted.isGreaterThan(0) ? granted.toFixed() : "1";
|
|
374
|
+
}
|
|
375
|
+
__name(cappedLeverage, "cappedLeverage");
|
|
376
|
+
function sumWeights(legs) {
|
|
377
|
+
let total = ZERO;
|
|
378
|
+
for (const leg of legs) {
|
|
379
|
+
total = add(total, String(leg.weight));
|
|
380
|
+
}
|
|
381
|
+
return total;
|
|
382
|
+
}
|
|
383
|
+
__name(sumWeights, "sumWeights");
|
|
384
|
+
function unsizedResult() {
|
|
385
|
+
return {
|
|
386
|
+
maxNotional: null,
|
|
387
|
+
marginRequired: null
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
__name(unsizedResult, "unsizedResult");
|
|
391
|
+
function toLegShare(leg, weightSum) {
|
|
392
|
+
return {
|
|
393
|
+
share: divide(String(leg.weight), weightSum),
|
|
394
|
+
imr: divide("1", leg.effectiveLeverage)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
__name(toLegShare, "toLegShare");
|
|
398
|
+
function marginPerUnitNotional(shares) {
|
|
399
|
+
let demand = ZERO;
|
|
400
|
+
for (const leg of shares) {
|
|
401
|
+
demand = add(demand, multiply(leg.share, leg.imr));
|
|
402
|
+
}
|
|
403
|
+
return demand;
|
|
404
|
+
}
|
|
405
|
+
__name(marginPerUnitNotional, "marginPerUnitNotional");
|
|
331
406
|
function contractsToBase(contracts, contractSize) {
|
|
332
407
|
return new BigNumber7(contracts).times(contractSize);
|
|
333
408
|
}
|
|
@@ -1108,13 +1183,9 @@ function resolveLeftoverTarget(args) {
|
|
|
1108
1183
|
const leftoverShorts = new Set(leftover.filter((entry) => entry.side === "SELL").map((entry) => entry.leg.symbol));
|
|
1109
1184
|
const target = book.open().find((position) => {
|
|
1110
1185
|
const { longs, shorts } = position.exposure.activeLegs();
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
const shortOk = [
|
|
1115
|
-
...leftoverShorts
|
|
1116
|
-
].every((a) => shorts.has(a));
|
|
1117
|
-
return longOk && shortOk;
|
|
1186
|
+
const sameActiveShape = setEq(leftoverLongs, longs) && setEq(leftoverShorts, shorts);
|
|
1187
|
+
const noDormantLegs = position.exposure.symbols().length === longs.size + shorts.size;
|
|
1188
|
+
return sameActiveShape && noDormantLegs;
|
|
1118
1189
|
});
|
|
1119
1190
|
if (target) {
|
|
1120
1191
|
return target;
|
|
@@ -2139,4 +2210,4 @@ function takeVerifier(handoffId, storage) {
|
|
|
2139
2210
|
}
|
|
2140
2211
|
__name(takeVerifier, "takeVerifier");
|
|
2141
2212
|
|
|
2142
|
-
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, finishXHandoff, generateDeterministicPositionKey, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, proxyFetch, sideSign, sign, signed, signedBySide, startXHandoff, stripXReturn, subtract, toDecimalString, validateLeverage, validateQuantity };
|
|
2213
|
+
export { BIPS, ZERO, abs, add, aggregateRealizedPositionState, baseToContracts, buildApplicables, compareValue, computeAssetEntryNotional, computeAssetGrossRealizedPnL, computeAssetRealizedPnLs, computeAssetUndeterminedSizes, computeAssetUnrealizedPnL, computeAssetUnrealizedPnLs, computeBasketWeightedRatioV1, computeEntryPriceForAsset, computeMaxBasketSize, computePositionEntryPrices, computeRealizedPnlBySymbol, computeRealizedPnlFromFills, computeRealizedPositionState, computeSyncPayload, computeUnrealizedPositionState, computeUnrealizedPositionStateFromEntryPrices, configureProxy, contractsToBase, countDecimals, deriveBasketKey, divide, exponentiate, finishXHandoff, generateDeterministicPositionKey, isEqualTo, isFiniteDecimal, isGreaterThan, isGreaterThanOrEqual, isLessThan, isLessThanOrEqual, isNegative, isPositive, isZero, min, multiply, negate, parse, precisePrice, preciseQuantity, proxyFetch, sideSign, sign, signed, signedBySide, sizeSinglePoolBasket, startXHandoff, stripXReturn, subtract, toDecimalString, validateLeverage, validateQuantity };
|
|
@@ -6,8 +6,8 @@ import type { MatchedPosition } from './types';
|
|
|
6
6
|
* - findExactCloseTarget picks the open position whose ACTIVE legs mirror the
|
|
7
7
|
* basket (dead legs ignored) — the basket closes that position first,
|
|
8
8
|
* - applyCloses reduces holders (the mirrored target first, then any other),
|
|
9
|
-
* - resolveLeftoverTarget
|
|
10
|
-
* new (same-shape ADDs consolidate here).
|
|
9
|
+
* - resolveLeftoverTarget merges into a position whose FULL leg set (active and
|
|
10
|
+
* dormant) is exactly the leftover's, else opens new (same-shape ADDs consolidate here).
|
|
11
11
|
* The asset SET of a position never grows, so positionKey == key(opening symbols).
|
|
12
12
|
*/
|
|
13
13
|
export declare function matchPositions(args: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.28.0",
|
|
31
31
|
"@scure/base": "^2.3.0",
|
|
32
32
|
"bignumber.js": "9.3.1"
|
|
33
33
|
},
|