100x-sdk 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +364 -0
- package/dist/100x-sdk.cjs.js +57601 -0
- package/dist/100x-sdk.cjs.js.map +1 -0
- package/dist/100x-sdk.esm.js +46234 -0
- package/dist/100x-sdk.esm.js.map +1 -0
- package/dist/100x-sdk.js +46245 -0
- package/dist/100x-sdk.js.map +1 -0
- package/dist/index.d.ts +386 -0
- package/package.json +69 -0
- package/src/idl/fun100x_localnet.json +3735 -0
- package/src/idl/fun100x_main.json +3735 -0
- package/src/index.js +50 -0
- package/src/modules/chain.js +1310 -0
- package/src/modules/fast.js +734 -0
- package/src/modules/param.js +171 -0
- package/src/modules/simulator/buy_sell_token.js +326 -0
- package/src/modules/simulator/calcLiq.js +778 -0
- package/src/modules/simulator/calc_sol_liq.js +268 -0
- package/src/modules/simulator/close_indices.js +217 -0
- package/src/modules/simulator/long_shrot_stop.js +1028 -0
- package/src/modules/simulator/stop_loss_utils.js +378 -0
- package/src/modules/simulator/utils.js +63 -0
- package/src/modules/simulator.js +386 -0
- package/src/modules/token.js +455 -0
- package/src/modules/tools.js +370 -0
- package/src/modules/trading.js +848 -0
- package/src/sdk.js +206 -0
- package/src/types/index.d.ts +386 -0
- package/src/utils/constants.js +57 -0
- package/src/utils/curve_amm.js +1215 -0
- package/src/utils/orderUtils.js +17 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
|
|
2
|
+
const CurveAMM = require('../../utils/curve_amm');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 计算使用指定SOL数量买入时能获得的Token数量
|
|
6
|
+
* @param {bigint|string} price - 当前交易价格 (u128格式)
|
|
7
|
+
* @param {bigint|string|number} buySolAmount - 需要花费的SOL数量 (lamports, 9位精度)
|
|
8
|
+
* @param {Array} orders - 锁定流动性的订单数组
|
|
9
|
+
* @param {number} onceMaxOrder - 单次循环最大订单数
|
|
10
|
+
* @param {string|number|null} passOrderID - 需要跳过的订单ID(与order_id字段比较)
|
|
11
|
+
* @param {string|number|null} initialVirtualSol - 流动池SOL数量
|
|
12
|
+
* @param {string|number|null} initialVirtualToken - 流动池Token数量
|
|
13
|
+
* @returns {Object} { tokenAmount: bigint, msg: string, closedOrdersCount: number }
|
|
14
|
+
*/
|
|
15
|
+
function calcLiqSolBuy(price, buySolAmount, orders, onceMaxOrder, passOrderID = null, initialVirtualSol = null, initialVirtualToken = null){
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
//console.log("calcLiqSolBuy: , price, buySolAmount, orders, onceMaxOrder, passOrderID , initialVirtualSol, initialVirtualToken=", price, buySolAmount, orders, onceMaxOrder, passOrderID , initialVirtualSol , initialVirtualToken);
|
|
19
|
+
|
|
20
|
+
// 1. 参数验证
|
|
21
|
+
// 转换为 bigint 以便比较
|
|
22
|
+
let priceBigInt;
|
|
23
|
+
let buySolAmountBigInt;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
priceBigInt = typeof price === 'bigint' ? price : BigInt(price);
|
|
27
|
+
buySolAmountBigInt = typeof buySolAmount === 'bigint' ? buySolAmount : BigInt(buySolAmount);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
tokenAmount: 0n,
|
|
31
|
+
msg: '参数格式错误:无法转换为bigint',
|
|
32
|
+
closedOrdersCount: 0
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 检查价格和金额是否有效
|
|
37
|
+
if (priceBigInt <= 0n) {
|
|
38
|
+
return {
|
|
39
|
+
tokenAmount: 0n,
|
|
40
|
+
msg: '计算失败:价格必须大于0',
|
|
41
|
+
closedOrdersCount: 0
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (buySolAmountBigInt <= 0n) {
|
|
46
|
+
return {
|
|
47
|
+
tokenAmount: 0n,
|
|
48
|
+
msg: '计算失败:买入金额必须大于0',
|
|
49
|
+
closedOrdersCount: 0
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 2. 设置默认流动池参数
|
|
54
|
+
const Decimal = require('decimal.js');
|
|
55
|
+
const virtualSol = initialVirtualSol !== null
|
|
56
|
+
? new Decimal(initialVirtualSol.toString()).div(CurveAMM.SOL_PRECISION_FACTOR_DECIMAL)
|
|
57
|
+
: CurveAMM.INITIAL_SOL_RESERVE_DECIMAL;
|
|
58
|
+
const virtualToken = initialVirtualToken !== null
|
|
59
|
+
? new Decimal(initialVirtualToken.toString()).div(CurveAMM.TOKEN_PRECISION_FACTOR_DECIMAL)
|
|
60
|
+
: CurveAMM.INITIAL_TOKEN_RESERVE_DECIMAL;
|
|
61
|
+
|
|
62
|
+
//console.log("calcLiqSolBuy: virtualSol,virtualToken=",virtualSol,virtualToken)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// 3. 处理空订单情况(第一阶段)
|
|
66
|
+
if (!orders || orders.length === 0) {
|
|
67
|
+
// 直接计算完整流动性下的买入
|
|
68
|
+
const result = CurveAMM.buyFromPriceWithSolInputWithParams(
|
|
69
|
+
price,
|
|
70
|
+
buySolAmount,
|
|
71
|
+
virtualSol,
|
|
72
|
+
virtualToken
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (result === null) {
|
|
76
|
+
return {
|
|
77
|
+
tokenAmount: 0n,
|
|
78
|
+
msg: '计算失败:价格或参数无效',
|
|
79
|
+
closedOrdersCount: 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const [endPrice, tokenAmount] = result;
|
|
84
|
+
return {
|
|
85
|
+
tokenAmount: tokenAmount,
|
|
86
|
+
msg: '流动性完整,无锁定订单',
|
|
87
|
+
closedOrdersCount: 0
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 4. 处理有订单的情况(第二阶段:分段流动性计算)
|
|
92
|
+
|
|
93
|
+
// 初始化变量
|
|
94
|
+
let currentPrice = priceBigInt; // 当前价格指针
|
|
95
|
+
let remainingSol = buySolAmountBigInt; // 剩余可用 SOL
|
|
96
|
+
let totalTokenAmount = 0n; // 累计获得的 token
|
|
97
|
+
let closedOrdersCount = 0; // 平仓订单数量
|
|
98
|
+
let processedOrders = 0; // 已处理订单数
|
|
99
|
+
|
|
100
|
+
// 检查当前价格是否高于第一个订单的结束价格
|
|
101
|
+
if (orders.length > 0) {
|
|
102
|
+
const firstOrderEndPrice = typeof orders[0].lock_lp_end_price === 'bigint'
|
|
103
|
+
? orders[0].lock_lp_end_price
|
|
104
|
+
: BigInt(orders[0].lock_lp_end_price);
|
|
105
|
+
|
|
106
|
+
if (currentPrice >= firstOrderEndPrice) {
|
|
107
|
+
return {
|
|
108
|
+
tokenAmount: 0n,
|
|
109
|
+
msg: '错误:当前价格已高于第一个订单的结束价格',
|
|
110
|
+
closedOrdersCount: 0
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 遍历订单(最多 onceMaxOrder 个)
|
|
116
|
+
for (let i = 0; i < orders.length && processedOrders < onceMaxOrder; i++) {
|
|
117
|
+
const order = orders[i];
|
|
118
|
+
|
|
119
|
+
// 检查是否需要跳过此订单
|
|
120
|
+
if (passOrderID !== null && order.order_id !== undefined) {
|
|
121
|
+
// 将两者都转换为字符串进行比较
|
|
122
|
+
const orderIdStr = String(order.order_id);
|
|
123
|
+
const passOrderIdStr = String(passOrderID);
|
|
124
|
+
|
|
125
|
+
if (orderIdStr === passOrderIdStr) {
|
|
126
|
+
// 跳过此订单,不处理其锁定区间,继续下一个订单
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 转换订单价格为 bigint
|
|
132
|
+
let lockStartPrice, lockEndPrice;
|
|
133
|
+
try {
|
|
134
|
+
lockStartPrice = typeof order.lock_lp_start_price === 'bigint'
|
|
135
|
+
? order.lock_lp_start_price
|
|
136
|
+
: BigInt(order.lock_lp_start_price);
|
|
137
|
+
lockEndPrice = typeof order.lock_lp_end_price === 'bigint'
|
|
138
|
+
? order.lock_lp_end_price
|
|
139
|
+
: BigInt(order.lock_lp_end_price);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
tokenAmount: 0n,
|
|
143
|
+
msg: `错误:订单${i}的价格格式无效`,
|
|
144
|
+
closedOrdersCount: closedOrdersCount
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 步骤1:计算可用区间(currentPrice → lockStartPrice)
|
|
149
|
+
if (currentPrice < lockStartPrice) {
|
|
150
|
+
// 1.1 计算这段区间需要多少 SOL 和能获得多少 token
|
|
151
|
+
const result = CurveAMM.buyFromPriceToPriceWithParams(
|
|
152
|
+
currentPrice,
|
|
153
|
+
lockStartPrice,
|
|
154
|
+
virtualSol,
|
|
155
|
+
virtualToken
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// 检查计算是否成功
|
|
159
|
+
if (result === null) {
|
|
160
|
+
return {
|
|
161
|
+
tokenAmount: 0n,
|
|
162
|
+
msg: `错误:计算价格区间失败(订单${i})`,
|
|
163
|
+
closedOrdersCount: closedOrdersCount
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const [solNeeded, tokenGained] = result;
|
|
168
|
+
|
|
169
|
+
// 1.2 判断剩余 SOL 是否足够
|
|
170
|
+
if (remainingSol >= solNeeded) {
|
|
171
|
+
// 足够:买完这段区间,继续下一段
|
|
172
|
+
remainingSol -= solNeeded;
|
|
173
|
+
totalTokenAmount += tokenGained;
|
|
174
|
+
currentPrice = lockStartPrice;
|
|
175
|
+
} else {
|
|
176
|
+
// 不够:用完剩余 SOL,计算能买多少,然后返回
|
|
177
|
+
const finalResult = CurveAMM.buyFromPriceWithSolInputWithParams(
|
|
178
|
+
currentPrice,
|
|
179
|
+
remainingSol,
|
|
180
|
+
virtualSol,
|
|
181
|
+
virtualToken
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (finalResult === null) {
|
|
185
|
+
return {
|
|
186
|
+
tokenAmount: 0n,
|
|
187
|
+
msg: '错误:计算最终买入失败',
|
|
188
|
+
closedOrdersCount: closedOrdersCount
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const [finalPrice, finalTokenGained] = finalResult;
|
|
193
|
+
totalTokenAmount += finalTokenGained;
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
tokenAmount: totalTokenAmount,
|
|
197
|
+
msg: `SOL用完,最终价格: ${finalPrice.toString()}`,
|
|
198
|
+
closedOrdersCount: closedOrdersCount
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 步骤2:跳过锁定区间(lockStartPrice → lockEndPrice)
|
|
204
|
+
currentPrice = lockEndPrice;
|
|
205
|
+
processedOrders++;
|
|
206
|
+
|
|
207
|
+
// 步骤3:判断是否平仓
|
|
208
|
+
// 如果价格到达 lockEndPrice,说明这个订单被平仓
|
|
209
|
+
closedOrdersCount++;
|
|
210
|
+
|
|
211
|
+
// 检查 SOL 是否已用完
|
|
212
|
+
if (remainingSol === 0n) {
|
|
213
|
+
return {
|
|
214
|
+
tokenAmount: totalTokenAmount,
|
|
215
|
+
msg: `SOL刚好用完,最终价格: ${currentPrice.toString()}`,
|
|
216
|
+
closedOrdersCount: closedOrdersCount
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 步骤4:处理最后的无限流动性区间
|
|
222
|
+
// 所有订单处理完后,用剩余 SOL 继续买入
|
|
223
|
+
if (remainingSol > 0n) {
|
|
224
|
+
const finalResult = CurveAMM.buyFromPriceWithSolInputWithParams(
|
|
225
|
+
currentPrice,
|
|
226
|
+
remainingSol,
|
|
227
|
+
virtualSol,
|
|
228
|
+
virtualToken
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (finalResult === null) {
|
|
232
|
+
return {
|
|
233
|
+
tokenAmount: 0n,
|
|
234
|
+
msg: '错误:计算无限流动性区间失败',
|
|
235
|
+
closedOrdersCount: closedOrdersCount
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const [finalPrice, finalTokenGained] = finalResult;
|
|
240
|
+
totalTokenAmount += finalTokenGained;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
tokenAmount: totalTokenAmount,
|
|
244
|
+
msg: `买入完成,最终价格: ${finalPrice.toString()}`,
|
|
245
|
+
closedOrdersCount: closedOrdersCount
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 步骤5:SOL 刚好用完
|
|
250
|
+
return {
|
|
251
|
+
tokenAmount: totalTokenAmount,
|
|
252
|
+
msg: `SOL刚好用完,最终价格: ${currentPrice.toString()}`,
|
|
253
|
+
closedOrdersCount: closedOrdersCount
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
function calcLiqSolSell(price, sellSolAmount, orders, onceMaxOrder, passOrder = null, initialVirtualSol = null, initialVirtualToken = null) {
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
calcLiqSolBuy,
|
|
267
|
+
calcLiqSolSell
|
|
268
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const { MAX_CANDIDATE_INDICES } = require('./utils');
|
|
2
|
+
|
|
3
|
+
// Calculate number of nodes to include before and after the main position
|
|
4
|
+
const CANDIDATE_NODES_EACH_SIDE = Math.floor((MAX_CANDIDATE_INDICES - 1) / 2);
|
|
5
|
+
|
|
6
|
+
// Special value indicating no prev/next order in the linked list
|
|
7
|
+
const NO_ORDER = 65535;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 根据 index 在订单数组中查找订单
|
|
11
|
+
* Find order in array by its index field
|
|
12
|
+
*
|
|
13
|
+
* @param {Array} orders - 订单数组 / Orders array
|
|
14
|
+
* @param {number} index - 订单的 index 字段值 / Order's index field value
|
|
15
|
+
* @returns {Object|null} 找到的订单对象,找不到返回 null / Found order object or null
|
|
16
|
+
*/
|
|
17
|
+
function findOrderByIndex(orders, index) {
|
|
18
|
+
for (let i = 0; i < orders.length; i++) {
|
|
19
|
+
if (orders[i].index === index) {
|
|
20
|
+
return orders[i];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 为做多平仓生成候选插入索引
|
|
28
|
+
* Generate candidate insertion indices for closing long position
|
|
29
|
+
*
|
|
30
|
+
* @param {string} mint - 代币地址 / Token address
|
|
31
|
+
* @param {number|string|anchor.BN} closeOrderId - 要平仓的订单ID (order_id, 不是 index) / Order ID to close (order_id, not index)
|
|
32
|
+
* @param {Object|null} ordersData - 订单数据(可选,如果不提供会自动获取)/ Orders data (optional, will fetch if not provided)
|
|
33
|
+
* @returns {Promise<Object>} 返回包含候选索引数组的对象 / Returns object containing candidate indices array
|
|
34
|
+
* - closeOrderIndices: {number[]} 候选插入位置索引数组 / Candidate insertion position indices array
|
|
35
|
+
*
|
|
36
|
+
* @throws {Error} 如果找不到对应的 closeOrderId / If closeOrderId is not found
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // 平仓做多订单
|
|
40
|
+
* const result = await sdk.simulator.simulateLongClose(
|
|
41
|
+
* 'HG9R8CE9N18U8zYqo6cqS4bFaCAzHbAhaAe1zq8Hq7PF',
|
|
42
|
+
* 1090 // order_id
|
|
43
|
+
* );
|
|
44
|
+
* console.log('候选索引:', result.closeOrderIndices);
|
|
45
|
+
* // 输出: { closeOrderIndices: [25, 15, 35, 5, 45, ...] }
|
|
46
|
+
*/
|
|
47
|
+
async function simulateLongClose(mint, closeOrderId, ordersData = null) {
|
|
48
|
+
// 1. 获取 down_orders 数据(做多订单在 down_orders 中)
|
|
49
|
+
if (!ordersData) {
|
|
50
|
+
ordersData = await this.sdk.data.orders(mint, { type: 'down_orders' });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!ordersData || !ordersData.success || !ordersData.data || !ordersData.data.orders) {
|
|
54
|
+
throw new Error('Failed to fetch down_orders data');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const orders = ordersData.data.orders;
|
|
58
|
+
|
|
59
|
+
// 2. 将 closeOrderId 转换为字符串(因为 API 返回的 order_id 是字符串)
|
|
60
|
+
const targetOrderId = closeOrderId.toString();
|
|
61
|
+
|
|
62
|
+
// 3. 在订单列表中查找匹配的订单
|
|
63
|
+
let targetOrderIndex = -1;
|
|
64
|
+
for (let i = 0; i < orders.length; i++) {
|
|
65
|
+
if (orders[i].order_id === targetOrderId) {
|
|
66
|
+
targetOrderIndex = i;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 4. 如果找不到订单,抛出错误
|
|
72
|
+
if (targetOrderIndex === -1) {
|
|
73
|
+
throw new Error(`Order with order_id ${targetOrderId} not found in down_orders`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 5. 获取目标订单的 OrderBook index
|
|
77
|
+
const targetOrder = orders[targetOrderIndex];
|
|
78
|
+
const mainIndex = targetOrder.index;
|
|
79
|
+
|
|
80
|
+
// 6. 生成候选索引数组(通过链表结构遍历前后节点)
|
|
81
|
+
const indices = [];
|
|
82
|
+
|
|
83
|
+
// 添加主位置
|
|
84
|
+
indices.push(mainIndex);
|
|
85
|
+
|
|
86
|
+
// 通过链表结构添加前后节点的索引
|
|
87
|
+
let prevNode = targetOrder;
|
|
88
|
+
let nextNode = targetOrder;
|
|
89
|
+
|
|
90
|
+
for (let offset = 1; offset <= CANDIDATE_NODES_EACH_SIDE; offset++) {
|
|
91
|
+
// 添加前面第 offset 个节点(通过 prev_order 链表指针)
|
|
92
|
+
if (prevNode.prev_order !== NO_ORDER) {
|
|
93
|
+
const prevOrder = findOrderByIndex(orders, prevNode.prev_order);
|
|
94
|
+
if (prevOrder && prevOrder.index !== undefined) {
|
|
95
|
+
indices.push(prevOrder.index);
|
|
96
|
+
prevNode = prevOrder; // 继续向前遍历
|
|
97
|
+
} else {
|
|
98
|
+
prevNode = { prev_order: NO_ORDER }; // 找不到则停止
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 添加后面第 offset 个节点(通过 next_order 链表指针)
|
|
103
|
+
if (nextNode.next_order !== NO_ORDER) {
|
|
104
|
+
const nextOrder = findOrderByIndex(orders, nextNode.next_order);
|
|
105
|
+
if (nextOrder && nextOrder.index !== undefined) {
|
|
106
|
+
indices.push(nextOrder.index);
|
|
107
|
+
nextNode = nextOrder; // 继续向后遍历
|
|
108
|
+
} else {
|
|
109
|
+
nextNode = { next_order: NO_ORDER }; // 找不到则停止
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 7. 返回结果
|
|
115
|
+
return {
|
|
116
|
+
closeOrderIndices: indices
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 为做空平仓生成候选插入索引
|
|
122
|
+
* Generate candidate insertion indices for closing short position
|
|
123
|
+
*
|
|
124
|
+
* @param {string} mint - 代币地址 / Token address
|
|
125
|
+
* @param {number|string|anchor.BN} closeOrderId - 要平仓的订单ID (order_id, 不是 index) / Order ID to close (order_id, not index)
|
|
126
|
+
* @param {Object|null} ordersData - 订单数据(可选,如果不提供会自动获取)/ Orders data (optional, will fetch if not provided)
|
|
127
|
+
* @returns {Promise<Object>} 返回包含候选索引数组的对象 / Returns object containing candidate indices array
|
|
128
|
+
* - closeOrderIndices: {number[]} 候选插入位置索引数组 / Candidate insertion position indices array
|
|
129
|
+
*
|
|
130
|
+
* @throws {Error} 如果找不到对应的 closeOrderId / If closeOrderId is not found
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* // 平仓做空订单
|
|
134
|
+
* const result = await sdk.simulator.simulateShortClose(
|
|
135
|
+
* 'HG9R8CE9N18U8zYqo6cqS4bFaCAzHbAhaAe1zq8Hq7PF',
|
|
136
|
+
* 1090 // order_id
|
|
137
|
+
* );
|
|
138
|
+
* console.log('候选索引:', result.closeOrderIndices);
|
|
139
|
+
* // 输出: { closeOrderIndices: [25, 15, 35, 5, 45, ...] }
|
|
140
|
+
*/
|
|
141
|
+
async function simulateShortClose(mint, closeOrderId, ordersData = null) {
|
|
142
|
+
// 1. 获取 up_orders 数据(做空订单在 up_orders 中)
|
|
143
|
+
if (!ordersData) {
|
|
144
|
+
ordersData = await this.sdk.data.orders(mint, { type: 'up_orders' });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!ordersData || !ordersData.success || !ordersData.data || !ordersData.data.orders) {
|
|
148
|
+
throw new Error('Failed to fetch up_orders data');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const orders = ordersData.data.orders;
|
|
152
|
+
|
|
153
|
+
// 2. 将 closeOrderId 转换为字符串(因为 API 返回的 order_id 是字符串)
|
|
154
|
+
const targetOrderId = closeOrderId.toString();
|
|
155
|
+
|
|
156
|
+
// 3. 在订单列表中查找匹配的订单
|
|
157
|
+
let targetOrderIndex = -1;
|
|
158
|
+
for (let i = 0; i < orders.length; i++) {
|
|
159
|
+
if (orders[i].order_id === targetOrderId) {
|
|
160
|
+
targetOrderIndex = i;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 4. 如果找不到订单,抛出错误
|
|
166
|
+
if (targetOrderIndex === -1) {
|
|
167
|
+
throw new Error(`Order with order_id ${targetOrderId} not found in up_orders`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// 5. 获取目标订单的 OrderBook index
|
|
171
|
+
const targetOrder = orders[targetOrderIndex];
|
|
172
|
+
const mainIndex = targetOrder.index;
|
|
173
|
+
|
|
174
|
+
// 6. 生成候选索引数组(通过链表结构遍历前后节点)
|
|
175
|
+
const indices = [];
|
|
176
|
+
|
|
177
|
+
// 添加主位置
|
|
178
|
+
indices.push(mainIndex);
|
|
179
|
+
|
|
180
|
+
// 通过链表结构添加前后节点的索引
|
|
181
|
+
let prevNode = targetOrder;
|
|
182
|
+
let nextNode = targetOrder;
|
|
183
|
+
|
|
184
|
+
for (let offset = 1; offset <= CANDIDATE_NODES_EACH_SIDE; offset++) {
|
|
185
|
+
// 添加前面第 offset 个节点(通过 prev_order 链表指针)
|
|
186
|
+
if (prevNode.prev_order !== NO_ORDER) {
|
|
187
|
+
const prevOrder = findOrderByIndex(orders, prevNode.prev_order);
|
|
188
|
+
if (prevOrder && prevOrder.index !== undefined) {
|
|
189
|
+
indices.push(prevOrder.index);
|
|
190
|
+
prevNode = prevOrder; // 继续向前遍历
|
|
191
|
+
} else {
|
|
192
|
+
prevNode = { prev_order: NO_ORDER }; // 找不到则停止
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 添加后面第 offset 个节点(通过 next_order 链表指针)
|
|
197
|
+
if (nextNode.next_order !== NO_ORDER) {
|
|
198
|
+
const nextOrder = findOrderByIndex(orders, nextNode.next_order);
|
|
199
|
+
if (nextOrder && nextOrder.index !== undefined) {
|
|
200
|
+
indices.push(nextOrder.index);
|
|
201
|
+
nextNode = nextOrder; // 继续向后遍历
|
|
202
|
+
} else {
|
|
203
|
+
nextNode = { next_order: NO_ORDER }; // 找不到则停止
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 7. 返回结果
|
|
209
|
+
return {
|
|
210
|
+
closeOrderIndices: indices
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
simulateLongClose,
|
|
216
|
+
simulateShortClose
|
|
217
|
+
};
|