@pear-protocol/utils 0.0.1
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 +9 -0
- package/dist/asset/asset.types.js +1 -0
- package/dist/asset/compute.d.ts +25 -0
- package/dist/asset/compute.js +41 -0
- package/dist/asset/index.d.ts +2 -0
- package/dist/asset/index.js +1 -0
- package/dist/basket/basket.types.d.ts +10 -0
- package/dist/basket/basket.types.js +1 -0
- package/dist/basket/compute.d.ts +16 -0
- package/dist/basket/compute.js +28 -0
- package/dist/basket/index.d.ts +1 -0
- package/dist/basket/index.js +1 -0
- package/dist/contract-size.d.ts +11 -0
- package/dist/contract-size.js +10 -0
- package/dist/fills/compute.d.ts +9 -0
- package/dist/fills/compute.js +18 -0
- package/dist/fills/fill.types.d.ts +10 -0
- package/dist/fills/fill.types.js +1 -0
- package/dist/fills/flips.d.ts +5 -0
- package/dist/fills/flips.js +23 -0
- package/dist/fills/index.d.ts +1 -0
- package/dist/fills/index.js +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/math/index.d.ts +23 -0
- package/dist/math/index.js +18 -0
- package/dist/position/compute.d.ts +59 -0
- package/dist/position/compute.js +81 -0
- package/dist/position/index.d.ts +1 -0
- package/dist/position/index.js +1 -0
- package/dist/position/position.types.d.ts +11 -0
- package/dist/position/position.types.js +1 -0
- package/dist/precise-price.d.ts +3 -0
- package/dist/precise-price.js +63 -0
- package/dist/precise-quantity.d.ts +3 -0
- package/dist/precise-quantity.js +36 -0
- package/dist/validate-leverage.d.ts +2 -0
- package/dist/validate-leverage.js +7 -0
- package/dist/validate-quantity.d.ts +2 -0
- package/dist/validate-quantity.js +22 -0
- package/package.json +36 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'bignumber.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { InstrumentId } from '@pear-protocol/types';
|
|
2
|
+
import BigNumber from 'bignumber.js';
|
|
3
|
+
import type { Fill } from '../fills/fill.types';
|
|
4
|
+
import type { AssetPnL } from './asset.types';
|
|
5
|
+
type ExposureMap = Record<InstrumentId, string>;
|
|
6
|
+
type PriceMap = Record<InstrumentId, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Entry notional for a SINGLE asset.
|
|
9
|
+
*
|
|
10
|
+
* Formula: |quantity| × weightedAverageEntryPrice
|
|
11
|
+
*/
|
|
12
|
+
export declare function computeAssetEntryNotional(signedQuantity: BigNumber, fills: Fill[]): BigNumber;
|
|
13
|
+
/**
|
|
14
|
+
* Compute unrealized PnL for a SINGLE asset.
|
|
15
|
+
*
|
|
16
|
+
* Formula: signedQty × (currentPrice − entryPrice)
|
|
17
|
+
*/
|
|
18
|
+
export declare function computeAssetUPnL(signedQuantity: BigNumber, currentPrice: BigNumber, fills: Fill[]): BigNumber;
|
|
19
|
+
/**
|
|
20
|
+
* Compute unrealized PnL for an entire position.
|
|
21
|
+
*
|
|
22
|
+
* Formula: Σ(signedQty × (currentPrice − entryPrice))
|
|
23
|
+
*/
|
|
24
|
+
export declare function computeAssetPnLs(exposureMap: ExposureMap, priceMap: PriceMap, fills: Fill[]): AssetPnL[];
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import { computeEntryPriceForAsset } from '../fills/compute';
|
|
3
|
+
import { ZERO } from '../math';
|
|
4
|
+
|
|
5
|
+
function computeAssetEntryNotional(signedQuantity, fills) {
|
|
6
|
+
if (signedQuantity.isZero()) return ZERO;
|
|
7
|
+
const entryPrice = computeEntryPriceForAsset(fills);
|
|
8
|
+
if (entryPrice.isZero()) return ZERO;
|
|
9
|
+
const unsignedQuantity = signedQuantity.abs();
|
|
10
|
+
return unsignedQuantity.multipliedBy(entryPrice);
|
|
11
|
+
}
|
|
12
|
+
function computeAssetUPnL(signedQuantity, currentPrice, fills) {
|
|
13
|
+
if (signedQuantity.isZero()) return ZERO;
|
|
14
|
+
const entryPrice = computeEntryPriceForAsset(fills);
|
|
15
|
+
if (entryPrice.isZero()) return ZERO;
|
|
16
|
+
const deltaPrice = currentPrice.minus(entryPrice);
|
|
17
|
+
return signedQuantity.multipliedBy(deltaPrice);
|
|
18
|
+
}
|
|
19
|
+
function computeAssetPnLs(exposureMap, priceMap, fills) {
|
|
20
|
+
const result = [];
|
|
21
|
+
for (const [id, signedQtyString] of Object.entries(exposureMap)) {
|
|
22
|
+
const priceString = priceMap[id];
|
|
23
|
+
if (!priceString) continue;
|
|
24
|
+
const signedQuantity = new BigNumber(signedQtyString);
|
|
25
|
+
if (signedQuantity.isZero()) continue;
|
|
26
|
+
const currentPrice = new BigNumber(priceString);
|
|
27
|
+
const assetFills = fills.filter((f) => f.symbol === id);
|
|
28
|
+
const entryPrice = computeEntryPriceForAsset(assetFills);
|
|
29
|
+
const pnl = signedQuantity.multipliedBy(currentPrice.minus(entryPrice));
|
|
30
|
+
result.push({
|
|
31
|
+
id,
|
|
32
|
+
signedQuantity,
|
|
33
|
+
entryPrice,
|
|
34
|
+
currentPrice,
|
|
35
|
+
pnl
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { computeAssetEntryNotional, computeAssetPnLs, computeAssetUPnL };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './compute';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { InstrumentId } from '@pear-protocol/types';
|
|
2
|
+
import BigNumber from 'bignumber.js';
|
|
3
|
+
import type { BasketIndexLeg, BasketLeg } from './basket.types';
|
|
4
|
+
type PriceMap = Record<InstrumentId, string>;
|
|
5
|
+
/**
|
|
6
|
+
* Synthetic basket price:
|
|
7
|
+
*
|
|
8
|
+
* Π(priceᵢ ^ (sign · weightᵢ))
|
|
9
|
+
*
|
|
10
|
+
* Computed in log-space:
|
|
11
|
+
*
|
|
12
|
+
* exp( Σ(sign · weight · ln(price)) )
|
|
13
|
+
*/
|
|
14
|
+
export declare function computeBasketWeightedRatioV1(basket: BasketLeg[], priceMap: PriceMap): BigNumber | null;
|
|
15
|
+
export declare function computeBasketIndexV1(basket: BasketIndexLeg[], priceMap: PriceMap): BigNumber | null;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import 'bignumber.js';
|
|
2
|
+
import { exponentiate } from '../math';
|
|
3
|
+
|
|
4
|
+
function computeBasketWeightedRatioV1(basket, priceMap) {
|
|
5
|
+
let logSum = 0;
|
|
6
|
+
for (const leg of basket) {
|
|
7
|
+
const price = priceMap[leg.symbol];
|
|
8
|
+
if (!price) return null;
|
|
9
|
+
const priceNum = Number(price);
|
|
10
|
+
if (priceNum <= 0) return null;
|
|
11
|
+
const sign = leg.side === "BUY" ? 1 : -1;
|
|
12
|
+
logSum += sign * leg.weight * Math.log(priceNum);
|
|
13
|
+
}
|
|
14
|
+
return exponentiate(logSum);
|
|
15
|
+
}
|
|
16
|
+
function computeBasketIndexV1(basket, priceMap) {
|
|
17
|
+
let logSum = 0;
|
|
18
|
+
for (const leg of basket) {
|
|
19
|
+
const price = priceMap[leg.symbol];
|
|
20
|
+
if (!price) return null;
|
|
21
|
+
const priceNum = Number(price);
|
|
22
|
+
if (priceNum <= 0) return null;
|
|
23
|
+
logSum += leg.weight * Math.log(priceNum);
|
|
24
|
+
}
|
|
25
|
+
return exponentiate(logSum);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { computeBasketIndexV1, computeBasketWeightedRatioV1 };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './compute';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './compute';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
/**
|
|
3
|
+
* Converts a quantity from contract units to base asset units.
|
|
4
|
+
* For most exchanges contractSize is 1 (1:1 mapping), but some (e.g., OKX) use fractional contract sizes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function contractsToBase(contracts: BigNumber.Value, contractSize: number): BigNumber;
|
|
7
|
+
/**
|
|
8
|
+
* Converts a quantity from base asset units to contract units.
|
|
9
|
+
* For most exchanges contractSize is 1 (1:1 mapping), but some (e.g., OKX) use fractional contract sizes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function baseToContracts(base: BigNumber.Value, contractSize: number): BigNumber;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
function contractsToBase(contracts, contractSize) {
|
|
4
|
+
return new BigNumber(contracts).times(contractSize);
|
|
5
|
+
}
|
|
6
|
+
function baseToContracts(base, contractSize) {
|
|
7
|
+
return new BigNumber(base).div(contractSize);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export { baseToContracts, contractsToBase };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import type { Fill } from './fill.types';
|
|
3
|
+
/**
|
|
4
|
+
* Compute weighted average entry price for a SINGLE INSTRUMENT.
|
|
5
|
+
*
|
|
6
|
+
* - Handles flips automatically
|
|
7
|
+
* - Uses ONLY active fills since last flip
|
|
8
|
+
*/
|
|
9
|
+
export declare function computeEntryPriceForAsset(fills: Fill[]): BigNumber;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import { ZERO } from '../math';
|
|
3
|
+
import { getActiveFillsSinceLastFlip } from './flips';
|
|
4
|
+
|
|
5
|
+
function computeEntryPriceForAsset(fills) {
|
|
6
|
+
const activeFills = getActiveFillsSinceLastFlip(fills);
|
|
7
|
+
let weightedSum = ZERO;
|
|
8
|
+
let totalQty = ZERO;
|
|
9
|
+
for (const fill of activeFills) {
|
|
10
|
+
const price = new BigNumber(fill.price);
|
|
11
|
+
const qty = new BigNumber(fill.quantity);
|
|
12
|
+
weightedSum = weightedSum.plus(price.times(qty));
|
|
13
|
+
totalQty = totalQty.plus(qty);
|
|
14
|
+
}
|
|
15
|
+
return totalQty.isZero() ? ZERO : weightedSum.div(totalQty);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { computeEntryPriceForAsset };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import { sign } from '../math';
|
|
3
|
+
|
|
4
|
+
function getActiveFillsSinceLastFlip(fills) {
|
|
5
|
+
const lastFlipIndex = detectLastFlipIndex(fills);
|
|
6
|
+
return fills.slice(lastFlipIndex + 1);
|
|
7
|
+
}
|
|
8
|
+
function detectLastFlipIndex(fills) {
|
|
9
|
+
let runningExposure = new BigNumber(0);
|
|
10
|
+
let lastFlipIndex = -1;
|
|
11
|
+
fills.forEach((fill, i) => {
|
|
12
|
+
const qty = new BigNumber(fill.quantity);
|
|
13
|
+
const signedQty = fill.side === "BUY" ? qty : qty.negated();
|
|
14
|
+
const nextExposure = runningExposure.plus(signedQty);
|
|
15
|
+
if (!runningExposure.isZero() && sign(runningExposure) !== sign(nextExposure)) {
|
|
16
|
+
lastFlipIndex = i;
|
|
17
|
+
}
|
|
18
|
+
runningExposure = nextExposure;
|
|
19
|
+
});
|
|
20
|
+
return lastFlipIndex;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { getActiveFillsSinceLastFlip };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './compute';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './compute';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './asset';
|
|
2
|
+
export * from './basket';
|
|
3
|
+
export * from './contract-size';
|
|
4
|
+
export * from './fills';
|
|
5
|
+
export { countDecimals, exponentiate } from './math';
|
|
6
|
+
export * from './position';
|
|
7
|
+
export * from './precise-price';
|
|
8
|
+
export * from './precise-quantity';
|
|
9
|
+
export * from './validate-leverage';
|
|
10
|
+
export * from './validate-quantity';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './asset';
|
|
2
|
+
export * from './basket';
|
|
3
|
+
export * from './contract-size';
|
|
4
|
+
export * from './fills';
|
|
5
|
+
export { countDecimals, exponentiate } from './math';
|
|
6
|
+
export * from './position';
|
|
7
|
+
export * from './precise-price';
|
|
8
|
+
export * from './precise-quantity';
|
|
9
|
+
export * from './validate-leverage';
|
|
10
|
+
export * from './validate-quantity';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
export declare const ZERO: BigNumber;
|
|
3
|
+
export declare const BIPS: BigNumber;
|
|
4
|
+
/**
|
|
5
|
+
* Compute exponent using native Math.exp, returned as BigNumber. This
|
|
6
|
+
* is intentionally numeric (not arbitrary precision) and meant for
|
|
7
|
+
* index / basket calculations where log-space stability matters more
|
|
8
|
+
* than exactness.
|
|
9
|
+
*
|
|
10
|
+
* @param value - The number to exponentiate.
|
|
11
|
+
* @returns The exponentiated value.
|
|
12
|
+
*/
|
|
13
|
+
export declare function exponentiate(value: number): BigNumber;
|
|
14
|
+
/**
|
|
15
|
+
* Compute the sign of a BigNumber.
|
|
16
|
+
* @param value - The value to compute the sign of.
|
|
17
|
+
* @returns The sign of the value.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sign(value: BigNumber): 1 | -1 | 0;
|
|
20
|
+
/**
|
|
21
|
+
* Count decimal places in a numeric string (e.g. "0.001" → 3, "1" → 0).
|
|
22
|
+
*/
|
|
23
|
+
export declare function countDecimals(value: string): number;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
const ZERO = new BigNumber(0);
|
|
4
|
+
const BIPS = new BigNumber(1e4);
|
|
5
|
+
function exponentiate(value) {
|
|
6
|
+
const result = Math.exp(value);
|
|
7
|
+
return new BigNumber(result);
|
|
8
|
+
}
|
|
9
|
+
function sign(value) {
|
|
10
|
+
if (value.isZero()) return 0;
|
|
11
|
+
return value.isPositive() ? 1 : -1;
|
|
12
|
+
}
|
|
13
|
+
function countDecimals(value) {
|
|
14
|
+
const parts = value.split(".");
|
|
15
|
+
return parts[1]?.length ?? 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { BIPS, ZERO, countDecimals, exponentiate, sign };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { InstrumentId } from '@pear-protocol/types';
|
|
2
|
+
import BigNumber from 'bignumber.js';
|
|
3
|
+
import type { AssetPnL } from '../asset/asset.types';
|
|
4
|
+
import type { Fill } from '../fills/fill.types';
|
|
5
|
+
import type { PositionPnL } from './position.types';
|
|
6
|
+
type ExposureMap = Record<InstrumentId, string>;
|
|
7
|
+
type PriceMap = Record<InstrumentId, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Current position notional (mark value).
|
|
10
|
+
*
|
|
11
|
+
* Formula: Σ(|quantity| × currentPrice)
|
|
12
|
+
*/
|
|
13
|
+
export declare function computePositionMarkNotional(assetPnLs: AssetPnL[]): BigNumber;
|
|
14
|
+
/**
|
|
15
|
+
* Compute the position entry notional.
|
|
16
|
+
*
|
|
17
|
+
* Formula: Σ(|quantity| × entryPrice)
|
|
18
|
+
*/
|
|
19
|
+
export declare function computePositionEntryNotional(assetPnLs: AssetPnL[]): BigNumber;
|
|
20
|
+
/**
|
|
21
|
+
* Compute the position unrealized PnL.
|
|
22
|
+
*
|
|
23
|
+
* Formula: Σ(pnl)
|
|
24
|
+
*/
|
|
25
|
+
export declare function computePositionUPnL(assetPnLs: AssetPnL[]): BigNumber;
|
|
26
|
+
/**
|
|
27
|
+
* Compute the position PnL in basis points.
|
|
28
|
+
*
|
|
29
|
+
* Formula: UPnL bips = upnl / entryNotional × 10_000
|
|
30
|
+
*/
|
|
31
|
+
export declare function computePositionUPnLBips(upnl: BigNumber, entryNotional: BigNumber): BigNumber | null;
|
|
32
|
+
/**
|
|
33
|
+
* Compute the average entry price of the current net position.
|
|
34
|
+
*
|
|
35
|
+
* This is equivalent to the weighted average price of the current basket,
|
|
36
|
+
* where the weights are the absolute quantities per asset.
|
|
37
|
+
*
|
|
38
|
+
* Formula: Σ(|quantity| × entryPrice) / Σ(|quantity|)
|
|
39
|
+
*/
|
|
40
|
+
export declare function computePositionEntryPrice(assetPnLs: AssetPnL[]): BigNumber | null;
|
|
41
|
+
/**
|
|
42
|
+
* Compute the current (mark) price of the current net position.
|
|
43
|
+
*
|
|
44
|
+
* This is the weighted average mark price of the current basket,
|
|
45
|
+
* where the weights are the absolute quantities per asset.
|
|
46
|
+
*
|
|
47
|
+
* Formula: Σ(|quantity| × currentPrice) / Σ(|quantity|)
|
|
48
|
+
*/
|
|
49
|
+
export declare function computePositionCurrentPrice(assetPnLs: AssetPnL[]): BigNumber | null;
|
|
50
|
+
/**
|
|
51
|
+
* Compute the position PnL from fills.
|
|
52
|
+
*
|
|
53
|
+
* @param exposureMap - The exposure map of the position.
|
|
54
|
+
* @param priceMap - The price map of the position.
|
|
55
|
+
* @param fills - The fills of the position.
|
|
56
|
+
* @returns The position PnL.
|
|
57
|
+
*/
|
|
58
|
+
export declare function computePositionFromFills(exposureMap: ExposureMap, priceMap: PriceMap, fills: Fill[]): PositionPnL;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import 'bignumber.js';
|
|
2
|
+
import { computeAssetPnLs } from '../asset';
|
|
3
|
+
import { ZERO, BIPS } from '../math';
|
|
4
|
+
|
|
5
|
+
function computePositionMarkNotional(assetPnLs) {
|
|
6
|
+
return assetPnLs.reduce((acc, asset) => {
|
|
7
|
+
if (asset.signedQuantity.isZero()) return acc;
|
|
8
|
+
return acc.plus(asset.signedQuantity.abs().multipliedBy(asset.currentPrice));
|
|
9
|
+
}, ZERO);
|
|
10
|
+
}
|
|
11
|
+
function computePositionEntryNotional(assetPnLs) {
|
|
12
|
+
return assetPnLs.reduce((acc, asset) => {
|
|
13
|
+
if (asset.signedQuantity.isZero()) return acc;
|
|
14
|
+
return acc.plus(asset.signedQuantity.abs().multipliedBy(asset.entryPrice));
|
|
15
|
+
}, ZERO);
|
|
16
|
+
}
|
|
17
|
+
function computePositionUPnL(assetPnLs) {
|
|
18
|
+
return assetPnLs.reduce((acc, asset) => acc.plus(asset.pnl), ZERO);
|
|
19
|
+
}
|
|
20
|
+
function computePositionUPnLBips(upnl, entryNotional) {
|
|
21
|
+
if (entryNotional.isZero()) return null;
|
|
22
|
+
return upnl.div(entryNotional).multipliedBy(BIPS);
|
|
23
|
+
}
|
|
24
|
+
function computePositionEntryPrice(assetPnLs) {
|
|
25
|
+
const { totalWeightedPrice, totalWeight } = assetPnLs.reduce(
|
|
26
|
+
(acc, { signedQuantity, entryPrice }) => {
|
|
27
|
+
const absQuantity = signedQuantity.abs();
|
|
28
|
+
return {
|
|
29
|
+
totalWeightedPrice: acc.totalWeightedPrice.plus(absQuantity.multipliedBy(entryPrice)),
|
|
30
|
+
totalWeight: acc.totalWeight.plus(absQuantity)
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
totalWeightedPrice: ZERO,
|
|
35
|
+
totalWeight: ZERO
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
if (totalWeight.isZero()) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return totalWeightedPrice.dividedBy(totalWeight);
|
|
42
|
+
}
|
|
43
|
+
function computePositionCurrentPrice(assetPnLs) {
|
|
44
|
+
const { totalWeightedPrice, totalWeight } = assetPnLs.reduce(
|
|
45
|
+
(acc, { signedQuantity, currentPrice }) => {
|
|
46
|
+
const absQuantity = signedQuantity.abs();
|
|
47
|
+
return {
|
|
48
|
+
totalWeightedPrice: acc.totalWeightedPrice.plus(absQuantity.multipliedBy(currentPrice)),
|
|
49
|
+
totalWeight: acc.totalWeight.plus(absQuantity)
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
totalWeightedPrice: ZERO,
|
|
54
|
+
totalWeight: ZERO
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
if (totalWeight.isZero()) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return totalWeightedPrice.dividedBy(totalWeight);
|
|
61
|
+
}
|
|
62
|
+
function computePositionFromFills(exposureMap, priceMap, fills) {
|
|
63
|
+
const assets = computeAssetPnLs(exposureMap, priceMap, fills);
|
|
64
|
+
const entryPrice = computePositionEntryPrice(assets);
|
|
65
|
+
const entryNotional = computePositionEntryNotional(assets);
|
|
66
|
+
const markPrice = computePositionCurrentPrice(assets);
|
|
67
|
+
const markNotional = computePositionMarkNotional(assets);
|
|
68
|
+
const upnl = computePositionUPnL(assets);
|
|
69
|
+
const upnlBips = computePositionUPnLBips(upnl, entryNotional);
|
|
70
|
+
return {
|
|
71
|
+
assets,
|
|
72
|
+
entryPrice,
|
|
73
|
+
entryNotional,
|
|
74
|
+
markPrice,
|
|
75
|
+
markNotional,
|
|
76
|
+
upnl,
|
|
77
|
+
upnlBips
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { computePositionCurrentPrice, computePositionEntryNotional, computePositionEntryPrice, computePositionFromFills, computePositionMarkNotional, computePositionUPnL, computePositionUPnLBips };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './compute';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './compute';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import type { AssetPnL } from '../asset/asset.types';
|
|
3
|
+
export type PositionPnL = {
|
|
4
|
+
assets: AssetPnL[];
|
|
5
|
+
entryPrice: BigNumber | null;
|
|
6
|
+
entryNotional: BigNumber;
|
|
7
|
+
markPrice: BigNumber | null;
|
|
8
|
+
markNotional: BigNumber;
|
|
9
|
+
upnl: BigNumber;
|
|
10
|
+
upnlBips: BigNumber | null;
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import 'bignumber.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Connector, InstrumentPrecision, Side } from '@pear-protocol/types';
|
|
2
|
+
import BigNumber from 'bignumber.js';
|
|
3
|
+
export declare function precisePrice(connector: Connector, raw: BigNumber.Value, precision: InstrumentPrecision, side?: Side, slippageToleranceBps?: number): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
BigNumber.config({
|
|
4
|
+
DECIMAL_PLACES: 30,
|
|
5
|
+
ROUNDING_MODE: BigNumber.ROUND_DOWN
|
|
6
|
+
});
|
|
7
|
+
function precisePrice(connector, raw, precision, side = "BUY", slippageToleranceBps = 0) {
|
|
8
|
+
const normalized = normalizePrice(raw);
|
|
9
|
+
const effectiveRaw = withSlippageTolerance(normalized, slippageToleranceBps, side);
|
|
10
|
+
switch (connector) {
|
|
11
|
+
case "hyperliquid":
|
|
12
|
+
return preciseHyperliquidPrice(effectiveRaw, precision, side);
|
|
13
|
+
case "binanceusdm":
|
|
14
|
+
case "bybit":
|
|
15
|
+
case "okx":
|
|
16
|
+
return preciseTickGridPrice(effectiveRaw, precision, side);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function withSlippageTolerance(normalized, slippageToleranceBps, side) {
|
|
20
|
+
if (slippageToleranceBps === 0) {
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
const bps = new BigNumber(slippageToleranceBps);
|
|
24
|
+
const factor = bps.div(1e4);
|
|
25
|
+
return side === "BUY" ? normalized.times(new BigNumber(1).plus(factor)) : normalized.times(new BigNumber(1).minus(factor));
|
|
26
|
+
}
|
|
27
|
+
function preciseHyperliquidPrice(price, precision, side) {
|
|
28
|
+
const maxDecimals = precision.price.decimals;
|
|
29
|
+
const maxSigFigs = precision.price.sigFigs ?? 5;
|
|
30
|
+
const rounding = side === "BUY" ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL;
|
|
31
|
+
price = price.decimalPlaces(maxDecimals, rounding);
|
|
32
|
+
price = clampSigFigs(price, maxSigFigs, maxDecimals, rounding);
|
|
33
|
+
return price.toFixed();
|
|
34
|
+
}
|
|
35
|
+
function preciseTickGridPrice(price, precision, side) {
|
|
36
|
+
const tick = precision.price.tick ?? 1e-4;
|
|
37
|
+
const rounding = side === "BUY" ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL;
|
|
38
|
+
const tickBN = new BigNumber(tick);
|
|
39
|
+
const scaled = price.div(tickBN);
|
|
40
|
+
const snapped = scaled.integerValue(rounding).times(tickBN);
|
|
41
|
+
const maxDecimals = precision.price.decimals;
|
|
42
|
+
const final = snapped.decimalPlaces(maxDecimals, rounding);
|
|
43
|
+
return final.toFixed();
|
|
44
|
+
}
|
|
45
|
+
function clampSigFigs(price, maxSigFigs, maxDecimals, rounding) {
|
|
46
|
+
const countSigFigs = (x) => x.toFixed().replace(".", "").replace(/^0+/, "").length;
|
|
47
|
+
if (countSigFigs(price) <= maxSigFigs) {
|
|
48
|
+
return price;
|
|
49
|
+
}
|
|
50
|
+
for (let d = maxDecimals; d >= 0; d--) {
|
|
51
|
+
const candidate = price.decimalPlaces(d, rounding);
|
|
52
|
+
if (countSigFigs(candidate) <= maxSigFigs) {
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return price;
|
|
57
|
+
}
|
|
58
|
+
function normalizePrice(value) {
|
|
59
|
+
const bn = new BigNumber(value);
|
|
60
|
+
return new BigNumber(bn.toFixed());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { precisePrice };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import { baseToContracts } from './contract-size';
|
|
3
|
+
|
|
4
|
+
BigNumber.config({
|
|
5
|
+
DECIMAL_PLACES: 30,
|
|
6
|
+
ROUNDING_MODE: BigNumber.ROUND_DOWN
|
|
7
|
+
});
|
|
8
|
+
function preciseQuantity(connector, raw, precision, side) {
|
|
9
|
+
switch (connector) {
|
|
10
|
+
case "hyperliquid":
|
|
11
|
+
return preciseHyperliquidQuantity(raw, precision, side);
|
|
12
|
+
case "binanceusdm":
|
|
13
|
+
case "bybit":
|
|
14
|
+
case "okx":
|
|
15
|
+
return preciseStepGridQuantity(raw, precision, side);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function preciseHyperliquidQuantity(raw, precision, side) {
|
|
19
|
+
const decimals = precision.quantity.decimals;
|
|
20
|
+
const qty = new BigNumber(raw);
|
|
21
|
+
const rounded = qty.decimalPlaces(decimals, side === "BUY" ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
|
|
22
|
+
return rounded.toFixed();
|
|
23
|
+
}
|
|
24
|
+
function preciseStepGridQuantity(raw, precision, side) {
|
|
25
|
+
const step = precision.quantity.step;
|
|
26
|
+
const qty = baseToContracts(raw, precision.quantity.contractSize);
|
|
27
|
+
const stepBN = new BigNumber(step);
|
|
28
|
+
const scaled = qty.div(stepBN);
|
|
29
|
+
const rounded = side === "BUY" ? scaled.integerValue(BigNumber.ROUND_FLOOR) : scaled.integerValue(BigNumber.ROUND_CEIL);
|
|
30
|
+
const result = rounded.times(stepBN);
|
|
31
|
+
const maxDecimals = precision.quantity.decimals;
|
|
32
|
+
const final = result.decimalPlaces(maxDecimals, side === "BUY" ? BigNumber.ROUND_FLOOR : BigNumber.ROUND_CEIL);
|
|
33
|
+
return final.toFixed();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { preciseQuantity };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import { contractsToBase } from './contract-size';
|
|
3
|
+
|
|
4
|
+
function validateQuantity(quantity, price, precision) {
|
|
5
|
+
const q = new BigNumber(quantity);
|
|
6
|
+
if (!q.isFinite() || q.lte(0)) return false;
|
|
7
|
+
const p = new BigNumber(price);
|
|
8
|
+
if (!p.isFinite() || p.lte(0)) return false;
|
|
9
|
+
const step = new BigNumber(precision.quantity.step);
|
|
10
|
+
const scaled = q.div(step);
|
|
11
|
+
if (!scaled.isInteger()) return false;
|
|
12
|
+
const notional = contractsToBase(q, precision.quantity.contractSize).times(p);
|
|
13
|
+
if (precision.notional?.min !== void 0 && precision.notional.min !== null && precision.notional.min > 0) {
|
|
14
|
+
if (notional.lt(new BigNumber(precision.notional.min))) return false;
|
|
15
|
+
}
|
|
16
|
+
if (precision.notional?.max !== void 0 && precision.notional.max !== null && precision.notional.max > 0) {
|
|
17
|
+
if (notional.gt(new BigNumber(precision.notional.max))) return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { validateQuantity };
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pear-protocol/utils",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Pear Protocol Utility functions",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup && tsc -p tsconfig.build.json",
|
|
24
|
+
"clean": "rm -rf dist .turbo",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@pear-protocol/types": "0.0.1",
|
|
29
|
+
"bignumber.js": "9.3.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@backend/typescript-config": "workspace:*",
|
|
33
|
+
"tsup": "8.5.1",
|
|
34
|
+
"typescript": "5.9.3"
|
|
35
|
+
}
|
|
36
|
+
}
|