pinpet-sdk 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,602 @@
1
+
2
+ const CurveAMM = require('../../utils/curve_amm');
3
+ const {transformOrdersData , checkPriceRangeOverlap} = require('./stop_loss_utils')
4
+ const { PRICE_ADJUSTMENT_PERCENTAGE } = require('./utils');
5
+
6
+ /**
7
+ * Simulate long position stop loss calculation
8
+ * @param {string} mint - Token address
9
+ * @param {bigint|string|number} buyTokenAmount - Token amount to buy for long position (u64 format, precision 10^6)
10
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
11
+ * @param {Object|null} lastPrice - Token info, default null
12
+ * @param {Object|null} ordersData - Orders data, default null
13
+ * @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
14
+ * @returns {Promise<Object>} Stop loss analysis result
15
+ */
16
+ async function simulateLongStopLoss(mint, buyTokenAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = 2000) {
17
+ try {
18
+ // Parameter validation
19
+ if (!mint || !buyTokenAmount || !stopLossPrice) {
20
+ throw new Error('Missing required parameters');
21
+ }
22
+
23
+ // Get current price
24
+ if (!lastPrice) {
25
+ //console.log('Getting current price...');
26
+ lastPrice = await this.sdk.data.price(mint);
27
+ if (!lastPrice) {
28
+ throw new Error('Failed to get current price');
29
+ }
30
+ }
31
+
32
+ // Get ordersData
33
+ if (!ordersData) {
34
+ //console.log('Getting orders data...');
35
+ ordersData = await this.sdk.data.orders(mint, { type: 'down_orders' });
36
+ if (!ordersData || !ordersData.success) {
37
+ throw new Error('Failed to get orders data');
38
+ }
39
+ }
40
+
41
+ // Calculate current price
42
+ let currentPrice;
43
+ if (lastPrice === null || lastPrice === undefined || lastPrice === '0') {
44
+ console.log('Current price is empty, using initial price');
45
+ currentPrice = CurveAMM.getInitialPrice();
46
+ } else {
47
+ currentPrice = BigInt(lastPrice);
48
+ if (!currentPrice || currentPrice === 0n) {
49
+ console.log('Current price is 0, using initial price');
50
+ currentPrice = CurveAMM.getInitialPrice();
51
+ }
52
+ }
53
+
54
+ // Transform orders data
55
+ const downOrders = transformOrdersData(ordersData);
56
+ //console.log(`Found ${downOrders.length} existing long orders`);
57
+
58
+ // Initialize stop loss prices
59
+ let stopLossStartPrice = BigInt(stopLossPrice);
60
+ let stopLossEndPrice;
61
+ let maxIterations = 1000; // Prevent infinite loop
62
+ let iteration = 0;
63
+ let finalOverlapResult = null; // Record final overlap result
64
+ let finalTradeAmount = 0n; // Record final trade amount
65
+
66
+ //console.log(`Start price: ${stopLossStartPrice}, Target token amount: ${buyTokenAmount}`);
67
+
68
+ // Loop to adjust stop loss price until no overlap
69
+ while (iteration < maxIterations) {
70
+ iteration++;
71
+
72
+ // Calculate stop loss end price
73
+ //console.log('Current stop loss start price:', stopLossStartPrice.toString());
74
+ const tradeResult = CurveAMM.sellFromPriceWithTokenInput(stopLossStartPrice, buyTokenAmount);
75
+ if (!tradeResult) {
76
+ throw new Error('Failed to calculate stop loss end price');
77
+ }
78
+
79
+ stopLossEndPrice = tradeResult[0]; // Price after trade completion
80
+ const tradeAmount = tradeResult[1]; // SOL输出量 / SOL output amount
81
+
82
+ //console.log(`迭代 ${iteration}: 起始价格=${stopLossStartPrice}, 结束价格=${stopLossEndPrice}, SOL输出量=${tradeAmount} / Iteration ${iteration}: Start=${stopLossStartPrice}, End=${stopLossEndPrice}, SOL output=${tradeAmount}`);
83
+
84
+ // 检查价格区间重叠 / Check price range overlap
85
+ const overlapResult = checkPriceRangeOverlap('down_orders', downOrders, stopLossStartPrice, stopLossEndPrice);
86
+
87
+ if (overlapResult.no_overlap) {
88
+ //console.log('价格区间无重叠,可以执行 / No price range overlap, can execute');
89
+ finalOverlapResult = overlapResult; // 记录最终的overlap结果 / Record final overlap result
90
+ finalTradeAmount = tradeAmount; // 记录最终的交易金额 / Record final trade amount
91
+ break;
92
+ }
93
+
94
+ //console.log(`发现重叠: ${overlapResult.overlap_reason} / Found overlap: ${overlapResult.overlap_reason}`);
95
+
96
+ // 调整起始价格(减少0.5%)/ Adjust start price (decrease by 0.5%)
97
+ // 使用方案2:直接计算 0.5% = 5/1000
98
+ const adjustmentAmount = (stopLossStartPrice * BigInt(PRICE_ADJUSTMENT_PERCENTAGE)) / 1000n;
99
+ stopLossStartPrice = stopLossStartPrice - adjustmentAmount;
100
+
101
+ //console.log(`调整后起始价格: ${stopLossStartPrice} / Adjusted start price: ${stopLossStartPrice}`);
102
+
103
+ // 安全检查:确保价格不会变成负数 / Safety check: ensure price doesn't become negative
104
+ if (stopLossStartPrice <= 0n) {
105
+ throw new Error('止损价格调整后变为负数,无法继续 / Stop loss price became negative after adjustment');
106
+ }
107
+ }
108
+
109
+ if (iteration >= maxIterations) {
110
+ throw new Error('达到最大迭代次数,无法找到合适的止损价格 / Reached maximum iterations, cannot find suitable stop loss price');
111
+ }
112
+
113
+ // 计算最终返回值 / Calculate final return values
114
+ const executableStopLossPrice = stopLossStartPrice;
115
+
116
+ // 计算止损百分比 / Calculate stop loss percentage
117
+ let stopLossPercentage = 0;
118
+ let leverage = 1;
119
+
120
+ if (currentPrice !== executableStopLossPrice) {
121
+ stopLossPercentage = Number((BigInt(10000) * (currentPrice - executableStopLossPrice)) / currentPrice) / 100;
122
+ leverage = Number((BigInt(10000) * currentPrice) / (currentPrice - executableStopLossPrice)) / 10000;
123
+ }
124
+
125
+ // 计算保证金 / Calculate margin requirement
126
+ let estimatedMargin = 0n;
127
+ try {
128
+ // 1. 计算从当前价格买入所需的SOL
129
+ const buyResult = CurveAMM.buyFromPriceWithTokenOutput(currentPrice, buyTokenAmount);
130
+ if (buyResult) {
131
+ const requiredSol = buyResult[1]; // SOL input amount
132
+
133
+ // 2. 计算平仓时扣除手续费后的收益
134
+ const closeOutputSolAfterFee = CurveAMM.calculateAmountAfterFee(finalTradeAmount, borrowFee);
135
+
136
+ // 3. 计算保证金 = 买入成本 - 平仓收益(扣费后)
137
+ if (closeOutputSolAfterFee !== null && requiredSol > closeOutputSolAfterFee) {
138
+ estimatedMargin = requiredSol - closeOutputSolAfterFee;
139
+ }
140
+ }
141
+ } catch (marginError) {
142
+ console.warn('Failed to calculate estimated margin:', marginError.message);
143
+ // Keep estimatedMargin as 0n
144
+ }
145
+
146
+ // console.log(`Calculation completed:`);
147
+ // console.log(` Executable stop loss price: ${executableStopLossPrice}`);
148
+ // console.log(` SOL output amount: ${finalTradeAmount}`);
149
+ // console.log(` Stop loss percentage: ${stopLossPercentage}%`);
150
+ // console.log(` Leverage: ${leverage}x`);
151
+ // console.log(` Previous order PDA: ${finalOverlapResult.prev_order_pda}`);
152
+ // console.log(` Next order PDA: ${finalOverlapResult.next_order_pda}`);
153
+
154
+ return {
155
+ executableStopLossPrice: executableStopLossPrice, // Calculated reasonable stop loss value
156
+ tradeAmount: finalTradeAmount, // SOL output amount
157
+ stopLossPercentage: stopLossPercentage, // Stop loss percentage relative to current price
158
+ leverage: leverage, // Leverage ratio
159
+ currentPrice: currentPrice, // Current price
160
+ iterations: iteration, // Number of adjustments
161
+ originalStopLossPrice: BigInt(stopLossPrice), // Original stop loss price
162
+ prev_order_pda: finalOverlapResult.prev_order_pda, // Previous order PDA
163
+ next_order_pda: finalOverlapResult.next_order_pda, // Next order PDA
164
+ estimatedMargin: estimatedMargin // Estimated margin requirement in SOL (lamports)
165
+ };
166
+
167
+ } catch (error) {
168
+ console.error('Failed to simulate stop loss calculation:', error.message);
169
+ throw error;
170
+ }
171
+ }
172
+
173
+
174
+ /**
175
+ * Simulate short position stop loss calculation
176
+ * @param {string} mint - Token address
177
+ * @param {bigint|string|number} sellTokenAmount - Token amount to sell for short position (u64 format, precision 10^6)
178
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
179
+ * @param {Object|null} lastPrice - Token info, default null
180
+ * @param {Object|null} ordersData - Orders data, default null
181
+ * @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
182
+ * @returns {Promise<Object>} Stop loss analysis result
183
+ */
184
+ async function simulateSellStopLoss(mint, sellTokenAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = 2000) {
185
+ try {
186
+ // Parameter validation
187
+ if (!mint || !sellTokenAmount || !stopLossPrice) {
188
+ throw new Error('Missing required parameters');
189
+ }
190
+
191
+ // Get current price
192
+ if (!lastPrice) {
193
+ //console.log('Getting current price...');
194
+ lastPrice = await this.sdk.data.price(mint);
195
+ if (!lastPrice) {
196
+ throw new Error('Failed to get current price');
197
+ }
198
+ }
199
+
200
+ // Get ordersData
201
+ if (!ordersData) {
202
+ //console.log('Getting orders data...');
203
+ ordersData = await this.sdk.data.orders(mint, { type: 'up_orders' });
204
+ if (!ordersData || !ordersData.success) {
205
+ throw new Error('Failed to get orders data');
206
+ }
207
+ }
208
+
209
+ // Calculate current price
210
+ let currentPrice;
211
+ if (lastPrice === null || lastPrice === undefined || lastPrice === '0') {
212
+ console.log('Current price is empty, using initial price');
213
+ currentPrice = CurveAMM.getInitialPrice();
214
+ } else {
215
+ currentPrice = BigInt(lastPrice);
216
+ if (!currentPrice || currentPrice === 0n) {
217
+ console.log('Current price is 0, using initial price');
218
+ currentPrice = CurveAMM.getInitialPrice();
219
+ }
220
+ }
221
+
222
+ // Transform orders data
223
+ const upOrders = transformOrdersData(ordersData);
224
+ //console.log(`Found ${upOrders.length} existing short orders`);
225
+
226
+ // Initialize stop loss prices
227
+ let stopLossStartPrice = BigInt(stopLossPrice);
228
+ let stopLossEndPrice;
229
+ let maxIterations = 1000; // Prevent infinite loop
230
+ let iteration = 0;
231
+ let finalOverlapResult = null; // Record final overlap result
232
+ let finalTradeAmount = 0n; // Record final trade amount
233
+
234
+ //console.log(`Start price: ${stopLossStartPrice}, Target token amount: ${sellTokenAmount}`);
235
+
236
+ // Loop to adjust stop loss price until no overlap
237
+ while (iteration < maxIterations) {
238
+ iteration++;
239
+
240
+ // Calculate stop loss end price
241
+ const tradeResult = CurveAMM.buyFromPriceWithTokenOutput(stopLossStartPrice, sellTokenAmount);
242
+ if (!tradeResult) {
243
+ throw new Error('Failed to calculate stop loss end price');
244
+ }
245
+
246
+ stopLossEndPrice = tradeResult[0]; // Price after trade completion
247
+ const tradeAmount = tradeResult[1]; // SOL输入量 / SOL input amount
248
+
249
+ //console.log(`迭代 ${iteration}: 起始价格=${stopLossStartPrice}, 结束价格=${stopLossEndPrice}, SOL输入量=${tradeAmount} / Iteration ${iteration}: Start=${stopLossStartPrice}, End=${stopLossEndPrice}, SOL input=${tradeAmount}`);
250
+
251
+ // 检查价格区间重叠 / Check price range overlap
252
+ const overlapResult = checkPriceRangeOverlap('up_orders', upOrders, stopLossStartPrice, stopLossEndPrice);
253
+
254
+ if (overlapResult.no_overlap) {
255
+ //console.log(' / No price range overlap, can execute');
256
+ finalOverlapResult = overlapResult; // 记录最终的overlap结果 / Record final overlap result
257
+ finalTradeAmount = tradeAmount; // 记录最终的交易金额 / Record final trade amount
258
+ break;
259
+ }
260
+
261
+ //console.log(`发现重叠: ${overlapResult.overlap_reason} / Found overlap: ${overlapResult.overlap_reason}`);
262
+
263
+ // 调整起始价格(增加0.5%)/ Adjust start price (increase by 0.5%)
264
+ // 使用方案2:直接计算 0.5% = 5/1000
265
+ const adjustmentAmount = (stopLossStartPrice * BigInt(PRICE_ADJUSTMENT_PERCENTAGE)) / 1000n;
266
+ stopLossStartPrice = stopLossStartPrice + adjustmentAmount;
267
+
268
+ //console.log(`调整后起始价格: ${stopLossStartPrice} / Adjusted start price: ${stopLossStartPrice}`);
269
+
270
+ // 安全检查:确保价格不会超过最大值 / Safety check: ensure price doesn't exceed maximum
271
+ if (stopLossStartPrice >= CurveAMM.MAX_U128_PRICE) {
272
+ throw new Error(`Stop loss price exceeded maximum after adjustment: ${stopLossStartPrice} >= ${CurveAMM.MAX_U128_PRICE}`);
273
+ }
274
+ }
275
+
276
+ if (iteration >= maxIterations) {
277
+ throw new Error('达到最大迭代次数,无法找到合适的止损价格 / Reached maximum iterations, cannot find suitable stop loss price');
278
+ }
279
+
280
+ // 计算最终返回值 / Calculate final return values
281
+ const executableStopLossPrice = stopLossStartPrice;
282
+
283
+ // 计算止损百分比 / Calculate stop loss percentage
284
+ // For short position, stop loss price is higher than current price, so it's a positive percentage
285
+ const stopLossPercentage = Number((BigInt(10000) * (executableStopLossPrice - currentPrice)) / currentPrice) / 100;
286
+
287
+ // 计算杠杆比例 / Calculate leverage ratio
288
+ // For short position, leverage = current price / (stop loss price - current price)
289
+ const leverage = Number((BigInt(10000) * currentPrice) / (executableStopLossPrice - currentPrice)) / 10000;
290
+
291
+ // 计算保证金 / Calculate margin requirement
292
+ let estimatedMargin = 0n;
293
+ try {
294
+ // 1. 计算从当前价格卖出代币获得的SOL(开仓收益,不含手续费)
295
+ const sellResult = CurveAMM.sellFromPriceWithTokenInput(currentPrice, sellTokenAmount);
296
+ if (sellResult) {
297
+ const openingSolGain = sellResult[1]; // 卖出获得的SOL
298
+
299
+ // 2. 计算开仓手续费
300
+ const openingFee = (openingSolGain * BigInt(borrowFee)) / 100000n;
301
+
302
+ // 3. 计算平仓成本(含手续费)
303
+ const feeAmount = (finalTradeAmount * BigInt(borrowFee)) / 100000n;
304
+ const closeCostWithFee = finalTradeAmount + feeAmount;
305
+
306
+ // 4. 计算保证金 = 平仓成本(含手续费) - 开仓收益 - 开仓手续费
307
+ if (closeCostWithFee > openingSolGain + openingFee) {
308
+ estimatedMargin = closeCostWithFee - openingSolGain - openingFee;
309
+ }
310
+ }
311
+ } catch (marginError) {
312
+ console.warn('Failed to calculate estimated margin for short position:', marginError.message);
313
+ // Keep estimatedMargin as 0n
314
+ }
315
+
316
+ // console.log(`Calculation completed:`);
317
+ // console.log(` Executable stop loss price: ${executableStopLossPrice}`);
318
+ // console.log(` SOL input amount: ${finalTradeAmount}`);
319
+ // console.log(` Stop loss percentage: ${stopLossPercentage}%`);
320
+ // console.log(` Leverage: ${leverage}x`);
321
+ // console.log(` Previous order PDA: ${finalOverlapResult.prev_order_pda}`);
322
+ // console.log(` Next order PDA: ${finalOverlapResult.next_order_pda}`);
323
+
324
+ return {
325
+ executableStopLossPrice: executableStopLossPrice, // Calculated reasonable stop loss value
326
+ tradeAmount: finalTradeAmount, // SOL input amount
327
+ stopLossPercentage: stopLossPercentage, // Stop loss percentage relative to current price
328
+ leverage: leverage, // Leverage ratio
329
+ currentPrice: currentPrice, // Current price
330
+ iterations: iteration, // Number of adjustments
331
+ originalStopLossPrice: BigInt(stopLossPrice), // Original stop loss price
332
+ prev_order_pda: finalOverlapResult.prev_order_pda, // Previous order PDA
333
+ next_order_pda: finalOverlapResult.next_order_pda, // Next order PDA
334
+ estimatedMargin: estimatedMargin // Estimated margin requirement in SOL (lamports)
335
+ };
336
+
337
+ } catch (error) {
338
+ console.error('Failed to simulate short position stop loss calculation:', error.message);
339
+ throw error;
340
+ }
341
+ }
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+ /**
352
+ * Simulate long position stop loss calculation with SOL amount input
353
+ * @param {string} mint - Token address
354
+ * @param {bigint|string|number} buySolAmount - SOL amount to spend for long position (u64 format, lamports)
355
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
356
+ * @param {Object|null} lastPrice - Token info, default null
357
+ * @param {Object|null} ordersData - Orders data, default null
358
+ * @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
359
+ * @returns {Promise<Object>} Stop loss analysis result (same as simulateLongStopLoss)
360
+ */
361
+ async function simulateLongSolStopLoss(mint, buySolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = 2000) {
362
+ try {
363
+ // Parameter validation
364
+ if (!mint || !buySolAmount || !stopLossPrice) {
365
+ throw new Error('Missing required parameters');
366
+ }
367
+
368
+ // Get current price if not provided
369
+ let currentPrice;
370
+ if (!lastPrice) {
371
+ lastPrice = await this.sdk.data.price(mint);
372
+ if (!lastPrice) {
373
+ throw new Error('Failed to get current price');
374
+ }
375
+ }
376
+
377
+ // Calculate current price
378
+ if (lastPrice === null || lastPrice === undefined || lastPrice === '0') {
379
+ currentPrice = CurveAMM.getInitialPrice();
380
+ } else {
381
+ currentPrice = BigInt(lastPrice);
382
+ if (!currentPrice || currentPrice === 0n) {
383
+ currentPrice = CurveAMM.getInitialPrice();
384
+ }
385
+ }
386
+
387
+ // Calculate initial token amount from SOL amount using sellFromPriceWithSolOutput
388
+ // This gives us how many tokens we can get when we later sell for buySolAmount SOL
389
+ const initialResult = CurveAMM.sellFromPriceWithSolOutput(currentPrice, buySolAmount);
390
+ if (!initialResult) {
391
+ throw new Error('Failed to calculate token amount from SOL amount');
392
+ }
393
+
394
+ let buyTokenAmount = initialResult[1]; // Token amount
395
+ let stopLossResult;
396
+ let iterations = 0;
397
+ const maxIterations = 15;
398
+
399
+ // 使用二分查找算法找到 estimatedMargin < buySolAmount 的最大值
400
+ // Use binary search algorithm to find maximum estimatedMargin that is less than buySolAmount
401
+ let left = 1n; // 最小值,确保有一个有效的下界
402
+ let right = buyTokenAmount * 10n; // 上界:初始值的10倍
403
+ let bestResult = null;
404
+ let bestMargin = 0n; // 记录最大的合法 estimatedMargin
405
+ let bestTokenAmount = buyTokenAmount;
406
+
407
+ // 二分查找主循环:寻找 estimatedMargin < buySolAmount 的最大值
408
+ while (iterations < maxIterations && left <= right) {
409
+ const mid = (left + right) / 2n;
410
+
411
+ // 计算当前 token 数量的结果
412
+ const currentResult = await simulateLongStopLoss.call(this, mint, mid, stopLossPrice, lastPrice, ordersData, borrowFee);
413
+ const currentMargin = currentResult.estimatedMargin;
414
+
415
+ //console.log(`Binary search iteration ${iterations}: tokenAmount=${mid}, estimatedMargin=${currentMargin}, target=${buySolAmount}`);
416
+
417
+ // 只考虑 estimatedMargin < buySolAmount 的情况
418
+ if (currentMargin < BigInt(buySolAmount)) {
419
+ // 这是一个合法的解,检查是否比当前最佳解更好
420
+ if (currentMargin > bestMargin) {
421
+ bestMargin = currentMargin;
422
+ bestResult = currentResult;
423
+ bestTokenAmount = mid;
424
+ //console.log(`Found better solution: estimatedMargin=${currentMargin}, tokenAmount=${mid}`);
425
+ }
426
+
427
+ // 如果差距已经很小(距离目标值小于10000000 lamports),可以提前退出
428
+ if (BigInt(buySolAmount) - currentMargin <= 10000000n) {
429
+ //console.log(`Found optimal solution: estimatedMargin=${currentMargin}, diff=${BigInt(buySolAmount) - currentMargin} (< 10000000 lamports tolerance)`);
430
+ break;
431
+ }
432
+
433
+ // 继续向右搜索,寻找更大的合法值
434
+ left = mid + 1n;
435
+ } else {
436
+ // estimatedMargin >= buySolAmount,需要减少 tokenAmount
437
+ //console.log(`estimatedMargin too large (${currentMargin} >= ${buySolAmount}), searching left`);
438
+ right = mid - 1n;
439
+ }
440
+
441
+ iterations++;
442
+ }
443
+
444
+ // 确保找到的结果满足要求
445
+ if (bestResult && bestMargin < BigInt(buySolAmount)) {
446
+ stopLossResult = bestResult;
447
+ buyTokenAmount = bestTokenAmount;
448
+ //console.log(`Binary search completed: best tokenAmount=${bestTokenAmount}, estimatedMargin=${bestMargin}, target=${buySolAmount}`);
449
+ } else {
450
+ // 如果没有找到合法解,使用一个很小的 tokenAmount 作为安全回退
451
+ //console.log(`No valid solution found (estimatedMargin < buySolAmount), using minimal tokenAmount`);
452
+ buyTokenAmount = buyTokenAmount / 10n; // 使用更小的值
453
+ if (buyTokenAmount <= 0n) buyTokenAmount = 1000000n; // 最小值保护
454
+ stopLossResult = await simulateLongStopLoss.call(this, mint, buyTokenAmount, stopLossPrice, lastPrice, ordersData, borrowFee);
455
+ }
456
+
457
+ // if (iterations >= maxIterations) {
458
+ // console.warn(`simulateLongSolStopLoss: Reached maximum iterations (${maxIterations}), tradeAmount=${stopLossResult.tradeAmount}, target=${buySolAmount}`);
459
+ // }
460
+
461
+ // Add buyTokenAmount and iteration info to the result
462
+ return {
463
+ ...stopLossResult,
464
+ buyTokenAmount: buyTokenAmount,
465
+ adjustmentIterations: iterations
466
+ };
467
+
468
+ } catch (error) {
469
+ console.error('Failed to simulate long stop loss with SOL amount:', error.message);
470
+ throw error;
471
+ }
472
+ }
473
+
474
+ /**
475
+ * Simulate short position stop loss calculation with SOL amount input
476
+ * @param {string} mint - Token address
477
+ * @param {bigint|string|number} sellSolAmount - SOL amount needed for short position stop loss (u64 format, lamports)
478
+ * @param {bigint|string|number} stopLossPrice - User desired stop loss price (u128 format)
479
+ * @param {Object|null} lastPrice - Token info, default null
480
+ * @param {Object|null} ordersData - Orders data, default null
481
+ * @param {number} borrowFee - Borrow fee rate, default 2000 (2000/100000 = 0.02%)
482
+ * @returns {Promise<Object>} Stop loss analysis result (same as simulateSellStopLoss)
483
+ */
484
+ async function simulateSellSolStopLoss(mint, sellSolAmount, stopLossPrice, lastPrice = null, ordersData = null, borrowFee = 2000) {
485
+ try {
486
+ // Parameter validation
487
+ if (!mint || !sellSolAmount || !stopLossPrice) {
488
+ throw new Error('Missing required parameters');
489
+ }
490
+
491
+ // Get current price if not provided
492
+ let currentPrice;
493
+ if (!lastPrice) {
494
+ lastPrice = await this.sdk.data.price(mint);
495
+ if (!lastPrice) {
496
+ throw new Error('Failed to get current price');
497
+ }
498
+ }
499
+
500
+ // Calculate current price
501
+ if (lastPrice === null || lastPrice === undefined || lastPrice === '0') {
502
+ currentPrice = CurveAMM.getInitialPrice();
503
+ } else {
504
+ currentPrice = BigInt(lastPrice);
505
+ if (!currentPrice || currentPrice === 0n) {
506
+ currentPrice = CurveAMM.getInitialPrice();
507
+ }
508
+ }
509
+
510
+ // Calculate initial token amount from SOL amount using buyFromPriceWithSolInput
511
+ // This gives us how many tokens we need to buy later using sellSolAmount SOL
512
+ const initialResult = CurveAMM.buyFromPriceWithSolInput(currentPrice, sellSolAmount);
513
+ if (!initialResult) {
514
+ throw new Error('Failed to calculate token amount from SOL amount');
515
+ }
516
+
517
+ let sellTokenAmount = initialResult[1]; // Token amount
518
+ let stopLossResult;
519
+ let iterations = 0;
520
+ const maxIterations = 15;
521
+
522
+ // 使用二分查找算法找到 estimatedMargin < sellSolAmount 的最大值
523
+ // Use binary search algorithm to find maximum estimatedMargin that is less than sellSolAmount
524
+ let left = 1n; // 最小值,确保有一个有效的下界
525
+ let right = sellTokenAmount * 10n; // 上界:初始值的10倍
526
+ let bestResult = null;
527
+ let bestMargin = 0n; // 记录最大的合法 estimatedMargin
528
+ let bestTokenAmount = sellTokenAmount;
529
+
530
+ // 二分查找主循环:寻找 estimatedMargin < sellSolAmount 的最大值
531
+ while (iterations < maxIterations && left <= right) {
532
+ const mid = (left + right) / 2n;
533
+
534
+ // 计算当前 token 数量的结果
535
+ const currentResult = await simulateSellStopLoss.call(this, mint, mid, stopLossPrice, lastPrice, ordersData, borrowFee);
536
+ const currentMargin = currentResult.estimatedMargin;
537
+
538
+ //console.log(`Binary search iteration ${iterations}: tokenAmount=${mid}, estimatedMargin=${currentMargin}, target=${sellSolAmount}`);
539
+
540
+ // 只考虑 estimatedMargin < sellSolAmount 的情况
541
+ if (currentMargin < BigInt(sellSolAmount)) {
542
+ // 这是一个合法的解,检查是否比当前最佳解更好
543
+ if (currentMargin > bestMargin) {
544
+ bestMargin = currentMargin;
545
+ bestResult = currentResult;
546
+ bestTokenAmount = mid;
547
+ //console.log(`Found better solution: estimatedMargin=${currentMargin}, tokenAmount=${mid}`);
548
+ }
549
+
550
+ // 如果差距已经很小(距离目标值小于10000000 lamports),可以提前退出
551
+ if (BigInt(sellSolAmount) - currentMargin <= 10000000n) {
552
+ //console.log(`Found optimal solution: estimatedMargin=${currentMargin}, diff=${BigInt(sellSolAmount) - currentMargin} (< 10000000 lamports tolerance)`);
553
+ break;
554
+ }
555
+
556
+ // 继续向右搜索,寻找更大的合法值
557
+ left = mid + 1n;
558
+ } else {
559
+ // estimatedMargin >= sellSolAmount,需要减少 tokenAmount
560
+ //console.log(`estimatedMargin too large (${currentMargin} >= ${sellSolAmount}), searching left`);
561
+ right = mid - 1n;
562
+ }
563
+
564
+ iterations++;
565
+ }
566
+
567
+ // 确保找到的结果满足要求
568
+ if (bestResult && bestMargin < BigInt(sellSolAmount)) {
569
+ stopLossResult = bestResult;
570
+ sellTokenAmount = bestTokenAmount;
571
+ //console.log(`Binary search completed: best tokenAmount=${bestTokenAmount}, estimatedMargin=${bestMargin}, target=${sellSolAmount}`);
572
+ } else {
573
+ // 如果没有找到合法解,使用一个很小的 tokenAmount 作为安全回退
574
+ //console.log(`No valid solution found (estimatedMargin < sellSolAmount), using minimal tokenAmount`);
575
+ sellTokenAmount = sellTokenAmount / 10n; // 使用更小的值
576
+ if (sellTokenAmount <= 0n) sellTokenAmount = 1000000n; // 最小值保护
577
+ stopLossResult = await simulateSellStopLoss.call(this, mint, sellTokenAmount, stopLossPrice, lastPrice, ordersData, borrowFee);
578
+ }
579
+
580
+ // if (iterations >= maxIterations) {
581
+ // //console.warn(`simulateSellSolStopLoss: Reached maximum iterations (${maxIterations}), tradeAmount=${stopLossResult.tradeAmount}, target=${sellSolAmount}`);
582
+ // }
583
+
584
+ // Add sellTokenAmount and iteration info to the result
585
+ return {
586
+ ...stopLossResult,
587
+ sellTokenAmount: sellTokenAmount,
588
+ adjustmentIterations: iterations
589
+ };
590
+
591
+ } catch (error) {
592
+ console.error('Failed to simulate short stop loss with SOL amount:', error.message);
593
+ throw error;
594
+ }
595
+ }
596
+
597
+ module.exports = {
598
+ simulateLongStopLoss,
599
+ simulateSellStopLoss,
600
+ simulateLongSolStopLoss,
601
+ simulateSellSolStopLoss
602
+ };