@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/index.js
CHANGED
|
@@ -71936,9 +71936,18 @@ class BaseExchange {
|
|
|
71936
71936
|
async previewSpotMarginHedge(_payload) {
|
|
71937
71937
|
throw new Error("Spot margin hedge preview is not supported on this exchange");
|
|
71938
71938
|
}
|
|
71939
|
+
async previewFuturesReplay(_payload) {
|
|
71940
|
+
throw new Error("Futures replay preview is not supported on this exchange");
|
|
71941
|
+
}
|
|
71939
71942
|
async placeSpotMarginHedge(_payload) {
|
|
71940
71943
|
throw new Error("Spot margin hedge placement is not supported on this exchange");
|
|
71941
71944
|
}
|
|
71945
|
+
async placeFuturesReplay(_payload) {
|
|
71946
|
+
throw new Error("Futures replay placement is not supported on this exchange");
|
|
71947
|
+
}
|
|
71948
|
+
async placeFuturesExactTrade(_payload) {
|
|
71949
|
+
throw new Error("Futures exact trade placement is not supported on this exchange");
|
|
71950
|
+
}
|
|
71942
71951
|
async placeSpotLimitOrders(_payload) {}
|
|
71943
71952
|
async buildCumulative(payload) {
|
|
71944
71953
|
const {
|
|
@@ -72347,19 +72356,198 @@ class BaseExchange {
|
|
|
72347
72356
|
// src/exchanges/binance/index.ts
|
|
72348
72357
|
var import_p_limit = __toESM(require_p_limit(), 1);
|
|
72349
72358
|
|
|
72350
|
-
// src/exchanges/binance/
|
|
72359
|
+
// src/exchanges/binance/futures-replay.ts
|
|
72351
72360
|
function roundNumber(value2, digits = 8) {
|
|
72352
72361
|
return Number(value2.toFixed(digits));
|
|
72353
72362
|
}
|
|
72354
|
-
|
|
72363
|
+
function parseNumber2(value2) {
|
|
72364
|
+
if (value2 === undefined || value2 === null || value2 === "")
|
|
72365
|
+
return;
|
|
72366
|
+
const parsed = Number(value2);
|
|
72367
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
72368
|
+
return;
|
|
72369
|
+
return roundNumber(parsed);
|
|
72370
|
+
}
|
|
72355
72371
|
function normalizeOrderInput(order) {
|
|
72372
|
+
const stop = parseNumber2(order.stop) ?? parseNumber2(order.stopPrice) ?? parseNumber2(order.triggerPrice);
|
|
72373
|
+
const type = order.type ? order.type.toUpperCase() : undefined;
|
|
72374
|
+
const quantity = order.quantity ?? order.qty ?? 0;
|
|
72356
72375
|
return {
|
|
72357
|
-
price: roundNumber(order.price),
|
|
72358
|
-
quantity: roundNumber(
|
|
72376
|
+
price: roundNumber(Number(order.price)),
|
|
72377
|
+
quantity: roundNumber(Number(quantity)),
|
|
72378
|
+
...stop ? { stop } : {},
|
|
72379
|
+
...type && type !== "LIMIT" ? { type } : {}
|
|
72380
|
+
};
|
|
72381
|
+
}
|
|
72382
|
+
function sortReplayOrders(orders) {
|
|
72383
|
+
return [...orders].sort((left, right) => {
|
|
72384
|
+
const leftTrigger = left.stop ?? left.price;
|
|
72385
|
+
const rightTrigger = right.stop ?? right.price;
|
|
72386
|
+
if (leftTrigger !== rightTrigger)
|
|
72387
|
+
return leftTrigger - rightTrigger;
|
|
72388
|
+
if (left.price !== right.price)
|
|
72389
|
+
return left.price - right.price;
|
|
72390
|
+
return left.quantity - right.quantity;
|
|
72391
|
+
});
|
|
72392
|
+
}
|
|
72393
|
+
function buildReplayStopLimitPrice(payload) {
|
|
72394
|
+
const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
|
|
72395
|
+
return roundNumber(payload.kind === "long" ? payload.stop * spread ** -1 : payload.stop * spread);
|
|
72396
|
+
}
|
|
72397
|
+
function normalizeRequestedEntryOrders(orders) {
|
|
72398
|
+
return sortReplayOrders(orders.map((order) => ({
|
|
72399
|
+
price: roundNumber(Number(order.price)),
|
|
72400
|
+
quantity: roundNumber(Number(order.quantity))
|
|
72401
|
+
})));
|
|
72402
|
+
}
|
|
72403
|
+
function normalizeRequestedStopOrders(payload) {
|
|
72404
|
+
return sortReplayOrders(payload.orders.map((order) => {
|
|
72405
|
+
const stop = parseNumber2(order.stop) ?? roundNumber(Number(order.price));
|
|
72406
|
+
return {
|
|
72407
|
+
price: order.price !== undefined ? roundNumber(Number(order.price)) : buildReplayStopLimitPrice({
|
|
72408
|
+
kind: payload.kind,
|
|
72409
|
+
stop
|
|
72410
|
+
}),
|
|
72411
|
+
quantity: roundNumber(Number(order.quantity)),
|
|
72412
|
+
stop,
|
|
72413
|
+
type: (order.type || "STOP").toUpperCase()
|
|
72414
|
+
};
|
|
72415
|
+
}));
|
|
72416
|
+
}
|
|
72417
|
+
function normalizeRequestedTakeProfitOrders(orders) {
|
|
72418
|
+
return sortReplayOrders(orders.map((order) => {
|
|
72419
|
+
const stop = parseNumber2(order.stop);
|
|
72420
|
+
const type = order.type?.toUpperCase();
|
|
72421
|
+
return {
|
|
72422
|
+
price: roundNumber(Number(order.price)),
|
|
72423
|
+
quantity: roundNumber(Number(order.quantity)),
|
|
72424
|
+
...stop ? { stop } : {},
|
|
72425
|
+
...type ? { type } : stop ? { type: "TAKE_PROFIT" } : {}
|
|
72426
|
+
};
|
|
72427
|
+
}));
|
|
72428
|
+
}
|
|
72429
|
+
function buildOrderKey(order) {
|
|
72430
|
+
return [
|
|
72431
|
+
order.type || "LIMIT",
|
|
72432
|
+
order.price,
|
|
72433
|
+
order.quantity,
|
|
72434
|
+
order.stop ?? ""
|
|
72435
|
+
].join(":");
|
|
72436
|
+
}
|
|
72437
|
+
function buildComparisonBucket(options) {
|
|
72438
|
+
const expected = sortReplayOrders(options.expected);
|
|
72439
|
+
const actual = sortReplayOrders(options.actual);
|
|
72440
|
+
const expected_keys = expected.map(buildOrderKey);
|
|
72441
|
+
const actual_keys = actual.map(buildOrderKey);
|
|
72442
|
+
return {
|
|
72443
|
+
expected,
|
|
72444
|
+
actual,
|
|
72445
|
+
missing: expected_keys.filter((key) => !actual_keys.includes(key)),
|
|
72446
|
+
extra: actual_keys.filter((key) => !expected_keys.includes(key))
|
|
72447
|
+
};
|
|
72448
|
+
}
|
|
72449
|
+
function buildFuturesReplaySnapshot(payload) {
|
|
72450
|
+
const entry_side = payload.kind === "long" ? "buy" : "sell";
|
|
72451
|
+
const close_side = payload.kind === "long" ? "sell" : "buy";
|
|
72452
|
+
const entry_orders = [];
|
|
72453
|
+
const stop_orders = [];
|
|
72454
|
+
const take_profit_orders = [];
|
|
72455
|
+
for (const order of payload.open_orders) {
|
|
72456
|
+
if (order.kind !== payload.kind)
|
|
72457
|
+
continue;
|
|
72458
|
+
const normalized_order = normalizeOrderInput(order);
|
|
72459
|
+
const type = (normalized_order.type || "LIMIT").toUpperCase();
|
|
72460
|
+
if (order.side === entry_side) {
|
|
72461
|
+
entry_orders.push(normalized_order);
|
|
72462
|
+
continue;
|
|
72463
|
+
}
|
|
72464
|
+
if (order.side !== close_side)
|
|
72465
|
+
continue;
|
|
72466
|
+
if (type.includes("TAKE_PROFIT") || normalized_order.stop === undefined) {
|
|
72467
|
+
take_profit_orders.push(normalized_order);
|
|
72468
|
+
continue;
|
|
72469
|
+
}
|
|
72470
|
+
stop_orders.push(normalized_order);
|
|
72471
|
+
}
|
|
72472
|
+
return {
|
|
72473
|
+
symbol: payload.symbol,
|
|
72474
|
+
kind: payload.kind,
|
|
72475
|
+
current_price: payload.current_price,
|
|
72476
|
+
position: payload.position,
|
|
72477
|
+
open_orders: payload.open_orders.filter((order) => order.kind === payload.kind),
|
|
72478
|
+
entry_orders: sortReplayOrders(entry_orders),
|
|
72479
|
+
stop_orders: sortReplayOrders(stop_orders),
|
|
72480
|
+
take_profit_orders: sortReplayOrders(take_profit_orders)
|
|
72481
|
+
};
|
|
72482
|
+
}
|
|
72483
|
+
function inferFuturesReplayInputFromLiveOrders(payload) {
|
|
72484
|
+
const snapshot = buildFuturesReplaySnapshot(payload);
|
|
72485
|
+
return {
|
|
72486
|
+
symbol: payload.symbol,
|
|
72487
|
+
kind: payload.kind,
|
|
72488
|
+
current_price: payload.current_price,
|
|
72489
|
+
position: payload.position,
|
|
72490
|
+
entry_orders: snapshot.entry_orders,
|
|
72491
|
+
stop_orders: snapshot.stop_orders,
|
|
72492
|
+
take_profit_orders: snapshot.take_profit_orders
|
|
72493
|
+
};
|
|
72494
|
+
}
|
|
72495
|
+
function compareFuturesReplayOrders(payload) {
|
|
72496
|
+
const entry = buildComparisonBucket({
|
|
72497
|
+
expected: payload.entry_orders,
|
|
72498
|
+
actual: payload.snapshot.entry_orders
|
|
72499
|
+
});
|
|
72500
|
+
const stop = buildComparisonBucket({
|
|
72501
|
+
expected: payload.stop_orders,
|
|
72502
|
+
actual: payload.snapshot.stop_orders
|
|
72503
|
+
});
|
|
72504
|
+
const take_profit = buildComparisonBucket({
|
|
72505
|
+
expected: payload.take_profit_orders,
|
|
72506
|
+
actual: payload.snapshot.take_profit_orders
|
|
72507
|
+
});
|
|
72508
|
+
return {
|
|
72509
|
+
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,
|
|
72510
|
+
entry,
|
|
72511
|
+
stop,
|
|
72512
|
+
take_profit
|
|
72513
|
+
};
|
|
72514
|
+
}
|
|
72515
|
+
function buildFuturesReplayPlan(payload) {
|
|
72516
|
+
return {
|
|
72517
|
+
symbol: payload.symbol,
|
|
72518
|
+
kind: payload.kind,
|
|
72519
|
+
orders_to_cancel: payload.snapshot.open_orders,
|
|
72520
|
+
orders_to_create: {
|
|
72521
|
+
entry: sortReplayOrders(payload.entry_orders),
|
|
72522
|
+
stop: sortReplayOrders(payload.stop_orders),
|
|
72523
|
+
take_profit: sortReplayOrders(payload.take_profit_orders)
|
|
72524
|
+
}
|
|
72525
|
+
};
|
|
72526
|
+
}
|
|
72527
|
+
function normalizeFuturesReplayInput(payload) {
|
|
72528
|
+
return {
|
|
72529
|
+
entry_orders: normalizeRequestedEntryOrders(payload.entry_orders),
|
|
72530
|
+
stop_orders: normalizeRequestedStopOrders({
|
|
72531
|
+
kind: payload.kind,
|
|
72532
|
+
orders: payload.stop_orders
|
|
72533
|
+
}),
|
|
72534
|
+
take_profit_orders: normalizeRequestedTakeProfitOrders(payload.take_profit_orders)
|
|
72535
|
+
};
|
|
72536
|
+
}
|
|
72537
|
+
|
|
72538
|
+
// src/exchanges/binance/spot-margin-hedge.ts
|
|
72539
|
+
function roundNumber2(value2, digits = 8) {
|
|
72540
|
+
return Number(value2.toFixed(digits));
|
|
72541
|
+
}
|
|
72542
|
+
var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
|
|
72543
|
+
function normalizeOrderInput2(order) {
|
|
72544
|
+
return {
|
|
72545
|
+
price: roundNumber2(order.price),
|
|
72546
|
+
quantity: roundNumber2(order.quantity)
|
|
72359
72547
|
};
|
|
72360
72548
|
}
|
|
72361
72549
|
function sortOrderInputs(orders) {
|
|
72362
|
-
return [...orders].map(
|
|
72550
|
+
return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
|
|
72363
72551
|
}
|
|
72364
72552
|
function normalizeOrderForComparison(order) {
|
|
72365
72553
|
const type = (order.type || "limit").toLowerCase();
|
|
@@ -72368,9 +72556,9 @@ function normalizeOrderForComparison(order) {
|
|
|
72368
72556
|
return {
|
|
72369
72557
|
side: order.side,
|
|
72370
72558
|
type,
|
|
72371
|
-
price:
|
|
72372
|
-
quantity:
|
|
72373
|
-
stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ?
|
|
72559
|
+
price: roundNumber2(order.price),
|
|
72560
|
+
quantity: roundNumber2(order.quantity),
|
|
72561
|
+
stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber2(parsed_stop) : undefined
|
|
72374
72562
|
};
|
|
72375
72563
|
}
|
|
72376
72564
|
function normalizeOrders(orders) {
|
|
@@ -72393,7 +72581,7 @@ function toPlannedOrders(orders) {
|
|
|
72393
72581
|
stop: order.stop
|
|
72394
72582
|
}));
|
|
72395
72583
|
}
|
|
72396
|
-
function
|
|
72584
|
+
function buildOrderKey2(order) {
|
|
72397
72585
|
return [
|
|
72398
72586
|
order.side,
|
|
72399
72587
|
order.type,
|
|
@@ -72403,8 +72591,8 @@ function buildOrderKey(order) {
|
|
|
72403
72591
|
].join(":");
|
|
72404
72592
|
}
|
|
72405
72593
|
function buildDiff(options) {
|
|
72406
|
-
const expectedKeys = options.expected.map(
|
|
72407
|
-
const actualKeys = options.actual.map(
|
|
72594
|
+
const expectedKeys = options.expected.map(buildOrderKey2);
|
|
72595
|
+
const actualKeys = options.actual.map(buildOrderKey2);
|
|
72408
72596
|
return {
|
|
72409
72597
|
missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
|
|
72410
72598
|
extra: actualKeys.filter((key) => !expectedKeys.includes(key))
|
|
@@ -72416,10 +72604,10 @@ function getQuoteAsset(symbol) {
|
|
|
72416
72604
|
return target || symbol.slice(-4);
|
|
72417
72605
|
}
|
|
72418
72606
|
function sumNotional(orders) {
|
|
72419
|
-
return
|
|
72607
|
+
return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
|
|
72420
72608
|
}
|
|
72421
72609
|
function sumQuantity(orders) {
|
|
72422
|
-
return
|
|
72610
|
+
return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
|
|
72423
72611
|
}
|
|
72424
72612
|
function buildLongOrderPlacement(options) {
|
|
72425
72613
|
const { order, current_price } = options;
|
|
@@ -72428,7 +72616,7 @@ function buildLongOrderPlacement(options) {
|
|
|
72428
72616
|
side: "buy",
|
|
72429
72617
|
type: "stop_loss_limit",
|
|
72430
72618
|
price: order.price,
|
|
72431
|
-
stop:
|
|
72619
|
+
stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
|
|
72432
72620
|
quantity: order.quantity
|
|
72433
72621
|
};
|
|
72434
72622
|
}
|
|
@@ -72496,11 +72684,11 @@ function buildSpotMarginHedgePlan(options) {
|
|
|
72496
72684
|
}));
|
|
72497
72685
|
const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
|
|
72498
72686
|
const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
|
|
72499
|
-
const borrow_delta =
|
|
72687
|
+
const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
|
|
72500
72688
|
const spot_long_notional = sumNotional(spot_long_orders);
|
|
72501
|
-
const transfer_to_spot_amount =
|
|
72689
|
+
const transfer_to_spot_amount = roundNumber2(Math.max(0, options.margin_type === "cross" ? options.borrow_amount : spot_long_notional - options.snapshot.spot_quote_free));
|
|
72502
72690
|
const short_quantity = sumQuantity(short_orders);
|
|
72503
|
-
const transfer_base_to_spot_amount =
|
|
72691
|
+
const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
|
|
72504
72692
|
return {
|
|
72505
72693
|
borrow_amount: options.borrow_amount,
|
|
72506
72694
|
borrow_delta,
|
|
@@ -73002,17 +73190,17 @@ async function createMarginLimitOrdersParallel(client, symbol, priceFormat, quan
|
|
|
73002
73190
|
return Object.fromEntries(Object.entries(v).filter(([, value2]) => value2 !== undefined));
|
|
73003
73191
|
};
|
|
73004
73192
|
const newOrders = orders.map(createMarginOrder);
|
|
73005
|
-
const
|
|
73006
|
-
const
|
|
73193
|
+
const results = [];
|
|
73194
|
+
for (const orderPayload of newOrders) {
|
|
73007
73195
|
try {
|
|
73008
73196
|
const result = await client.marginAccountNewOrder(orderPayload);
|
|
73009
73197
|
console.log("Margin order result:", result);
|
|
73010
|
-
|
|
73198
|
+
results.push(result);
|
|
73011
73199
|
} catch (error) {
|
|
73012
73200
|
console.error("Error processing margin order:", error);
|
|
73013
73201
|
throw error;
|
|
73014
73202
|
}
|
|
73015
|
-
}
|
|
73203
|
+
}
|
|
73016
73204
|
return results;
|
|
73017
73205
|
}
|
|
73018
73206
|
async function getIsolatedMarginAccountInfo(client, symbol) {
|
|
@@ -74159,6 +74347,30 @@ class BinanceExchange extends BaseExchange {
|
|
|
74159
74347
|
throw error;
|
|
74160
74348
|
}
|
|
74161
74349
|
}
|
|
74350
|
+
async getFuturesSymbolFormats(symbol) {
|
|
74351
|
+
if (typeof this.client.getExchangeInfo !== "function") {
|
|
74352
|
+
return {
|
|
74353
|
+
price_places: "%.8f",
|
|
74354
|
+
decimal_places: "%.8f"
|
|
74355
|
+
};
|
|
74356
|
+
}
|
|
74357
|
+
try {
|
|
74358
|
+
const exchange_info = await this.client.getExchangeInfo();
|
|
74359
|
+
const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
|
|
74360
|
+
const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
|
|
74361
|
+
const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
|
|
74362
|
+
return {
|
|
74363
|
+
price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
|
|
74364
|
+
decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
|
|
74365
|
+
};
|
|
74366
|
+
} catch (error) {
|
|
74367
|
+
console.error("Error fetching futures symbol formats:", error);
|
|
74368
|
+
return {
|
|
74369
|
+
price_places: "%.8f",
|
|
74370
|
+
decimal_places: "%.8f"
|
|
74371
|
+
};
|
|
74372
|
+
}
|
|
74373
|
+
}
|
|
74162
74374
|
async getSpotSymbolFormats(symbol) {
|
|
74163
74375
|
if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
|
|
74164
74376
|
return {
|
|
@@ -74213,28 +74425,146 @@ class BinanceExchange extends BaseExchange {
|
|
|
74213
74425
|
}
|
|
74214
74426
|
return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
|
|
74215
74427
|
}
|
|
74428
|
+
async getFuturesReplaySnapshot(payload) {
|
|
74429
|
+
const symbol = payload.symbol.toUpperCase();
|
|
74430
|
+
const [open_orders, position_info, current_price] = await Promise.all([
|
|
74431
|
+
this.getOpenOrders({ symbol }),
|
|
74432
|
+
this.getPositionInfo(symbol),
|
|
74433
|
+
this.getCurrentPrice(symbol)
|
|
74434
|
+
]);
|
|
74435
|
+
return buildFuturesReplaySnapshot({
|
|
74436
|
+
symbol,
|
|
74437
|
+
kind: payload.kind,
|
|
74438
|
+
current_price,
|
|
74439
|
+
position: {
|
|
74440
|
+
size: position_info[payload.kind]?.size || 0
|
|
74441
|
+
},
|
|
74442
|
+
open_orders
|
|
74443
|
+
});
|
|
74444
|
+
}
|
|
74445
|
+
async previewFuturesReplay(payload) {
|
|
74446
|
+
const snapshot = payload.snapshot ? {
|
|
74447
|
+
...payload.snapshot,
|
|
74448
|
+
current_price: payload.snapshot.current_price ?? await this.getCurrentPrice(payload.symbol)
|
|
74449
|
+
} : await this.getFuturesReplaySnapshot({
|
|
74450
|
+
symbol: payload.symbol,
|
|
74451
|
+
kind: payload.kind
|
|
74452
|
+
});
|
|
74453
|
+
const shouldInferEntryOrders = !payload.entry_orders || payload.entry_orders.length === 0;
|
|
74454
|
+
const shouldInferStopOrders = !payload.stop_orders || payload.stop_orders.length === 0;
|
|
74455
|
+
const shouldInferTakeProfitOrders = !payload.take_profit_orders || payload.take_profit_orders.length === 0;
|
|
74456
|
+
const inferred_input = shouldInferEntryOrders || shouldInferStopOrders || shouldInferTakeProfitOrders ? inferFuturesReplayInputFromLiveOrders({
|
|
74457
|
+
symbol: payload.symbol,
|
|
74458
|
+
kind: payload.kind,
|
|
74459
|
+
current_price: snapshot.current_price,
|
|
74460
|
+
position: snapshot.position,
|
|
74461
|
+
open_orders: snapshot.open_orders
|
|
74462
|
+
}) : undefined;
|
|
74463
|
+
const explicit_input = normalizeFuturesReplayInput({
|
|
74464
|
+
kind: payload.kind,
|
|
74465
|
+
entry_orders: payload.entry_orders || [],
|
|
74466
|
+
stop_orders: payload.stop_orders || [],
|
|
74467
|
+
take_profit_orders: payload.take_profit_orders || []
|
|
74468
|
+
});
|
|
74469
|
+
const entry_orders = shouldInferEntryOrders ? inferred_input?.entry_orders || [] : explicit_input.entry_orders;
|
|
74470
|
+
const stop_orders = shouldInferStopOrders ? inferred_input?.stop_orders || [] : explicit_input.stop_orders;
|
|
74471
|
+
const take_profit_orders = shouldInferTakeProfitOrders ? inferred_input?.take_profit_orders || [] : explicit_input.take_profit_orders;
|
|
74472
|
+
const requested_input = {
|
|
74473
|
+
symbol: payload.symbol,
|
|
74474
|
+
kind: payload.kind,
|
|
74475
|
+
entry_orders,
|
|
74476
|
+
stop_orders,
|
|
74477
|
+
take_profit_orders
|
|
74478
|
+
};
|
|
74479
|
+
const plan = buildFuturesReplayPlan({
|
|
74480
|
+
...requested_input,
|
|
74481
|
+
snapshot
|
|
74482
|
+
});
|
|
74483
|
+
const comparison = compareFuturesReplayOrders({
|
|
74484
|
+
...requested_input,
|
|
74485
|
+
snapshot
|
|
74486
|
+
});
|
|
74487
|
+
return {
|
|
74488
|
+
snapshot,
|
|
74489
|
+
inferred_input,
|
|
74490
|
+
requested_input,
|
|
74491
|
+
plan,
|
|
74492
|
+
comparison
|
|
74493
|
+
};
|
|
74494
|
+
}
|
|
74495
|
+
async placeFuturesReplay(payload) {
|
|
74496
|
+
const preview = await this.previewFuturesReplay(payload);
|
|
74497
|
+
if (!payload.place) {
|
|
74498
|
+
return preview;
|
|
74499
|
+
}
|
|
74500
|
+
const symbol = payload.symbol.toUpperCase();
|
|
74501
|
+
await cancelAllOrders(this.client, symbol, {
|
|
74502
|
+
kind: payload.kind
|
|
74503
|
+
});
|
|
74504
|
+
const refreshed_snapshot = await this.getFuturesReplaySnapshot({
|
|
74505
|
+
symbol,
|
|
74506
|
+
kind: payload.kind
|
|
74507
|
+
});
|
|
74508
|
+
const execution_plan = buildFuturesReplayPlan({
|
|
74509
|
+
symbol,
|
|
74510
|
+
kind: payload.kind,
|
|
74511
|
+
entry_orders: preview.requested_input.entry_orders,
|
|
74512
|
+
stop_orders: preview.requested_input.stop_orders,
|
|
74513
|
+
take_profit_orders: preview.requested_input.take_profit_orders,
|
|
74514
|
+
snapshot: refreshed_snapshot
|
|
74515
|
+
});
|
|
74516
|
+
const { price_places, decimal_places } = await this.getFuturesSymbolFormats(symbol);
|
|
74517
|
+
const entry_orders = execution_plan.orders_to_create.entry.map((order) => ({
|
|
74518
|
+
...order,
|
|
74519
|
+
side: payload.kind === "long" ? "buy" : "sell",
|
|
74520
|
+
kind: payload.kind
|
|
74521
|
+
}));
|
|
74522
|
+
const stop_orders = execution_plan.orders_to_create.stop.map((order) => ({
|
|
74523
|
+
...order,
|
|
74524
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
74525
|
+
kind: payload.kind,
|
|
74526
|
+
type: order.type || "STOP"
|
|
74527
|
+
}));
|
|
74528
|
+
const take_profit_orders = execution_plan.orders_to_create.take_profit.map((order) => ({
|
|
74529
|
+
...order,
|
|
74530
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
74531
|
+
kind: payload.kind,
|
|
74532
|
+
...order.stop ? { type: order.type || "TAKE_PROFIT" } : {}
|
|
74533
|
+
}));
|
|
74534
|
+
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]) : [];
|
|
74535
|
+
return {
|
|
74536
|
+
...preview,
|
|
74537
|
+
execution_plan,
|
|
74538
|
+
refreshed_snapshot,
|
|
74539
|
+
execution
|
|
74540
|
+
};
|
|
74541
|
+
}
|
|
74542
|
+
async placeFuturesExactTrade(payload) {
|
|
74543
|
+
return await this.placeFuturesReplay(payload);
|
|
74544
|
+
}
|
|
74216
74545
|
async getSpotMarginHedgeSnapshot(payload) {
|
|
74217
74546
|
if (!this.main_client) {
|
|
74218
74547
|
throw new Error("Main client not available for spot and margin trading");
|
|
74219
74548
|
}
|
|
74220
|
-
if (payload.margin_type !== "isolated") {
|
|
74221
|
-
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
|
|
74222
|
-
}
|
|
74223
74549
|
const symbol = payload.symbol.toUpperCase();
|
|
74224
74550
|
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
74225
74551
|
const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
74226
74552
|
const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
|
|
74227
|
-
const
|
|
74553
|
+
const is_isolated = payload.margin_type === "isolated";
|
|
74554
|
+
const [spot_orders, margin_orders, spot_balances, margin_account] = await Promise.all([
|
|
74228
74555
|
this.getSpotOpenOrders(symbol),
|
|
74229
|
-
this.getMarginOpenOrders(symbol,
|
|
74556
|
+
this.getMarginOpenOrders(symbol, is_isolated),
|
|
74230
74557
|
this.getSpotBalances([baseAsset, quoteAsset]),
|
|
74231
|
-
this.getIsolatedMarginPosition(symbol)
|
|
74558
|
+
is_isolated ? this.getIsolatedMarginPosition(symbol) : this.getCrossMarginAccount()
|
|
74232
74559
|
]);
|
|
74233
74560
|
const current_price = await this.getSpotCurrentPrice(symbol);
|
|
74234
74561
|
const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
|
|
74235
74562
|
const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
|
|
74236
|
-
const
|
|
74237
|
-
const
|
|
74563
|
+
const isolated_margin_account = is_isolated ? margin_account : undefined;
|
|
74564
|
+
const cross_margin_account = !is_isolated ? margin_account : undefined;
|
|
74565
|
+
const isolated_asset = isolated_margin_account?.assets?.[0];
|
|
74566
|
+
const cross_quote_asset = !is_isolated ? cross_margin_account?.userAssets?.find((asset) => asset.asset?.toUpperCase?.() === quoteAsset) : undefined;
|
|
74567
|
+
const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || cross_quote_asset?.borrowed?.toString?.() || "0");
|
|
74238
74568
|
return {
|
|
74239
74569
|
symbol,
|
|
74240
74570
|
margin_type: payload.margin_type,
|
|
@@ -74247,7 +74577,8 @@ class BinanceExchange extends BaseExchange {
|
|
|
74247
74577
|
margin_orders,
|
|
74248
74578
|
current_price,
|
|
74249
74579
|
spot_balances,
|
|
74250
|
-
isolated_margin_position
|
|
74580
|
+
isolated_margin_position: isolated_margin_account,
|
|
74581
|
+
cross_margin_account
|
|
74251
74582
|
};
|
|
74252
74583
|
}
|
|
74253
74584
|
async previewSpotMarginHedge(payload) {
|
|
@@ -74304,14 +74635,12 @@ class BinanceExchange extends BaseExchange {
|
|
|
74304
74635
|
if (!this.main_client) {
|
|
74305
74636
|
throw new Error("Main client not available for spot and margin trading");
|
|
74306
74637
|
}
|
|
74307
|
-
if (payload.margin_type !== "isolated") {
|
|
74308
|
-
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
|
|
74309
|
-
}
|
|
74310
74638
|
const symbol = payload.symbol.toUpperCase();
|
|
74311
74639
|
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
74312
74640
|
const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
74313
74641
|
const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
|
|
74314
74642
|
const requested_input = preview.requested_input;
|
|
74643
|
+
const is_isolated = payload.margin_type === "isolated";
|
|
74315
74644
|
if (preview.snapshot.spot_orders.length > 0) {
|
|
74316
74645
|
await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
|
|
74317
74646
|
orderId: order.orderId || order.order_id || order.id,
|
|
@@ -74319,7 +74648,7 @@ class BinanceExchange extends BaseExchange {
|
|
|
74319
74648
|
})));
|
|
74320
74649
|
}
|
|
74321
74650
|
if (preview.snapshot.margin_orders.length > 0) {
|
|
74322
|
-
await this.cancelMarginOrders(symbol,
|
|
74651
|
+
await this.cancelMarginOrders(symbol, is_isolated, preview.snapshot.margin_orders.map((order) => ({
|
|
74323
74652
|
orderId: order.orderId || order.order_id || order.id,
|
|
74324
74653
|
origClientOrderId: order.origClientOrderId
|
|
74325
74654
|
})));
|
|
@@ -74341,50 +74670,120 @@ class BinanceExchange extends BaseExchange {
|
|
|
74341
74670
|
});
|
|
74342
74671
|
let borrow_result = null;
|
|
74343
74672
|
if (execution_plan.borrow_delta > 0) {
|
|
74344
|
-
|
|
74673
|
+
const borrow_payload = {
|
|
74345
74674
|
asset: quote_asset,
|
|
74346
74675
|
amount: execution_plan.borrow_delta,
|
|
74347
|
-
isIsolated: "TRUE",
|
|
74348
|
-
symbol,
|
|
74349
74676
|
type: "BORROW"
|
|
74677
|
+
};
|
|
74678
|
+
if (is_isolated) {
|
|
74679
|
+
borrow_payload.isIsolated = "TRUE";
|
|
74680
|
+
borrow_payload.symbol = symbol;
|
|
74681
|
+
}
|
|
74682
|
+
borrow_result = await this.main_client.submitMarginAccountBorrowRepay(borrow_payload);
|
|
74683
|
+
}
|
|
74684
|
+
async function getCrossTransferAmount(asset, amount) {
|
|
74685
|
+
if (is_isolated) {
|
|
74686
|
+
return amount;
|
|
74687
|
+
}
|
|
74688
|
+
if (!this.main_client || typeof this.main_client.queryMaxTransferOutAmount !== "function") {
|
|
74689
|
+
return amount;
|
|
74690
|
+
}
|
|
74691
|
+
const response = await this.main_client.queryMaxTransferOutAmount({
|
|
74692
|
+
asset
|
|
74350
74693
|
});
|
|
74694
|
+
const allowed_amount = parseFloat(response?.amount?.toString?.() || "0");
|
|
74695
|
+
if (!Number.isFinite(allowed_amount)) {
|
|
74696
|
+
return amount;
|
|
74697
|
+
}
|
|
74698
|
+
return Math.max(0, Math.min(amount, allowed_amount));
|
|
74351
74699
|
}
|
|
74352
74700
|
let quote_transfer_result = null;
|
|
74701
|
+
let quote_transfer_amount = execution_plan.transfer_to_spot_amount;
|
|
74353
74702
|
if (execution_plan.transfer_to_spot_amount > 0) {
|
|
74354
|
-
|
|
74355
|
-
|
|
74356
|
-
|
|
74357
|
-
|
|
74358
|
-
|
|
74359
|
-
|
|
74360
|
-
|
|
74703
|
+
quote_transfer_amount = await getCrossTransferAmount.call(this, quote_asset, execution_plan.transfer_to_spot_amount);
|
|
74704
|
+
if (quote_transfer_amount > 0) {
|
|
74705
|
+
quote_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
|
|
74706
|
+
asset: quote_asset,
|
|
74707
|
+
amount: quote_transfer_amount,
|
|
74708
|
+
symbol,
|
|
74709
|
+
transFrom: "ISOLATED_MARGIN",
|
|
74710
|
+
transTo: "SPOT"
|
|
74711
|
+
}) : await this.main_client.submitUniversalTransfer({
|
|
74712
|
+
type: "MARGIN_MAIN",
|
|
74713
|
+
asset: quote_asset,
|
|
74714
|
+
amount: quote_transfer_amount
|
|
74715
|
+
});
|
|
74716
|
+
}
|
|
74361
74717
|
}
|
|
74362
74718
|
let base_transfer_result = null;
|
|
74719
|
+
let base_transfer_amount = execution_plan.transfer_base_to_spot_amount;
|
|
74363
74720
|
if (execution_plan.transfer_base_to_spot_amount > 0) {
|
|
74364
|
-
|
|
74365
|
-
|
|
74366
|
-
|
|
74367
|
-
|
|
74368
|
-
|
|
74369
|
-
|
|
74370
|
-
|
|
74721
|
+
base_transfer_amount = await getCrossTransferAmount.call(this, base_asset, execution_plan.transfer_base_to_spot_amount);
|
|
74722
|
+
if (base_transfer_amount > 0) {
|
|
74723
|
+
base_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
|
|
74724
|
+
asset: base_asset,
|
|
74725
|
+
amount: base_transfer_amount,
|
|
74726
|
+
symbol,
|
|
74727
|
+
transFrom: "ISOLATED_MARGIN",
|
|
74728
|
+
transTo: "SPOT"
|
|
74729
|
+
}) : await this.main_client.submitUniversalTransfer({
|
|
74730
|
+
type: "MARGIN_MAIN",
|
|
74731
|
+
asset: base_asset,
|
|
74732
|
+
amount: base_transfer_amount
|
|
74733
|
+
});
|
|
74734
|
+
}
|
|
74371
74735
|
}
|
|
74372
|
-
const
|
|
74736
|
+
const spot_quote_available = refreshed_snapshot.spot_quote_free + quote_transfer_amount;
|
|
74737
|
+
const requested_max_spot_buy_orders = requested_input.max_spot_buy_orders ?? 5;
|
|
74738
|
+
const sorted_long_orders = [...requested_input.long_orders].sort((a, b) => a.price - b.price);
|
|
74739
|
+
let affordable_spot_buy_orders = 0;
|
|
74740
|
+
let affordable_spot_buy_notional = 0;
|
|
74741
|
+
for (const order of sorted_long_orders.slice(0, requested_max_spot_buy_orders)) {
|
|
74742
|
+
const next_notional = affordable_spot_buy_notional + order.price * order.quantity;
|
|
74743
|
+
if (next_notional <= spot_quote_available + 0.00000001) {
|
|
74744
|
+
affordable_spot_buy_orders += 1;
|
|
74745
|
+
affordable_spot_buy_notional = next_notional;
|
|
74746
|
+
} else {
|
|
74747
|
+
break;
|
|
74748
|
+
}
|
|
74749
|
+
}
|
|
74750
|
+
const placement_plan = buildSpotMarginHedgePlan({
|
|
74751
|
+
borrow_amount: requested_input.borrow_amount,
|
|
74752
|
+
symbol: requested_input.symbol,
|
|
74753
|
+
margin_type: requested_input.margin_type,
|
|
74754
|
+
long_orders: requested_input.long_orders,
|
|
74755
|
+
short_orders: requested_input.short_orders,
|
|
74756
|
+
current_price: refreshed_snapshot.current_price,
|
|
74757
|
+
max_spot_buy_orders: affordable_spot_buy_orders,
|
|
74758
|
+
snapshot: {
|
|
74759
|
+
...refreshed_snapshot,
|
|
74760
|
+
borrowed_quote_amount: refreshed_snapshot.borrowed_quote_amount + execution_plan.borrow_delta,
|
|
74761
|
+
spot_quote_free: spot_quote_available,
|
|
74762
|
+
spot_base_free: refreshed_snapshot.spot_base_free + base_transfer_amount
|
|
74763
|
+
}
|
|
74764
|
+
});
|
|
74765
|
+
const spot_result = placement_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
|
|
74373
74766
|
symbol,
|
|
74374
|
-
orders:
|
|
74767
|
+
orders: placement_plan.orders_to_create.spot,
|
|
74375
74768
|
price_places: symbol_formats.price_places,
|
|
74376
74769
|
decimal_places: symbol_formats.decimal_places
|
|
74377
74770
|
}) : [];
|
|
74378
|
-
const margin_result =
|
|
74771
|
+
const margin_result = placement_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
|
|
74379
74772
|
symbol,
|
|
74380
|
-
orders:
|
|
74381
|
-
isIsolated:
|
|
74773
|
+
orders: placement_plan.orders_to_create.margin,
|
|
74774
|
+
isIsolated: is_isolated,
|
|
74382
74775
|
price_places: symbol_formats.price_places,
|
|
74383
74776
|
decimal_places: symbol_formats.decimal_places
|
|
74384
74777
|
}) : [];
|
|
74385
74778
|
return {
|
|
74386
74779
|
...preview,
|
|
74387
|
-
execution_plan
|
|
74780
|
+
execution_plan: {
|
|
74781
|
+
...placement_plan,
|
|
74782
|
+
borrow_amount: execution_plan.borrow_amount,
|
|
74783
|
+
borrow_delta: execution_plan.borrow_delta,
|
|
74784
|
+
transfer_to_spot_amount: quote_transfer_amount,
|
|
74785
|
+
transfer_base_to_spot_amount: base_transfer_amount
|
|
74786
|
+
},
|
|
74388
74787
|
refreshed_snapshot,
|
|
74389
74788
|
execution: {
|
|
74390
74789
|
borrow: borrow_result ? {
|
|
@@ -74394,12 +74793,12 @@ class BinanceExchange extends BaseExchange {
|
|
|
74394
74793
|
} : null,
|
|
74395
74794
|
quote_transfer: quote_transfer_result ? {
|
|
74396
74795
|
asset: quote_asset,
|
|
74397
|
-
amount:
|
|
74796
|
+
amount: quote_transfer_amount,
|
|
74398
74797
|
response: quote_transfer_result
|
|
74399
74798
|
} : null,
|
|
74400
74799
|
base_transfer: base_transfer_result ? {
|
|
74401
74800
|
asset: base_asset,
|
|
74402
|
-
amount:
|
|
74801
|
+
amount: base_transfer_amount,
|
|
74403
74802
|
response: base_transfer_result
|
|
74404
74803
|
} : null,
|
|
74405
74804
|
spot_orders: spot_result,
|