@lifi/perps-sdk 24.0.0 → 25.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/README.md +34 -28
- package/dist/cjs/decimal/arithmetic.d.ts +2 -0
- package/dist/cjs/decimal/arithmetic.d.ts.map +1 -1
- package/dist/cjs/decimal/arithmetic.js +6 -1
- package/dist/cjs/decimal/arithmetic.js.map +1 -1
- package/dist/cjs/decimal/convert.d.ts +0 -2
- package/dist/cjs/decimal/convert.d.ts.map +1 -1
- package/dist/cjs/decimal/convert.js +2 -11
- package/dist/cjs/decimal/convert.js.map +1 -1
- package/dist/cjs/decimal/decimalStringToBig.d.ts.map +1 -1
- package/dist/cjs/decimal/decimalStringToBig.js +3 -5
- package/dist/cjs/decimal/decimalStringToBig.js.map +1 -1
- package/dist/cjs/decimal/parse.d.ts +1 -0
- package/dist/cjs/decimal/parse.d.ts.map +1 -1
- package/dist/cjs/decimal/parse.js +2 -1
- package/dist/cjs/decimal/parse.js.map +1 -1
- package/dist/cjs/errors/invalidInput.d.ts +3 -0
- package/dist/cjs/errors/invalidInput.d.ts.map +1 -0
- package/dist/cjs/errors/invalidInput.js +9 -0
- package/dist/cjs/errors/invalidInput.js.map +1 -0
- package/dist/cjs/index.d.ts +5 -4
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +10 -6
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/math/order.d.ts +4 -4
- package/dist/cjs/math/order.d.ts.map +1 -1
- package/dist/cjs/math/order.js +31 -18
- package/dist/cjs/math/order.js.map +1 -1
- package/dist/cjs/math/position.d.ts +2 -2
- package/dist/cjs/math/position.d.ts.map +1 -1
- package/dist/cjs/math/position.js +17 -14
- package/dist/cjs/math/position.js.map +1 -1
- package/dist/cjs/types/provider.d.ts.map +1 -1
- package/dist/cjs/utils/warnOnce.d.ts +6 -2
- package/dist/cjs/utils/warnOnce.d.ts.map +1 -1
- package/dist/cjs/utils/warnOnce.js +12 -6
- package/dist/cjs/utils/warnOnce.js.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/cjs/websocket/wsLog.d.ts +1 -1
- package/dist/cjs/websocket/wsLog.d.ts.map +1 -1
- package/dist/cjs/websocket/wsLog.js +4 -3
- package/dist/cjs/websocket/wsLog.js.map +1 -1
- package/dist/cjs/wire/orderAmounts.d.ts +3 -2
- package/dist/cjs/wire/orderAmounts.d.ts.map +1 -1
- package/dist/cjs/wire/orderAmounts.js +17 -13
- package/dist/cjs/wire/orderAmounts.js.map +1 -1
- package/dist/cjs/wire/refuel.js +2 -2
- package/dist/cjs/wire/refuel.js.map +1 -1
- package/dist/esm/decimal/arithmetic.d.ts +24 -10
- package/dist/esm/decimal/arithmetic.d.ts.map +1 -1
- package/dist/esm/decimal/arithmetic.js +26 -10
- package/dist/esm/decimal/arithmetic.js.map +1 -1
- package/dist/esm/decimal/compare.d.ts +3 -3
- package/dist/esm/decimal/compare.js +3 -3
- package/dist/esm/decimal/convert.d.ts +2 -24
- package/dist/esm/decimal/convert.d.ts.map +1 -1
- package/dist/esm/decimal/convert.js +3 -32
- package/dist/esm/decimal/convert.js.map +1 -1
- package/dist/esm/decimal/decimalStringToBig.d.ts +4 -5
- package/dist/esm/decimal/decimalStringToBig.d.ts.map +1 -1
- package/dist/esm/decimal/decimalStringToBig.js +7 -10
- package/dist/esm/decimal/decimalStringToBig.js.map +1 -1
- package/dist/esm/decimal/format.js +3 -3
- package/dist/esm/decimal/parse.d.ts +8 -1
- package/dist/esm/decimal/parse.d.ts.map +1 -1
- package/dist/esm/decimal/parse.js +8 -1
- package/dist/esm/decimal/parse.js.map +1 -1
- package/dist/esm/errors/invalidInput.d.ts +4 -0
- package/dist/esm/errors/invalidInput.d.ts.map +1 -0
- package/dist/esm/errors/invalidInput.js +7 -0
- package/dist/esm/errors/invalidInput.js.map +1 -0
- package/dist/esm/index.d.ts +5 -4
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/math/fills.d.ts +4 -4
- package/dist/esm/math/fills.js +4 -4
- package/dist/esm/math/order.d.ts +24 -26
- package/dist/esm/math/order.d.ts.map +1 -1
- package/dist/esm/math/order.js +52 -41
- package/dist/esm/math/order.js.map +1 -1
- package/dist/esm/math/position.d.ts +30 -26
- package/dist/esm/math/position.d.ts.map +1 -1
- package/dist/esm/math/position.js +46 -39
- package/dist/esm/math/position.js.map +1 -1
- package/dist/esm/services/resolveSubscribeQuote.d.ts +1 -1
- package/dist/esm/services/resolveSubscribeQuote.js +1 -1
- package/dist/esm/types/provider.d.ts +5 -3
- package/dist/esm/types/provider.d.ts.map +1 -1
- package/dist/esm/utils/createSafeFunction.d.ts +1 -1
- package/dist/esm/utils/createSafeFunction.js +1 -1
- package/dist/esm/utils/warnOnce.d.ts +17 -6
- package/dist/esm/utils/warnOnce.d.ts.map +1 -1
- package/dist/esm/utils/warnOnce.js +22 -10
- package/dist/esm/utils/warnOnce.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/websocket/wsLog.d.ts +4 -4
- package/dist/esm/websocket/wsLog.d.ts.map +1 -1
- package/dist/esm/websocket/wsLog.js +8 -7
- package/dist/esm/websocket/wsLog.js.map +1 -1
- package/dist/esm/wire/orderAmounts.d.ts +9 -7
- package/dist/esm/wire/orderAmounts.d.ts.map +1 -1
- package/dist/esm/wire/orderAmounts.js +22 -18
- package/dist/esm/wire/orderAmounts.js.map +1 -1
- package/dist/esm/wire/refuel.d.ts +1 -1
- package/dist/esm/wire/refuel.js +4 -4
- package/dist/esm/wire/refuel.js.map +1 -1
- package/dist/esm/wire/transferable.d.ts +2 -2
- package/dist/esm/wire/transferable.js +2 -2
- package/dist/types/decimal/arithmetic.d.ts +24 -10
- package/dist/types/decimal/arithmetic.d.ts.map +1 -1
- package/dist/types/decimal/compare.d.ts +3 -3
- package/dist/types/decimal/convert.d.ts +2 -24
- package/dist/types/decimal/convert.d.ts.map +1 -1
- package/dist/types/decimal/decimalStringToBig.d.ts +4 -5
- package/dist/types/decimal/decimalStringToBig.d.ts.map +1 -1
- package/dist/types/decimal/parse.d.ts +8 -1
- package/dist/types/decimal/parse.d.ts.map +1 -1
- package/dist/types/errors/invalidInput.d.ts +4 -0
- package/dist/types/errors/invalidInput.d.ts.map +1 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/math/fills.d.ts +4 -4
- package/dist/types/math/order.d.ts +24 -26
- package/dist/types/math/order.d.ts.map +1 -1
- package/dist/types/math/position.d.ts +30 -26
- package/dist/types/math/position.d.ts.map +1 -1
- package/dist/types/services/resolveSubscribeQuote.d.ts +1 -1
- package/dist/types/types/provider.d.ts +5 -3
- package/dist/types/types/provider.d.ts.map +1 -1
- package/dist/types/utils/createSafeFunction.d.ts +1 -1
- package/dist/types/utils/warnOnce.d.ts +17 -6
- package/dist/types/utils/warnOnce.d.ts.map +1 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/websocket/wsLog.d.ts +4 -4
- package/dist/types/websocket/wsLog.d.ts.map +1 -1
- package/dist/types/wire/orderAmounts.d.ts +9 -7
- package/dist/types/wire/orderAmounts.d.ts.map +1 -1
- package/dist/types/wire/refuel.d.ts +1 -1
- package/dist/types/wire/transferable.d.ts +2 -2
- package/package.json +2 -2
- package/src/decimal/arithmetic.ts +36 -10
- package/src/decimal/compare.ts +3 -3
- package/src/decimal/convert.ts +3 -40
- package/src/decimal/decimalStringToBig.ts +7 -12
- package/src/decimal/format.ts +3 -3
- package/src/decimal/parse.ts +12 -1
- package/src/errors/invalidInput.ts +10 -0
- package/src/index.ts +8 -3
- package/src/math/fills.ts +4 -4
- package/src/math/order.ts +56 -48
- package/src/math/position.ts +50 -44
- package/src/services/resolveSubscribeQuote.ts +1 -1
- package/src/types/provider.ts +5 -3
- package/src/utils/createSafeFunction.ts +1 -1
- package/src/utils/warnOnce.ts +28 -12
- package/src/version.ts +1 -1
- package/src/websocket/wsLog.ts +22 -9
- package/src/wire/orderAmounts.ts +42 -24
- package/src/wire/refuel.ts +4 -4
- package/src/wire/transferable.ts +2 -2
package/src/math/position.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Display-tier position formulas and margin-adjustment predicates. Every
|
|
3
3
|
* formula takes and gives decimal strings; arithmetic is exact Big.js, with
|
|
4
4
|
* division to 40 decimal places. Each formula throws `ValidationError` on an
|
|
5
|
-
* input that
|
|
5
|
+
* input that does not match the decimal pattern and has a `safe*` pair that gives
|
|
6
6
|
* `undefined` in place of the throw.
|
|
7
7
|
*
|
|
8
8
|
* Sign convention: long = +1, short = -1. Sizes passed to the `estimate*`
|
|
@@ -12,26 +12,24 @@
|
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
MarginMode,
|
|
15
|
-
PerpsErrorCode,
|
|
16
15
|
type PerpsMarket,
|
|
17
16
|
type Position,
|
|
18
17
|
PositionMarginAdjustment,
|
|
19
18
|
} from '@lifi/perps-types'
|
|
20
19
|
import { DivBig } from '../decimal/big.js'
|
|
21
|
-
import { numberToDecimalString } from '../decimal/convert.js'
|
|
22
20
|
import {
|
|
23
21
|
bigToDecimalString,
|
|
24
22
|
decimalStringToBig,
|
|
25
23
|
decimalStringToDivBig,
|
|
26
24
|
} from '../decimal/decimalStringToBig.js'
|
|
27
|
-
import {
|
|
25
|
+
import { invalidInput } from '../errors/invalidInput.js'
|
|
28
26
|
import type { LiquidationEstimateParams } from '../types/provider.js'
|
|
29
27
|
import { createSafeFunction } from '../utils/createSafeFunction.js'
|
|
30
28
|
|
|
31
29
|
/**
|
|
32
30
|
* Notional value of a position: `|size| × price`.
|
|
33
31
|
*
|
|
34
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
32
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
|
|
35
33
|
* @public
|
|
36
34
|
*/
|
|
37
35
|
export function calculateNotionalValue(size: string, price: string): string {
|
|
@@ -50,7 +48,7 @@ export const safeCalculateNotionalValue = createSafeFunction(
|
|
|
50
48
|
* Unrealized PnL: `(currentPrice - entryPrice) × size`, where `size` is
|
|
51
49
|
* positive for a long and negative for a short.
|
|
52
50
|
*
|
|
53
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
51
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
|
|
54
52
|
* @public
|
|
55
53
|
*/
|
|
56
54
|
export function calculateUnrealizedPnl(
|
|
@@ -73,16 +71,16 @@ export const safeCalculateUnrealizedPnl = createSafeFunction(
|
|
|
73
71
|
|
|
74
72
|
/**
|
|
75
73
|
* Return on equity as a percentage (`'10'` for 10%): `pnl ÷ margin × 100`.
|
|
76
|
-
* A zero margin gives `'0'`.
|
|
77
74
|
*
|
|
78
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
75
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the
|
|
76
|
+
* decimal pattern or `margin` is zero.
|
|
79
77
|
* @public
|
|
80
78
|
*/
|
|
81
79
|
export function calculateRoe(pnl: string, margin: string): string {
|
|
82
80
|
const marginBig = decimalStringToBig(margin)
|
|
83
81
|
const pnlBig = decimalStringToDivBig(pnl)
|
|
84
82
|
if (marginBig.eq(0)) {
|
|
85
|
-
|
|
83
|
+
throw invalidInput('margin', 'must not be zero')
|
|
86
84
|
}
|
|
87
85
|
return bigToDecimalString(pnlBig.div(marginBig).times(100))
|
|
88
86
|
}
|
|
@@ -93,7 +91,7 @@ export const safeCalculateRoe = createSafeFunction('calculateRoe', calculateRoe)
|
|
|
93
91
|
/**
|
|
94
92
|
* Required margin: `notionalValue ÷ leverage`.
|
|
95
93
|
*
|
|
96
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
94
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern
|
|
97
95
|
* or `leverage` is zero.
|
|
98
96
|
* @public
|
|
99
97
|
*/
|
|
@@ -104,10 +102,7 @@ export function calculateRequiredMargin(
|
|
|
104
102
|
const notionalBig = decimalStringToDivBig(notionalValue)
|
|
105
103
|
const leverageBig = decimalStringToBig(leverage)
|
|
106
104
|
if (leverageBig.eq(0)) {
|
|
107
|
-
throw
|
|
108
|
-
PerpsErrorCode.ValidationError,
|
|
109
|
-
'Leverage must not be zero.'
|
|
110
|
-
)
|
|
105
|
+
throw invalidInput('leverage', 'must not be zero')
|
|
111
106
|
}
|
|
112
107
|
return bigToDecimalString(notionalBig.div(leverageBig))
|
|
113
108
|
}
|
|
@@ -120,9 +115,10 @@ export const safeCalculateRequiredMargin = createSafeFunction(
|
|
|
120
115
|
|
|
121
116
|
/**
|
|
122
117
|
* Distance from the current price to the liquidation price, as an absolute
|
|
123
|
-
* percentage of the current price.
|
|
118
|
+
* percentage of the current price.
|
|
124
119
|
*
|
|
125
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
120
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the
|
|
121
|
+
* decimal pattern or `currentPrice` is zero.
|
|
126
122
|
* @public
|
|
127
123
|
*/
|
|
128
124
|
export function calculateLiquidationDistance(params: {
|
|
@@ -132,7 +128,7 @@ export function calculateLiquidationDistance(params: {
|
|
|
132
128
|
const liquidationPrice = decimalStringToDivBig(params.liquidationPrice)
|
|
133
129
|
const currentPrice = decimalStringToBig(params.currentPrice)
|
|
134
130
|
if (currentPrice.eq(0)) {
|
|
135
|
-
|
|
131
|
+
throw invalidInput('currentPrice', 'must not be zero')
|
|
136
132
|
}
|
|
137
133
|
return bigToDecimalString(
|
|
138
134
|
liquidationPrice.minus(currentPrice).div(currentPrice).abs().times(100)
|
|
@@ -147,9 +143,9 @@ export const safeCalculateLiquidationDistance = createSafeFunction(
|
|
|
147
143
|
|
|
148
144
|
/**
|
|
149
145
|
* Effective leverage of an open position: `positionValueUsd ÷ marginUsd`.
|
|
150
|
-
* A zero margin gives `'0'`.
|
|
151
146
|
*
|
|
152
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
147
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the
|
|
148
|
+
* decimal pattern or `marginUsd` is zero.
|
|
153
149
|
* @public
|
|
154
150
|
*/
|
|
155
151
|
export function calculateEffectiveLeverage(params: {
|
|
@@ -159,7 +155,7 @@ export function calculateEffectiveLeverage(params: {
|
|
|
159
155
|
const positionValue = decimalStringToDivBig(params.positionValueUsd)
|
|
160
156
|
const margin = decimalStringToBig(params.marginUsd)
|
|
161
157
|
if (margin.eq(0)) {
|
|
162
|
-
|
|
158
|
+
throw invalidInput('marginUsd', 'must not be zero')
|
|
163
159
|
}
|
|
164
160
|
return bigToDecimalString(positionValue.div(margin))
|
|
165
161
|
}
|
|
@@ -182,9 +178,12 @@ export const safeCalculateEffectiveLeverage = createSafeFunction(
|
|
|
182
178
|
*
|
|
183
179
|
* @param maintenanceMarginRate - Venue maintenance margin rate as a fraction
|
|
184
180
|
* (`'0.01'` = 1%).
|
|
185
|
-
* @returns The estimate, or `undefined`
|
|
186
|
-
*
|
|
187
|
-
*
|
|
181
|
+
* @returns The estimate, or `undefined` when `1 / leverage` is at or below
|
|
182
|
+
* `maintenanceMarginRate`: the position opens at or past liquidation, so
|
|
183
|
+
* the model has no price on the correct side of entry.
|
|
184
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the
|
|
185
|
+
* decimal pattern, `leverage` is zero, or `maintenanceMarginRate` gives a
|
|
186
|
+
* zero denominator (`1` for a long, `-1` for a short).
|
|
188
187
|
* @public
|
|
189
188
|
*/
|
|
190
189
|
export function estimateLiquidationPrice(params: {
|
|
@@ -198,13 +197,20 @@ export function estimateLiquidationPrice(params: {
|
|
|
198
197
|
const mmr = decimalStringToBig(params.maintenanceMarginRate)
|
|
199
198
|
const side = params.isLong ? 1 : -1
|
|
200
199
|
const denominator = mmr.times(-side).plus(1)
|
|
201
|
-
if (leverage.eq(0)
|
|
200
|
+
if (leverage.eq(0)) {
|
|
201
|
+
throw invalidInput('leverage', 'must not be zero')
|
|
202
|
+
}
|
|
203
|
+
if (denominator.eq(0)) {
|
|
204
|
+
throw invalidInput(
|
|
205
|
+
'maintenanceMarginRate',
|
|
206
|
+
`must not be ${params.isLong ? '1' : '-1'} for this side`
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
const marginFractionAvailable = new DivBig(1).div(leverage).minus(mmr)
|
|
210
|
+
if (marginFractionAvailable.lte(0)) {
|
|
202
211
|
return undefined
|
|
203
212
|
}
|
|
204
|
-
const marginAvailable =
|
|
205
|
-
.div(leverage)
|
|
206
|
-
.minus(mmr)
|
|
207
|
-
.times(entryPrice)
|
|
213
|
+
const marginAvailable = marginFractionAvailable.times(entryPrice)
|
|
208
214
|
return bigToDecimalString(
|
|
209
215
|
entryPrice.minus(marginAvailable.times(side).div(denominator))
|
|
210
216
|
)
|
|
@@ -221,8 +227,10 @@ export const safeEstimateLiquidationPrice = createSafeFunction(
|
|
|
221
227
|
* `market.maintenanceMarginRate`.
|
|
222
228
|
*
|
|
223
229
|
* @returns The estimate, or `undefined` when the market carries no
|
|
224
|
-
* `maintenanceMarginRate` or
|
|
225
|
-
*
|
|
230
|
+
* `maintenanceMarginRate` or {@link estimateLiquidationPrice} gives
|
|
231
|
+
* `undefined`.
|
|
232
|
+
* @throws {PerpsError} `ValidationError` on an input that
|
|
233
|
+
* {@link estimateLiquidationPrice} rejects.
|
|
226
234
|
* @public
|
|
227
235
|
*/
|
|
228
236
|
export function estimateLiquidationPriceAtMarketRate(
|
|
@@ -236,7 +244,7 @@ export function estimateLiquidationPriceAtMarketRate(
|
|
|
236
244
|
entryPrice: params.entryPrice,
|
|
237
245
|
leverage: params.leverage,
|
|
238
246
|
isLong: params.isLong,
|
|
239
|
-
maintenanceMarginRate:
|
|
247
|
+
maintenanceMarginRate: market.maintenanceMarginRate,
|
|
240
248
|
})
|
|
241
249
|
}
|
|
242
250
|
|
|
@@ -252,7 +260,7 @@ export const safeEstimateLiquidationPriceAtMarketRate = createSafeFunction(
|
|
|
252
260
|
* liquidation level, a short when it rises to it. A non-positive price on
|
|
253
261
|
* either side counts as unknown, so the result is `false`.
|
|
254
262
|
*
|
|
255
|
-
* @throws {PerpsError} `ValidationError` when a price
|
|
263
|
+
* @throws {PerpsError} `ValidationError` when a price does not match the decimal pattern.
|
|
256
264
|
* @public
|
|
257
265
|
*/
|
|
258
266
|
export function wouldImmediatelyLiquidate(params: {
|
|
@@ -283,9 +291,8 @@ export const safeWouldImmediatelyLiquidate = createSafeFunction(
|
|
|
283
291
|
*
|
|
284
292
|
* @param fillPrice - Expected fill price (mid for market, limit price for
|
|
285
293
|
* limit orders).
|
|
286
|
-
* @
|
|
287
|
-
* is not positive.
|
|
288
|
-
* @throws {PerpsError} `ValidationError` when an input is not a decimal string.
|
|
294
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the
|
|
295
|
+
* decimal pattern or `currentSize + addSize` is not positive.
|
|
289
296
|
* @public
|
|
290
297
|
*/
|
|
291
298
|
export function estimateAverageEntryPrice(params: {
|
|
@@ -293,14 +300,14 @@ export function estimateAverageEntryPrice(params: {
|
|
|
293
300
|
currentEntry: string
|
|
294
301
|
addSize: string
|
|
295
302
|
fillPrice: string
|
|
296
|
-
}): string
|
|
303
|
+
}): string {
|
|
297
304
|
const currentSize = decimalStringToDivBig(params.currentSize)
|
|
298
305
|
const addSize = decimalStringToBig(params.addSize)
|
|
299
306
|
const currentEntry = decimalStringToBig(params.currentEntry)
|
|
300
307
|
const fillPrice = decimalStringToBig(params.fillPrice)
|
|
301
308
|
const totalSize = currentSize.plus(addSize)
|
|
302
309
|
if (totalSize.lte(0)) {
|
|
303
|
-
|
|
310
|
+
throw invalidInput('currentSize + addSize', 'must be positive')
|
|
304
311
|
}
|
|
305
312
|
return bigToDecimalString(
|
|
306
313
|
currentSize
|
|
@@ -320,9 +327,8 @@ export const safeEstimateAverageEntryPrice = createSafeFunction(
|
|
|
320
327
|
* Estimated effective leverage after adding margin and notional:
|
|
321
328
|
* `(currentNotional + addNotional) ÷ (currentMargin + addMargin)`.
|
|
322
329
|
*
|
|
323
|
-
* @
|
|
324
|
-
* positive.
|
|
325
|
-
* @throws {PerpsError} `ValidationError` when an input is not a decimal string.
|
|
330
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the
|
|
331
|
+
* decimal pattern or `currentMargin + addMargin` is not positive.
|
|
326
332
|
* @public
|
|
327
333
|
*/
|
|
328
334
|
export function estimateNewLeverage(params: {
|
|
@@ -330,7 +336,7 @@ export function estimateNewLeverage(params: {
|
|
|
330
336
|
currentMargin: string
|
|
331
337
|
addNotional: string
|
|
332
338
|
addMargin: string
|
|
333
|
-
}): string
|
|
339
|
+
}): string {
|
|
334
340
|
const totalNotional = decimalStringToDivBig(params.currentNotional).plus(
|
|
335
341
|
decimalStringToBig(params.addNotional)
|
|
336
342
|
)
|
|
@@ -338,7 +344,7 @@ export function estimateNewLeverage(params: {
|
|
|
338
344
|
decimalStringToBig(params.addMargin)
|
|
339
345
|
)
|
|
340
346
|
if (totalMargin.lte(0)) {
|
|
341
|
-
|
|
347
|
+
throw invalidInput('currentMargin + addMargin', 'must be positive')
|
|
342
348
|
}
|
|
343
349
|
return bigToDecimalString(totalNotional.div(totalMargin))
|
|
344
350
|
}
|
|
@@ -354,7 +360,7 @@ export const safeEstimateNewLeverage = createSafeFunction(
|
|
|
354
360
|
* `(markPrice - entryPrice) × size × (isLong ? 1 : -1)`.
|
|
355
361
|
*
|
|
356
362
|
* @param size - Position size as a non-negative magnitude.
|
|
357
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
363
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
|
|
358
364
|
* @public
|
|
359
365
|
*/
|
|
360
366
|
export function estimateUnrealizedPnl(params: {
|
|
@@ -382,7 +388,7 @@ export const safeEstimateUnrealizedPnl = createSafeFunction(
|
|
|
382
388
|
* `(closePrice - entryPrice) × closeSize × (isLong ? 1 : -1)`.
|
|
383
389
|
*
|
|
384
390
|
* @param closeSize - Size being closed as a non-negative magnitude.
|
|
385
|
-
* @throws {PerpsError} `ValidationError` when an input
|
|
391
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the decimal pattern.
|
|
386
392
|
* @public
|
|
387
393
|
*/
|
|
388
394
|
export function calculateRealizedPnl(params: {
|
|
@@ -45,7 +45,7 @@ export const QUOTE_THROTTLE_MS = 100
|
|
|
45
45
|
* price, a throwing `onQuote`) is logged via `wsLog.listenerFailure` and the
|
|
46
46
|
* emission is skipped, on both the immediate and the trailing-timer path.
|
|
47
47
|
*
|
|
48
|
-
* @throws {PerpsError} `ValidationError` when `params.size`
|
|
48
|
+
* @throws {PerpsError} `ValidationError` when `params.size` does not match the decimal pattern.
|
|
49
49
|
* @throws {PerpsError} `MarketNotFound` when no market matches symbol+type.
|
|
50
50
|
* @internal
|
|
51
51
|
*/
|
package/src/types/provider.ts
CHANGED
|
@@ -599,9 +599,11 @@ export interface PerpsProviderPlugin {
|
|
|
599
599
|
* positions, prefer `Position.liquidationPrice` from the venue. Pure —
|
|
600
600
|
* does no I/O.
|
|
601
601
|
*
|
|
602
|
-
* @returns The estimated liquidation price, or `undefined` when
|
|
603
|
-
*
|
|
604
|
-
*
|
|
602
|
+
* @returns The estimated liquidation price, or `undefined` when `market`
|
|
603
|
+
* lacks the margin metadata the model needs or `leverage` leaves no
|
|
604
|
+
* margin above the maintenance requirement.
|
|
605
|
+
* @throws {PerpsError} `ValidationError` when an input does not match the
|
|
606
|
+
* decimal pattern or is degenerate, such as a zero leverage.
|
|
605
607
|
*/
|
|
606
608
|
estimateLiquidationPrice(
|
|
607
609
|
market: PerpsMarket,
|
package/src/utils/warnOnce.ts
CHANGED
|
@@ -30,6 +30,19 @@ export const createWarnOnce = (): ((key: string, message: string) => void) => {
|
|
|
30
30
|
|
|
31
31
|
const MAX_VALUE_CHARS = 64
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Cut venue text to a fixed length, so a warner key or message built from it
|
|
35
|
+
* stays small.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export const cutVenueText = (value: unknown): string => {
|
|
40
|
+
const text = String(value)
|
|
41
|
+
return text.length > MAX_VALUE_CHARS
|
|
42
|
+
? `${text.slice(0, MAX_VALUE_CHARS)}…`
|
|
43
|
+
: text
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
/**
|
|
34
47
|
* Describe a skipped venue row. The venue value is cut to a fixed length, so
|
|
35
48
|
* a warner key built from the text stays small.
|
|
@@ -41,22 +54,22 @@ export const skippedRowMessage = (
|
|
|
41
54
|
row: string,
|
|
42
55
|
field: string,
|
|
43
56
|
value: unknown,
|
|
44
|
-
expected: string
|
|
57
|
+
expected: string,
|
|
58
|
+
marketId?: string
|
|
45
59
|
): string => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
text.length > MAX_VALUE_CHARS ? `${text.slice(0, MAX_VALUE_CHARS)}…` : text
|
|
49
|
-
return `[${source}] skipping ${row} row: \`${field}\` is not a valid ${expected}: '${shown}'`
|
|
60
|
+
const market = marketId === undefined ? '' : ` on market '${marketId}'`
|
|
61
|
+
return `[${source}] skipping ${row} row${market}: \`${field}\` is not a valid ${expected}: '${cutVenueText(value)}'`
|
|
50
62
|
}
|
|
51
63
|
|
|
52
64
|
const warnSkippedRowOnce = createWarnOnce()
|
|
53
65
|
|
|
54
66
|
/**
|
|
55
|
-
* Warn once for each provider, row, field
|
|
56
|
-
* mapper skipped a venue row because a required field is not valid.
|
|
57
|
-
* holds no venue value, so a second bad value for the same field
|
|
67
|
+
* Warn once for each provider, row, field, expected kind and market that a
|
|
68
|
+
* provider mapper skipped a venue row because a required field is not valid.
|
|
69
|
+
* The key holds no venue value, so a second bad value for the same field on
|
|
70
|
+
* the same market stays silent.
|
|
58
71
|
*
|
|
59
|
-
* @param expected - What the field must be. Defaults to `'decimal'`.
|
|
72
|
+
* @param options.expected - What the field must be. Defaults to `'decimal'`.
|
|
60
73
|
* @internal
|
|
61
74
|
*/
|
|
62
75
|
export const warnSkippedVenueRow = (
|
|
@@ -64,10 +77,13 @@ export const warnSkippedVenueRow = (
|
|
|
64
77
|
row: string,
|
|
65
78
|
field: string,
|
|
66
79
|
value: unknown,
|
|
67
|
-
|
|
80
|
+
{
|
|
81
|
+
marketId,
|
|
82
|
+
expected = 'decimal',
|
|
83
|
+
}: { marketId?: string; expected?: string } = {}
|
|
68
84
|
): void => {
|
|
69
85
|
warnSkippedRowOnce(
|
|
70
|
-
`${provider}|${row}|${field}|${expected}`,
|
|
71
|
-
skippedRowMessage(provider, row, field, value, expected)
|
|
86
|
+
`${provider}|${row}|${field}|${expected}|${marketId ?? ''}`,
|
|
87
|
+
skippedRowMessage(provider, row, field, value, expected, marketId)
|
|
72
88
|
)
|
|
73
89
|
}
|
package/src/version.ts
CHANGED
package/src/websocket/wsLog.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createWarnOnce,
|
|
3
|
+
cutVenueText,
|
|
4
|
+
skippedRowMessage,
|
|
5
|
+
} from '../utils/warnOnce.js'
|
|
2
6
|
|
|
3
7
|
const MAX_PAYLOAD_CHARS = 512
|
|
4
8
|
|
|
@@ -64,29 +68,38 @@ export const wsLog = {
|
|
|
64
68
|
},
|
|
65
69
|
/**
|
|
66
70
|
* A frame row or order-book level with a required field that is not valid.
|
|
67
|
-
* Only that row is skipped. Logged once at `warn` for each provider, row
|
|
68
|
-
* field; the key holds no venue value.
|
|
71
|
+
* Only that row is skipped. Logged once at `warn` for each provider, row,
|
|
72
|
+
* field and market; the key holds no venue value.
|
|
69
73
|
*/
|
|
70
74
|
skippedRow(
|
|
71
75
|
provider: string,
|
|
72
76
|
row: string,
|
|
73
77
|
field: string,
|
|
74
|
-
value: unknown
|
|
78
|
+
value: unknown,
|
|
79
|
+
marketId?: string
|
|
75
80
|
): void {
|
|
76
81
|
warnSkippedRowOnce(
|
|
77
|
-
`${provider}|${row}|${field}`,
|
|
78
|
-
skippedRowMessage(
|
|
82
|
+
`${provider}|${row}|${field}|${marketId ?? ''}`,
|
|
83
|
+
skippedRowMessage(
|
|
84
|
+
`${provider}:ws`,
|
|
85
|
+
row,
|
|
86
|
+
field,
|
|
87
|
+
value,
|
|
88
|
+
'decimal',
|
|
89
|
+
marketId
|
|
90
|
+
)
|
|
79
91
|
)
|
|
80
92
|
},
|
|
81
93
|
/**
|
|
82
94
|
* A frame row the mapper rejected for a reason other than one field, such as
|
|
83
95
|
* a lifecycle state the SDK does not carry. Logged once at `warn` for each
|
|
84
|
-
* provider and
|
|
96
|
+
* provider, row and reason; the reason is cut to a fixed length.
|
|
85
97
|
*/
|
|
86
98
|
droppedRow(provider: string, row: string, reason: string): void {
|
|
99
|
+
const shown = cutVenueText(reason)
|
|
87
100
|
warnSkippedRowOnce(
|
|
88
|
-
`${provider}|${row}|dropped`,
|
|
89
|
-
`[${provider}:ws] skipping ${row} row: ${
|
|
101
|
+
`${provider}|${row}|dropped|${shown}`,
|
|
102
|
+
`[${provider}:ws] skipping ${row} row: ${shown}`
|
|
90
103
|
)
|
|
91
104
|
},
|
|
92
105
|
/**
|
package/src/wire/orderAmounts.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type DecimalString,
|
|
3
|
+
type Market,
|
|
4
|
+
PerpsErrorCode,
|
|
5
|
+
} from '@lifi/perps-types'
|
|
2
6
|
import type Big from 'big.js'
|
|
3
7
|
import { DivBig, TruncBig } from '../decimal/big.js'
|
|
4
8
|
import { isDecimalStringZero } from '../decimal/compare.js'
|
|
5
9
|
import { isDecimalString } from '../decimal/parse.js'
|
|
10
|
+
import { invalidInput } from '../errors/invalidInput.js'
|
|
11
|
+
import { PerpsError } from '../errors/PerpsError.js'
|
|
6
12
|
import type { PerpsSDKClient } from '../types/provider.js'
|
|
13
|
+
import { createSafeFunction } from '../utils/createSafeFunction.js'
|
|
7
14
|
import { snapOrderSize } from './snap.js'
|
|
8
15
|
|
|
9
16
|
/** @public */
|
|
@@ -31,14 +38,20 @@ export interface OrderAmounts {
|
|
|
31
38
|
notional: DecimalString
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
function requirePositiveDecimal(value: DecimalString, parameter: string): Big {
|
|
42
|
+
const parsed = isDecimalString(value) ? new TruncBig(value) : undefined
|
|
43
|
+
if (parsed === undefined || !parsed.gt(0)) {
|
|
44
|
+
throw invalidInput(
|
|
45
|
+
parameter,
|
|
46
|
+
`must be a positive decimal, got '${String(value)}'`
|
|
47
|
+
)
|
|
37
48
|
}
|
|
38
|
-
|
|
39
|
-
return parsed.gt(0) ? parsed : null
|
|
49
|
+
return parsed
|
|
40
50
|
}
|
|
41
51
|
|
|
52
|
+
const lotSize = (market: Market): DecimalString =>
|
|
53
|
+
market.sizeIncrement ?? new TruncBig(`1e-${market.szDecimals}`).toFixed()
|
|
54
|
+
|
|
42
55
|
/**
|
|
43
56
|
* The three order-entry amounts, derived from whichever one the user typed.
|
|
44
57
|
* Only `size` snaps, onto the market's venue lot grid, because an order sends
|
|
@@ -46,26 +59,22 @@ function positiveDecimal(value: DecimalString): Big | null {
|
|
|
46
59
|
* `notional` is the snapped size × price, and a derived margin is
|
|
47
60
|
* `notional ÷ leverage` in `DivBig` (40 places, half-up).
|
|
48
61
|
*
|
|
49
|
-
* @
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* provider plugin.
|
|
62
|
+
* @throws {PerpsError} `ValidationError` that names `amount` or `price` when
|
|
63
|
+
* it is not a positive decimal, or `leverage` when it is not a finite number
|
|
64
|
+
* above zero. `ValidationError` with the lot size when the snapped size is
|
|
65
|
+
* zero (below one lot). `SDKError` when `market.providerId` names no
|
|
66
|
+
* registered provider plugin.
|
|
54
67
|
* @public
|
|
55
68
|
*/
|
|
56
|
-
export function calculateOrderAmounts(
|
|
57
|
-
input: OrderAmountsInput
|
|
58
|
-
): OrderAmounts | null {
|
|
69
|
+
export function calculateOrderAmounts(input: OrderAmountsInput): OrderAmounts {
|
|
59
70
|
const { sdk, market, held, leverage } = input
|
|
60
|
-
const amount =
|
|
61
|
-
const price =
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
) {
|
|
68
|
-
return null
|
|
71
|
+
const amount = requirePositiveDecimal(input.amount, 'amount')
|
|
72
|
+
const price = requirePositiveDecimal(input.price, 'price')
|
|
73
|
+
if (!Number.isFinite(leverage) || leverage <= 0) {
|
|
74
|
+
throw invalidInput(
|
|
75
|
+
'leverage',
|
|
76
|
+
`must be a finite number above zero, got ${leverage}`
|
|
77
|
+
)
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
const requested =
|
|
@@ -76,7 +85,10 @@ export function calculateOrderAmounts(
|
|
|
76
85
|
: amount.times(leverage).div(price).toFixed()
|
|
77
86
|
const size = snapOrderSize(sdk, market, requested)
|
|
78
87
|
if (isDecimalStringZero(size)) {
|
|
79
|
-
|
|
88
|
+
throw new PerpsError(
|
|
89
|
+
PerpsErrorCode.ValidationError,
|
|
90
|
+
`The order size ${requested} is below the lot size ${lotSize(market)}.`
|
|
91
|
+
)
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
const notional = new TruncBig(size).times(price)
|
|
@@ -89,3 +101,9 @@ export function calculateOrderAmounts(
|
|
|
89
101
|
notional: notional.toFixed(),
|
|
90
102
|
}
|
|
91
103
|
}
|
|
104
|
+
|
|
105
|
+
/** @public */
|
|
106
|
+
export const safeCalculateOrderAmounts = createSafeFunction(
|
|
107
|
+
'calculateOrderAmounts',
|
|
108
|
+
calculateOrderAmounts
|
|
109
|
+
)
|
package/src/wire/refuel.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type DecimalString, PerpsErrorCode } from '@lifi/perps-types'
|
|
|
2
2
|
import type Big from 'big.js'
|
|
3
3
|
import { DivBig } from '../decimal/big.js'
|
|
4
4
|
import { BASE_UNITS_PATTERN } from '../decimal/convert.js'
|
|
5
|
-
import {
|
|
5
|
+
import { decimalStringToDivBig } from '../decimal/decimalStringToBig.js'
|
|
6
6
|
import { PerpsError } from '../errors/PerpsError.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -48,7 +48,7 @@ function requireBaseUnits(value: DecimalString, field: string): Big {
|
|
|
48
48
|
* when `recommendedAmount`, `recommendedUsd` or `priceUsd` is not greater
|
|
49
49
|
* than zero: a route with nothing to refuel, not a failure.
|
|
50
50
|
* @throws {PerpsError} `ValidationError` when a base-unit field is not an
|
|
51
|
-
* integer string, a USD field
|
|
51
|
+
* integer string, a USD field does not match the decimal pattern, or `decimals`
|
|
52
52
|
* is not a non-negative integer.
|
|
53
53
|
* @public
|
|
54
54
|
*/
|
|
@@ -67,8 +67,8 @@ export function calculateRefuelAmount(
|
|
|
67
67
|
'recommendedAmount'
|
|
68
68
|
)
|
|
69
69
|
const balance = requireBaseUnits(input.nativeBalance, 'nativeBalance')
|
|
70
|
-
const recommendedUsd =
|
|
71
|
-
const priceUsd =
|
|
70
|
+
const recommendedUsd = decimalStringToDivBig(input.recommendedUsd)
|
|
71
|
+
const priceUsd = decimalStringToDivBig(input.priceUsd)
|
|
72
72
|
const deficit = recommended.minus(balance)
|
|
73
73
|
if (
|
|
74
74
|
!recommended.gt(0) ||
|
package/src/wire/transferable.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { decimalStringToBig } from '../decimal/decimalStringToBig.js'
|
|
|
9
9
|
* @param venueFigure - The venue's free figure for the category, which can
|
|
10
10
|
* exceed `units` or fall below zero.
|
|
11
11
|
* @param units - Units held in the category, the upper bound of the clamp.
|
|
12
|
-
* @throws {PerpsError} `ValidationError` when either input
|
|
13
|
-
*
|
|
12
|
+
* @throws {PerpsError} `ValidationError` when either input does not match the
|
|
13
|
+
* decimal pattern, naming the field.
|
|
14
14
|
* @public
|
|
15
15
|
*/
|
|
16
16
|
export function calculateTransferable(
|