@gbozee/ultimate 0.0.2-next.73 → 0.0.2-next.75
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/index.cjs +458 -59
- package/dist/index.d.ts +71 -0
- package/dist/index.js +458 -59
- package/dist/mcp-server.cjs +458 -59
- package/dist/mcp-server.js +458 -59
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -75719,9 +75719,18 @@ class BaseExchange {
|
|
|
75719
75719
|
async previewSpotMarginHedge(_payload) {
|
|
75720
75720
|
throw new Error("Spot margin hedge preview is not supported on this exchange");
|
|
75721
75721
|
}
|
|
75722
|
+
async previewFuturesReplay(_payload) {
|
|
75723
|
+
throw new Error("Futures replay preview is not supported on this exchange");
|
|
75724
|
+
}
|
|
75722
75725
|
async placeSpotMarginHedge(_payload) {
|
|
75723
75726
|
throw new Error("Spot margin hedge placement is not supported on this exchange");
|
|
75724
75727
|
}
|
|
75728
|
+
async placeFuturesReplay(_payload) {
|
|
75729
|
+
throw new Error("Futures replay placement is not supported on this exchange");
|
|
75730
|
+
}
|
|
75731
|
+
async placeFuturesExactTrade(_payload) {
|
|
75732
|
+
throw new Error("Futures exact trade placement is not supported on this exchange");
|
|
75733
|
+
}
|
|
75725
75734
|
async placeSpotLimitOrders(_payload) {}
|
|
75726
75735
|
async buildCumulative(payload) {
|
|
75727
75736
|
const {
|
|
@@ -76130,19 +76139,198 @@ class BaseExchange {
|
|
|
76130
76139
|
// src/exchanges/binance/index.ts
|
|
76131
76140
|
var import_p_limit = __toESM(require_p_limit(), 1);
|
|
76132
76141
|
|
|
76133
|
-
// src/exchanges/binance/
|
|
76142
|
+
// src/exchanges/binance/futures-replay.ts
|
|
76134
76143
|
function roundNumber(value2, digits = 8) {
|
|
76135
76144
|
return Number(value2.toFixed(digits));
|
|
76136
76145
|
}
|
|
76137
|
-
|
|
76146
|
+
function parseNumber2(value2) {
|
|
76147
|
+
if (value2 === undefined || value2 === null || value2 === "")
|
|
76148
|
+
return;
|
|
76149
|
+
const parsed = Number(value2);
|
|
76150
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
76151
|
+
return;
|
|
76152
|
+
return roundNumber(parsed);
|
|
76153
|
+
}
|
|
76138
76154
|
function normalizeOrderInput(order) {
|
|
76155
|
+
const stop = parseNumber2(order.stop) ?? parseNumber2(order.stopPrice) ?? parseNumber2(order.triggerPrice);
|
|
76156
|
+
const type = order.type ? order.type.toUpperCase() : undefined;
|
|
76157
|
+
const quantity = order.quantity ?? order.qty ?? 0;
|
|
76139
76158
|
return {
|
|
76140
|
-
price: roundNumber(order.price),
|
|
76141
|
-
quantity: roundNumber(
|
|
76159
|
+
price: roundNumber(Number(order.price)),
|
|
76160
|
+
quantity: roundNumber(Number(quantity)),
|
|
76161
|
+
...stop ? { stop } : {},
|
|
76162
|
+
...type && type !== "LIMIT" ? { type } : {}
|
|
76163
|
+
};
|
|
76164
|
+
}
|
|
76165
|
+
function sortReplayOrders(orders) {
|
|
76166
|
+
return [...orders].sort((left, right) => {
|
|
76167
|
+
const leftTrigger = left.stop ?? left.price;
|
|
76168
|
+
const rightTrigger = right.stop ?? right.price;
|
|
76169
|
+
if (leftTrigger !== rightTrigger)
|
|
76170
|
+
return leftTrigger - rightTrigger;
|
|
76171
|
+
if (left.price !== right.price)
|
|
76172
|
+
return left.price - right.price;
|
|
76173
|
+
return left.quantity - right.quantity;
|
|
76174
|
+
});
|
|
76175
|
+
}
|
|
76176
|
+
function buildReplayStopLimitPrice(payload) {
|
|
76177
|
+
const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
|
|
76178
|
+
return roundNumber(payload.kind === "long" ? payload.stop * spread ** -1 : payload.stop * spread);
|
|
76179
|
+
}
|
|
76180
|
+
function normalizeRequestedEntryOrders(orders) {
|
|
76181
|
+
return sortReplayOrders(orders.map((order) => ({
|
|
76182
|
+
price: roundNumber(Number(order.price)),
|
|
76183
|
+
quantity: roundNumber(Number(order.quantity))
|
|
76184
|
+
})));
|
|
76185
|
+
}
|
|
76186
|
+
function normalizeRequestedStopOrders(payload) {
|
|
76187
|
+
return sortReplayOrders(payload.orders.map((order) => {
|
|
76188
|
+
const stop = parseNumber2(order.stop) ?? roundNumber(Number(order.price));
|
|
76189
|
+
return {
|
|
76190
|
+
price: order.price !== undefined ? roundNumber(Number(order.price)) : buildReplayStopLimitPrice({
|
|
76191
|
+
kind: payload.kind,
|
|
76192
|
+
stop
|
|
76193
|
+
}),
|
|
76194
|
+
quantity: roundNumber(Number(order.quantity)),
|
|
76195
|
+
stop,
|
|
76196
|
+
type: (order.type || "STOP").toUpperCase()
|
|
76197
|
+
};
|
|
76198
|
+
}));
|
|
76199
|
+
}
|
|
76200
|
+
function normalizeRequestedTakeProfitOrders(orders) {
|
|
76201
|
+
return sortReplayOrders(orders.map((order) => {
|
|
76202
|
+
const stop = parseNumber2(order.stop);
|
|
76203
|
+
const type = order.type?.toUpperCase();
|
|
76204
|
+
return {
|
|
76205
|
+
price: roundNumber(Number(order.price)),
|
|
76206
|
+
quantity: roundNumber(Number(order.quantity)),
|
|
76207
|
+
...stop ? { stop } : {},
|
|
76208
|
+
...type ? { type } : stop ? { type: "TAKE_PROFIT" } : {}
|
|
76209
|
+
};
|
|
76210
|
+
}));
|
|
76211
|
+
}
|
|
76212
|
+
function buildOrderKey(order) {
|
|
76213
|
+
return [
|
|
76214
|
+
order.type || "LIMIT",
|
|
76215
|
+
order.price,
|
|
76216
|
+
order.quantity,
|
|
76217
|
+
order.stop ?? ""
|
|
76218
|
+
].join(":");
|
|
76219
|
+
}
|
|
76220
|
+
function buildComparisonBucket(options) {
|
|
76221
|
+
const expected = sortReplayOrders(options.expected);
|
|
76222
|
+
const actual = sortReplayOrders(options.actual);
|
|
76223
|
+
const expected_keys = expected.map(buildOrderKey);
|
|
76224
|
+
const actual_keys = actual.map(buildOrderKey);
|
|
76225
|
+
return {
|
|
76226
|
+
expected,
|
|
76227
|
+
actual,
|
|
76228
|
+
missing: expected_keys.filter((key) => !actual_keys.includes(key)),
|
|
76229
|
+
extra: actual_keys.filter((key) => !expected_keys.includes(key))
|
|
76230
|
+
};
|
|
76231
|
+
}
|
|
76232
|
+
function buildFuturesReplaySnapshot(payload) {
|
|
76233
|
+
const entry_side = payload.kind === "long" ? "buy" : "sell";
|
|
76234
|
+
const close_side = payload.kind === "long" ? "sell" : "buy";
|
|
76235
|
+
const entry_orders = [];
|
|
76236
|
+
const stop_orders = [];
|
|
76237
|
+
const take_profit_orders = [];
|
|
76238
|
+
for (const order of payload.open_orders) {
|
|
76239
|
+
if (order.kind !== payload.kind)
|
|
76240
|
+
continue;
|
|
76241
|
+
const normalized_order = normalizeOrderInput(order);
|
|
76242
|
+
const type = (normalized_order.type || "LIMIT").toUpperCase();
|
|
76243
|
+
if (order.side === entry_side) {
|
|
76244
|
+
entry_orders.push(normalized_order);
|
|
76245
|
+
continue;
|
|
76246
|
+
}
|
|
76247
|
+
if (order.side !== close_side)
|
|
76248
|
+
continue;
|
|
76249
|
+
if (type.includes("TAKE_PROFIT") || normalized_order.stop === undefined) {
|
|
76250
|
+
take_profit_orders.push(normalized_order);
|
|
76251
|
+
continue;
|
|
76252
|
+
}
|
|
76253
|
+
stop_orders.push(normalized_order);
|
|
76254
|
+
}
|
|
76255
|
+
return {
|
|
76256
|
+
symbol: payload.symbol,
|
|
76257
|
+
kind: payload.kind,
|
|
76258
|
+
current_price: payload.current_price,
|
|
76259
|
+
position: payload.position,
|
|
76260
|
+
open_orders: payload.open_orders.filter((order) => order.kind === payload.kind),
|
|
76261
|
+
entry_orders: sortReplayOrders(entry_orders),
|
|
76262
|
+
stop_orders: sortReplayOrders(stop_orders),
|
|
76263
|
+
take_profit_orders: sortReplayOrders(take_profit_orders)
|
|
76264
|
+
};
|
|
76265
|
+
}
|
|
76266
|
+
function inferFuturesReplayInputFromLiveOrders(payload) {
|
|
76267
|
+
const snapshot = buildFuturesReplaySnapshot(payload);
|
|
76268
|
+
return {
|
|
76269
|
+
symbol: payload.symbol,
|
|
76270
|
+
kind: payload.kind,
|
|
76271
|
+
current_price: payload.current_price,
|
|
76272
|
+
position: payload.position,
|
|
76273
|
+
entry_orders: snapshot.entry_orders,
|
|
76274
|
+
stop_orders: snapshot.stop_orders,
|
|
76275
|
+
take_profit_orders: snapshot.take_profit_orders
|
|
76276
|
+
};
|
|
76277
|
+
}
|
|
76278
|
+
function compareFuturesReplayOrders(payload) {
|
|
76279
|
+
const entry = buildComparisonBucket({
|
|
76280
|
+
expected: payload.entry_orders,
|
|
76281
|
+
actual: payload.snapshot.entry_orders
|
|
76282
|
+
});
|
|
76283
|
+
const stop = buildComparisonBucket({
|
|
76284
|
+
expected: payload.stop_orders,
|
|
76285
|
+
actual: payload.snapshot.stop_orders
|
|
76286
|
+
});
|
|
76287
|
+
const take_profit = buildComparisonBucket({
|
|
76288
|
+
expected: payload.take_profit_orders,
|
|
76289
|
+
actual: payload.snapshot.take_profit_orders
|
|
76290
|
+
});
|
|
76291
|
+
return {
|
|
76292
|
+
matches: entry.missing.length === 0 && entry.extra.length === 0 && stop.missing.length === 0 && stop.extra.length === 0 && take_profit.missing.length === 0 && take_profit.extra.length === 0,
|
|
76293
|
+
entry,
|
|
76294
|
+
stop,
|
|
76295
|
+
take_profit
|
|
76296
|
+
};
|
|
76297
|
+
}
|
|
76298
|
+
function buildFuturesReplayPlan(payload) {
|
|
76299
|
+
return {
|
|
76300
|
+
symbol: payload.symbol,
|
|
76301
|
+
kind: payload.kind,
|
|
76302
|
+
orders_to_cancel: payload.snapshot.open_orders,
|
|
76303
|
+
orders_to_create: {
|
|
76304
|
+
entry: sortReplayOrders(payload.entry_orders),
|
|
76305
|
+
stop: sortReplayOrders(payload.stop_orders),
|
|
76306
|
+
take_profit: sortReplayOrders(payload.take_profit_orders)
|
|
76307
|
+
}
|
|
76308
|
+
};
|
|
76309
|
+
}
|
|
76310
|
+
function normalizeFuturesReplayInput(payload) {
|
|
76311
|
+
return {
|
|
76312
|
+
entry_orders: normalizeRequestedEntryOrders(payload.entry_orders),
|
|
76313
|
+
stop_orders: normalizeRequestedStopOrders({
|
|
76314
|
+
kind: payload.kind,
|
|
76315
|
+
orders: payload.stop_orders
|
|
76316
|
+
}),
|
|
76317
|
+
take_profit_orders: normalizeRequestedTakeProfitOrders(payload.take_profit_orders)
|
|
76318
|
+
};
|
|
76319
|
+
}
|
|
76320
|
+
|
|
76321
|
+
// src/exchanges/binance/spot-margin-hedge.ts
|
|
76322
|
+
function roundNumber2(value2, digits = 8) {
|
|
76323
|
+
return Number(value2.toFixed(digits));
|
|
76324
|
+
}
|
|
76325
|
+
var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
|
|
76326
|
+
function normalizeOrderInput2(order) {
|
|
76327
|
+
return {
|
|
76328
|
+
price: roundNumber2(order.price),
|
|
76329
|
+
quantity: roundNumber2(order.quantity)
|
|
76142
76330
|
};
|
|
76143
76331
|
}
|
|
76144
76332
|
function sortOrderInputs(orders) {
|
|
76145
|
-
return [...orders].map(
|
|
76333
|
+
return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
|
|
76146
76334
|
}
|
|
76147
76335
|
function normalizeOrderForComparison(order) {
|
|
76148
76336
|
const type = (order.type || "limit").toLowerCase();
|
|
@@ -76151,9 +76339,9 @@ function normalizeOrderForComparison(order) {
|
|
|
76151
76339
|
return {
|
|
76152
76340
|
side: order.side,
|
|
76153
76341
|
type,
|
|
76154
|
-
price:
|
|
76155
|
-
quantity:
|
|
76156
|
-
stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ?
|
|
76342
|
+
price: roundNumber2(order.price),
|
|
76343
|
+
quantity: roundNumber2(order.quantity),
|
|
76344
|
+
stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber2(parsed_stop) : undefined
|
|
76157
76345
|
};
|
|
76158
76346
|
}
|
|
76159
76347
|
function normalizeOrders(orders) {
|
|
@@ -76176,7 +76364,7 @@ function toPlannedOrders(orders) {
|
|
|
76176
76364
|
stop: order.stop
|
|
76177
76365
|
}));
|
|
76178
76366
|
}
|
|
76179
|
-
function
|
|
76367
|
+
function buildOrderKey2(order) {
|
|
76180
76368
|
return [
|
|
76181
76369
|
order.side,
|
|
76182
76370
|
order.type,
|
|
@@ -76186,8 +76374,8 @@ function buildOrderKey(order) {
|
|
|
76186
76374
|
].join(":");
|
|
76187
76375
|
}
|
|
76188
76376
|
function buildDiff(options) {
|
|
76189
|
-
const expectedKeys = options.expected.map(
|
|
76190
|
-
const actualKeys = options.actual.map(
|
|
76377
|
+
const expectedKeys = options.expected.map(buildOrderKey2);
|
|
76378
|
+
const actualKeys = options.actual.map(buildOrderKey2);
|
|
76191
76379
|
return {
|
|
76192
76380
|
missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
|
|
76193
76381
|
extra: actualKeys.filter((key) => !expectedKeys.includes(key))
|
|
@@ -76199,10 +76387,10 @@ function getQuoteAsset(symbol) {
|
|
|
76199
76387
|
return target || symbol.slice(-4);
|
|
76200
76388
|
}
|
|
76201
76389
|
function sumNotional(orders) {
|
|
76202
|
-
return
|
|
76390
|
+
return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
|
|
76203
76391
|
}
|
|
76204
76392
|
function sumQuantity(orders) {
|
|
76205
|
-
return
|
|
76393
|
+
return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
|
|
76206
76394
|
}
|
|
76207
76395
|
function buildLongOrderPlacement(options) {
|
|
76208
76396
|
const { order, current_price } = options;
|
|
@@ -76211,7 +76399,7 @@ function buildLongOrderPlacement(options) {
|
|
|
76211
76399
|
side: "buy",
|
|
76212
76400
|
type: "stop_loss_limit",
|
|
76213
76401
|
price: order.price,
|
|
76214
|
-
stop:
|
|
76402
|
+
stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
|
|
76215
76403
|
quantity: order.quantity
|
|
76216
76404
|
};
|
|
76217
76405
|
}
|
|
@@ -76279,11 +76467,11 @@ function buildSpotMarginHedgePlan(options) {
|
|
|
76279
76467
|
}));
|
|
76280
76468
|
const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
|
|
76281
76469
|
const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
|
|
76282
|
-
const borrow_delta =
|
|
76470
|
+
const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
|
|
76283
76471
|
const spot_long_notional = sumNotional(spot_long_orders);
|
|
76284
|
-
const transfer_to_spot_amount =
|
|
76472
|
+
const transfer_to_spot_amount = roundNumber2(Math.max(0, options.margin_type === "cross" ? options.borrow_amount : spot_long_notional - options.snapshot.spot_quote_free));
|
|
76285
76473
|
const short_quantity = sumQuantity(short_orders);
|
|
76286
|
-
const transfer_base_to_spot_amount =
|
|
76474
|
+
const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
|
|
76287
76475
|
return {
|
|
76288
76476
|
borrow_amount: options.borrow_amount,
|
|
76289
76477
|
borrow_delta,
|
|
@@ -76785,17 +76973,17 @@ async function createMarginLimitOrdersParallel(client, symbol, priceFormat, quan
|
|
|
76785
76973
|
return Object.fromEntries(Object.entries(v).filter(([, value2]) => value2 !== undefined));
|
|
76786
76974
|
};
|
|
76787
76975
|
const newOrders = orders.map(createMarginOrder);
|
|
76788
|
-
const
|
|
76789
|
-
const
|
|
76976
|
+
const results = [];
|
|
76977
|
+
for (const orderPayload of newOrders) {
|
|
76790
76978
|
try {
|
|
76791
76979
|
const result = await client.marginAccountNewOrder(orderPayload);
|
|
76792
76980
|
console.log("Margin order result:", result);
|
|
76793
|
-
|
|
76981
|
+
results.push(result);
|
|
76794
76982
|
} catch (error) {
|
|
76795
76983
|
console.error("Error processing margin order:", error);
|
|
76796
76984
|
throw error;
|
|
76797
76985
|
}
|
|
76798
|
-
}
|
|
76986
|
+
}
|
|
76799
76987
|
return results;
|
|
76800
76988
|
}
|
|
76801
76989
|
async function getIsolatedMarginAccountInfo(client, symbol) {
|
|
@@ -77942,6 +78130,30 @@ class BinanceExchange extends BaseExchange {
|
|
|
77942
78130
|
throw error;
|
|
77943
78131
|
}
|
|
77944
78132
|
}
|
|
78133
|
+
async getFuturesSymbolFormats(symbol) {
|
|
78134
|
+
if (typeof this.client.getExchangeInfo !== "function") {
|
|
78135
|
+
return {
|
|
78136
|
+
price_places: "%.8f",
|
|
78137
|
+
decimal_places: "%.8f"
|
|
78138
|
+
};
|
|
78139
|
+
}
|
|
78140
|
+
try {
|
|
78141
|
+
const exchange_info = await this.client.getExchangeInfo();
|
|
78142
|
+
const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
|
|
78143
|
+
const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
|
|
78144
|
+
const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
|
|
78145
|
+
return {
|
|
78146
|
+
price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
|
|
78147
|
+
decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
|
|
78148
|
+
};
|
|
78149
|
+
} catch (error) {
|
|
78150
|
+
console.error("Error fetching futures symbol formats:", error);
|
|
78151
|
+
return {
|
|
78152
|
+
price_places: "%.8f",
|
|
78153
|
+
decimal_places: "%.8f"
|
|
78154
|
+
};
|
|
78155
|
+
}
|
|
78156
|
+
}
|
|
77945
78157
|
async getSpotSymbolFormats(symbol) {
|
|
77946
78158
|
if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
|
|
77947
78159
|
return {
|
|
@@ -77996,28 +78208,146 @@ class BinanceExchange extends BaseExchange {
|
|
|
77996
78208
|
}
|
|
77997
78209
|
return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
|
|
77998
78210
|
}
|
|
78211
|
+
async getFuturesReplaySnapshot(payload) {
|
|
78212
|
+
const symbol = payload.symbol.toUpperCase();
|
|
78213
|
+
const [open_orders, position_info, current_price] = await Promise.all([
|
|
78214
|
+
this.getOpenOrders({ symbol }),
|
|
78215
|
+
this.getPositionInfo(symbol),
|
|
78216
|
+
this.getCurrentPrice(symbol)
|
|
78217
|
+
]);
|
|
78218
|
+
return buildFuturesReplaySnapshot({
|
|
78219
|
+
symbol,
|
|
78220
|
+
kind: payload.kind,
|
|
78221
|
+
current_price,
|
|
78222
|
+
position: {
|
|
78223
|
+
size: position_info[payload.kind]?.size || 0
|
|
78224
|
+
},
|
|
78225
|
+
open_orders
|
|
78226
|
+
});
|
|
78227
|
+
}
|
|
78228
|
+
async previewFuturesReplay(payload) {
|
|
78229
|
+
const snapshot = payload.snapshot ? {
|
|
78230
|
+
...payload.snapshot,
|
|
78231
|
+
current_price: payload.snapshot.current_price ?? await this.getCurrentPrice(payload.symbol)
|
|
78232
|
+
} : await this.getFuturesReplaySnapshot({
|
|
78233
|
+
symbol: payload.symbol,
|
|
78234
|
+
kind: payload.kind
|
|
78235
|
+
});
|
|
78236
|
+
const shouldInferEntryOrders = !payload.entry_orders || payload.entry_orders.length === 0;
|
|
78237
|
+
const shouldInferStopOrders = !payload.stop_orders || payload.stop_orders.length === 0;
|
|
78238
|
+
const shouldInferTakeProfitOrders = !payload.take_profit_orders || payload.take_profit_orders.length === 0;
|
|
78239
|
+
const inferred_input = shouldInferEntryOrders || shouldInferStopOrders || shouldInferTakeProfitOrders ? inferFuturesReplayInputFromLiveOrders({
|
|
78240
|
+
symbol: payload.symbol,
|
|
78241
|
+
kind: payload.kind,
|
|
78242
|
+
current_price: snapshot.current_price,
|
|
78243
|
+
position: snapshot.position,
|
|
78244
|
+
open_orders: snapshot.open_orders
|
|
78245
|
+
}) : undefined;
|
|
78246
|
+
const explicit_input = normalizeFuturesReplayInput({
|
|
78247
|
+
kind: payload.kind,
|
|
78248
|
+
entry_orders: payload.entry_orders || [],
|
|
78249
|
+
stop_orders: payload.stop_orders || [],
|
|
78250
|
+
take_profit_orders: payload.take_profit_orders || []
|
|
78251
|
+
});
|
|
78252
|
+
const entry_orders = shouldInferEntryOrders ? inferred_input?.entry_orders || [] : explicit_input.entry_orders;
|
|
78253
|
+
const stop_orders = shouldInferStopOrders ? inferred_input?.stop_orders || [] : explicit_input.stop_orders;
|
|
78254
|
+
const take_profit_orders = shouldInferTakeProfitOrders ? inferred_input?.take_profit_orders || [] : explicit_input.take_profit_orders;
|
|
78255
|
+
const requested_input = {
|
|
78256
|
+
symbol: payload.symbol,
|
|
78257
|
+
kind: payload.kind,
|
|
78258
|
+
entry_orders,
|
|
78259
|
+
stop_orders,
|
|
78260
|
+
take_profit_orders
|
|
78261
|
+
};
|
|
78262
|
+
const plan = buildFuturesReplayPlan({
|
|
78263
|
+
...requested_input,
|
|
78264
|
+
snapshot
|
|
78265
|
+
});
|
|
78266
|
+
const comparison = compareFuturesReplayOrders({
|
|
78267
|
+
...requested_input,
|
|
78268
|
+
snapshot
|
|
78269
|
+
});
|
|
78270
|
+
return {
|
|
78271
|
+
snapshot,
|
|
78272
|
+
inferred_input,
|
|
78273
|
+
requested_input,
|
|
78274
|
+
plan,
|
|
78275
|
+
comparison
|
|
78276
|
+
};
|
|
78277
|
+
}
|
|
78278
|
+
async placeFuturesReplay(payload) {
|
|
78279
|
+
const preview = await this.previewFuturesReplay(payload);
|
|
78280
|
+
if (!payload.place) {
|
|
78281
|
+
return preview;
|
|
78282
|
+
}
|
|
78283
|
+
const symbol = payload.symbol.toUpperCase();
|
|
78284
|
+
await cancelAllOrders(this.client, symbol, {
|
|
78285
|
+
kind: payload.kind
|
|
78286
|
+
});
|
|
78287
|
+
const refreshed_snapshot = await this.getFuturesReplaySnapshot({
|
|
78288
|
+
symbol,
|
|
78289
|
+
kind: payload.kind
|
|
78290
|
+
});
|
|
78291
|
+
const execution_plan = buildFuturesReplayPlan({
|
|
78292
|
+
symbol,
|
|
78293
|
+
kind: payload.kind,
|
|
78294
|
+
entry_orders: preview.requested_input.entry_orders,
|
|
78295
|
+
stop_orders: preview.requested_input.stop_orders,
|
|
78296
|
+
take_profit_orders: preview.requested_input.take_profit_orders,
|
|
78297
|
+
snapshot: refreshed_snapshot
|
|
78298
|
+
});
|
|
78299
|
+
const { price_places, decimal_places } = await this.getFuturesSymbolFormats(symbol);
|
|
78300
|
+
const entry_orders = execution_plan.orders_to_create.entry.map((order) => ({
|
|
78301
|
+
...order,
|
|
78302
|
+
side: payload.kind === "long" ? "buy" : "sell",
|
|
78303
|
+
kind: payload.kind
|
|
78304
|
+
}));
|
|
78305
|
+
const stop_orders = execution_plan.orders_to_create.stop.map((order) => ({
|
|
78306
|
+
...order,
|
|
78307
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
78308
|
+
kind: payload.kind,
|
|
78309
|
+
type: order.type || "STOP"
|
|
78310
|
+
}));
|
|
78311
|
+
const take_profit_orders = execution_plan.orders_to_create.take_profit.map((order) => ({
|
|
78312
|
+
...order,
|
|
78313
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
78314
|
+
kind: payload.kind,
|
|
78315
|
+
...order.stop ? { type: order.type || "TAKE_PROFIT" } : {}
|
|
78316
|
+
}));
|
|
78317
|
+
const execution = entry_orders.length > 0 || stop_orders.length > 0 || take_profit_orders.length > 0 ? await createLimitPurchaseOrdersParallel(this.client, symbol, price_places, decimal_places, [...entry_orders, ...stop_orders, ...take_profit_orders]) : [];
|
|
78318
|
+
return {
|
|
78319
|
+
...preview,
|
|
78320
|
+
execution_plan,
|
|
78321
|
+
refreshed_snapshot,
|
|
78322
|
+
execution
|
|
78323
|
+
};
|
|
78324
|
+
}
|
|
78325
|
+
async placeFuturesExactTrade(payload) {
|
|
78326
|
+
return await this.placeFuturesReplay(payload);
|
|
78327
|
+
}
|
|
77999
78328
|
async getSpotMarginHedgeSnapshot(payload) {
|
|
78000
78329
|
if (!this.main_client) {
|
|
78001
78330
|
throw new Error("Main client not available for spot and margin trading");
|
|
78002
78331
|
}
|
|
78003
|
-
if (payload.margin_type !== "isolated") {
|
|
78004
|
-
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
|
|
78005
|
-
}
|
|
78006
78332
|
const symbol = payload.symbol.toUpperCase();
|
|
78007
78333
|
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
78008
78334
|
const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
78009
78335
|
const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
|
|
78010
|
-
const
|
|
78336
|
+
const is_isolated = payload.margin_type === "isolated";
|
|
78337
|
+
const [spot_orders, margin_orders, spot_balances, margin_account] = await Promise.all([
|
|
78011
78338
|
this.getSpotOpenOrders(symbol),
|
|
78012
|
-
this.getMarginOpenOrders(symbol,
|
|
78339
|
+
this.getMarginOpenOrders(symbol, is_isolated),
|
|
78013
78340
|
this.getSpotBalances([baseAsset, quoteAsset]),
|
|
78014
|
-
this.getIsolatedMarginPosition(symbol)
|
|
78341
|
+
is_isolated ? this.getIsolatedMarginPosition(symbol) : this.getCrossMarginAccount()
|
|
78015
78342
|
]);
|
|
78016
78343
|
const current_price = await this.getSpotCurrentPrice(symbol);
|
|
78017
78344
|
const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
|
|
78018
78345
|
const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
|
|
78019
|
-
const
|
|
78020
|
-
const
|
|
78346
|
+
const isolated_margin_account = is_isolated ? margin_account : undefined;
|
|
78347
|
+
const cross_margin_account = !is_isolated ? margin_account : undefined;
|
|
78348
|
+
const isolated_asset = isolated_margin_account?.assets?.[0];
|
|
78349
|
+
const cross_quote_asset = !is_isolated ? cross_margin_account?.userAssets?.find((asset) => asset.asset?.toUpperCase?.() === quoteAsset) : undefined;
|
|
78350
|
+
const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || cross_quote_asset?.borrowed?.toString?.() || "0");
|
|
78021
78351
|
return {
|
|
78022
78352
|
symbol,
|
|
78023
78353
|
margin_type: payload.margin_type,
|
|
@@ -78030,7 +78360,8 @@ class BinanceExchange extends BaseExchange {
|
|
|
78030
78360
|
margin_orders,
|
|
78031
78361
|
current_price,
|
|
78032
78362
|
spot_balances,
|
|
78033
|
-
isolated_margin_position
|
|
78363
|
+
isolated_margin_position: isolated_margin_account,
|
|
78364
|
+
cross_margin_account
|
|
78034
78365
|
};
|
|
78035
78366
|
}
|
|
78036
78367
|
async previewSpotMarginHedge(payload) {
|
|
@@ -78087,14 +78418,12 @@ class BinanceExchange extends BaseExchange {
|
|
|
78087
78418
|
if (!this.main_client) {
|
|
78088
78419
|
throw new Error("Main client not available for spot and margin trading");
|
|
78089
78420
|
}
|
|
78090
|
-
if (payload.margin_type !== "isolated") {
|
|
78091
|
-
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
|
|
78092
|
-
}
|
|
78093
78421
|
const symbol = payload.symbol.toUpperCase();
|
|
78094
78422
|
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
78095
78423
|
const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
78096
78424
|
const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
|
|
78097
78425
|
const requested_input = preview.requested_input;
|
|
78426
|
+
const is_isolated = payload.margin_type === "isolated";
|
|
78098
78427
|
if (preview.snapshot.spot_orders.length > 0) {
|
|
78099
78428
|
await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
|
|
78100
78429
|
orderId: order.orderId || order.order_id || order.id,
|
|
@@ -78102,7 +78431,7 @@ class BinanceExchange extends BaseExchange {
|
|
|
78102
78431
|
})));
|
|
78103
78432
|
}
|
|
78104
78433
|
if (preview.snapshot.margin_orders.length > 0) {
|
|
78105
|
-
await this.cancelMarginOrders(symbol,
|
|
78434
|
+
await this.cancelMarginOrders(symbol, is_isolated, preview.snapshot.margin_orders.map((order) => ({
|
|
78106
78435
|
orderId: order.orderId || order.order_id || order.id,
|
|
78107
78436
|
origClientOrderId: order.origClientOrderId
|
|
78108
78437
|
})));
|
|
@@ -78124,50 +78453,120 @@ class BinanceExchange extends BaseExchange {
|
|
|
78124
78453
|
});
|
|
78125
78454
|
let borrow_result = null;
|
|
78126
78455
|
if (execution_plan.borrow_delta > 0) {
|
|
78127
|
-
|
|
78456
|
+
const borrow_payload = {
|
|
78128
78457
|
asset: quote_asset,
|
|
78129
78458
|
amount: execution_plan.borrow_delta,
|
|
78130
|
-
isIsolated: "TRUE",
|
|
78131
|
-
symbol,
|
|
78132
78459
|
type: "BORROW"
|
|
78460
|
+
};
|
|
78461
|
+
if (is_isolated) {
|
|
78462
|
+
borrow_payload.isIsolated = "TRUE";
|
|
78463
|
+
borrow_payload.symbol = symbol;
|
|
78464
|
+
}
|
|
78465
|
+
borrow_result = await this.main_client.submitMarginAccountBorrowRepay(borrow_payload);
|
|
78466
|
+
}
|
|
78467
|
+
async function getCrossTransferAmount(asset, amount) {
|
|
78468
|
+
if (is_isolated) {
|
|
78469
|
+
return amount;
|
|
78470
|
+
}
|
|
78471
|
+
if (!this.main_client || typeof this.main_client.queryMaxTransferOutAmount !== "function") {
|
|
78472
|
+
return amount;
|
|
78473
|
+
}
|
|
78474
|
+
const response = await this.main_client.queryMaxTransferOutAmount({
|
|
78475
|
+
asset
|
|
78133
78476
|
});
|
|
78477
|
+
const allowed_amount = parseFloat(response?.amount?.toString?.() || "0");
|
|
78478
|
+
if (!Number.isFinite(allowed_amount)) {
|
|
78479
|
+
return amount;
|
|
78480
|
+
}
|
|
78481
|
+
return Math.max(0, Math.min(amount, allowed_amount));
|
|
78134
78482
|
}
|
|
78135
78483
|
let quote_transfer_result = null;
|
|
78484
|
+
let quote_transfer_amount = execution_plan.transfer_to_spot_amount;
|
|
78136
78485
|
if (execution_plan.transfer_to_spot_amount > 0) {
|
|
78137
|
-
|
|
78138
|
-
|
|
78139
|
-
|
|
78140
|
-
|
|
78141
|
-
|
|
78142
|
-
|
|
78143
|
-
|
|
78486
|
+
quote_transfer_amount = await getCrossTransferAmount.call(this, quote_asset, execution_plan.transfer_to_spot_amount);
|
|
78487
|
+
if (quote_transfer_amount > 0) {
|
|
78488
|
+
quote_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
|
|
78489
|
+
asset: quote_asset,
|
|
78490
|
+
amount: quote_transfer_amount,
|
|
78491
|
+
symbol,
|
|
78492
|
+
transFrom: "ISOLATED_MARGIN",
|
|
78493
|
+
transTo: "SPOT"
|
|
78494
|
+
}) : await this.main_client.submitUniversalTransfer({
|
|
78495
|
+
type: "MARGIN_MAIN",
|
|
78496
|
+
asset: quote_asset,
|
|
78497
|
+
amount: quote_transfer_amount
|
|
78498
|
+
});
|
|
78499
|
+
}
|
|
78144
78500
|
}
|
|
78145
78501
|
let base_transfer_result = null;
|
|
78502
|
+
let base_transfer_amount = execution_plan.transfer_base_to_spot_amount;
|
|
78146
78503
|
if (execution_plan.transfer_base_to_spot_amount > 0) {
|
|
78147
|
-
|
|
78148
|
-
|
|
78149
|
-
|
|
78150
|
-
|
|
78151
|
-
|
|
78152
|
-
|
|
78153
|
-
|
|
78504
|
+
base_transfer_amount = await getCrossTransferAmount.call(this, base_asset, execution_plan.transfer_base_to_spot_amount);
|
|
78505
|
+
if (base_transfer_amount > 0) {
|
|
78506
|
+
base_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
|
|
78507
|
+
asset: base_asset,
|
|
78508
|
+
amount: base_transfer_amount,
|
|
78509
|
+
symbol,
|
|
78510
|
+
transFrom: "ISOLATED_MARGIN",
|
|
78511
|
+
transTo: "SPOT"
|
|
78512
|
+
}) : await this.main_client.submitUniversalTransfer({
|
|
78513
|
+
type: "MARGIN_MAIN",
|
|
78514
|
+
asset: base_asset,
|
|
78515
|
+
amount: base_transfer_amount
|
|
78516
|
+
});
|
|
78517
|
+
}
|
|
78154
78518
|
}
|
|
78155
|
-
const
|
|
78519
|
+
const spot_quote_available = refreshed_snapshot.spot_quote_free + quote_transfer_amount;
|
|
78520
|
+
const requested_max_spot_buy_orders = requested_input.max_spot_buy_orders ?? 5;
|
|
78521
|
+
const sorted_long_orders = [...requested_input.long_orders].sort((a, b) => a.price - b.price);
|
|
78522
|
+
let affordable_spot_buy_orders = 0;
|
|
78523
|
+
let affordable_spot_buy_notional = 0;
|
|
78524
|
+
for (const order of sorted_long_orders.slice(0, requested_max_spot_buy_orders)) {
|
|
78525
|
+
const next_notional = affordable_spot_buy_notional + order.price * order.quantity;
|
|
78526
|
+
if (next_notional <= spot_quote_available + 0.00000001) {
|
|
78527
|
+
affordable_spot_buy_orders += 1;
|
|
78528
|
+
affordable_spot_buy_notional = next_notional;
|
|
78529
|
+
} else {
|
|
78530
|
+
break;
|
|
78531
|
+
}
|
|
78532
|
+
}
|
|
78533
|
+
const placement_plan = buildSpotMarginHedgePlan({
|
|
78534
|
+
borrow_amount: requested_input.borrow_amount,
|
|
78535
|
+
symbol: requested_input.symbol,
|
|
78536
|
+
margin_type: requested_input.margin_type,
|
|
78537
|
+
long_orders: requested_input.long_orders,
|
|
78538
|
+
short_orders: requested_input.short_orders,
|
|
78539
|
+
current_price: refreshed_snapshot.current_price,
|
|
78540
|
+
max_spot_buy_orders: affordable_spot_buy_orders,
|
|
78541
|
+
snapshot: {
|
|
78542
|
+
...refreshed_snapshot,
|
|
78543
|
+
borrowed_quote_amount: refreshed_snapshot.borrowed_quote_amount + execution_plan.borrow_delta,
|
|
78544
|
+
spot_quote_free: spot_quote_available,
|
|
78545
|
+
spot_base_free: refreshed_snapshot.spot_base_free + base_transfer_amount
|
|
78546
|
+
}
|
|
78547
|
+
});
|
|
78548
|
+
const spot_result = placement_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
|
|
78156
78549
|
symbol,
|
|
78157
|
-
orders:
|
|
78550
|
+
orders: placement_plan.orders_to_create.spot,
|
|
78158
78551
|
price_places: symbol_formats.price_places,
|
|
78159
78552
|
decimal_places: symbol_formats.decimal_places
|
|
78160
78553
|
}) : [];
|
|
78161
|
-
const margin_result =
|
|
78554
|
+
const margin_result = placement_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
|
|
78162
78555
|
symbol,
|
|
78163
|
-
orders:
|
|
78164
|
-
isIsolated:
|
|
78556
|
+
orders: placement_plan.orders_to_create.margin,
|
|
78557
|
+
isIsolated: is_isolated,
|
|
78165
78558
|
price_places: symbol_formats.price_places,
|
|
78166
78559
|
decimal_places: symbol_formats.decimal_places
|
|
78167
78560
|
}) : [];
|
|
78168
78561
|
return {
|
|
78169
78562
|
...preview,
|
|
78170
|
-
execution_plan
|
|
78563
|
+
execution_plan: {
|
|
78564
|
+
...placement_plan,
|
|
78565
|
+
borrow_amount: execution_plan.borrow_amount,
|
|
78566
|
+
borrow_delta: execution_plan.borrow_delta,
|
|
78567
|
+
transfer_to_spot_amount: quote_transfer_amount,
|
|
78568
|
+
transfer_base_to_spot_amount: base_transfer_amount
|
|
78569
|
+
},
|
|
78171
78570
|
refreshed_snapshot,
|
|
78172
78571
|
execution: {
|
|
78173
78572
|
borrow: borrow_result ? {
|
|
@@ -78177,12 +78576,12 @@ class BinanceExchange extends BaseExchange {
|
|
|
78177
78576
|
} : null,
|
|
78178
78577
|
quote_transfer: quote_transfer_result ? {
|
|
78179
78578
|
asset: quote_asset,
|
|
78180
|
-
amount:
|
|
78579
|
+
amount: quote_transfer_amount,
|
|
78181
78580
|
response: quote_transfer_result
|
|
78182
78581
|
} : null,
|
|
78183
78582
|
base_transfer: base_transfer_result ? {
|
|
78184
78583
|
asset: base_asset,
|
|
78185
|
-
amount:
|
|
78584
|
+
amount: base_transfer_amount,
|
|
78186
78585
|
response: base_transfer_result
|
|
78187
78586
|
} : null,
|
|
78188
78587
|
spot_orders: spot_result,
|