@metamask-previews/perps-controller 10.0.0-preview-48f22dbe8 → 10.0.0-preview-a42e8d0d2
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/CHANGELOG.md +0 -15
- package/dist/providers/HyperLiquidProvider.cjs +30 -219
- package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
- package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
- package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
- package/dist/providers/HyperLiquidProvider.mjs +31 -220
- package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.cjs +0 -23
- package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.d.cts +0 -21
- package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.d.mts +0 -21
- package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
- package/dist/services/HyperLiquidSubscriptionService.mjs +0 -23
- package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +2 -18
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.mts +2 -18
- package/dist/types/index.d.mts.map +1 -1
- package/dist/types/index.mjs.map +1 -1
- package/dist/utils/orderCalculations.cjs +21 -126
- package/dist/utils/orderCalculations.cjs.map +1 -1
- package/dist/utils/orderCalculations.d.cts +0 -24
- package/dist/utils/orderCalculations.d.cts.map +1 -1
- package/dist/utils/orderCalculations.d.mts +0 -24
- package/dist/utils/orderCalculations.d.mts.map +1 -1
- package/dist/utils/orderCalculations.mjs +20 -124
- package/dist/utils/orderCalculations.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildOrdersArray = exports.calculateOrderPriceAndSize = exports.calculateFinalPositionSize = exports.
|
|
3
|
+
exports.buildOrdersArray = exports.calculateOrderPriceAndSize = exports.calculateFinalPositionSize = exports.getMaxAllowedAmount = exports.calculateMarginRequired = exports.calculatePositionSize = void 0;
|
|
4
4
|
const hyperLiquidConfig_js_1 = require("../constants/hyperLiquidConfig.cjs");
|
|
5
5
|
const perpsConfig_js_1 = require("../constants/perpsConfig.cjs");
|
|
6
6
|
const perpsErrorCodes_js_1 = require("../perpsErrorCodes.cjs");
|
|
7
7
|
const hyperLiquidAdapter_js_1 = require("./hyperLiquidAdapter.cjs");
|
|
8
|
-
/**
|
|
9
|
-
* Tolerance used when deciding whether a scaled size is already on the size
|
|
10
|
-
* grid, guarding against floating-point representation error.
|
|
11
|
-
*/
|
|
12
|
-
const FLOAT_TOLERANCE = 1e-6;
|
|
13
8
|
/**
|
|
14
9
|
* Calculate position size based on USD amount and asset price
|
|
15
10
|
*
|
|
@@ -88,60 +83,6 @@ function getMaxAllowedAmount(params) {
|
|
|
88
83
|
return Math.max(0, Math.floor(bufferedMax));
|
|
89
84
|
}
|
|
90
85
|
exports.getMaxAllowedAmount = getMaxAllowedAmount;
|
|
91
|
-
/**
|
|
92
|
-
* Round a size down onto the asset's size grid.
|
|
93
|
-
*
|
|
94
|
-
* Used for reduce-only orders, where rounding up would push the size past the
|
|
95
|
-
* live position size. Values already on the grid are snapped rather than
|
|
96
|
-
* truncated, because floating-point math can leave them just below a grid
|
|
97
|
-
* point (0.0123 * 10000 === 122.99999999999999) and truncating would drop a
|
|
98
|
-
* whole increment.
|
|
99
|
-
*
|
|
100
|
-
* The result is never greater than `size`, for negative sizes as well as
|
|
101
|
-
* positive: the snap only ever recovers a grid point the input already
|
|
102
|
-
* represents, so a value genuinely below a grid point is stepped down even when
|
|
103
|
-
* the tolerance would have reached the point above it.
|
|
104
|
-
*
|
|
105
|
-
* A size whose scaled form reaches `2^53` is returned unchanged: doubles cannot
|
|
106
|
-
* represent consecutive integers there, so the grid is finer than the spacing
|
|
107
|
-
* between representable values and there is nothing to round down to.
|
|
108
|
-
*
|
|
109
|
-
* @param size - Size to round down.
|
|
110
|
-
* @param szDecimals - The asset's size decimal precision.
|
|
111
|
-
* @returns The size rounded down onto the size grid, never exceeding `size`.
|
|
112
|
-
*/
|
|
113
|
-
function floorToSizeDecimals(size, szDecimals) {
|
|
114
|
-
const multiplier = Math.pow(10, szDecimals);
|
|
115
|
-
const scaled = size * multiplier;
|
|
116
|
-
// Past 2^53 a double cannot represent consecutive integers, so `units -= 1`
|
|
117
|
-
// below would be a no-op and the step-down loop would never terminate. The
|
|
118
|
-
// size grid is finer than the spacing between representable values at that
|
|
119
|
-
// magnitude, so there is no increment to shave: return the input unchanged.
|
|
120
|
-
if (!Number.isFinite(scaled) || Math.abs(scaled) >= Number.MAX_SAFE_INTEGER) {
|
|
121
|
-
return size;
|
|
122
|
-
}
|
|
123
|
-
const nearest = Math.round(scaled);
|
|
124
|
-
// The tolerance scales with the magnitude, because double-precision error
|
|
125
|
-
// does too: a fixed epsilon would stop absorbing representation error for
|
|
126
|
-
// sizes that scale past ~1e10 and would then shave off a whole increment.
|
|
127
|
-
const tolerance = Math.max(FLOAT_TOLERANCE, Math.abs(scaled) * Number.EPSILON * 8);
|
|
128
|
-
let units = Math.abs(scaled - nearest) < tolerance ? nearest : Math.floor(scaled);
|
|
129
|
-
// Step down until the result no longer exceeds the input. One pass is not
|
|
130
|
-
// enough: a tolerance wide enough to absorb representation error at large
|
|
131
|
-
// magnitudes also reaches the next grid point, and for an input less than half
|
|
132
|
-
// an ulp below a grid point `size * multiplier` evaluates to exactly that grid
|
|
133
|
-
// integer, so flooring the scaled value returns the same too-large result.
|
|
134
|
-
// The comparison alone is the whole termination condition: for a non-negative
|
|
135
|
-
// size the loop stops at or before zero, and for a negative size it stops once
|
|
136
|
-
// the value is no longer above the input. Guarding on `units` instead would
|
|
137
|
-
// skip a negative size below the tolerance, which snaps to `-0` — and
|
|
138
|
-
// `-0 !== 0` is false. The 2^53 bail-out above keeps this bounded.
|
|
139
|
-
while (units / multiplier > size) {
|
|
140
|
-
units -= 1;
|
|
141
|
-
}
|
|
142
|
-
return units / multiplier;
|
|
143
|
-
}
|
|
144
|
-
exports.floorToSizeDecimals = floorToSizeDecimals;
|
|
145
86
|
/**
|
|
146
87
|
* Calculates final position size using USD as source of truth with price validation
|
|
147
88
|
*
|
|
@@ -152,63 +93,34 @@ exports.floorToSizeDecimals = floorToSizeDecimals;
|
|
|
152
93
|
* @returns Final position size as a number
|
|
153
94
|
*/
|
|
154
95
|
function calculateFinalPositionSize(params) {
|
|
155
|
-
const { usdAmount, size, currentPrice, priceAtCalculation, maxSlippageBps, szDecimals, leverage,
|
|
96
|
+
const { usdAmount, size, currentPrice, priceAtCalculation, maxSlippageBps, szDecimals, leverage, debugLogger, } = params;
|
|
156
97
|
let finalPositionSize;
|
|
157
|
-
// Validate price staleness whenever the caller supplied a calculation-time
|
|
158
|
-
// price. This runs before the sizing branches on purpose: a full close submits
|
|
159
|
-
// the exact live position size rather than a USD-derived one, and it must still
|
|
160
|
-
// be rejected when the price has moved past the caller's tolerance.
|
|
161
|
-
if (priceAtCalculation) {
|
|
162
|
-
const priceDeltaBps = Math.abs(((currentPrice - priceAtCalculation) / priceAtCalculation) * 10000);
|
|
163
|
-
const maxSlippageBpsValue = maxSlippageBps ?? perpsConfig_js_1.ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;
|
|
164
|
-
if (priceDeltaBps > maxSlippageBpsValue) {
|
|
165
|
-
throw new Error(`Price moved too much: ${priceDeltaBps.toFixed(0)} bps (max: ${maxSlippageBpsValue} bps). ` +
|
|
166
|
-
`Expected: ${priceAtCalculation.toFixed(2)}, Current: ${currentPrice.toFixed(2)}`);
|
|
167
|
-
}
|
|
168
|
-
debugLogger?.log('Price validation passed:', {
|
|
169
|
-
priceAtCalculation,
|
|
170
|
-
currentPrice,
|
|
171
|
-
deltaBps: priceDeltaBps.toFixed(2),
|
|
172
|
-
maxSlippageBps: maxSlippageBpsValue,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
98
|
if (usdAmount && parseFloat(usdAmount) > 0) {
|
|
176
99
|
// USD amount provided - use it as source of truth
|
|
177
100
|
const usdValue = parseFloat(usdAmount);
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (reduceOnly && size) {
|
|
186
|
-
const requestedSize = parseFloat(size);
|
|
187
|
-
// A supplied size must be positive, or the cap below would submit a
|
|
188
|
-
// zero/negative order. Reject it rather than silently falling back to the
|
|
189
|
-
// USD-derived size, matching how closePosition treats the same input.
|
|
190
|
-
if (!Number.isFinite(requestedSize) || requestedSize <= 0) {
|
|
191
|
-
throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
101
|
+
// 1. Validate price staleness if priceAtCalculation provided
|
|
102
|
+
if (priceAtCalculation) {
|
|
103
|
+
const priceDeltaBps = Math.abs(((currentPrice - priceAtCalculation) / priceAtCalculation) * 10000);
|
|
104
|
+
const maxSlippageBpsValue = maxSlippageBps ?? perpsConfig_js_1.ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;
|
|
105
|
+
if (priceDeltaBps > maxSlippageBpsValue) {
|
|
106
|
+
throw new Error(`Price moved too much: ${priceDeltaBps.toFixed(0)} bps (max: ${maxSlippageBpsValue} bps). ` +
|
|
107
|
+
`Expected: ${priceAtCalculation.toFixed(2)}, Current: ${currentPrice.toFixed(2)}`);
|
|
192
108
|
}
|
|
193
|
-
|
|
109
|
+
debugLogger?.log('Price validation passed:', {
|
|
110
|
+
priceAtCalculation,
|
|
111
|
+
currentPrice,
|
|
112
|
+
deltaBps: priceDeltaBps.toFixed(2),
|
|
113
|
+
maxSlippageBps: maxSlippageBpsValue,
|
|
114
|
+
});
|
|
194
115
|
}
|
|
195
|
-
//
|
|
116
|
+
// 2. Recalculate position size with fresh price
|
|
117
|
+
finalPositionSize = usdValue / currentPrice;
|
|
118
|
+
// 3. Apply size decimals rounding
|
|
196
119
|
const multiplier = Math.pow(10, szDecimals);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
? floorToSizeDecimals(finalPositionSize, szDecimals)
|
|
200
|
-
: Math.round(finalPositionSize * multiplier) / multiplier;
|
|
201
|
-
// Rounding down can zero out a reduce-only order whose USD value is worth
|
|
202
|
-
// less than one size increment. Fail with a clear error instead of
|
|
203
|
-
// submitting a size of "0" the exchange will reject.
|
|
204
|
-
if (reduceOnly && finalPositionSize <= 0 && sizeBeforeRounding > 0) {
|
|
205
|
-
throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
206
|
-
}
|
|
207
|
-
// 4. Ensure rounded size meets requested USD (fix validation gap).
|
|
208
|
-
// Skipped for reduce-only orders: adding an increment there would submit
|
|
209
|
-
// more than the position holds and HyperLiquid rejects the order.
|
|
120
|
+
finalPositionSize = Math.round(finalPositionSize * multiplier) / multiplier;
|
|
121
|
+
// 4. Ensure rounded size meets requested USD (fix validation gap)
|
|
210
122
|
let actualNotionalValue = finalPositionSize * currentPrice;
|
|
211
|
-
if (
|
|
123
|
+
if (actualNotionalValue < usdValue) {
|
|
212
124
|
// Add 1 minimum increment to meet requested USD
|
|
213
125
|
finalPositionSize += 1 / multiplier;
|
|
214
126
|
actualNotionalValue = finalPositionSize * currentPrice;
|
|
@@ -243,23 +155,6 @@ function calculateFinalPositionSize(params) {
|
|
|
243
155
|
else {
|
|
244
156
|
// Legacy: Use provided size (backward compatibility)
|
|
245
157
|
finalPositionSize = parseFloat(size ?? '0');
|
|
246
|
-
// Reduce-only sizes are formatted with toFixed() further down, which rounds
|
|
247
|
-
// up; truncate onto the size grid first so a close can never exceed the
|
|
248
|
-
// position it is closing.
|
|
249
|
-
if (reduceOnly) {
|
|
250
|
-
// A supplied size must be positive, or formatHyperLiquidSize would render
|
|
251
|
-
// a zero or negative order size. The USD branch above rejects the same
|
|
252
|
-
// input.
|
|
253
|
-
if (size && !(finalPositionSize > 0)) {
|
|
254
|
-
throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
255
|
-
}
|
|
256
|
-
const sizeBeforeFlooring = finalPositionSize;
|
|
257
|
-
finalPositionSize = floorToSizeDecimals(finalPositionSize, szDecimals);
|
|
258
|
-
// A positive size that floors to zero is worth less than one increment
|
|
259
|
-
if (finalPositionSize <= 0 && sizeBeforeFlooring > 0) {
|
|
260
|
-
throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
158
|
debugLogger?.log('Using legacy size calculation (no USD amount provided):', {
|
|
264
159
|
providedSize: size,
|
|
265
160
|
finalSize: finalPositionSize,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orderCalculations.cjs","sourceRoot":"","sources":["../../src/utils/orderCalculations.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Hex } from '@metamask/utils';\n\nimport { BASIS_POINTS_DIVISOR } from '../constants/hyperLiquidConfig.js';\nimport {\n MAX_ORDER_MARGIN_BUFFER,\n ORDER_SLIPPAGE_CONFIG,\n} from '../constants/perpsConfig.js';\nimport { PERPS_ERROR_CODES } from '../perpsErrorCodes.js';\nimport type { SDKOrderParams } from '../types/hyperliquid-types.js';\nimport type { PerpsDebugLogger } from '../types/index.js';\nimport {\n formatHyperLiquidPrice,\n formatHyperLiquidSize,\n} from './hyperLiquidAdapter.js';\n\n/**\n * Optional debug logger for order calculation functions.\n * When provided, enables detailed logging for debugging.\n */\nexport type OrderCalculationsDebugLogger = PerpsDebugLogger | undefined;\n\n/**\n * Tolerance used when deciding whether a scaled size is already on the size\n * grid, guarding against floating-point representation error.\n */\nconst FLOAT_TOLERANCE = 1e-6;\n\ntype PositionSizeParams = {\n amount: string;\n price: number;\n szDecimals: number;\n};\n\ntype MarginRequiredParams = {\n amount: string;\n leverage: number;\n};\n\ntype MaxAllowedAmountParams = {\n spendableBalance: number;\n assetPrice: number;\n assetSzDecimals: number;\n leverage: number;\n};\n\n// Advanced order calculation interfaces\nexport type CalculateFinalPositionSizeParams = {\n usdAmount?: string;\n size?: string;\n currentPrice: number;\n priceAtCalculation?: number;\n maxSlippageBps?: number;\n szDecimals: number;\n leverage?: number;\n // Reduce-only orders (position closes) may never round up: HyperLiquid\n // rejects a reduce-only order whose size exceeds the live position with\n // \"Reduce only order would increase position\".\n reduceOnly?: boolean;\n debugLogger?: OrderCalculationsDebugLogger;\n};\n\nexport type CalculateFinalPositionSizeResult = {\n finalPositionSize: number;\n};\n\nexport type CalculateOrderPriceAndSizeParams = {\n orderType: 'market' | 'limit';\n isBuy: boolean;\n finalPositionSize: number;\n currentPrice: number;\n limitPrice?: string;\n // Max slippage in basis points (e.g. 300 = 3%). Only applied to market orders;\n // limit orders use limitPrice directly. Falls back to ORDER_SLIPPAGE_CONFIG\n // .DefaultMarketSlippageBps when omitted on a market order.\n maxSlippageBps?: number;\n szDecimals: number;\n};\n\nexport type CalculateOrderPriceAndSizeResult = {\n orderPrice: number;\n formattedSize: string;\n formattedPrice: string;\n};\n\nexport type BuildOrdersArrayParams = {\n assetId: number;\n isBuy: boolean;\n formattedPrice: string;\n formattedSize: string;\n reduceOnly: boolean;\n orderType: 'market' | 'limit';\n clientOrderId?: string;\n takeProfitPrice?: string;\n stopLossPrice?: string;\n szDecimals: number;\n grouping?: 'na' | 'normalTpsl' | 'positionTpsl';\n};\n\nexport type BuildOrdersArrayResult = {\n orders: SDKOrderParams[];\n grouping: 'na' | 'normalTpsl' | 'positionTpsl';\n};\n\n/**\n * Calculate position size based on USD amount and asset price\n *\n * @param params - Amount in USD, current asset price, and required decimal precision\n * @returns Position size formatted to the asset's decimal precision\n */\nexport function calculatePositionSize(params: PositionSizeParams): string {\n const { amount, price, szDecimals } = params;\n\n // Validate required parameters\n if (szDecimals === undefined || szDecimals === null) {\n throw new Error('szDecimals is required for position size calculation');\n }\n if (szDecimals < 0) {\n throw new Error(`szDecimals must be >= 0, got: ${szDecimals}`);\n }\n\n const amountNum = parseFloat(amount || '0');\n\n if (isNaN(amountNum) || isNaN(price) || amountNum === 0 || price === 0) {\n return (0).toFixed(szDecimals);\n }\n\n const positionSize = amountNum / price;\n const multiplier = Math.pow(10, szDecimals);\n let rounded = Math.round(positionSize * multiplier) / multiplier;\n\n // Ensure rounded size meets requested USD (fix validation gap)\n const actualUsd = rounded * price;\n if (actualUsd < amountNum) {\n rounded += 1 / multiplier;\n }\n\n return rounded.toFixed(szDecimals);\n}\n\n/**\n * Calculate margin required for a position\n *\n * @param params - Position amount and leverage\n * @returns Margin required formatted to 2 decimal places\n */\nexport function calculateMarginRequired(params: MarginRequiredParams): string {\n const { amount, leverage } = params;\n const amountNum = parseFloat(amount || '0');\n\n if (\n isNaN(amountNum) ||\n isNaN(leverage) ||\n amountNum === 0 ||\n leverage === 0\n ) {\n return '0.00';\n }\n\n return (amountNum / leverage).toFixed(2);\n}\n\nexport function getMaxAllowedAmount(params: MaxAllowedAmountParams): number {\n const { spendableBalance, assetPrice, assetSzDecimals, leverage } = params;\n if (spendableBalance === 0 || !assetPrice || assetSzDecimals === undefined) {\n return 0;\n }\n\n // The theoretical maximum is simply spendableBalance * leverage\n const theoreticalMax = spendableBalance * leverage;\n\n // But we need to account for position size rounding\n // Find the largest whole dollar amount that fits within this limit\n let maxAmount = Math.floor(theoreticalMax);\n\n // Verify this amount doesn't exceed available balance after rounding\n const testPositionSize = calculatePositionSize({\n amount: maxAmount.toString(),\n price: assetPrice,\n szDecimals: assetSzDecimals,\n });\n\n const actualNotionalValue = parseFloat(testPositionSize) * assetPrice;\n const requiredMargin = actualNotionalValue / leverage;\n\n // If rounding caused us to exceed available balance, step down by one position increment\n if (requiredMargin > spendableBalance) {\n const minPositionSizeIncrement = 1 / Math.pow(10, assetSzDecimals);\n const positionSizeIncrementUsd = Math.ceil(\n minPositionSizeIncrement * assetPrice,\n );\n maxAmount -= positionSizeIncrementUsd;\n }\n\n // Apply margin buffer to reduce \"Insufficient margin\" rejections from the exchange\n // (fees, rounding, and exchange-side checks can make 100% theoretical max fail)\n const bufferedMax = maxAmount * (1 - MAX_ORDER_MARGIN_BUFFER);\n\n return Math.max(0, Math.floor(bufferedMax));\n}\n\n/**\n * Round a size down onto the asset's size grid.\n *\n * Used for reduce-only orders, where rounding up would push the size past the\n * live position size. Values already on the grid are snapped rather than\n * truncated, because floating-point math can leave them just below a grid\n * point (0.0123 * 10000 === 122.99999999999999) and truncating would drop a\n * whole increment.\n *\n * The result is never greater than `size`, for negative sizes as well as\n * positive: the snap only ever recovers a grid point the input already\n * represents, so a value genuinely below a grid point is stepped down even when\n * the tolerance would have reached the point above it.\n *\n * A size whose scaled form reaches `2^53` is returned unchanged: doubles cannot\n * represent consecutive integers there, so the grid is finer than the spacing\n * between representable values and there is nothing to round down to.\n *\n * @param size - Size to round down.\n * @param szDecimals - The asset's size decimal precision.\n * @returns The size rounded down onto the size grid, never exceeding `size`.\n */\nexport function floorToSizeDecimals(size: number, szDecimals: number): number {\n const multiplier = Math.pow(10, szDecimals);\n const scaled = size * multiplier;\n\n // Past 2^53 a double cannot represent consecutive integers, so `units -= 1`\n // below would be a no-op and the step-down loop would never terminate. The\n // size grid is finer than the spacing between representable values at that\n // magnitude, so there is no increment to shave: return the input unchanged.\n if (!Number.isFinite(scaled) || Math.abs(scaled) >= Number.MAX_SAFE_INTEGER) {\n return size;\n }\n\n const nearest = Math.round(scaled);\n // The tolerance scales with the magnitude, because double-precision error\n // does too: a fixed epsilon would stop absorbing representation error for\n // sizes that scale past ~1e10 and would then shave off a whole increment.\n const tolerance = Math.max(\n FLOAT_TOLERANCE,\n Math.abs(scaled) * Number.EPSILON * 8,\n );\n let units =\n Math.abs(scaled - nearest) < tolerance ? nearest : Math.floor(scaled);\n\n // Step down until the result no longer exceeds the input. One pass is not\n // enough: a tolerance wide enough to absorb representation error at large\n // magnitudes also reaches the next grid point, and for an input less than half\n // an ulp below a grid point `size * multiplier` evaluates to exactly that grid\n // integer, so flooring the scaled value returns the same too-large result.\n // The comparison alone is the whole termination condition: for a non-negative\n // size the loop stops at or before zero, and for a negative size it stops once\n // the value is no longer above the input. Guarding on `units` instead would\n // skip a negative size below the tolerance, which snaps to `-0` — and\n // `-0 !== 0` is false. The 2^53 bail-out above keeps this bounded.\n while (units / multiplier > size) {\n units -= 1;\n }\n\n return units / multiplier;\n}\n\n/**\n * Calculates final position size using USD as source of truth with price validation\n *\n * This function implements the hybrid approach where USD is the source of truth,\n * but includes price staleness validation and proper rounding to prevent precision loss.\n *\n * @param params - USD amount, size, prices, and configuration\n * @returns Final position size as a number\n */\nexport function calculateFinalPositionSize(\n params: CalculateFinalPositionSizeParams,\n): CalculateFinalPositionSizeResult {\n const {\n usdAmount,\n size,\n currentPrice,\n priceAtCalculation,\n maxSlippageBps,\n szDecimals,\n leverage,\n reduceOnly,\n debugLogger,\n } = params;\n\n let finalPositionSize: number;\n\n // Validate price staleness whenever the caller supplied a calculation-time\n // price. This runs before the sizing branches on purpose: a full close submits\n // the exact live position size rather than a USD-derived one, and it must still\n // be rejected when the price has moved past the caller's tolerance.\n if (priceAtCalculation) {\n const priceDeltaBps = Math.abs(\n ((currentPrice - priceAtCalculation) / priceAtCalculation) * 10000,\n );\n const maxSlippageBpsValue =\n maxSlippageBps ?? ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;\n\n if (priceDeltaBps > maxSlippageBpsValue) {\n throw new Error(\n `Price moved too much: ${priceDeltaBps.toFixed(0)} bps (max: ${maxSlippageBpsValue} bps). ` +\n `Expected: ${priceAtCalculation.toFixed(2)}, Current: ${currentPrice.toFixed(2)}`,\n );\n }\n\n debugLogger?.log('Price validation passed:', {\n priceAtCalculation,\n currentPrice,\n deltaBps: priceDeltaBps.toFixed(2),\n maxSlippageBps: maxSlippageBpsValue,\n });\n }\n\n if (usdAmount && parseFloat(usdAmount) > 0) {\n // USD amount provided - use it as source of truth\n const usdValue = parseFloat(usdAmount);\n\n // Recalculate position size with fresh price\n finalPositionSize = usdValue / currentPrice;\n\n // A reduce-only order may never exceed the size the caller asked to close:\n // that size is already clamped to the live position, while the USD amount was\n // computed against an older price and can imply a larger size after an\n // adverse move. Capping here keeps USD accuracy in the common case and makes\n // the caller's clamp binding.\n if (reduceOnly && size) {\n const requestedSize = parseFloat(size);\n\n // A supplied size must be positive, or the cap below would submit a\n // zero/negative order. Reject it rather than silently falling back to the\n // USD-derived size, matching how closePosition treats the same input.\n if (!Number.isFinite(requestedSize) || requestedSize <= 0) {\n throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);\n }\n\n finalPositionSize = Math.min(finalPositionSize, requestedSize);\n }\n\n // 3. Apply size decimals rounding (reduce-only never rounds up)\n const multiplier = Math.pow(10, szDecimals);\n const sizeBeforeRounding = finalPositionSize;\n finalPositionSize = reduceOnly\n ? floorToSizeDecimals(finalPositionSize, szDecimals)\n : Math.round(finalPositionSize * multiplier) / multiplier;\n\n // Rounding down can zero out a reduce-only order whose USD value is worth\n // less than one size increment. Fail with a clear error instead of\n // submitting a size of \"0\" the exchange will reject.\n if (reduceOnly && finalPositionSize <= 0 && sizeBeforeRounding > 0) {\n throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);\n }\n\n // 4. Ensure rounded size meets requested USD (fix validation gap).\n // Skipped for reduce-only orders: adding an increment there would submit\n // more than the position holds and HyperLiquid rejects the order.\n let actualNotionalValue = finalPositionSize * currentPrice;\n if (!reduceOnly && actualNotionalValue < usdValue) {\n // Add 1 minimum increment to meet requested USD\n finalPositionSize += 1 / multiplier;\n actualNotionalValue = finalPositionSize * currentPrice;\n\n debugLogger?.log('Position size adjusted to meet USD minimum:', {\n requestedUsd: usdValue,\n beforeAdjustment: finalPositionSize - 1 / multiplier,\n afterAdjustment: finalPositionSize,\n actualUsd: actualNotionalValue,\n });\n }\n\n const requiredMargin = actualNotionalValue / (leverage ?? 1);\n\n // Log if rounding caused significant difference\n const usdDifference = Math.abs(actualNotionalValue - usdValue);\n if (usdDifference > 0.01) {\n debugLogger?.log(\n 'Position size rounding caused USD difference (acceptable):',\n {\n requestedUsd: usdValue,\n actualUsd: actualNotionalValue,\n difference: usdDifference,\n positionSize: finalPositionSize,\n },\n );\n }\n\n debugLogger?.log('Recalculated position size with fresh price:', {\n usdAmount: usdValue,\n priceAtCalculation,\n currentPrice,\n originalSize: size,\n recalculatedSize: finalPositionSize,\n requiredMargin,\n minIncrement: 1 / multiplier,\n });\n } else {\n // Legacy: Use provided size (backward compatibility)\n finalPositionSize = parseFloat(size ?? '0');\n\n // Reduce-only sizes are formatted with toFixed() further down, which rounds\n // up; truncate onto the size grid first so a close can never exceed the\n // position it is closing.\n if (reduceOnly) {\n // A supplied size must be positive, or formatHyperLiquidSize would render\n // a zero or negative order size. The USD branch above rejects the same\n // input.\n if (size && !(finalPositionSize > 0)) {\n throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);\n }\n\n const sizeBeforeFlooring = finalPositionSize;\n finalPositionSize = floorToSizeDecimals(finalPositionSize, szDecimals);\n\n // A positive size that floors to zero is worth less than one increment\n if (finalPositionSize <= 0 && sizeBeforeFlooring > 0) {\n throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);\n }\n }\n\n debugLogger?.log(\n 'Using legacy size calculation (no USD amount provided):',\n {\n providedSize: size,\n finalSize: finalPositionSize,\n },\n );\n }\n\n return { finalPositionSize };\n}\n\n/**\n * Calculates order price and formatted size based on order type\n *\n * @param params - Order parameters including type, direction, size, and prices\n * @returns Formatted order price, size, and price string\n */\nexport function calculateOrderPriceAndSize(\n params: CalculateOrderPriceAndSizeParams,\n): CalculateOrderPriceAndSizeResult {\n const {\n orderType,\n isBuy,\n finalPositionSize,\n currentPrice,\n limitPrice,\n maxSlippageBps,\n szDecimals,\n } = params;\n\n let orderPrice: number;\n let formattedSize: string;\n\n if (orderType === 'market') {\n // Market orders: apply slippage buffer to the live price so HyperLiquid\n // receives a worst-case acceptable limit price. Falls back to the\n // documented default if the caller does not provide one.\n const effectiveBps =\n maxSlippageBps ?? ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;\n const slippageValue = effectiveBps / BASIS_POINTS_DIVISOR;\n orderPrice = isBuy\n ? currentPrice * (1 + slippageValue)\n : currentPrice * (1 - slippageValue);\n formattedSize = formatHyperLiquidSize({\n size: finalPositionSize,\n szDecimals,\n });\n } else {\n // Limit orders: use provided price (no slippage applied)\n if (!limitPrice) {\n throw new Error(PERPS_ERROR_CODES.ORDER_LIMIT_PRICE_REQUIRED);\n }\n orderPrice = parseFloat(limitPrice);\n formattedSize = formatHyperLiquidSize({\n size: finalPositionSize,\n szDecimals,\n });\n }\n\n const formattedPrice = formatHyperLiquidPrice({\n price: orderPrice,\n szDecimals,\n });\n\n return { orderPrice, formattedSize, formattedPrice };\n}\n\n/**\n * Builds orders array including main order and optional TP/SL orders\n *\n * @param params - Order construction parameters\n * @returns Array of SDK order params and grouping type\n */\nexport function buildOrdersArray(\n params: BuildOrdersArrayParams,\n): BuildOrdersArrayResult {\n const {\n assetId,\n isBuy,\n formattedPrice,\n formattedSize,\n reduceOnly,\n orderType,\n clientOrderId,\n takeProfitPrice,\n stopLossPrice,\n szDecimals,\n grouping,\n } = params;\n\n const orders: SDKOrderParams[] = [];\n\n // 1. Main order\n const mainOrder: SDKOrderParams = {\n a: assetId,\n b: isBuy,\n p: formattedPrice,\n s: formattedSize,\n r: reduceOnly || false,\n t:\n orderType === 'limit'\n ? { limit: { tif: 'Gtc' } }\n : { limit: { tif: 'FrontendMarket' } },\n c: clientOrderId ? (clientOrderId as Hex) : undefined,\n };\n orders.push(mainOrder);\n\n // 2. Take Profit order\n if (takeProfitPrice) {\n const tpOrder: SDKOrderParams = {\n a: assetId,\n b: !isBuy,\n p: formatHyperLiquidPrice({\n price: parseFloat(takeProfitPrice),\n szDecimals,\n }),\n s: formattedSize,\n r: true,\n t: {\n trigger: {\n isMarket: false,\n triggerPx: formatHyperLiquidPrice({\n price: parseFloat(takeProfitPrice),\n szDecimals,\n }),\n tpsl: 'tp',\n },\n },\n };\n orders.push(tpOrder);\n }\n\n // 3. Stop Loss order\n if (stopLossPrice) {\n // Apply 10% slippage to SL limit price (executes as market order when triggered)\n // HyperLiquid recommended: 10% for TP/SL orders\n const stopLossPriceNum = parseFloat(stopLossPrice);\n const slippageValue = ORDER_SLIPPAGE_CONFIG.DefaultTpslSlippageBps / 10000;\n const limitPriceWithSlippage = isBuy\n ? stopLossPriceNum * (1 - slippageValue) // Selling to close long: willing to accept LESS (slippage protection)\n : stopLossPriceNum * (1 + slippageValue); // Buying to close short: willing to pay MORE (slippage protection)\n\n const slOrder: SDKOrderParams = {\n a: assetId,\n b: !isBuy,\n p: formatHyperLiquidPrice({\n price: limitPriceWithSlippage,\n szDecimals,\n }),\n s: formattedSize,\n r: true,\n t: {\n trigger: {\n isMarket: true,\n triggerPx: formatHyperLiquidPrice({\n price: stopLossPriceNum,\n szDecimals,\n }),\n tpsl: 'sl',\n },\n },\n };\n orders.push(slOrder);\n }\n\n // Determine grouping\n const finalGrouping: 'na' | 'normalTpsl' | 'positionTpsl' =\n grouping ?? ((takeProfitPrice ?? stopLossPrice) ? 'normalTpsl' : 'na');\n\n return { orders, grouping: finalGrouping };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"orderCalculations.cjs","sourceRoot":"","sources":["../../src/utils/orderCalculations.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Hex } from '@metamask/utils';\n\nimport { BASIS_POINTS_DIVISOR } from '../constants/hyperLiquidConfig.js';\nimport {\n MAX_ORDER_MARGIN_BUFFER,\n ORDER_SLIPPAGE_CONFIG,\n} from '../constants/perpsConfig.js';\nimport { PERPS_ERROR_CODES } from '../perpsErrorCodes.js';\nimport type { SDKOrderParams } from '../types/hyperliquid-types.js';\nimport type { PerpsDebugLogger } from '../types/index.js';\nimport {\n formatHyperLiquidPrice,\n formatHyperLiquidSize,\n} from './hyperLiquidAdapter.js';\n\n/**\n * Optional debug logger for order calculation functions.\n * When provided, enables detailed logging for debugging.\n */\nexport type OrderCalculationsDebugLogger = PerpsDebugLogger | undefined;\n\ntype PositionSizeParams = {\n amount: string;\n price: number;\n szDecimals: number;\n};\n\ntype MarginRequiredParams = {\n amount: string;\n leverage: number;\n};\n\ntype MaxAllowedAmountParams = {\n spendableBalance: number;\n assetPrice: number;\n assetSzDecimals: number;\n leverage: number;\n};\n\n// Advanced order calculation interfaces\nexport type CalculateFinalPositionSizeParams = {\n usdAmount?: string;\n size?: string;\n currentPrice: number;\n priceAtCalculation?: number;\n maxSlippageBps?: number;\n szDecimals: number;\n leverage?: number;\n debugLogger?: OrderCalculationsDebugLogger;\n};\n\nexport type CalculateFinalPositionSizeResult = {\n finalPositionSize: number;\n};\n\nexport type CalculateOrderPriceAndSizeParams = {\n orderType: 'market' | 'limit';\n isBuy: boolean;\n finalPositionSize: number;\n currentPrice: number;\n limitPrice?: string;\n // Max slippage in basis points (e.g. 300 = 3%). Only applied to market orders;\n // limit orders use limitPrice directly. Falls back to ORDER_SLIPPAGE_CONFIG\n // .DefaultMarketSlippageBps when omitted on a market order.\n maxSlippageBps?: number;\n szDecimals: number;\n};\n\nexport type CalculateOrderPriceAndSizeResult = {\n orderPrice: number;\n formattedSize: string;\n formattedPrice: string;\n};\n\nexport type BuildOrdersArrayParams = {\n assetId: number;\n isBuy: boolean;\n formattedPrice: string;\n formattedSize: string;\n reduceOnly: boolean;\n orderType: 'market' | 'limit';\n clientOrderId?: string;\n takeProfitPrice?: string;\n stopLossPrice?: string;\n szDecimals: number;\n grouping?: 'na' | 'normalTpsl' | 'positionTpsl';\n};\n\nexport type BuildOrdersArrayResult = {\n orders: SDKOrderParams[];\n grouping: 'na' | 'normalTpsl' | 'positionTpsl';\n};\n\n/**\n * Calculate position size based on USD amount and asset price\n *\n * @param params - Amount in USD, current asset price, and required decimal precision\n * @returns Position size formatted to the asset's decimal precision\n */\nexport function calculatePositionSize(params: PositionSizeParams): string {\n const { amount, price, szDecimals } = params;\n\n // Validate required parameters\n if (szDecimals === undefined || szDecimals === null) {\n throw new Error('szDecimals is required for position size calculation');\n }\n if (szDecimals < 0) {\n throw new Error(`szDecimals must be >= 0, got: ${szDecimals}`);\n }\n\n const amountNum = parseFloat(amount || '0');\n\n if (isNaN(amountNum) || isNaN(price) || amountNum === 0 || price === 0) {\n return (0).toFixed(szDecimals);\n }\n\n const positionSize = amountNum / price;\n const multiplier = Math.pow(10, szDecimals);\n let rounded = Math.round(positionSize * multiplier) / multiplier;\n\n // Ensure rounded size meets requested USD (fix validation gap)\n const actualUsd = rounded * price;\n if (actualUsd < amountNum) {\n rounded += 1 / multiplier;\n }\n\n return rounded.toFixed(szDecimals);\n}\n\n/**\n * Calculate margin required for a position\n *\n * @param params - Position amount and leverage\n * @returns Margin required formatted to 2 decimal places\n */\nexport function calculateMarginRequired(params: MarginRequiredParams): string {\n const { amount, leverage } = params;\n const amountNum = parseFloat(amount || '0');\n\n if (\n isNaN(amountNum) ||\n isNaN(leverage) ||\n amountNum === 0 ||\n leverage === 0\n ) {\n return '0.00';\n }\n\n return (amountNum / leverage).toFixed(2);\n}\n\nexport function getMaxAllowedAmount(params: MaxAllowedAmountParams): number {\n const { spendableBalance, assetPrice, assetSzDecimals, leverage } = params;\n if (spendableBalance === 0 || !assetPrice || assetSzDecimals === undefined) {\n return 0;\n }\n\n // The theoretical maximum is simply spendableBalance * leverage\n const theoreticalMax = spendableBalance * leverage;\n\n // But we need to account for position size rounding\n // Find the largest whole dollar amount that fits within this limit\n let maxAmount = Math.floor(theoreticalMax);\n\n // Verify this amount doesn't exceed available balance after rounding\n const testPositionSize = calculatePositionSize({\n amount: maxAmount.toString(),\n price: assetPrice,\n szDecimals: assetSzDecimals,\n });\n\n const actualNotionalValue = parseFloat(testPositionSize) * assetPrice;\n const requiredMargin = actualNotionalValue / leverage;\n\n // If rounding caused us to exceed available balance, step down by one position increment\n if (requiredMargin > spendableBalance) {\n const minPositionSizeIncrement = 1 / Math.pow(10, assetSzDecimals);\n const positionSizeIncrementUsd = Math.ceil(\n minPositionSizeIncrement * assetPrice,\n );\n maxAmount -= positionSizeIncrementUsd;\n }\n\n // Apply margin buffer to reduce \"Insufficient margin\" rejections from the exchange\n // (fees, rounding, and exchange-side checks can make 100% theoretical max fail)\n const bufferedMax = maxAmount * (1 - MAX_ORDER_MARGIN_BUFFER);\n\n return Math.max(0, Math.floor(bufferedMax));\n}\n\n/**\n * Calculates final position size using USD as source of truth with price validation\n *\n * This function implements the hybrid approach where USD is the source of truth,\n * but includes price staleness validation and proper rounding to prevent precision loss.\n *\n * @param params - USD amount, size, prices, and configuration\n * @returns Final position size as a number\n */\nexport function calculateFinalPositionSize(\n params: CalculateFinalPositionSizeParams,\n): CalculateFinalPositionSizeResult {\n const {\n usdAmount,\n size,\n currentPrice,\n priceAtCalculation,\n maxSlippageBps,\n szDecimals,\n leverage,\n debugLogger,\n } = params;\n\n let finalPositionSize: number;\n\n if (usdAmount && parseFloat(usdAmount) > 0) {\n // USD amount provided - use it as source of truth\n const usdValue = parseFloat(usdAmount);\n\n // 1. Validate price staleness if priceAtCalculation provided\n if (priceAtCalculation) {\n const priceDeltaBps = Math.abs(\n ((currentPrice - priceAtCalculation) / priceAtCalculation) * 10000,\n );\n const maxSlippageBpsValue =\n maxSlippageBps ?? ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;\n\n if (priceDeltaBps > maxSlippageBpsValue) {\n throw new Error(\n `Price moved too much: ${priceDeltaBps.toFixed(0)} bps (max: ${maxSlippageBpsValue} bps). ` +\n `Expected: ${priceAtCalculation.toFixed(2)}, Current: ${currentPrice.toFixed(2)}`,\n );\n }\n\n debugLogger?.log('Price validation passed:', {\n priceAtCalculation,\n currentPrice,\n deltaBps: priceDeltaBps.toFixed(2),\n maxSlippageBps: maxSlippageBpsValue,\n });\n }\n\n // 2. Recalculate position size with fresh price\n finalPositionSize = usdValue / currentPrice;\n\n // 3. Apply size decimals rounding\n const multiplier = Math.pow(10, szDecimals);\n finalPositionSize = Math.round(finalPositionSize * multiplier) / multiplier;\n\n // 4. Ensure rounded size meets requested USD (fix validation gap)\n let actualNotionalValue = finalPositionSize * currentPrice;\n if (actualNotionalValue < usdValue) {\n // Add 1 minimum increment to meet requested USD\n finalPositionSize += 1 / multiplier;\n actualNotionalValue = finalPositionSize * currentPrice;\n\n debugLogger?.log('Position size adjusted to meet USD minimum:', {\n requestedUsd: usdValue,\n beforeAdjustment: finalPositionSize - 1 / multiplier,\n afterAdjustment: finalPositionSize,\n actualUsd: actualNotionalValue,\n });\n }\n\n const requiredMargin = actualNotionalValue / (leverage ?? 1);\n\n // Log if rounding caused significant difference\n const usdDifference = Math.abs(actualNotionalValue - usdValue);\n if (usdDifference > 0.01) {\n debugLogger?.log(\n 'Position size rounding caused USD difference (acceptable):',\n {\n requestedUsd: usdValue,\n actualUsd: actualNotionalValue,\n difference: usdDifference,\n positionSize: finalPositionSize,\n },\n );\n }\n\n debugLogger?.log('Recalculated position size with fresh price:', {\n usdAmount: usdValue,\n priceAtCalculation,\n currentPrice,\n originalSize: size,\n recalculatedSize: finalPositionSize,\n requiredMargin,\n minIncrement: 1 / multiplier,\n });\n } else {\n // Legacy: Use provided size (backward compatibility)\n finalPositionSize = parseFloat(size ?? '0');\n\n debugLogger?.log(\n 'Using legacy size calculation (no USD amount provided):',\n {\n providedSize: size,\n finalSize: finalPositionSize,\n },\n );\n }\n\n return { finalPositionSize };\n}\n\n/**\n * Calculates order price and formatted size based on order type\n *\n * @param params - Order parameters including type, direction, size, and prices\n * @returns Formatted order price, size, and price string\n */\nexport function calculateOrderPriceAndSize(\n params: CalculateOrderPriceAndSizeParams,\n): CalculateOrderPriceAndSizeResult {\n const {\n orderType,\n isBuy,\n finalPositionSize,\n currentPrice,\n limitPrice,\n maxSlippageBps,\n szDecimals,\n } = params;\n\n let orderPrice: number;\n let formattedSize: string;\n\n if (orderType === 'market') {\n // Market orders: apply slippage buffer to the live price so HyperLiquid\n // receives a worst-case acceptable limit price. Falls back to the\n // documented default if the caller does not provide one.\n const effectiveBps =\n maxSlippageBps ?? ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;\n const slippageValue = effectiveBps / BASIS_POINTS_DIVISOR;\n orderPrice = isBuy\n ? currentPrice * (1 + slippageValue)\n : currentPrice * (1 - slippageValue);\n formattedSize = formatHyperLiquidSize({\n size: finalPositionSize,\n szDecimals,\n });\n } else {\n // Limit orders: use provided price (no slippage applied)\n if (!limitPrice) {\n throw new Error(PERPS_ERROR_CODES.ORDER_LIMIT_PRICE_REQUIRED);\n }\n orderPrice = parseFloat(limitPrice);\n formattedSize = formatHyperLiquidSize({\n size: finalPositionSize,\n szDecimals,\n });\n }\n\n const formattedPrice = formatHyperLiquidPrice({\n price: orderPrice,\n szDecimals,\n });\n\n return { orderPrice, formattedSize, formattedPrice };\n}\n\n/**\n * Builds orders array including main order and optional TP/SL orders\n *\n * @param params - Order construction parameters\n * @returns Array of SDK order params and grouping type\n */\nexport function buildOrdersArray(\n params: BuildOrdersArrayParams,\n): BuildOrdersArrayResult {\n const {\n assetId,\n isBuy,\n formattedPrice,\n formattedSize,\n reduceOnly,\n orderType,\n clientOrderId,\n takeProfitPrice,\n stopLossPrice,\n szDecimals,\n grouping,\n } = params;\n\n const orders: SDKOrderParams[] = [];\n\n // 1. Main order\n const mainOrder: SDKOrderParams = {\n a: assetId,\n b: isBuy,\n p: formattedPrice,\n s: formattedSize,\n r: reduceOnly || false,\n t:\n orderType === 'limit'\n ? { limit: { tif: 'Gtc' } }\n : { limit: { tif: 'FrontendMarket' } },\n c: clientOrderId ? (clientOrderId as Hex) : undefined,\n };\n orders.push(mainOrder);\n\n // 2. Take Profit order\n if (takeProfitPrice) {\n const tpOrder: SDKOrderParams = {\n a: assetId,\n b: !isBuy,\n p: formatHyperLiquidPrice({\n price: parseFloat(takeProfitPrice),\n szDecimals,\n }),\n s: formattedSize,\n r: true,\n t: {\n trigger: {\n isMarket: false,\n triggerPx: formatHyperLiquidPrice({\n price: parseFloat(takeProfitPrice),\n szDecimals,\n }),\n tpsl: 'tp',\n },\n },\n };\n orders.push(tpOrder);\n }\n\n // 3. Stop Loss order\n if (stopLossPrice) {\n // Apply 10% slippage to SL limit price (executes as market order when triggered)\n // HyperLiquid recommended: 10% for TP/SL orders\n const stopLossPriceNum = parseFloat(stopLossPrice);\n const slippageValue = ORDER_SLIPPAGE_CONFIG.DefaultTpslSlippageBps / 10000;\n const limitPriceWithSlippage = isBuy\n ? stopLossPriceNum * (1 - slippageValue) // Selling to close long: willing to accept LESS (slippage protection)\n : stopLossPriceNum * (1 + slippageValue); // Buying to close short: willing to pay MORE (slippage protection)\n\n const slOrder: SDKOrderParams = {\n a: assetId,\n b: !isBuy,\n p: formatHyperLiquidPrice({\n price: limitPriceWithSlippage,\n szDecimals,\n }),\n s: formattedSize,\n r: true,\n t: {\n trigger: {\n isMarket: true,\n triggerPx: formatHyperLiquidPrice({\n price: stopLossPriceNum,\n szDecimals,\n }),\n tpsl: 'sl',\n },\n },\n };\n orders.push(slOrder);\n }\n\n // Determine grouping\n const finalGrouping: 'na' | 'normalTpsl' | 'positionTpsl' =\n grouping ?? ((takeProfitPrice ?? stopLossPrice) ? 'normalTpsl' : 'na');\n\n return { orders, grouping: finalGrouping };\n}\n"]}
|
|
@@ -28,7 +28,6 @@ export type CalculateFinalPositionSizeParams = {
|
|
|
28
28
|
maxSlippageBps?: number;
|
|
29
29
|
szDecimals: number;
|
|
30
30
|
leverage?: number;
|
|
31
|
-
reduceOnly?: boolean;
|
|
32
31
|
debugLogger?: OrderCalculationsDebugLogger;
|
|
33
32
|
};
|
|
34
33
|
export type CalculateFinalPositionSizeResult = {
|
|
@@ -80,29 +79,6 @@ export declare function calculatePositionSize(params: PositionSizeParams): strin
|
|
|
80
79
|
*/
|
|
81
80
|
export declare function calculateMarginRequired(params: MarginRequiredParams): string;
|
|
82
81
|
export declare function getMaxAllowedAmount(params: MaxAllowedAmountParams): number;
|
|
83
|
-
/**
|
|
84
|
-
* Round a size down onto the asset's size grid.
|
|
85
|
-
*
|
|
86
|
-
* Used for reduce-only orders, where rounding up would push the size past the
|
|
87
|
-
* live position size. Values already on the grid are snapped rather than
|
|
88
|
-
* truncated, because floating-point math can leave them just below a grid
|
|
89
|
-
* point (0.0123 * 10000 === 122.99999999999999) and truncating would drop a
|
|
90
|
-
* whole increment.
|
|
91
|
-
*
|
|
92
|
-
* The result is never greater than `size`, for negative sizes as well as
|
|
93
|
-
* positive: the snap only ever recovers a grid point the input already
|
|
94
|
-
* represents, so a value genuinely below a grid point is stepped down even when
|
|
95
|
-
* the tolerance would have reached the point above it.
|
|
96
|
-
*
|
|
97
|
-
* A size whose scaled form reaches `2^53` is returned unchanged: doubles cannot
|
|
98
|
-
* represent consecutive integers there, so the grid is finer than the spacing
|
|
99
|
-
* between representable values and there is nothing to round down to.
|
|
100
|
-
*
|
|
101
|
-
* @param size - Size to round down.
|
|
102
|
-
* @param szDecimals - The asset's size decimal precision.
|
|
103
|
-
* @returns The size rounded down onto the size grid, never exceeding `size`.
|
|
104
|
-
*/
|
|
105
|
-
export declare function floorToSizeDecimals(size: number, szDecimals: number): number;
|
|
106
82
|
/**
|
|
107
83
|
* Calculates final position size using USD as source of truth with price validation
|
|
108
84
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orderCalculations.d.cts","sourceRoot":"","sources":["../../src/utils/orderCalculations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,uCAAsC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA0B;AAM1D;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG,gBAAgB,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"orderCalculations.d.cts","sourceRoot":"","sources":["../../src/utils/orderCalculations.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,7 +28,6 @@ export type CalculateFinalPositionSizeParams = {
|
|
|
28
28
|
maxSlippageBps?: number;
|
|
29
29
|
szDecimals: number;
|
|
30
30
|
leverage?: number;
|
|
31
|
-
reduceOnly?: boolean;
|
|
32
31
|
debugLogger?: OrderCalculationsDebugLogger;
|
|
33
32
|
};
|
|
34
33
|
export type CalculateFinalPositionSizeResult = {
|
|
@@ -80,29 +79,6 @@ export declare function calculatePositionSize(params: PositionSizeParams): strin
|
|
|
80
79
|
*/
|
|
81
80
|
export declare function calculateMarginRequired(params: MarginRequiredParams): string;
|
|
82
81
|
export declare function getMaxAllowedAmount(params: MaxAllowedAmountParams): number;
|
|
83
|
-
/**
|
|
84
|
-
* Round a size down onto the asset's size grid.
|
|
85
|
-
*
|
|
86
|
-
* Used for reduce-only orders, where rounding up would push the size past the
|
|
87
|
-
* live position size. Values already on the grid are snapped rather than
|
|
88
|
-
* truncated, because floating-point math can leave them just below a grid
|
|
89
|
-
* point (0.0123 * 10000 === 122.99999999999999) and truncating would drop a
|
|
90
|
-
* whole increment.
|
|
91
|
-
*
|
|
92
|
-
* The result is never greater than `size`, for negative sizes as well as
|
|
93
|
-
* positive: the snap only ever recovers a grid point the input already
|
|
94
|
-
* represents, so a value genuinely below a grid point is stepped down even when
|
|
95
|
-
* the tolerance would have reached the point above it.
|
|
96
|
-
*
|
|
97
|
-
* A size whose scaled form reaches `2^53` is returned unchanged: doubles cannot
|
|
98
|
-
* represent consecutive integers there, so the grid is finer than the spacing
|
|
99
|
-
* between representable values and there is nothing to round down to.
|
|
100
|
-
*
|
|
101
|
-
* @param size - Size to round down.
|
|
102
|
-
* @param szDecimals - The asset's size decimal precision.
|
|
103
|
-
* @returns The size rounded down onto the size grid, never exceeding `size`.
|
|
104
|
-
*/
|
|
105
|
-
export declare function floorToSizeDecimals(size: number, szDecimals: number): number;
|
|
106
82
|
/**
|
|
107
83
|
* Calculates final position size using USD as source of truth with price validation
|
|
108
84
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orderCalculations.d.mts","sourceRoot":"","sources":["../../src/utils/orderCalculations.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,uCAAsC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,2BAA0B;AAM1D;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG,gBAAgB,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"orderCalculations.d.mts","sourceRoot":"","sources":["../../src/utils/orderCalculations.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,11 +2,6 @@ import { BASIS_POINTS_DIVISOR } from "../constants/hyperLiquidConfig.mjs";
|
|
|
2
2
|
import { MAX_ORDER_MARGIN_BUFFER, ORDER_SLIPPAGE_CONFIG } from "../constants/perpsConfig.mjs";
|
|
3
3
|
import { PERPS_ERROR_CODES } from "../perpsErrorCodes.mjs";
|
|
4
4
|
import { formatHyperLiquidPrice, formatHyperLiquidSize } from "./hyperLiquidAdapter.mjs";
|
|
5
|
-
/**
|
|
6
|
-
* Tolerance used when deciding whether a scaled size is already on the size
|
|
7
|
-
* grid, guarding against floating-point representation error.
|
|
8
|
-
*/
|
|
9
|
-
const FLOAT_TOLERANCE = 1e-6;
|
|
10
5
|
/**
|
|
11
6
|
* Calculate position size based on USD amount and asset price
|
|
12
7
|
*
|
|
@@ -82,59 +77,6 @@ export function getMaxAllowedAmount(params) {
|
|
|
82
77
|
const bufferedMax = maxAmount * (1 - MAX_ORDER_MARGIN_BUFFER);
|
|
83
78
|
return Math.max(0, Math.floor(bufferedMax));
|
|
84
79
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Round a size down onto the asset's size grid.
|
|
87
|
-
*
|
|
88
|
-
* Used for reduce-only orders, where rounding up would push the size past the
|
|
89
|
-
* live position size. Values already on the grid are snapped rather than
|
|
90
|
-
* truncated, because floating-point math can leave them just below a grid
|
|
91
|
-
* point (0.0123 * 10000 === 122.99999999999999) and truncating would drop a
|
|
92
|
-
* whole increment.
|
|
93
|
-
*
|
|
94
|
-
* The result is never greater than `size`, for negative sizes as well as
|
|
95
|
-
* positive: the snap only ever recovers a grid point the input already
|
|
96
|
-
* represents, so a value genuinely below a grid point is stepped down even when
|
|
97
|
-
* the tolerance would have reached the point above it.
|
|
98
|
-
*
|
|
99
|
-
* A size whose scaled form reaches `2^53` is returned unchanged: doubles cannot
|
|
100
|
-
* represent consecutive integers there, so the grid is finer than the spacing
|
|
101
|
-
* between representable values and there is nothing to round down to.
|
|
102
|
-
*
|
|
103
|
-
* @param size - Size to round down.
|
|
104
|
-
* @param szDecimals - The asset's size decimal precision.
|
|
105
|
-
* @returns The size rounded down onto the size grid, never exceeding `size`.
|
|
106
|
-
*/
|
|
107
|
-
export function floorToSizeDecimals(size, szDecimals) {
|
|
108
|
-
const multiplier = Math.pow(10, szDecimals);
|
|
109
|
-
const scaled = size * multiplier;
|
|
110
|
-
// Past 2^53 a double cannot represent consecutive integers, so `units -= 1`
|
|
111
|
-
// below would be a no-op and the step-down loop would never terminate. The
|
|
112
|
-
// size grid is finer than the spacing between representable values at that
|
|
113
|
-
// magnitude, so there is no increment to shave: return the input unchanged.
|
|
114
|
-
if (!Number.isFinite(scaled) || Math.abs(scaled) >= Number.MAX_SAFE_INTEGER) {
|
|
115
|
-
return size;
|
|
116
|
-
}
|
|
117
|
-
const nearest = Math.round(scaled);
|
|
118
|
-
// The tolerance scales with the magnitude, because double-precision error
|
|
119
|
-
// does too: a fixed epsilon would stop absorbing representation error for
|
|
120
|
-
// sizes that scale past ~1e10 and would then shave off a whole increment.
|
|
121
|
-
const tolerance = Math.max(FLOAT_TOLERANCE, Math.abs(scaled) * Number.EPSILON * 8);
|
|
122
|
-
let units = Math.abs(scaled - nearest) < tolerance ? nearest : Math.floor(scaled);
|
|
123
|
-
// Step down until the result no longer exceeds the input. One pass is not
|
|
124
|
-
// enough: a tolerance wide enough to absorb representation error at large
|
|
125
|
-
// magnitudes also reaches the next grid point, and for an input less than half
|
|
126
|
-
// an ulp below a grid point `size * multiplier` evaluates to exactly that grid
|
|
127
|
-
// integer, so flooring the scaled value returns the same too-large result.
|
|
128
|
-
// The comparison alone is the whole termination condition: for a non-negative
|
|
129
|
-
// size the loop stops at or before zero, and for a negative size it stops once
|
|
130
|
-
// the value is no longer above the input. Guarding on `units` instead would
|
|
131
|
-
// skip a negative size below the tolerance, which snaps to `-0` — and
|
|
132
|
-
// `-0 !== 0` is false. The 2^53 bail-out above keeps this bounded.
|
|
133
|
-
while (units / multiplier > size) {
|
|
134
|
-
units -= 1;
|
|
135
|
-
}
|
|
136
|
-
return units / multiplier;
|
|
137
|
-
}
|
|
138
80
|
/**
|
|
139
81
|
* Calculates final position size using USD as source of truth with price validation
|
|
140
82
|
*
|
|
@@ -145,63 +87,34 @@ export function floorToSizeDecimals(size, szDecimals) {
|
|
|
145
87
|
* @returns Final position size as a number
|
|
146
88
|
*/
|
|
147
89
|
export function calculateFinalPositionSize(params) {
|
|
148
|
-
const { usdAmount, size, currentPrice, priceAtCalculation, maxSlippageBps, szDecimals, leverage,
|
|
90
|
+
const { usdAmount, size, currentPrice, priceAtCalculation, maxSlippageBps, szDecimals, leverage, debugLogger, } = params;
|
|
149
91
|
let finalPositionSize;
|
|
150
|
-
// Validate price staleness whenever the caller supplied a calculation-time
|
|
151
|
-
// price. This runs before the sizing branches on purpose: a full close submits
|
|
152
|
-
// the exact live position size rather than a USD-derived one, and it must still
|
|
153
|
-
// be rejected when the price has moved past the caller's tolerance.
|
|
154
|
-
if (priceAtCalculation) {
|
|
155
|
-
const priceDeltaBps = Math.abs(((currentPrice - priceAtCalculation) / priceAtCalculation) * 10000);
|
|
156
|
-
const maxSlippageBpsValue = maxSlippageBps ?? ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;
|
|
157
|
-
if (priceDeltaBps > maxSlippageBpsValue) {
|
|
158
|
-
throw new Error(`Price moved too much: ${priceDeltaBps.toFixed(0)} bps (max: ${maxSlippageBpsValue} bps). ` +
|
|
159
|
-
`Expected: ${priceAtCalculation.toFixed(2)}, Current: ${currentPrice.toFixed(2)}`);
|
|
160
|
-
}
|
|
161
|
-
debugLogger?.log('Price validation passed:', {
|
|
162
|
-
priceAtCalculation,
|
|
163
|
-
currentPrice,
|
|
164
|
-
deltaBps: priceDeltaBps.toFixed(2),
|
|
165
|
-
maxSlippageBps: maxSlippageBpsValue,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
92
|
if (usdAmount && parseFloat(usdAmount) > 0) {
|
|
169
93
|
// USD amount provided - use it as source of truth
|
|
170
94
|
const usdValue = parseFloat(usdAmount);
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (reduceOnly && size) {
|
|
179
|
-
const requestedSize = parseFloat(size);
|
|
180
|
-
// A supplied size must be positive, or the cap below would submit a
|
|
181
|
-
// zero/negative order. Reject it rather than silently falling back to the
|
|
182
|
-
// USD-derived size, matching how closePosition treats the same input.
|
|
183
|
-
if (!Number.isFinite(requestedSize) || requestedSize <= 0) {
|
|
184
|
-
throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
95
|
+
// 1. Validate price staleness if priceAtCalculation provided
|
|
96
|
+
if (priceAtCalculation) {
|
|
97
|
+
const priceDeltaBps = Math.abs(((currentPrice - priceAtCalculation) / priceAtCalculation) * 10000);
|
|
98
|
+
const maxSlippageBpsValue = maxSlippageBps ?? ORDER_SLIPPAGE_CONFIG.DefaultMarketSlippageBps;
|
|
99
|
+
if (priceDeltaBps > maxSlippageBpsValue) {
|
|
100
|
+
throw new Error(`Price moved too much: ${priceDeltaBps.toFixed(0)} bps (max: ${maxSlippageBpsValue} bps). ` +
|
|
101
|
+
`Expected: ${priceAtCalculation.toFixed(2)}, Current: ${currentPrice.toFixed(2)}`);
|
|
185
102
|
}
|
|
186
|
-
|
|
103
|
+
debugLogger?.log('Price validation passed:', {
|
|
104
|
+
priceAtCalculation,
|
|
105
|
+
currentPrice,
|
|
106
|
+
deltaBps: priceDeltaBps.toFixed(2),
|
|
107
|
+
maxSlippageBps: maxSlippageBpsValue,
|
|
108
|
+
});
|
|
187
109
|
}
|
|
188
|
-
//
|
|
110
|
+
// 2. Recalculate position size with fresh price
|
|
111
|
+
finalPositionSize = usdValue / currentPrice;
|
|
112
|
+
// 3. Apply size decimals rounding
|
|
189
113
|
const multiplier = Math.pow(10, szDecimals);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
? floorToSizeDecimals(finalPositionSize, szDecimals)
|
|
193
|
-
: Math.round(finalPositionSize * multiplier) / multiplier;
|
|
194
|
-
// Rounding down can zero out a reduce-only order whose USD value is worth
|
|
195
|
-
// less than one size increment. Fail with a clear error instead of
|
|
196
|
-
// submitting a size of "0" the exchange will reject.
|
|
197
|
-
if (reduceOnly && finalPositionSize <= 0 && sizeBeforeRounding > 0) {
|
|
198
|
-
throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
199
|
-
}
|
|
200
|
-
// 4. Ensure rounded size meets requested USD (fix validation gap).
|
|
201
|
-
// Skipped for reduce-only orders: adding an increment there would submit
|
|
202
|
-
// more than the position holds and HyperLiquid rejects the order.
|
|
114
|
+
finalPositionSize = Math.round(finalPositionSize * multiplier) / multiplier;
|
|
115
|
+
// 4. Ensure rounded size meets requested USD (fix validation gap)
|
|
203
116
|
let actualNotionalValue = finalPositionSize * currentPrice;
|
|
204
|
-
if (
|
|
117
|
+
if (actualNotionalValue < usdValue) {
|
|
205
118
|
// Add 1 minimum increment to meet requested USD
|
|
206
119
|
finalPositionSize += 1 / multiplier;
|
|
207
120
|
actualNotionalValue = finalPositionSize * currentPrice;
|
|
@@ -236,23 +149,6 @@ export function calculateFinalPositionSize(params) {
|
|
|
236
149
|
else {
|
|
237
150
|
// Legacy: Use provided size (backward compatibility)
|
|
238
151
|
finalPositionSize = parseFloat(size ?? '0');
|
|
239
|
-
// Reduce-only sizes are formatted with toFixed() further down, which rounds
|
|
240
|
-
// up; truncate onto the size grid first so a close can never exceed the
|
|
241
|
-
// position it is closing.
|
|
242
|
-
if (reduceOnly) {
|
|
243
|
-
// A supplied size must be positive, or formatHyperLiquidSize would render
|
|
244
|
-
// a zero or negative order size. The USD branch above rejects the same
|
|
245
|
-
// input.
|
|
246
|
-
if (size && !(finalPositionSize > 0)) {
|
|
247
|
-
throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
248
|
-
}
|
|
249
|
-
const sizeBeforeFlooring = finalPositionSize;
|
|
250
|
-
finalPositionSize = floorToSizeDecimals(finalPositionSize, szDecimals);
|
|
251
|
-
// A positive size that floors to zero is worth less than one increment
|
|
252
|
-
if (finalPositionSize <= 0 && sizeBeforeFlooring > 0) {
|
|
253
|
-
throw new Error(PERPS_ERROR_CODES.ORDER_SIZE_POSITIVE);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
152
|
debugLogger?.log('Using legacy size calculation (no USD amount provided):', {
|
|
257
153
|
providedSize: size,
|
|
258
154
|
finalSize: finalPositionSize,
|