100x-sdk 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/100x-sdk.cjs.js +7037 -3901
- package/dist/100x-sdk.esm.js +6240 -3341
- package/dist/100x-sdk.js +6240 -3341
- package/dist/100x-sdk.js.map +1 -1
- package/dist/index.d.ts +19 -19
- package/package.json +1 -1
- package/src/modules/chain.js +26 -26
- package/src/modules/fast.js +88 -88
- package/src/modules/param.js +6 -6
- package/src/modules/simulator/buy_sell_token.js +38 -39
- package/src/modules/simulator/calcLiq.js +176 -198
- package/src/modules/simulator/calc_sol_liq.js +40 -40
- package/src/modules/simulator/close_indices.js +51 -54
- package/src/modules/simulator/long_shrot_stop.js +332 -332
- package/src/modules/simulator/stop_loss_utils.js +125 -125
- package/src/modules/simulator/utils.js +1 -2
- package/src/modules/simulator.js +14 -19
- package/src/modules/token.js +69 -69
- package/src/modules/tools.js +1 -1
- package/src/modules/trading.js +97 -97
- package/src/sdk.js +22 -23
- package/src/types/index.d.ts +19 -19
- package/src/utils/constants.js +2 -2
- package/src/utils/curve_amm.js +54 -54
- package/src/utils/orderUtils.js +1 -3
|
@@ -8,122 +8,122 @@ const JSONbig = require('json-bigint')({ storeAsString: false });
|
|
|
8
8
|
/**
|
|
9
9
|
* Simulate long position stop loss calculation
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Simulates the stop loss calculation for a long position, returning the executable stop loss price and related parameters.
|
|
12
|
+
* This function automatically adjusts the stop loss price to avoid overlapping with the price ranges of existing orders,
|
|
13
|
+
* and returns the insertion position index array required for contract execution.
|
|
14
14
|
*
|
|
15
|
-
* @param {string} mint - Token address
|
|
16
|
-
* @param {bigint|string|number} buyTokenAmount - Token amount to buy for long position (u64 format, precision 10^9)
|
|
17
|
-
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
18
|
-
* @param {Object|null} lastPrice - Token info, default null
|
|
19
|
-
* @param {Object|null} ordersData - Orders data, default null
|
|
20
|
-
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
15
|
+
* @param {string} mint - Token address
|
|
16
|
+
* @param {bigint|string|number} buyTokenAmount - Token amount to buy for long position (u64 format, precision 10^9)
|
|
17
|
+
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
18
|
+
* @param {Object|null} lastPrice - Token info, default null (auto-fetched if null)
|
|
19
|
+
* @param {Object|null} ordersData - Orders data, default null (auto-fetched if null)
|
|
20
|
+
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
21
21
|
*
|
|
22
|
-
* @returns {Promise<Object>} Stop loss analysis result
|
|
23
|
-
* @returns {bigint} returns.executableStopLossPrice -
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
-
* -
|
|
22
|
+
* @returns {Promise<Object>} Stop loss analysis result
|
|
23
|
+
* @returns {bigint} returns.executableStopLossPrice - Calculated executable stop loss price (u128 format)
|
|
24
|
+
* - This is the stop loss price adjusted to not overlap with existing orders
|
|
25
|
+
* - May be lower than the user-provided stopLossPrice (to avoid overlap)
|
|
26
|
+
* - Can be used directly as the closePrice parameter of sdk.trading.long()
|
|
27
27
|
*
|
|
28
|
-
* @returns {bigint} returns.tradeAmount -
|
|
29
|
-
* -
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
28
|
+
* @returns {bigint} returns.tradeAmount - Estimated SOL amount obtained from selling at stop loss (lamports)
|
|
29
|
+
* - This is the SOL obtained from selling buyTokenAmount tokens at the executableStopLossPrice
|
|
30
|
+
* - Does not include fee deduction
|
|
31
|
+
* - Used to estimate the proceeds at stop loss
|
|
32
32
|
*
|
|
33
|
-
* @returns {number} returns.stopLossPercentage -
|
|
34
|
-
* -
|
|
35
|
-
* -
|
|
36
|
-
* -
|
|
33
|
+
* @returns {number} returns.stopLossPercentage - Stop loss percentage (relative to current price)
|
|
34
|
+
* - Formula: ((currentPrice - executableStopLossPrice) / currentPrice) * 100
|
|
35
|
+
* - For example: 3.5 means the stop loss price is 3.5% lower than the current price
|
|
36
|
+
* - For a long position this value should be positive (stop loss price below current price)
|
|
37
37
|
*
|
|
38
|
-
* @returns {number} returns.leverage -
|
|
39
|
-
* -
|
|
40
|
-
* -
|
|
41
|
-
* -
|
|
38
|
+
* @returns {number} returns.leverage - Leverage ratio
|
|
39
|
+
* - Formula: currentPrice / (currentPrice - executableStopLossPrice)
|
|
40
|
+
* - For example: 28.57 means about 28.57x leverage
|
|
41
|
+
* - The higher the leverage, the higher the risk, but also the higher the potential return
|
|
42
42
|
*
|
|
43
|
-
* @returns {bigint} returns.currentPrice -
|
|
44
|
-
* -
|
|
45
|
-
* -
|
|
43
|
+
* @returns {bigint} returns.currentPrice - Current price (u128 format)
|
|
44
|
+
* - The current token price used in the calculation
|
|
45
|
+
* - Used for reference and validation
|
|
46
46
|
*
|
|
47
|
-
* @returns {number} returns.iterations -
|
|
48
|
-
* -
|
|
49
|
-
* -
|
|
50
|
-
* -
|
|
47
|
+
* @returns {number} returns.iterations - Number of price adjustment iterations
|
|
48
|
+
* - The number of times the function automatically adjusted the stop loss price to avoid price range overlap
|
|
49
|
+
* - Each adjustment lowers the price by PRICE_ADJUSTMENT_PERCENTAGE (default 0.5%)
|
|
50
|
+
* - If the iteration count is too high, you may need to reselect the stop loss price
|
|
51
51
|
*
|
|
52
|
-
* @returns {bigint} returns.originalStopLossPrice -
|
|
53
|
-
* -
|
|
54
|
-
* -
|
|
52
|
+
* @returns {bigint} returns.originalStopLossPrice - User-provided original stop loss price (u128 format)
|
|
53
|
+
* - Used to compare the price difference before and after adjustment
|
|
54
|
+
* - If executableStopLossPrice differs greatly from this, it indicates existing orders are dense
|
|
55
55
|
*
|
|
56
|
-
* @returns {number[]} returns.close_insert_indices -
|
|
57
|
-
* -
|
|
58
|
-
* -
|
|
59
|
-
* -
|
|
60
|
-
* -
|
|
61
|
-
* -
|
|
62
|
-
* -
|
|
63
|
-
* -
|
|
56
|
+
* @returns {number[]} returns.close_insert_indices - Candidate index array for the closing order insertion position ⭐ New
|
|
57
|
+
* - The array contains the OrderBook index values of multiple candidate insertion positions
|
|
58
|
+
* - Structure: [main position index, 1st before, 1st after, 2nd before, 2nd after, 3rd before, 3rd after]
|
|
59
|
+
* - For example: [25, 10, 33, 5, 40, 2, 50] means the main position is index 25, with alternative positions including indices 10, 33, etc.
|
|
60
|
+
* - Contains up to 7 index values (1 main position + 3 before + 3 after)
|
|
61
|
+
* - If the orderbook is empty, returns [65535] (u16::MAX, meaning insert at the head)
|
|
62
|
+
* - Usage: passed as the closeInsertIndices parameter of sdk.trading.long()
|
|
63
|
+
* - Improves success rate: even if the order at the main position is deleted, the contract can try other candidate positions
|
|
64
64
|
*
|
|
65
|
-
* @returns {bigint} returns.estimatedMargin -
|
|
66
|
-
* -
|
|
67
|
-
* -
|
|
68
|
-
* -
|
|
69
|
-
* -
|
|
65
|
+
* @returns {bigint} returns.estimatedMargin - Estimated required margin (SOL lamports)
|
|
66
|
+
* - Formula: buy cost - close proceeds (after fee deduction)
|
|
67
|
+
* - This is the minimum margin required to execute this stop loss strategy
|
|
68
|
+
* - Can be used as the marginSolMax parameter of sdk.trading.long()
|
|
69
|
+
* - When actually calling, it is recommended to add a 10-20% buffer to handle price fluctuations
|
|
70
70
|
*
|
|
71
|
-
* @throws {Error}
|
|
72
|
-
* @throws {Error}
|
|
73
|
-
* @throws {Error}
|
|
74
|
-
* @throws {Error}
|
|
71
|
+
* @throws {Error} When required parameters are missing
|
|
72
|
+
* @throws {Error} When price or orders data cannot be fetched
|
|
73
|
+
* @throws {Error} When a suitable stop loss price cannot be found after reaching the maximum iterations
|
|
74
|
+
* @throws {Error} When the price becomes negative after adjustment
|
|
75
75
|
*
|
|
76
76
|
* @example
|
|
77
|
-
* //
|
|
77
|
+
* // Basic usage: long 1 token, stop loss price at 97% of the current price
|
|
78
78
|
* const result = await sdk.simulator.simulateLongStopLoss(
|
|
79
79
|
* '4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X', // mint
|
|
80
|
-
* 1000000000n, // 1 token (
|
|
81
|
-
* BigInt('97000000000000000000') //
|
|
80
|
+
* 1000000000n, // 1 token (precision 10^9)
|
|
81
|
+
* BigInt('97000000000000000000') // stop loss price
|
|
82
82
|
* );
|
|
83
83
|
*
|
|
84
|
-
* console.log(
|
|
85
|
-
* console.log(
|
|
86
|
-
* console.log(
|
|
87
|
-
* console.log(
|
|
88
|
-
* console.log(
|
|
84
|
+
* console.log(`Executable stop loss price: ${result.executableStopLossPrice}`);
|
|
85
|
+
* console.log(`Stop loss percentage: ${result.stopLossPercentage}%`);
|
|
86
|
+
* console.log(`Leverage: ${result.leverage}x`);
|
|
87
|
+
* console.log(`Estimated margin: ${result.estimatedMargin} lamports`);
|
|
88
|
+
* console.log(`Insert position indices: ${result.close_insert_indices}`);
|
|
89
89
|
*
|
|
90
90
|
* @example
|
|
91
|
-
* //
|
|
91
|
+
* // Full workflow: simulate then execute a long trade
|
|
92
92
|
* async function openLongPosition(sdk, mint, buyTokenAmount, stopLossPrice) {
|
|
93
|
-
* // 1.
|
|
93
|
+
* // 1. Simulate stop loss calculation
|
|
94
94
|
* const simulation = await sdk.simulator.simulateLongStopLoss(
|
|
95
95
|
* mint,
|
|
96
96
|
* buyTokenAmount,
|
|
97
97
|
* stopLossPrice
|
|
98
98
|
* );
|
|
99
99
|
*
|
|
100
|
-
* // 2.
|
|
100
|
+
* // 2. Check whether the stop loss price was significantly adjusted
|
|
101
101
|
* const priceDiff = Number((simulation.originalStopLossPrice - simulation.executableStopLossPrice) * 10000n / simulation.originalStopLossPrice) / 100;
|
|
102
102
|
* if (priceDiff > 1.0) {
|
|
103
|
-
* console.warn(
|
|
103
|
+
* console.warn(`Stop loss price was adjusted by ${priceDiff}%, existing orders are dense`);
|
|
104
104
|
* }
|
|
105
105
|
*
|
|
106
|
-
* // 3.
|
|
107
|
-
* const maxSolAmount = simulation.estimatedMargin * 120n / 100n; //
|
|
108
|
-
* const marginSolMax = simulation.estimatedMargin * 115n / 100n; //
|
|
106
|
+
* // 3. Prepare transaction parameters
|
|
107
|
+
* const maxSolAmount = simulation.estimatedMargin * 120n / 100n; // add 20% buffer
|
|
108
|
+
* const marginSolMax = simulation.estimatedMargin * 115n / 100n; // add 15% buffer
|
|
109
109
|
*
|
|
110
|
-
* // 4.
|
|
110
|
+
* // 4. Execute the long trade
|
|
111
111
|
* const tx = await sdk.trading.long({
|
|
112
112
|
* mint: mint,
|
|
113
113
|
* buyTokenAmount: buyTokenAmount,
|
|
114
114
|
* maxSolAmount: maxSolAmount,
|
|
115
115
|
* marginSolMax: marginSolMax,
|
|
116
116
|
* closePrice: simulation.executableStopLossPrice,
|
|
117
|
-
* closeInsertIndices: simulation.close_insert_indices // ⭐
|
|
117
|
+
* closeInsertIndices: simulation.close_insert_indices // ⭐ use the new index array
|
|
118
118
|
* });
|
|
119
119
|
*
|
|
120
120
|
* return tx;
|
|
121
121
|
* }
|
|
122
122
|
*
|
|
123
|
-
* @see {@link simulateShortStopLoss}
|
|
124
|
-
* @see {@link simulateLongSolStopLoss}
|
|
123
|
+
* @see {@link simulateShortStopLoss} Stop loss calculation for short positions
|
|
124
|
+
* @see {@link simulateLongSolStopLoss} SOL-amount-based stop loss calculation for long positions
|
|
125
125
|
* @since 2.0.0
|
|
126
|
-
* @version 2.0.0 -
|
|
126
|
+
* @version 2.0.0 - Changed from returning prev_order_pda/next_order_pda to returning close_insert_indices
|
|
127
127
|
*/
|
|
128
128
|
async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null) {
|
|
129
129
|
try {
|
|
@@ -132,12 +132,12 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
|
|
|
132
132
|
throw new Error('Missing required parameters');
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
//
|
|
135
|
+
// If borrowFee or pool parameters are not provided, fetch them from the chain in a single call
|
|
136
136
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
137
137
|
const curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
138
138
|
if (borrowFee === null) borrowFee = curveAccount.borrowFee;
|
|
139
|
-
//
|
|
140
|
-
//
|
|
139
|
+
// The chain returns u64 raw units (lamports/smallest unit), which need to be divided by 10^9 to convert to human-readable units
|
|
140
|
+
// Consistent with the conversion method in calcLiq.js
|
|
141
141
|
if (initialVirtualSol === null) initialVirtualSol = new Decimal(curveAccount.initialVirtualSol.toString()).div(CurveAMM.SOL_PRECISION_FACTOR_DECIMAL).toString();
|
|
142
142
|
if (initialVirtualToken === null) initialVirtualToken = new Decimal(curveAccount.initialVirtualToken.toString()).div(CurveAMM.TOKEN_PRECISION_FACTOR_DECIMAL).toString();
|
|
143
143
|
}
|
|
@@ -191,7 +191,7 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
|
|
|
191
191
|
let finalOverlapResult = null; // Record final overlap result
|
|
192
192
|
let finalTradeAmount = 0n; // Record final trade amount
|
|
193
193
|
|
|
194
|
-
//
|
|
194
|
+
// Check and adjust the stop loss price to meet the minimum distance requirement (long: stop loss price must be below current price by at least MIN_STOP_LOSS_PERCENT)
|
|
195
195
|
// Check and adjust stop loss price to meet minimum distance requirement (long: stop loss must be below current price by at least MIN_STOP_LOSS_PERCENT)
|
|
196
196
|
const minAllowedStopLoss = currentPrice - (currentPrice * BigInt(MIN_STOP_LOSS_PERCENT)) / 1000n;
|
|
197
197
|
if (stopLossStartPrice > minAllowedStopLoss) {
|
|
@@ -230,33 +230,33 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
stopLossEndPrice = tradeResult[0]; // Price after trade completion
|
|
233
|
-
const tradeAmount = tradeResult[1]; // SOL
|
|
233
|
+
const tradeAmount = tradeResult[1]; // SOL output amount
|
|
234
234
|
|
|
235
235
|
// console.log(` - stopLossEndPrice: ${stopLossEndPrice.toString()}`);
|
|
236
236
|
// console.log(` - tradeAmount: ${tradeAmount.toString()}`);
|
|
237
237
|
|
|
238
|
-
//console.log(
|
|
238
|
+
//console.log(`Iteration ${iteration}: startPrice=${stopLossStartPrice}, endPrice=${stopLossEndPrice}, SOL output=${tradeAmount} / Iteration ${iteration}: Start=${stopLossStartPrice}, End=${stopLossEndPrice}, SOL output=${tradeAmount}`);
|
|
239
239
|
|
|
240
|
-
//
|
|
240
|
+
// Check price range overlap
|
|
241
241
|
const overlapResult = checkPriceRangeOverlap('down_orders', downOrders, stopLossStartPrice, stopLossEndPrice);
|
|
242
|
-
|
|
242
|
+
|
|
243
243
|
if (overlapResult.no_overlap) {
|
|
244
|
-
//console.log('
|
|
245
|
-
finalOverlapResult = overlapResult; //
|
|
246
|
-
finalTradeAmount = tradeAmount; //
|
|
244
|
+
//console.log('No price range overlap, can execute / No price range overlap, can execute');
|
|
245
|
+
finalOverlapResult = overlapResult; // Record final overlap result
|
|
246
|
+
finalTradeAmount = tradeAmount; // Record final trade amount
|
|
247
247
|
break;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
//console.log(
|
|
250
|
+
//console.log(`Found overlap: ${overlapResult.overlap_reason} / Found overlap: ${overlapResult.overlap_reason}`);
|
|
251
251
|
|
|
252
|
-
//
|
|
253
|
-
//
|
|
252
|
+
// Adjust start price (decrease by 0.5%)
|
|
253
|
+
// Using approach 2: directly compute 0.5% = 5/1000
|
|
254
254
|
const adjustmentAmount = (stopLossStartPrice * BigInt(PRICE_ADJUSTMENT_PERCENTAGE)) / 1000n;
|
|
255
255
|
stopLossStartPrice = stopLossStartPrice - adjustmentAmount;
|
|
256
256
|
|
|
257
|
-
//console.log(
|
|
257
|
+
//console.log(`Adjusted start price: ${stopLossStartPrice} / Adjusted start price: ${stopLossStartPrice}`);
|
|
258
258
|
|
|
259
|
-
//
|
|
259
|
+
// Safety check: ensure the price does not become negative
|
|
260
260
|
if (stopLossStartPrice <= 0n) {
|
|
261
261
|
throw new Error('止损价格调整后变为负数,无法继续 / Stop loss price became negative after adjustment');
|
|
262
262
|
}
|
|
@@ -266,30 +266,30 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
|
|
|
266
266
|
throw new Error('达到最大迭代次数,无法找到合适的止损价格 / Reached maximum iterations, cannot find suitable stop loss price');
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
//
|
|
269
|
+
// Calculate final return values
|
|
270
270
|
const executableStopLossPrice = stopLossStartPrice;
|
|
271
|
-
|
|
272
|
-
//
|
|
271
|
+
|
|
272
|
+
// Calculate stop loss percentage
|
|
273
273
|
let stopLossPercentage = 0;
|
|
274
274
|
let leverage = 1;
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
if (currentPrice !== executableStopLossPrice) {
|
|
277
277
|
stopLossPercentage = Number((BigInt(10000) * (currentPrice - executableStopLossPrice)) / currentPrice) / 100;
|
|
278
278
|
leverage = Number((BigInt(10000) * currentPrice) / (currentPrice - executableStopLossPrice)) / 10000;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
//
|
|
281
|
+
// Calculate margin requirement
|
|
282
282
|
let estimatedMargin = 0n;
|
|
283
283
|
try {
|
|
284
|
-
// 1.
|
|
284
|
+
// 1. Calculate the SOL required to buy from the current price
|
|
285
285
|
const buyResult = CurveAMM.buyFromPriceWithTokenOutputWithParams(currentPrice, buyTokenAmount, initialVirtualSol, initialVirtualToken);
|
|
286
286
|
if (buyResult) {
|
|
287
287
|
const requiredSol = buyResult[1]; // SOL input amount
|
|
288
288
|
|
|
289
|
-
// 2.
|
|
289
|
+
// 2. Calculate the proceeds at close after deducting the fee
|
|
290
290
|
const closeOutputSolAfterFee = CurveAMM.calculateAmountAfterFee(finalTradeAmount, borrowFee);
|
|
291
|
-
|
|
292
|
-
// 3.
|
|
291
|
+
|
|
292
|
+
// 3. Calculate margin = buy cost - close proceeds (after fee deduction)
|
|
293
293
|
if (closeOutputSolAfterFee !== null && requiredSol > closeOutputSolAfterFee) {
|
|
294
294
|
estimatedMargin = requiredSol - closeOutputSolAfterFee;
|
|
295
295
|
}
|
|
@@ -328,122 +328,122 @@ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPri
|
|
|
328
328
|
/**
|
|
329
329
|
* Simulate short position stop loss calculation
|
|
330
330
|
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
331
|
+
* Simulates the stop loss calculation for a short position, returning the executable stop loss price and related parameters.
|
|
332
|
+
* This function automatically adjusts the stop loss price to avoid overlapping with the price ranges of existing orders,
|
|
333
|
+
* and returns the insertion position index array required for contract execution.
|
|
334
334
|
*
|
|
335
|
-
* @param {string} mint - Token address
|
|
336
|
-
* @param {bigint|string|number} sellTokenAmount - Token amount to sell for short position (u64 format, precision 10^9)
|
|
337
|
-
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
338
|
-
* @param {Object|null} lastPrice - Token info, default null
|
|
339
|
-
* @param {Object|null} ordersData - Orders data, default null
|
|
340
|
-
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
335
|
+
* @param {string} mint - Token address
|
|
336
|
+
* @param {bigint|string|number} sellTokenAmount - Token amount to sell for short position (u64 format, precision 10^9)
|
|
337
|
+
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
338
|
+
* @param {Object|null} lastPrice - Token info, default null (auto-fetched if null)
|
|
339
|
+
* @param {Object|null} ordersData - Orders data, default null (auto-fetched if null)
|
|
340
|
+
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
341
341
|
*
|
|
342
|
-
* @returns {Promise<Object>} Stop loss analysis result
|
|
343
|
-
* @returns {bigint} returns.executableStopLossPrice -
|
|
344
|
-
* -
|
|
345
|
-
* -
|
|
346
|
-
* -
|
|
342
|
+
* @returns {Promise<Object>} Stop loss analysis result
|
|
343
|
+
* @returns {bigint} returns.executableStopLossPrice - Calculated executable stop loss price (u128 format)
|
|
344
|
+
* - This is the stop loss price adjusted to not overlap with existing orders
|
|
345
|
+
* - May be higher than the user-provided stopLossPrice (to avoid overlap)
|
|
346
|
+
* - Can be used directly as the closePrice parameter of sdk.trading.short()
|
|
347
347
|
*
|
|
348
|
-
* @returns {bigint} returns.tradeAmount -
|
|
349
|
-
* -
|
|
350
|
-
* -
|
|
351
|
-
* -
|
|
348
|
+
* @returns {bigint} returns.tradeAmount - Estimated SOL amount needed to buy back at stop loss (lamports)
|
|
349
|
+
* - This is the SOL needed to buy back sellTokenAmount tokens at the executableStopLossPrice
|
|
350
|
+
* - Does not include fees
|
|
351
|
+
* - Used to estimate the cost at stop loss
|
|
352
352
|
*
|
|
353
|
-
* @returns {number} returns.stopLossPercentage -
|
|
354
|
-
* -
|
|
355
|
-
* -
|
|
356
|
-
* -
|
|
353
|
+
* @returns {number} returns.stopLossPercentage - Stop loss percentage (relative to current price)
|
|
354
|
+
* - Formula: ((executableStopLossPrice - currentPrice) / currentPrice) * 100
|
|
355
|
+
* - For example: 3.5 means the stop loss price is 3.5% higher than the current price
|
|
356
|
+
* - For a short position this value should be positive (stop loss price above current price)
|
|
357
357
|
*
|
|
358
|
-
* @returns {number} returns.leverage -
|
|
359
|
-
* -
|
|
360
|
-
* -
|
|
361
|
-
* -
|
|
358
|
+
* @returns {number} returns.leverage - Leverage ratio
|
|
359
|
+
* - Formula: currentPrice / (executableStopLossPrice - currentPrice)
|
|
360
|
+
* - For example: 28.57 means about 28.57x leverage
|
|
361
|
+
* - The higher the leverage, the higher the risk, but also the higher the potential return
|
|
362
362
|
*
|
|
363
|
-
* @returns {bigint} returns.currentPrice -
|
|
364
|
-
* -
|
|
365
|
-
* -
|
|
363
|
+
* @returns {bigint} returns.currentPrice - Current price (u128 format)
|
|
364
|
+
* - The current token price used in the calculation
|
|
365
|
+
* - Used for reference and validation
|
|
366
366
|
*
|
|
367
|
-
* @returns {number} returns.iterations -
|
|
368
|
-
* -
|
|
369
|
-
* -
|
|
370
|
-
* -
|
|
367
|
+
* @returns {number} returns.iterations - Number of price adjustment iterations
|
|
368
|
+
* - The number of times the function automatically adjusted the stop loss price to avoid price range overlap
|
|
369
|
+
* - Each adjustment raises the price by PRICE_ADJUSTMENT_PERCENTAGE (default 0.5%)
|
|
370
|
+
* - If the iteration count is too high, you may need to reselect the stop loss price
|
|
371
371
|
*
|
|
372
|
-
* @returns {bigint} returns.originalStopLossPrice -
|
|
373
|
-
* -
|
|
374
|
-
* -
|
|
372
|
+
* @returns {bigint} returns.originalStopLossPrice - User-provided original stop loss price (u128 format)
|
|
373
|
+
* - Used to compare the price difference before and after adjustment
|
|
374
|
+
* - If executableStopLossPrice differs greatly from this, it indicates existing orders are dense
|
|
375
375
|
*
|
|
376
|
-
* @returns {number[]} returns.close_insert_indices -
|
|
377
|
-
* -
|
|
378
|
-
* -
|
|
379
|
-
* -
|
|
380
|
-
* -
|
|
381
|
-
* -
|
|
382
|
-
* -
|
|
383
|
-
* -
|
|
376
|
+
* @returns {number[]} returns.close_insert_indices - Candidate index array for the closing order insertion position ⭐ New
|
|
377
|
+
* - The array contains the OrderBook index values of multiple candidate insertion positions
|
|
378
|
+
* - Structure: [main position index, 1st before, 1st after, 2nd before, 2nd after, 3rd before, 3rd after]
|
|
379
|
+
* - For example: [25, 10, 33, 5, 40, 2, 50] means the main position is index 25, with alternative positions including indices 10, 33, etc.
|
|
380
|
+
* - Contains up to 7 index values (1 main position + 3 before + 3 after)
|
|
381
|
+
* - If the orderbook is empty, returns [65535] (u16::MAX, meaning insert at the head)
|
|
382
|
+
* - Usage: passed as the closeInsertIndices parameter of sdk.trading.short()
|
|
383
|
+
* - Improves success rate: even if the order at the main position is deleted, the contract can try other candidate positions
|
|
384
384
|
*
|
|
385
|
-
* @returns {bigint} returns.estimatedMargin -
|
|
386
|
-
* -
|
|
387
|
-
* -
|
|
388
|
-
* -
|
|
389
|
-
* -
|
|
385
|
+
* @returns {bigint} returns.estimatedMargin - Estimated required margin (SOL lamports)
|
|
386
|
+
* - Formula: close cost (including fee) - open proceeds - open fee
|
|
387
|
+
* - This is the minimum margin required to execute this stop loss strategy
|
|
388
|
+
* - Can be used as the marginSolMax parameter of sdk.trading.short()
|
|
389
|
+
* - When actually calling, it is recommended to add a 10-20% buffer to handle price fluctuations
|
|
390
390
|
*
|
|
391
|
-
* @throws {Error}
|
|
392
|
-
* @throws {Error}
|
|
393
|
-
* @throws {Error}
|
|
394
|
-
* @throws {Error}
|
|
391
|
+
* @throws {Error} When required parameters are missing
|
|
392
|
+
* @throws {Error} When price or orders data cannot be fetched
|
|
393
|
+
* @throws {Error} When a suitable stop loss price cannot be found after reaching the maximum iterations
|
|
394
|
+
* @throws {Error} When the price exceeds the maximum value after adjustment
|
|
395
395
|
*
|
|
396
396
|
* @example
|
|
397
|
-
* //
|
|
397
|
+
* // Basic usage: short 1 token, stop loss price at 103% of the current price
|
|
398
398
|
* const result = await sdk.simulator.simulateShortStopLoss(
|
|
399
399
|
* '4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X', // mint
|
|
400
|
-
* 1000000000n, // 1 token (
|
|
401
|
-
* BigInt('103000000000000000000') //
|
|
400
|
+
* 1000000000n, // 1 token (precision 10^9)
|
|
401
|
+
* BigInt('103000000000000000000') // stop loss price
|
|
402
402
|
* );
|
|
403
403
|
*
|
|
404
|
-
* console.log(
|
|
405
|
-
* console.log(
|
|
406
|
-
* console.log(
|
|
407
|
-
* console.log(
|
|
408
|
-
* console.log(
|
|
404
|
+
* console.log(`Executable stop loss price: ${result.executableStopLossPrice}`);
|
|
405
|
+
* console.log(`Stop loss percentage: ${result.stopLossPercentage}%`);
|
|
406
|
+
* console.log(`Leverage: ${result.leverage}x`);
|
|
407
|
+
* console.log(`Estimated margin: ${result.estimatedMargin} lamports`);
|
|
408
|
+
* console.log(`Insert position indices: ${result.close_insert_indices}`);
|
|
409
409
|
*
|
|
410
410
|
* @example
|
|
411
|
-
* //
|
|
411
|
+
* // Full workflow: simulate then execute a short trade
|
|
412
412
|
* async function openShortPosition(sdk, mint, sellTokenAmount, stopLossPrice) {
|
|
413
|
-
* // 1.
|
|
413
|
+
* // 1. Simulate stop loss calculation
|
|
414
414
|
* const simulation = await sdk.simulator.simulateShortStopLoss(
|
|
415
415
|
* mint,
|
|
416
416
|
* sellTokenAmount,
|
|
417
417
|
* stopLossPrice
|
|
418
418
|
* );
|
|
419
419
|
*
|
|
420
|
-
* // 2.
|
|
420
|
+
* // 2. Check whether the stop loss price was significantly adjusted
|
|
421
421
|
* const priceDiff = Number((simulation.executableStopLossPrice - simulation.originalStopLossPrice) * 10000n / simulation.originalStopLossPrice) / 100;
|
|
422
422
|
* if (priceDiff > 1.0) {
|
|
423
|
-
* console.warn(
|
|
423
|
+
* console.warn(`Stop loss price was adjusted by ${priceDiff}%, existing orders are dense`);
|
|
424
424
|
* }
|
|
425
425
|
*
|
|
426
|
-
* // 3.
|
|
427
|
-
* const minSolOutput = simulation.tradeAmount * 80n / 100n; //
|
|
428
|
-
* const marginSolMax = simulation.estimatedMargin * 115n / 100n; //
|
|
426
|
+
* // 3. Prepare transaction parameters
|
|
427
|
+
* const minSolOutput = simulation.tradeAmount * 80n / 100n; // obtain at least 80%
|
|
428
|
+
* const marginSolMax = simulation.estimatedMargin * 115n / 100n; // add 15% buffer
|
|
429
429
|
*
|
|
430
|
-
* // 4.
|
|
430
|
+
* // 4. Execute the short trade
|
|
431
431
|
* const tx = await sdk.trading.short({
|
|
432
432
|
* mint: mint,
|
|
433
433
|
* borrowSellTokenAmount: sellTokenAmount,
|
|
434
434
|
* minSolOutput: minSolOutput,
|
|
435
435
|
* marginSolMax: marginSolMax,
|
|
436
436
|
* closePrice: simulation.executableStopLossPrice,
|
|
437
|
-
* closeInsertIndices: simulation.close_insert_indices // ⭐
|
|
437
|
+
* closeInsertIndices: simulation.close_insert_indices // ⭐ use the new index array
|
|
438
438
|
* });
|
|
439
439
|
*
|
|
440
440
|
* return tx;
|
|
441
441
|
* }
|
|
442
442
|
*
|
|
443
|
-
* @see {@link simulateLongStopLoss}
|
|
444
|
-
* @see {@link simulateShortSolStopLoss}
|
|
443
|
+
* @see {@link simulateLongStopLoss} Stop loss calculation for long positions
|
|
444
|
+
* @see {@link simulateShortSolStopLoss} SOL-amount-based stop loss calculation for short positions
|
|
445
445
|
* @since 2.0.0
|
|
446
|
-
* @version 2.0.0 -
|
|
446
|
+
* @version 2.0.0 - Changed from returning prev_order_pda/next_order_pda to returning close_insert_indices
|
|
447
447
|
*/
|
|
448
448
|
async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null) {
|
|
449
449
|
try {
|
|
@@ -452,12 +452,12 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
452
452
|
throw new Error('Missing required parameters');
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
-
//
|
|
455
|
+
// If borrowFee or pool parameters are not provided, fetch them from the chain in a single call
|
|
456
456
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
457
457
|
const curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
458
458
|
if (borrowFee === null) borrowFee = curveAccount.borrowFee;
|
|
459
|
-
//
|
|
460
|
-
//
|
|
459
|
+
// The chain returns u64 raw units (lamports/smallest unit), which need to be divided by 10^9 to convert to human-readable units
|
|
460
|
+
// Consistent with the conversion method in calcLiq.js
|
|
461
461
|
if (initialVirtualSol === null) initialVirtualSol = new Decimal(curveAccount.initialVirtualSol.toString()).div(CurveAMM.SOL_PRECISION_FACTOR_DECIMAL).toString();
|
|
462
462
|
if (initialVirtualToken === null) initialVirtualToken = new Decimal(curveAccount.initialVirtualToken.toString()).div(CurveAMM.TOKEN_PRECISION_FACTOR_DECIMAL).toString();
|
|
463
463
|
}
|
|
@@ -508,7 +508,7 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
508
508
|
let finalOverlapResult = null; // Record final overlap result
|
|
509
509
|
let finalTradeAmount = 0n; // Record final trade amount
|
|
510
510
|
|
|
511
|
-
//
|
|
511
|
+
// Check and adjust the stop loss price to meet the minimum distance requirement (short: stop loss price must be above current price by at least MIN_STOP_LOSS_PERCENT)
|
|
512
512
|
// Check and adjust stop loss price to meet minimum distance requirement (short: stop loss must be above current price by at least MIN_STOP_LOSS_PERCENT)
|
|
513
513
|
const minAllowedStopLoss = currentPrice + (currentPrice * BigInt(MIN_STOP_LOSS_PERCENT)) / 1000n;
|
|
514
514
|
if (stopLossStartPrice < minAllowedStopLoss) {
|
|
@@ -543,33 +543,33 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
stopLossEndPrice = tradeResult[0]; // Price after trade completion
|
|
546
|
-
const tradeAmount = tradeResult[1]; // SOL
|
|
546
|
+
const tradeAmount = tradeResult[1]; // SOL input amount
|
|
547
547
|
|
|
548
548
|
// console.log(` - stopLossEndPrice: ${stopLossEndPrice.toString()}`);
|
|
549
549
|
// console.log(` - tradeAmount: ${tradeAmount.toString()}`);
|
|
550
550
|
|
|
551
|
-
//console.log(
|
|
551
|
+
//console.log(`Iteration ${iteration}: startPrice=${stopLossStartPrice}, endPrice=${stopLossEndPrice}, SOL input=${tradeAmount} / Iteration ${iteration}: Start=${stopLossStartPrice}, End=${stopLossEndPrice}, SOL input=${tradeAmount}`);
|
|
552
552
|
|
|
553
|
-
//
|
|
553
|
+
// Check price range overlap
|
|
554
554
|
const overlapResult = checkPriceRangeOverlap('up_orders', upOrders, stopLossStartPrice, stopLossEndPrice);
|
|
555
|
-
|
|
555
|
+
|
|
556
556
|
if (overlapResult.no_overlap) {
|
|
557
557
|
//console.log(' / No price range overlap, can execute');
|
|
558
|
-
finalOverlapResult = overlapResult; //
|
|
559
|
-
finalTradeAmount = tradeAmount; //
|
|
558
|
+
finalOverlapResult = overlapResult; // Record final overlap result
|
|
559
|
+
finalTradeAmount = tradeAmount; // Record final trade amount
|
|
560
560
|
break;
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
-
//console.log(
|
|
563
|
+
//console.log(`Found overlap: ${overlapResult.overlap_reason} / Found overlap: ${overlapResult.overlap_reason}`);
|
|
564
564
|
|
|
565
|
-
//
|
|
566
|
-
//
|
|
565
|
+
// Adjust start price (increase by 0.5%)
|
|
566
|
+
// Using approach 2: directly compute 0.5% = 5/1000
|
|
567
567
|
const adjustmentAmount = (stopLossStartPrice * BigInt(PRICE_ADJUSTMENT_PERCENTAGE)) / 1000n;
|
|
568
568
|
stopLossStartPrice = stopLossStartPrice + adjustmentAmount;
|
|
569
569
|
|
|
570
|
-
//console.log(
|
|
570
|
+
//console.log(`Adjusted start price: ${stopLossStartPrice} / Adjusted start price: ${stopLossStartPrice}`);
|
|
571
571
|
|
|
572
|
-
//
|
|
572
|
+
// Safety check: ensure the price does not exceed the maximum
|
|
573
573
|
if (stopLossStartPrice >= CurveAMM.MAX_U128_PRICE) {
|
|
574
574
|
throw new Error(`Stop loss price exceeded maximum after adjustment: ${stopLossStartPrice} >= ${CurveAMM.MAX_U128_PRICE}`);
|
|
575
575
|
}
|
|
@@ -579,34 +579,34 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
579
579
|
throw new Error('达到最大迭代次数,无法找到合适的止损价格 / Reached maximum iterations, cannot find suitable stop loss price');
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
-
//
|
|
582
|
+
// Calculate final return values
|
|
583
583
|
const executableStopLossPrice = stopLossStartPrice;
|
|
584
|
-
|
|
585
|
-
//
|
|
584
|
+
|
|
585
|
+
// Calculate stop loss percentage
|
|
586
586
|
// For short position, stop loss price is higher than current price, so it's a positive percentage
|
|
587
587
|
const stopLossPercentage = Number((BigInt(10000) * (executableStopLossPrice - currentPrice)) / currentPrice) / 100;
|
|
588
|
-
|
|
589
|
-
//
|
|
588
|
+
|
|
589
|
+
// Calculate leverage ratio
|
|
590
590
|
// For short position, leverage = current price / (stop loss price - current price)
|
|
591
591
|
const leverage = Number((BigInt(10000) * currentPrice) / (executableStopLossPrice - currentPrice)) / 10000;
|
|
592
592
|
|
|
593
|
-
//
|
|
594
|
-
//
|
|
593
|
+
// Calculate margin requirement
|
|
594
|
+
// Consistent with the contract formula (long_short.rs lines 890-894):
|
|
595
595
|
// real_margin_sol = close_buy_sol_with_fee - output_sol - fee_sol
|
|
596
|
-
//
|
|
597
|
-
//
|
|
596
|
+
// where output_sol is the net SOL after fees, and fee_sol is the open fee
|
|
597
|
+
// expanded: real_margin_sol = close_buy_sol_with_fee - raw_sell_sol
|
|
598
598
|
let estimatedMargin = 0n;
|
|
599
|
-
let rawSellSol = 0n; //
|
|
599
|
+
let rawSellSol = 0n; // Raw SOL obtained from selling tokens (before fees), for the caller to compute minSolOutput
|
|
600
600
|
try {
|
|
601
|
-
// 1.
|
|
601
|
+
// 1. Calculate the raw SOL (before fees) obtained from selling tokens at the current price
|
|
602
602
|
const sellResult = CurveAMM.sellFromPriceWithTokenInputWithParams(currentPrice, sellTokenAmount, initialVirtualSol, initialVirtualToken);
|
|
603
603
|
if (sellResult) {
|
|
604
|
-
rawSellSol = sellResult[1]; //
|
|
604
|
+
rawSellSol = sellResult[1]; // Raw SOL obtained from the sale (before fees)
|
|
605
605
|
|
|
606
|
-
// 2.
|
|
606
|
+
// 2. Calculate the close cost (including fees, using ceiling division to match the contract)
|
|
607
607
|
const closeCostWithFee = CurveAMM.calculateTotalAmountWithFee(finalTradeAmount, borrowFee);
|
|
608
608
|
|
|
609
|
-
// 3.
|
|
609
|
+
// 3. Margin = close cost (including fee) - raw sell SOL
|
|
610
610
|
if (closeCostWithFee !== null && closeCostWithFee > rawSellSol) {
|
|
611
611
|
estimatedMargin = closeCostWithFee - rawSellSol;
|
|
612
612
|
}
|
|
@@ -625,7 +625,7 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
625
625
|
|
|
626
626
|
return {
|
|
627
627
|
executableStopLossPrice: executableStopLossPrice, // Calculated reasonable stop loss value
|
|
628
|
-
tradeAmount: finalTradeAmount, // SOL input amount (
|
|
628
|
+
tradeAmount: finalTradeAmount, // SOL input amount (SOL needed to buy back tokens at close)
|
|
629
629
|
stopLossPercentage: stopLossPercentage, // Stop loss percentage relative to current price
|
|
630
630
|
leverage: leverage, // Leverage ratio
|
|
631
631
|
currentPrice: currentPrice, // Current price
|
|
@@ -633,7 +633,7 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
633
633
|
originalStopLossPrice: BigInt(stopLossPrice), // Original stop loss price
|
|
634
634
|
close_insert_indices: finalOverlapResult.close_insert_indices, // Candidate insertion indices for closing order
|
|
635
635
|
estimatedMargin: estimatedMargin, // Estimated margin requirement in SOL (lamports)
|
|
636
|
-
rawSellSol: rawSellSol //
|
|
636
|
+
rawSellSol: rawSellSol // Raw SOL obtained from selling tokens (before fees), used by the caller to compute minSolOutput
|
|
637
637
|
};
|
|
638
638
|
|
|
639
639
|
} catch (error) {
|
|
@@ -653,54 +653,54 @@ async function simulateShortStopLoss(mint, sellTokenAmount, stopLossPrice, lastP
|
|
|
653
653
|
/**
|
|
654
654
|
* Simulate long position stop loss calculation with SOL amount input
|
|
655
655
|
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
656
|
+
* SOL-amount-based stop loss calculation for a long position. This function automatically computes the corresponding token amount,
|
|
657
|
+
* so that the margin requirement is close to the SOL amount provided by the user.
|
|
658
658
|
*
|
|
659
|
-
* @param {string} mint - Token address
|
|
660
|
-
* @param {bigint|string|number} buySolAmount - SOL amount to spend for long position (u64 format, lamports)
|
|
661
|
-
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
662
|
-
* @param {Object|null} lastPrice - Token info, default null
|
|
663
|
-
* @param {Object|null} ordersData - Orders data, default null
|
|
664
|
-
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
659
|
+
* @param {string} mint - Token address
|
|
660
|
+
* @param {bigint|string|number} buySolAmount - SOL amount to spend for long position (u64 format, lamports)
|
|
661
|
+
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
662
|
+
* @param {Object|null} lastPrice - Token info, default null (auto-fetched if null)
|
|
663
|
+
* @param {Object|null} ordersData - Orders data, default null (auto-fetched if null)
|
|
664
|
+
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
665
665
|
*
|
|
666
|
-
* @returns {Promise<Object>} Stop loss analysis result
|
|
667
|
-
* @returns {bigint} returns.executableStopLossPrice -
|
|
668
|
-
* @returns {bigint} returns.tradeAmount -
|
|
669
|
-
* @returns {number} returns.stopLossPercentage -
|
|
670
|
-
* @returns {number} returns.leverage -
|
|
671
|
-
* @returns {bigint} returns.currentPrice -
|
|
672
|
-
* @returns {number} returns.iterations -
|
|
673
|
-
* @returns {bigint} returns.originalStopLossPrice -
|
|
674
|
-
* @returns {number[]} returns.close_insert_indices -
|
|
675
|
-
* @returns {bigint} returns.estimatedMargin -
|
|
676
|
-
* @returns {bigint} returns.buyTokenAmount -
|
|
677
|
-
* -
|
|
678
|
-
* -
|
|
679
|
-
* -
|
|
680
|
-
* @returns {number} returns.adjustmentIterations -
|
|
681
|
-
* -
|
|
682
|
-
* -
|
|
666
|
+
* @returns {Promise<Object>} Stop loss analysis result
|
|
667
|
+
* @returns {bigint} returns.executableStopLossPrice - Executable stop loss price (u128 format) - same as {@link simulateLongStopLoss}
|
|
668
|
+
* @returns {bigint} returns.tradeAmount - Estimated SOL amount obtained from selling at stop loss (lamports) - same as {@link simulateLongStopLoss}
|
|
669
|
+
* @returns {number} returns.stopLossPercentage - Stop loss percentage - same as {@link simulateLongStopLoss}
|
|
670
|
+
* @returns {number} returns.leverage - Leverage ratio - same as {@link simulateLongStopLoss}
|
|
671
|
+
* @returns {bigint} returns.currentPrice - Current price (u128 format) - same as {@link simulateLongStopLoss}
|
|
672
|
+
* @returns {number} returns.iterations - Number of price adjustment iterations - same as {@link simulateLongStopLoss}
|
|
673
|
+
* @returns {bigint} returns.originalStopLossPrice - Original stop loss price (u128 format) - same as {@link simulateLongStopLoss}
|
|
674
|
+
* @returns {number[]} returns.close_insert_indices - Candidate index array for the closing order insertion position ⭐ - same as {@link simulateLongStopLoss}
|
|
675
|
+
* @returns {bigint} returns.estimatedMargin - Estimated required margin (SOL lamports) - same as {@link simulateLongStopLoss}
|
|
676
|
+
* @returns {bigint} returns.buyTokenAmount - Calculated token amount to buy ⭐ Additional field
|
|
677
|
+
* - This is the token amount reverse-calculated from buySolAmount
|
|
678
|
+
* - Such that estimatedMargin is close to buySolAmount
|
|
679
|
+
* - Can be used directly as the buyTokenAmount parameter of sdk.trading.long()
|
|
680
|
+
* @returns {number} returns.adjustmentIterations - Number of token amount adjustment iterations ⭐ Additional field
|
|
681
|
+
* - The number of iterations the binary search algorithm used to adjust the token amount
|
|
682
|
+
* - Used to assess calculation precision
|
|
683
683
|
*
|
|
684
|
-
* @throws {Error}
|
|
685
|
-
* @throws {Error}
|
|
686
|
-
* @throws {Error}
|
|
684
|
+
* @throws {Error} When required parameters are missing
|
|
685
|
+
* @throws {Error} When price or orders data cannot be fetched
|
|
686
|
+
* @throws {Error} When the token amount cannot be calculated
|
|
687
687
|
*
|
|
688
688
|
* @example
|
|
689
|
-
* //
|
|
689
|
+
* // Basic usage: spend 0.1 SOL to go long, stop loss price at 97% of the current price
|
|
690
690
|
* const result = await sdk.simulator.simulateLongSolStopLoss(
|
|
691
691
|
* '4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X', // mint
|
|
692
|
-
* 100000000n, // 0.1 SOL (
|
|
693
|
-
* BigInt('97000000000000000000') //
|
|
692
|
+
* 100000000n, // 0.1 SOL (precision 10^9)
|
|
693
|
+
* BigInt('97000000000000000000') // stop loss price
|
|
694
694
|
* );
|
|
695
695
|
*
|
|
696
|
-
* console.log(
|
|
697
|
-
* console.log(
|
|
698
|
-
* console.log(
|
|
696
|
+
* console.log(`Token amount to buy: ${result.buyTokenAmount}`);
|
|
697
|
+
* console.log(`Estimated margin: ${result.estimatedMargin} lamports`);
|
|
698
|
+
* console.log(`Insert position indices: ${result.close_insert_indices}`);
|
|
699
699
|
*
|
|
700
|
-
* @see {@link simulateLongStopLoss}
|
|
701
|
-
* @see {@link simulateShortSolStopLoss}
|
|
700
|
+
* @see {@link simulateLongStopLoss} Token-amount-based stop loss calculation for long positions
|
|
701
|
+
* @see {@link simulateShortSolStopLoss} SOL-amount-based stop loss calculation for short positions
|
|
702
702
|
* @since 2.0.0
|
|
703
|
-
* @version 2.0.0 -
|
|
703
|
+
* @version 2.0.0 - Changed from returning prev_order_pda/next_order_pda to returning close_insert_indices
|
|
704
704
|
*/
|
|
705
705
|
async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
|
|
706
706
|
try {
|
|
@@ -709,7 +709,7 @@ async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPr
|
|
|
709
709
|
throw new Error('Missing required parameters');
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
-
//
|
|
712
|
+
// If borrowFee or pool parameters are not provided, fetch them from the chain in a single call (supports passing in curveAccount externally to avoid duplicate RPC)
|
|
713
713
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
714
714
|
if (!curveAccount) {
|
|
715
715
|
curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
@@ -750,70 +750,70 @@ async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPr
|
|
|
750
750
|
let iterations = 0;
|
|
751
751
|
const maxIterations = 50;
|
|
752
752
|
|
|
753
|
-
//
|
|
754
|
-
//
|
|
753
|
+
// Dynamically compute the binary search upper bound based on the leverage ratio
|
|
754
|
+
// At high leverage (e.g. 20x) the stop loss distance is small, each token contributes little margin, so more tokens are needed to consume the full margin
|
|
755
755
|
// Calculate dynamic binary search upper bound based on leverage
|
|
756
756
|
const stopLossPriceBigInt = BigInt(stopLossPrice);
|
|
757
757
|
const priceDiff = currentPrice - stopLossPriceBigInt;
|
|
758
758
|
const estimatedLeverage = priceDiff > 0n ? Number(currentPrice * 10000n / priceDiff) / 10000 : 10;
|
|
759
|
-
const safeMultiplier = BigInt(Math.ceil(estimatedLeverage * 3)); //
|
|
760
|
-
const multiplier = safeMultiplier > 10n ? safeMultiplier : 10n; //
|
|
759
|
+
const safeMultiplier = BigInt(Math.ceil(estimatedLeverage * 3)); // 3x safety factor
|
|
760
|
+
const multiplier = safeMultiplier > 10n ? safeMultiplier : 10n; // minimum 10x
|
|
761
761
|
|
|
762
|
-
//
|
|
762
|
+
// Use a binary search algorithm to find the maximum estimatedMargin that is less than buySolAmount
|
|
763
763
|
// Use binary search algorithm to find maximum estimatedMargin that is less than buySolAmount
|
|
764
|
-
let left = 1n; //
|
|
765
|
-
let right = buyTokenAmount * multiplier; //
|
|
764
|
+
let left = 1n; // minimum value, ensuring a valid lower bound
|
|
765
|
+
let right = buyTokenAmount * multiplier; // upper bound: dynamically computed based on leverage
|
|
766
766
|
let bestResult = null;
|
|
767
|
-
let bestMargin = 0n; //
|
|
767
|
+
let bestMargin = 0n; // record the maximum valid estimatedMargin
|
|
768
768
|
let bestTokenAmount = buyTokenAmount;
|
|
769
|
-
|
|
770
|
-
//
|
|
769
|
+
|
|
770
|
+
// Binary search main loop: find the maximum estimatedMargin that is less than buySolAmount
|
|
771
771
|
while (iterations < maxIterations && left <= right) {
|
|
772
772
|
const mid = (left + right) / 2n;
|
|
773
|
-
|
|
774
|
-
//
|
|
773
|
+
|
|
774
|
+
// Calculate the result for the current token amount
|
|
775
775
|
const currentResult = await simulateLongStopLoss.call(this, mint, mid, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken);
|
|
776
776
|
const currentMargin = currentResult.estimatedMargin;
|
|
777
|
-
|
|
777
|
+
|
|
778
778
|
//console.log(`Binary search iteration ${iterations}: tokenAmount=${mid}, estimatedMargin=${currentMargin}, target=${buySolAmount}`);
|
|
779
|
-
|
|
780
|
-
//
|
|
779
|
+
|
|
780
|
+
// Only consider the case where estimatedMargin < buySolAmount
|
|
781
781
|
if (currentMargin < BigInt(buySolAmount)) {
|
|
782
|
-
//
|
|
782
|
+
// This is a valid solution, check whether it is better than the current best solution
|
|
783
783
|
if (currentMargin > bestMargin) {
|
|
784
784
|
bestMargin = currentMargin;
|
|
785
785
|
bestResult = currentResult;
|
|
786
786
|
bestTokenAmount = mid;
|
|
787
787
|
//console.log(`Found better solution: estimatedMargin=${currentMargin}, tokenAmount=${mid}`);
|
|
788
788
|
}
|
|
789
|
-
|
|
790
|
-
//
|
|
789
|
+
|
|
790
|
+
// If the gap is already very small (within 10000000 lamports of the target), we can exit early
|
|
791
791
|
if (BigInt(buySolAmount) - currentMargin <= 10000000n) {
|
|
792
792
|
//console.log(`Found optimal solution: estimatedMargin=${currentMargin}, diff=${BigInt(buySolAmount) - currentMargin} (< 10000000 lamports tolerance)`);
|
|
793
793
|
break;
|
|
794
794
|
}
|
|
795
|
-
|
|
796
|
-
//
|
|
795
|
+
|
|
796
|
+
// Continue searching to the right for a larger valid value
|
|
797
797
|
left = mid + 1n;
|
|
798
798
|
} else {
|
|
799
|
-
// estimatedMargin >= buySolAmount
|
|
799
|
+
// estimatedMargin >= buySolAmount, need to reduce tokenAmount
|
|
800
800
|
//console.log(`estimatedMargin too large (${currentMargin} >= ${buySolAmount}), searching left`);
|
|
801
801
|
right = mid - 1n;
|
|
802
802
|
}
|
|
803
|
-
|
|
803
|
+
|
|
804
804
|
iterations++;
|
|
805
805
|
}
|
|
806
|
-
|
|
807
|
-
//
|
|
806
|
+
|
|
807
|
+
// Ensure the found result meets the requirement
|
|
808
808
|
if (bestResult && bestMargin < BigInt(buySolAmount)) {
|
|
809
809
|
stopLossResult = bestResult;
|
|
810
810
|
buyTokenAmount = bestTokenAmount;
|
|
811
811
|
//console.log(`Binary search completed: best tokenAmount=${bestTokenAmount}, estimatedMargin=${bestMargin}, target=${buySolAmount}`);
|
|
812
812
|
} else {
|
|
813
|
-
//
|
|
813
|
+
// If no valid solution is found, use a very small tokenAmount as a safe fallback
|
|
814
814
|
//console.log(`No valid solution found (estimatedMargin < buySolAmount), using minimal tokenAmount`);
|
|
815
|
-
buyTokenAmount = buyTokenAmount / 10n; //
|
|
816
|
-
if (buyTokenAmount <= 0n) buyTokenAmount = 1000000000n; //
|
|
815
|
+
buyTokenAmount = buyTokenAmount / 10n; // use a smaller value
|
|
816
|
+
if (buyTokenAmount <= 0n) buyTokenAmount = 1000000000n; // minimum value protection (0.001 token with 9 decimals)
|
|
817
817
|
stopLossResult = await simulateLongStopLoss.call(this, mint, buyTokenAmount, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken);
|
|
818
818
|
}
|
|
819
819
|
|
|
@@ -837,54 +837,54 @@ async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPr
|
|
|
837
837
|
/**
|
|
838
838
|
* Simulate short position stop loss calculation with SOL amount input
|
|
839
839
|
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
840
|
+
* SOL-amount-based stop loss calculation for a short position. This function automatically computes the corresponding token amount,
|
|
841
|
+
* so that the margin requirement is close to the SOL amount provided by the user.
|
|
842
842
|
*
|
|
843
|
-
* @param {string} mint - Token address
|
|
844
|
-
* @param {bigint|string|number} sellSolAmount - SOL amount needed for short position stop loss (u64 format, lamports)
|
|
845
|
-
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
846
|
-
* @param {Object|null} lastPrice - Token info, default null
|
|
847
|
-
* @param {Object|null} ordersData - Orders data, default null
|
|
848
|
-
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
843
|
+
* @param {string} mint - Token address
|
|
844
|
+
* @param {bigint|string|number} sellSolAmount - SOL amount needed for short position stop loss (u64 format, lamports)
|
|
845
|
+
* @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
|
|
846
|
+
* @param {Object|null} lastPrice - Token info, default null (auto-fetched if null)
|
|
847
|
+
* @param {Object|null} ordersData - Orders data, default null (auto-fetched if null)
|
|
848
|
+
* @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
|
|
849
849
|
*
|
|
850
|
-
* @returns {Promise<Object>} Stop loss analysis result
|
|
851
|
-
* @returns {bigint} returns.executableStopLossPrice -
|
|
852
|
-
* @returns {bigint} returns.tradeAmount -
|
|
853
|
-
* @returns {number} returns.stopLossPercentage -
|
|
854
|
-
* @returns {number} returns.leverage -
|
|
855
|
-
* @returns {bigint} returns.currentPrice -
|
|
856
|
-
* @returns {number} returns.iterations -
|
|
857
|
-
* @returns {bigint} returns.originalStopLossPrice -
|
|
858
|
-
* @returns {number[]} returns.close_insert_indices -
|
|
859
|
-
* @returns {bigint} returns.estimatedMargin -
|
|
860
|
-
* @returns {bigint} returns.sellTokenAmount -
|
|
861
|
-
* -
|
|
862
|
-
* -
|
|
863
|
-
* -
|
|
864
|
-
* @returns {number} returns.adjustmentIterations -
|
|
865
|
-
* -
|
|
866
|
-
* -
|
|
850
|
+
* @returns {Promise<Object>} Stop loss analysis result
|
|
851
|
+
* @returns {bigint} returns.executableStopLossPrice - Executable stop loss price (u128 format) - same as {@link simulateShortStopLoss}
|
|
852
|
+
* @returns {bigint} returns.tradeAmount - Estimated SOL amount needed to buy at stop loss (lamports) - same as {@link simulateShortStopLoss}
|
|
853
|
+
* @returns {number} returns.stopLossPercentage - Stop loss percentage - same as {@link simulateShortStopLoss}
|
|
854
|
+
* @returns {number} returns.leverage - Leverage ratio - same as {@link simulateShortStopLoss}
|
|
855
|
+
* @returns {bigint} returns.currentPrice - Current price (u128 format) - same as {@link simulateShortStopLoss}
|
|
856
|
+
* @returns {number} returns.iterations - Number of price adjustment iterations - same as {@link simulateShortStopLoss}
|
|
857
|
+
* @returns {bigint} returns.originalStopLossPrice - Original stop loss price (u128 format) - same as {@link simulateShortStopLoss}
|
|
858
|
+
* @returns {number[]} returns.close_insert_indices - Candidate index array for the closing order insertion position ⭐ - same as {@link simulateShortStopLoss}
|
|
859
|
+
* @returns {bigint} returns.estimatedMargin - Estimated required margin (SOL lamports) - same as {@link simulateShortStopLoss}
|
|
860
|
+
* @returns {bigint} returns.sellTokenAmount - Calculated token amount to sell ⭐ Additional field
|
|
861
|
+
* - This is the token amount reverse-calculated from sellSolAmount
|
|
862
|
+
* - Such that estimatedMargin is close to sellSolAmount
|
|
863
|
+
* - Can be used directly as the borrowSellTokenAmount parameter of sdk.trading.short()
|
|
864
|
+
* @returns {number} returns.adjustmentIterations - Number of token amount adjustment iterations ⭐ Additional field
|
|
865
|
+
* - The number of iterations the binary search algorithm used to adjust the token amount
|
|
866
|
+
* - Used to assess calculation precision
|
|
867
867
|
*
|
|
868
|
-
* @throws {Error}
|
|
869
|
-
* @throws {Error}
|
|
870
|
-
* @throws {Error}
|
|
868
|
+
* @throws {Error} When required parameters are missing
|
|
869
|
+
* @throws {Error} When price or orders data cannot be fetched
|
|
870
|
+
* @throws {Error} When the token amount cannot be calculated
|
|
871
871
|
*
|
|
872
872
|
* @example
|
|
873
|
-
* //
|
|
873
|
+
* // Basic usage: spend 0.1 SOL to go short, stop loss price at 103% of the current price
|
|
874
874
|
* const result = await sdk.simulator.simulateShortSolStopLoss(
|
|
875
875
|
* '4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X', // mint
|
|
876
|
-
* 100000000n, // 0.1 SOL (
|
|
877
|
-
* BigInt('103000000000000000000') //
|
|
876
|
+
* 100000000n, // 0.1 SOL (precision 10^9)
|
|
877
|
+
* BigInt('103000000000000000000') // stop loss price
|
|
878
878
|
* );
|
|
879
879
|
*
|
|
880
|
-
* console.log(
|
|
881
|
-
* console.log(
|
|
882
|
-
* console.log(
|
|
880
|
+
* console.log(`Token amount to sell: ${result.sellTokenAmount}`);
|
|
881
|
+
* console.log(`Estimated margin: ${result.estimatedMargin} lamports`);
|
|
882
|
+
* console.log(`Insert position indices: ${result.close_insert_indices}`);
|
|
883
883
|
*
|
|
884
|
-
* @see {@link simulateShortStopLoss}
|
|
885
|
-
* @see {@link simulateLongSolStopLoss}
|
|
884
|
+
* @see {@link simulateShortStopLoss} Token-amount-based stop loss calculation for short positions
|
|
885
|
+
* @see {@link simulateLongSolStopLoss} SOL-amount-based stop loss calculation for long positions
|
|
886
886
|
* @since 2.0.0
|
|
887
|
-
* @version 2.0.0 -
|
|
887
|
+
* @version 2.0.0 - Changed from returning prev_order_pda/next_order_pda to returning close_insert_indices
|
|
888
888
|
*/
|
|
889
889
|
async function simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = null, initialVirtualSol = null, initialVirtualToken = null, curveAccount = null) {
|
|
890
890
|
try {
|
|
@@ -893,7 +893,7 @@ async function simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, last
|
|
|
893
893
|
throw new Error('Missing required parameters');
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
-
//
|
|
896
|
+
// If borrowFee or pool parameters are not provided, fetch them from the chain in a single call (supports passing in curveAccount externally to avoid duplicate RPC)
|
|
897
897
|
if (borrowFee === null || initialVirtualSol === null || initialVirtualToken === null) {
|
|
898
898
|
if (!curveAccount) {
|
|
899
899
|
curveAccount = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
|
|
@@ -936,70 +936,70 @@ async function simulateShortSolStopLoss(mint, sellSolAmount, stopLossPrice, last
|
|
|
936
936
|
let iterations = 0;
|
|
937
937
|
const maxIterations = 50;
|
|
938
938
|
|
|
939
|
-
//
|
|
940
|
-
//
|
|
939
|
+
// Dynamically compute the binary search upper bound based on the leverage ratio
|
|
940
|
+
// At high leverage (e.g. 20x) the stop loss distance is small, each token contributes little margin, so more tokens are needed to consume the full margin
|
|
941
941
|
// Calculate dynamic binary search upper bound based on leverage
|
|
942
942
|
const stopLossPriceBigInt = BigInt(stopLossPrice);
|
|
943
943
|
const priceDiff = stopLossPriceBigInt - currentPrice;
|
|
944
944
|
const estimatedLeverage = priceDiff > 0n ? Number(currentPrice * 10000n / priceDiff) / 10000 : 10;
|
|
945
|
-
const safeMultiplier = BigInt(Math.ceil(estimatedLeverage * 3)); //
|
|
946
|
-
const multiplier = safeMultiplier > 10n ? safeMultiplier : 10n; //
|
|
945
|
+
const safeMultiplier = BigInt(Math.ceil(estimatedLeverage * 3)); // 3x safety factor
|
|
946
|
+
const multiplier = safeMultiplier > 10n ? safeMultiplier : 10n; // minimum 10x
|
|
947
947
|
|
|
948
|
-
//
|
|
948
|
+
// Use a binary search algorithm to find the maximum estimatedMargin that is less than sellSolAmount
|
|
949
949
|
// Use binary search algorithm to find maximum estimatedMargin that is less than sellSolAmount
|
|
950
|
-
let left = 1n; //
|
|
951
|
-
let right = sellTokenAmount * multiplier; //
|
|
950
|
+
let left = 1n; // minimum value, ensuring a valid lower bound
|
|
951
|
+
let right = sellTokenAmount * multiplier; // upper bound: dynamically computed based on leverage
|
|
952
952
|
let bestResult = null;
|
|
953
|
-
let bestMargin = 0n; //
|
|
953
|
+
let bestMargin = 0n; // record the maximum valid estimatedMargin
|
|
954
954
|
let bestTokenAmount = sellTokenAmount;
|
|
955
|
-
|
|
956
|
-
//
|
|
955
|
+
|
|
956
|
+
// Binary search main loop: find the maximum estimatedMargin that is less than sellSolAmount
|
|
957
957
|
while (iterations < maxIterations && left <= right) {
|
|
958
958
|
const mid = (left + right) / 2n;
|
|
959
|
-
|
|
960
|
-
//
|
|
959
|
+
|
|
960
|
+
// Calculate the result for the current token amount
|
|
961
961
|
const currentResult = await simulateShortStopLoss.call(this, mint, mid, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken);
|
|
962
962
|
const currentMargin = currentResult.estimatedMargin;
|
|
963
|
-
|
|
963
|
+
|
|
964
964
|
//console.log(`Binary search iteration ${iterations}: tokenAmount=${mid}, estimatedMargin=${currentMargin}, target=${sellSolAmount}`);
|
|
965
|
-
|
|
966
|
-
//
|
|
965
|
+
|
|
966
|
+
// Only consider the case where estimatedMargin < sellSolAmount
|
|
967
967
|
if (currentMargin < BigInt(sellSolAmount)) {
|
|
968
|
-
//
|
|
968
|
+
// This is a valid solution, check whether it is better than the current best solution
|
|
969
969
|
if (currentMargin > bestMargin) {
|
|
970
970
|
bestMargin = currentMargin;
|
|
971
971
|
bestResult = currentResult;
|
|
972
972
|
bestTokenAmount = mid;
|
|
973
973
|
//console.log(`Found better solution: estimatedMargin=${currentMargin}, tokenAmount=${mid}`);
|
|
974
974
|
}
|
|
975
|
-
|
|
976
|
-
//
|
|
975
|
+
|
|
976
|
+
// If the gap is already very small (within 10000000 lamports of the target), we can exit early
|
|
977
977
|
if (BigInt(sellSolAmount) - currentMargin <= 10000000n) {
|
|
978
978
|
//console.log(`Found optimal solution: estimatedMargin=${currentMargin}, diff=${BigInt(sellSolAmount) - currentMargin} (< 10000000 lamports tolerance)`);
|
|
979
979
|
break;
|
|
980
980
|
}
|
|
981
|
-
|
|
982
|
-
//
|
|
981
|
+
|
|
982
|
+
// Continue searching to the right for a larger valid value
|
|
983
983
|
left = mid + 1n;
|
|
984
984
|
} else {
|
|
985
|
-
// estimatedMargin >= sellSolAmount
|
|
985
|
+
// estimatedMargin >= sellSolAmount, need to reduce tokenAmount
|
|
986
986
|
//console.log(`estimatedMargin too large (${currentMargin} >= ${sellSolAmount}), searching left`);
|
|
987
987
|
right = mid - 1n;
|
|
988
988
|
}
|
|
989
|
-
|
|
989
|
+
|
|
990
990
|
iterations++;
|
|
991
991
|
}
|
|
992
|
-
|
|
993
|
-
//
|
|
992
|
+
|
|
993
|
+
// Ensure the found result meets the requirement
|
|
994
994
|
if (bestResult && bestMargin < BigInt(sellSolAmount)) {
|
|
995
995
|
stopLossResult = bestResult;
|
|
996
996
|
sellTokenAmount = bestTokenAmount;
|
|
997
997
|
//console.log(`Binary search completed: best tokenAmount=${bestTokenAmount}, estimatedMargin=${bestMargin}, target=${sellSolAmount}`);
|
|
998
998
|
} else {
|
|
999
|
-
//
|
|
999
|
+
// If no valid solution is found, use a very small tokenAmount as a safe fallback
|
|
1000
1000
|
//console.log(`No valid solution found (estimatedMargin < sellSolAmount), using minimal tokenAmount`);
|
|
1001
|
-
sellTokenAmount = sellTokenAmount / 10n; //
|
|
1002
|
-
if (sellTokenAmount <= 0n) sellTokenAmount = 1000000000n; //
|
|
1001
|
+
sellTokenAmount = sellTokenAmount / 10n; // use a smaller value
|
|
1002
|
+
if (sellTokenAmount <= 0n) sellTokenAmount = 1000000000n; // minimum value protection (0.001 token with 9 decimals)
|
|
1003
1003
|
stopLossResult = await simulateShortStopLoss.call(this, mint, sellTokenAmount, stopLossPrice, lastPrice, ordersData, borrowFee, initialVirtualSol, initialVirtualToken);
|
|
1004
1004
|
}
|
|
1005
1005
|
|