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.
- package/README.md +674 -0
- package/dist/index.d.ts +342 -0
- package/dist/pinpet-sdk.cjs.js +50648 -0
- package/dist/pinpet-sdk.cjs.js.map +1 -0
- package/dist/pinpet-sdk.esm.js +39265 -0
- package/dist/pinpet-sdk.esm.js.map +1 -0
- package/dist/pinpet-sdk.js +39277 -0
- package/dist/pinpet-sdk.js.map +1 -0
- package/package.json +67 -0
- package/src/idl/pinpet.json +3511 -0
- package/src/index.js +43 -0
- package/src/modules/chain.js +1102 -0
- package/src/modules/close.js +0 -0
- package/src/modules/fast.js +431 -0
- package/src/modules/param.js +171 -0
- package/src/modules/simulator/buy.js_del +711 -0
- package/src/modules/simulator/buy_sell_token.js +300 -0
- package/src/modules/simulator/calcLiq.js +701 -0
- package/src/modules/simulator/long_shrot_stop.js +602 -0
- package/src/modules/simulator/sell.js_del +323 -0
- package/src/modules/simulator/stop_loss_utils.js +223 -0
- package/src/modules/simulator/utils.js +44 -0
- package/src/modules/simulator.js +133 -0
- package/src/modules/token.js +140 -0
- package/src/modules/trading.js +1087 -0
- package/src/sdk.js +370 -0
- package/src/types/index.d.ts +342 -0
- package/src/utils/constants.js +49 -0
- package/src/utils/curve_amm.js +884 -0
- package/src/utils/orderUtils.js +465 -0
|
@@ -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
|
+
};
|