@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.cjs
CHANGED
|
@@ -72038,9 +72038,18 @@ class BaseExchange {
|
|
|
72038
72038
|
async previewSpotMarginHedge(_payload) {
|
|
72039
72039
|
throw new Error("Spot margin hedge preview is not supported on this exchange");
|
|
72040
72040
|
}
|
|
72041
|
+
async previewFuturesReplay(_payload) {
|
|
72042
|
+
throw new Error("Futures replay preview is not supported on this exchange");
|
|
72043
|
+
}
|
|
72041
72044
|
async placeSpotMarginHedge(_payload) {
|
|
72042
72045
|
throw new Error("Spot margin hedge placement is not supported on this exchange");
|
|
72043
72046
|
}
|
|
72047
|
+
async placeFuturesReplay(_payload) {
|
|
72048
|
+
throw new Error("Futures replay placement is not supported on this exchange");
|
|
72049
|
+
}
|
|
72050
|
+
async placeFuturesExactTrade(_payload) {
|
|
72051
|
+
throw new Error("Futures exact trade placement is not supported on this exchange");
|
|
72052
|
+
}
|
|
72044
72053
|
async placeSpotLimitOrders(_payload) {}
|
|
72045
72054
|
async buildCumulative(payload) {
|
|
72046
72055
|
const {
|
|
@@ -72449,19 +72458,198 @@ class BaseExchange {
|
|
|
72449
72458
|
// src/exchanges/binance/index.ts
|
|
72450
72459
|
var import_p_limit = __toESM(require_p_limit(), 1);
|
|
72451
72460
|
|
|
72452
|
-
// src/exchanges/binance/
|
|
72461
|
+
// src/exchanges/binance/futures-replay.ts
|
|
72453
72462
|
function roundNumber(value2, digits = 8) {
|
|
72454
72463
|
return Number(value2.toFixed(digits));
|
|
72455
72464
|
}
|
|
72456
|
-
|
|
72465
|
+
function parseNumber2(value2) {
|
|
72466
|
+
if (value2 === undefined || value2 === null || value2 === "")
|
|
72467
|
+
return;
|
|
72468
|
+
const parsed = Number(value2);
|
|
72469
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
72470
|
+
return;
|
|
72471
|
+
return roundNumber(parsed);
|
|
72472
|
+
}
|
|
72457
72473
|
function normalizeOrderInput(order) {
|
|
72474
|
+
const stop = parseNumber2(order.stop) ?? parseNumber2(order.stopPrice) ?? parseNumber2(order.triggerPrice);
|
|
72475
|
+
const type = order.type ? order.type.toUpperCase() : undefined;
|
|
72476
|
+
const quantity = order.quantity ?? order.qty ?? 0;
|
|
72458
72477
|
return {
|
|
72459
|
-
price: roundNumber(order.price),
|
|
72460
|
-
quantity: roundNumber(
|
|
72478
|
+
price: roundNumber(Number(order.price)),
|
|
72479
|
+
quantity: roundNumber(Number(quantity)),
|
|
72480
|
+
...stop ? { stop } : {},
|
|
72481
|
+
...type && type !== "LIMIT" ? { type } : {}
|
|
72482
|
+
};
|
|
72483
|
+
}
|
|
72484
|
+
function sortReplayOrders(orders) {
|
|
72485
|
+
return [...orders].sort((left, right) => {
|
|
72486
|
+
const leftTrigger = left.stop ?? left.price;
|
|
72487
|
+
const rightTrigger = right.stop ?? right.price;
|
|
72488
|
+
if (leftTrigger !== rightTrigger)
|
|
72489
|
+
return leftTrigger - rightTrigger;
|
|
72490
|
+
if (left.price !== right.price)
|
|
72491
|
+
return left.price - right.price;
|
|
72492
|
+
return left.quantity - right.quantity;
|
|
72493
|
+
});
|
|
72494
|
+
}
|
|
72495
|
+
function buildReplayStopLimitPrice(payload) {
|
|
72496
|
+
const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
|
|
72497
|
+
return roundNumber(payload.kind === "long" ? payload.stop * spread ** -1 : payload.stop * spread);
|
|
72498
|
+
}
|
|
72499
|
+
function normalizeRequestedEntryOrders(orders) {
|
|
72500
|
+
return sortReplayOrders(orders.map((order) => ({
|
|
72501
|
+
price: roundNumber(Number(order.price)),
|
|
72502
|
+
quantity: roundNumber(Number(order.quantity))
|
|
72503
|
+
})));
|
|
72504
|
+
}
|
|
72505
|
+
function normalizeRequestedStopOrders(payload) {
|
|
72506
|
+
return sortReplayOrders(payload.orders.map((order) => {
|
|
72507
|
+
const stop = parseNumber2(order.stop) ?? roundNumber(Number(order.price));
|
|
72508
|
+
return {
|
|
72509
|
+
price: order.price !== undefined ? roundNumber(Number(order.price)) : buildReplayStopLimitPrice({
|
|
72510
|
+
kind: payload.kind,
|
|
72511
|
+
stop
|
|
72512
|
+
}),
|
|
72513
|
+
quantity: roundNumber(Number(order.quantity)),
|
|
72514
|
+
stop,
|
|
72515
|
+
type: (order.type || "STOP").toUpperCase()
|
|
72516
|
+
};
|
|
72517
|
+
}));
|
|
72518
|
+
}
|
|
72519
|
+
function normalizeRequestedTakeProfitOrders(orders) {
|
|
72520
|
+
return sortReplayOrders(orders.map((order) => {
|
|
72521
|
+
const stop = parseNumber2(order.stop);
|
|
72522
|
+
const type = order.type?.toUpperCase();
|
|
72523
|
+
return {
|
|
72524
|
+
price: roundNumber(Number(order.price)),
|
|
72525
|
+
quantity: roundNumber(Number(order.quantity)),
|
|
72526
|
+
...stop ? { stop } : {},
|
|
72527
|
+
...type ? { type } : stop ? { type: "TAKE_PROFIT" } : {}
|
|
72528
|
+
};
|
|
72529
|
+
}));
|
|
72530
|
+
}
|
|
72531
|
+
function buildOrderKey(order) {
|
|
72532
|
+
return [
|
|
72533
|
+
order.type || "LIMIT",
|
|
72534
|
+
order.price,
|
|
72535
|
+
order.quantity,
|
|
72536
|
+
order.stop ?? ""
|
|
72537
|
+
].join(":");
|
|
72538
|
+
}
|
|
72539
|
+
function buildComparisonBucket(options) {
|
|
72540
|
+
const expected = sortReplayOrders(options.expected);
|
|
72541
|
+
const actual = sortReplayOrders(options.actual);
|
|
72542
|
+
const expected_keys = expected.map(buildOrderKey);
|
|
72543
|
+
const actual_keys = actual.map(buildOrderKey);
|
|
72544
|
+
return {
|
|
72545
|
+
expected,
|
|
72546
|
+
actual,
|
|
72547
|
+
missing: expected_keys.filter((key) => !actual_keys.includes(key)),
|
|
72548
|
+
extra: actual_keys.filter((key) => !expected_keys.includes(key))
|
|
72549
|
+
};
|
|
72550
|
+
}
|
|
72551
|
+
function buildFuturesReplaySnapshot(payload) {
|
|
72552
|
+
const entry_side = payload.kind === "long" ? "buy" : "sell";
|
|
72553
|
+
const close_side = payload.kind === "long" ? "sell" : "buy";
|
|
72554
|
+
const entry_orders = [];
|
|
72555
|
+
const stop_orders = [];
|
|
72556
|
+
const take_profit_orders = [];
|
|
72557
|
+
for (const order of payload.open_orders) {
|
|
72558
|
+
if (order.kind !== payload.kind)
|
|
72559
|
+
continue;
|
|
72560
|
+
const normalized_order = normalizeOrderInput(order);
|
|
72561
|
+
const type = (normalized_order.type || "LIMIT").toUpperCase();
|
|
72562
|
+
if (order.side === entry_side) {
|
|
72563
|
+
entry_orders.push(normalized_order);
|
|
72564
|
+
continue;
|
|
72565
|
+
}
|
|
72566
|
+
if (order.side !== close_side)
|
|
72567
|
+
continue;
|
|
72568
|
+
if (type.includes("TAKE_PROFIT") || normalized_order.stop === undefined) {
|
|
72569
|
+
take_profit_orders.push(normalized_order);
|
|
72570
|
+
continue;
|
|
72571
|
+
}
|
|
72572
|
+
stop_orders.push(normalized_order);
|
|
72573
|
+
}
|
|
72574
|
+
return {
|
|
72575
|
+
symbol: payload.symbol,
|
|
72576
|
+
kind: payload.kind,
|
|
72577
|
+
current_price: payload.current_price,
|
|
72578
|
+
position: payload.position,
|
|
72579
|
+
open_orders: payload.open_orders.filter((order) => order.kind === payload.kind),
|
|
72580
|
+
entry_orders: sortReplayOrders(entry_orders),
|
|
72581
|
+
stop_orders: sortReplayOrders(stop_orders),
|
|
72582
|
+
take_profit_orders: sortReplayOrders(take_profit_orders)
|
|
72583
|
+
};
|
|
72584
|
+
}
|
|
72585
|
+
function inferFuturesReplayInputFromLiveOrders(payload) {
|
|
72586
|
+
const snapshot = buildFuturesReplaySnapshot(payload);
|
|
72587
|
+
return {
|
|
72588
|
+
symbol: payload.symbol,
|
|
72589
|
+
kind: payload.kind,
|
|
72590
|
+
current_price: payload.current_price,
|
|
72591
|
+
position: payload.position,
|
|
72592
|
+
entry_orders: snapshot.entry_orders,
|
|
72593
|
+
stop_orders: snapshot.stop_orders,
|
|
72594
|
+
take_profit_orders: snapshot.take_profit_orders
|
|
72595
|
+
};
|
|
72596
|
+
}
|
|
72597
|
+
function compareFuturesReplayOrders(payload) {
|
|
72598
|
+
const entry = buildComparisonBucket({
|
|
72599
|
+
expected: payload.entry_orders,
|
|
72600
|
+
actual: payload.snapshot.entry_orders
|
|
72601
|
+
});
|
|
72602
|
+
const stop = buildComparisonBucket({
|
|
72603
|
+
expected: payload.stop_orders,
|
|
72604
|
+
actual: payload.snapshot.stop_orders
|
|
72605
|
+
});
|
|
72606
|
+
const take_profit = buildComparisonBucket({
|
|
72607
|
+
expected: payload.take_profit_orders,
|
|
72608
|
+
actual: payload.snapshot.take_profit_orders
|
|
72609
|
+
});
|
|
72610
|
+
return {
|
|
72611
|
+
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,
|
|
72612
|
+
entry,
|
|
72613
|
+
stop,
|
|
72614
|
+
take_profit
|
|
72615
|
+
};
|
|
72616
|
+
}
|
|
72617
|
+
function buildFuturesReplayPlan(payload) {
|
|
72618
|
+
return {
|
|
72619
|
+
symbol: payload.symbol,
|
|
72620
|
+
kind: payload.kind,
|
|
72621
|
+
orders_to_cancel: payload.snapshot.open_orders,
|
|
72622
|
+
orders_to_create: {
|
|
72623
|
+
entry: sortReplayOrders(payload.entry_orders),
|
|
72624
|
+
stop: sortReplayOrders(payload.stop_orders),
|
|
72625
|
+
take_profit: sortReplayOrders(payload.take_profit_orders)
|
|
72626
|
+
}
|
|
72627
|
+
};
|
|
72628
|
+
}
|
|
72629
|
+
function normalizeFuturesReplayInput(payload) {
|
|
72630
|
+
return {
|
|
72631
|
+
entry_orders: normalizeRequestedEntryOrders(payload.entry_orders),
|
|
72632
|
+
stop_orders: normalizeRequestedStopOrders({
|
|
72633
|
+
kind: payload.kind,
|
|
72634
|
+
orders: payload.stop_orders
|
|
72635
|
+
}),
|
|
72636
|
+
take_profit_orders: normalizeRequestedTakeProfitOrders(payload.take_profit_orders)
|
|
72637
|
+
};
|
|
72638
|
+
}
|
|
72639
|
+
|
|
72640
|
+
// src/exchanges/binance/spot-margin-hedge.ts
|
|
72641
|
+
function roundNumber2(value2, digits = 8) {
|
|
72642
|
+
return Number(value2.toFixed(digits));
|
|
72643
|
+
}
|
|
72644
|
+
var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
|
|
72645
|
+
function normalizeOrderInput2(order) {
|
|
72646
|
+
return {
|
|
72647
|
+
price: roundNumber2(order.price),
|
|
72648
|
+
quantity: roundNumber2(order.quantity)
|
|
72461
72649
|
};
|
|
72462
72650
|
}
|
|
72463
72651
|
function sortOrderInputs(orders) {
|
|
72464
|
-
return [...orders].map(
|
|
72652
|
+
return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
|
|
72465
72653
|
}
|
|
72466
72654
|
function normalizeOrderForComparison(order) {
|
|
72467
72655
|
const type = (order.type || "limit").toLowerCase();
|
|
@@ -72470,9 +72658,9 @@ function normalizeOrderForComparison(order) {
|
|
|
72470
72658
|
return {
|
|
72471
72659
|
side: order.side,
|
|
72472
72660
|
type,
|
|
72473
|
-
price:
|
|
72474
|
-
quantity:
|
|
72475
|
-
stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ?
|
|
72661
|
+
price: roundNumber2(order.price),
|
|
72662
|
+
quantity: roundNumber2(order.quantity),
|
|
72663
|
+
stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber2(parsed_stop) : undefined
|
|
72476
72664
|
};
|
|
72477
72665
|
}
|
|
72478
72666
|
function normalizeOrders(orders) {
|
|
@@ -72495,7 +72683,7 @@ function toPlannedOrders(orders) {
|
|
|
72495
72683
|
stop: order.stop
|
|
72496
72684
|
}));
|
|
72497
72685
|
}
|
|
72498
|
-
function
|
|
72686
|
+
function buildOrderKey2(order) {
|
|
72499
72687
|
return [
|
|
72500
72688
|
order.side,
|
|
72501
72689
|
order.type,
|
|
@@ -72505,8 +72693,8 @@ function buildOrderKey(order) {
|
|
|
72505
72693
|
].join(":");
|
|
72506
72694
|
}
|
|
72507
72695
|
function buildDiff(options) {
|
|
72508
|
-
const expectedKeys = options.expected.map(
|
|
72509
|
-
const actualKeys = options.actual.map(
|
|
72696
|
+
const expectedKeys = options.expected.map(buildOrderKey2);
|
|
72697
|
+
const actualKeys = options.actual.map(buildOrderKey2);
|
|
72510
72698
|
return {
|
|
72511
72699
|
missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
|
|
72512
72700
|
extra: actualKeys.filter((key) => !expectedKeys.includes(key))
|
|
@@ -72518,10 +72706,10 @@ function getQuoteAsset(symbol) {
|
|
|
72518
72706
|
return target || symbol.slice(-4);
|
|
72519
72707
|
}
|
|
72520
72708
|
function sumNotional(orders) {
|
|
72521
|
-
return
|
|
72709
|
+
return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
|
|
72522
72710
|
}
|
|
72523
72711
|
function sumQuantity(orders) {
|
|
72524
|
-
return
|
|
72712
|
+
return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
|
|
72525
72713
|
}
|
|
72526
72714
|
function buildLongOrderPlacement(options) {
|
|
72527
72715
|
const { order, current_price } = options;
|
|
@@ -72530,7 +72718,7 @@ function buildLongOrderPlacement(options) {
|
|
|
72530
72718
|
side: "buy",
|
|
72531
72719
|
type: "stop_loss_limit",
|
|
72532
72720
|
price: order.price,
|
|
72533
|
-
stop:
|
|
72721
|
+
stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
|
|
72534
72722
|
quantity: order.quantity
|
|
72535
72723
|
};
|
|
72536
72724
|
}
|
|
@@ -72598,11 +72786,11 @@ function buildSpotMarginHedgePlan(options) {
|
|
|
72598
72786
|
}));
|
|
72599
72787
|
const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
|
|
72600
72788
|
const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
|
|
72601
|
-
const borrow_delta =
|
|
72789
|
+
const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
|
|
72602
72790
|
const spot_long_notional = sumNotional(spot_long_orders);
|
|
72603
|
-
const transfer_to_spot_amount =
|
|
72791
|
+
const transfer_to_spot_amount = roundNumber2(Math.max(0, options.margin_type === "cross" ? options.borrow_amount : spot_long_notional - options.snapshot.spot_quote_free));
|
|
72604
72792
|
const short_quantity = sumQuantity(short_orders);
|
|
72605
|
-
const transfer_base_to_spot_amount =
|
|
72793
|
+
const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
|
|
72606
72794
|
return {
|
|
72607
72795
|
borrow_amount: options.borrow_amount,
|
|
72608
72796
|
borrow_delta,
|
|
@@ -73104,17 +73292,17 @@ async function createMarginLimitOrdersParallel(client, symbol, priceFormat, quan
|
|
|
73104
73292
|
return Object.fromEntries(Object.entries(v).filter(([, value2]) => value2 !== undefined));
|
|
73105
73293
|
};
|
|
73106
73294
|
const newOrders = orders.map(createMarginOrder);
|
|
73107
|
-
const
|
|
73108
|
-
const
|
|
73295
|
+
const results = [];
|
|
73296
|
+
for (const orderPayload of newOrders) {
|
|
73109
73297
|
try {
|
|
73110
73298
|
const result = await client.marginAccountNewOrder(orderPayload);
|
|
73111
73299
|
console.log("Margin order result:", result);
|
|
73112
|
-
|
|
73300
|
+
results.push(result);
|
|
73113
73301
|
} catch (error) {
|
|
73114
73302
|
console.error("Error processing margin order:", error);
|
|
73115
73303
|
throw error;
|
|
73116
73304
|
}
|
|
73117
|
-
}
|
|
73305
|
+
}
|
|
73118
73306
|
return results;
|
|
73119
73307
|
}
|
|
73120
73308
|
async function getIsolatedMarginAccountInfo(client, symbol) {
|
|
@@ -74261,6 +74449,30 @@ class BinanceExchange extends BaseExchange {
|
|
|
74261
74449
|
throw error;
|
|
74262
74450
|
}
|
|
74263
74451
|
}
|
|
74452
|
+
async getFuturesSymbolFormats(symbol) {
|
|
74453
|
+
if (typeof this.client.getExchangeInfo !== "function") {
|
|
74454
|
+
return {
|
|
74455
|
+
price_places: "%.8f",
|
|
74456
|
+
decimal_places: "%.8f"
|
|
74457
|
+
};
|
|
74458
|
+
}
|
|
74459
|
+
try {
|
|
74460
|
+
const exchange_info = await this.client.getExchangeInfo();
|
|
74461
|
+
const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
|
|
74462
|
+
const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
|
|
74463
|
+
const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
|
|
74464
|
+
return {
|
|
74465
|
+
price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
|
|
74466
|
+
decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
|
|
74467
|
+
};
|
|
74468
|
+
} catch (error) {
|
|
74469
|
+
console.error("Error fetching futures symbol formats:", error);
|
|
74470
|
+
return {
|
|
74471
|
+
price_places: "%.8f",
|
|
74472
|
+
decimal_places: "%.8f"
|
|
74473
|
+
};
|
|
74474
|
+
}
|
|
74475
|
+
}
|
|
74264
74476
|
async getSpotSymbolFormats(symbol) {
|
|
74265
74477
|
if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
|
|
74266
74478
|
return {
|
|
@@ -74315,28 +74527,146 @@ class BinanceExchange extends BaseExchange {
|
|
|
74315
74527
|
}
|
|
74316
74528
|
return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
|
|
74317
74529
|
}
|
|
74530
|
+
async getFuturesReplaySnapshot(payload) {
|
|
74531
|
+
const symbol = payload.symbol.toUpperCase();
|
|
74532
|
+
const [open_orders, position_info, current_price] = await Promise.all([
|
|
74533
|
+
this.getOpenOrders({ symbol }),
|
|
74534
|
+
this.getPositionInfo(symbol),
|
|
74535
|
+
this.getCurrentPrice(symbol)
|
|
74536
|
+
]);
|
|
74537
|
+
return buildFuturesReplaySnapshot({
|
|
74538
|
+
symbol,
|
|
74539
|
+
kind: payload.kind,
|
|
74540
|
+
current_price,
|
|
74541
|
+
position: {
|
|
74542
|
+
size: position_info[payload.kind]?.size || 0
|
|
74543
|
+
},
|
|
74544
|
+
open_orders
|
|
74545
|
+
});
|
|
74546
|
+
}
|
|
74547
|
+
async previewFuturesReplay(payload) {
|
|
74548
|
+
const snapshot = payload.snapshot ? {
|
|
74549
|
+
...payload.snapshot,
|
|
74550
|
+
current_price: payload.snapshot.current_price ?? await this.getCurrentPrice(payload.symbol)
|
|
74551
|
+
} : await this.getFuturesReplaySnapshot({
|
|
74552
|
+
symbol: payload.symbol,
|
|
74553
|
+
kind: payload.kind
|
|
74554
|
+
});
|
|
74555
|
+
const shouldInferEntryOrders = !payload.entry_orders || payload.entry_orders.length === 0;
|
|
74556
|
+
const shouldInferStopOrders = !payload.stop_orders || payload.stop_orders.length === 0;
|
|
74557
|
+
const shouldInferTakeProfitOrders = !payload.take_profit_orders || payload.take_profit_orders.length === 0;
|
|
74558
|
+
const inferred_input = shouldInferEntryOrders || shouldInferStopOrders || shouldInferTakeProfitOrders ? inferFuturesReplayInputFromLiveOrders({
|
|
74559
|
+
symbol: payload.symbol,
|
|
74560
|
+
kind: payload.kind,
|
|
74561
|
+
current_price: snapshot.current_price,
|
|
74562
|
+
position: snapshot.position,
|
|
74563
|
+
open_orders: snapshot.open_orders
|
|
74564
|
+
}) : undefined;
|
|
74565
|
+
const explicit_input = normalizeFuturesReplayInput({
|
|
74566
|
+
kind: payload.kind,
|
|
74567
|
+
entry_orders: payload.entry_orders || [],
|
|
74568
|
+
stop_orders: payload.stop_orders || [],
|
|
74569
|
+
take_profit_orders: payload.take_profit_orders || []
|
|
74570
|
+
});
|
|
74571
|
+
const entry_orders = shouldInferEntryOrders ? inferred_input?.entry_orders || [] : explicit_input.entry_orders;
|
|
74572
|
+
const stop_orders = shouldInferStopOrders ? inferred_input?.stop_orders || [] : explicit_input.stop_orders;
|
|
74573
|
+
const take_profit_orders = shouldInferTakeProfitOrders ? inferred_input?.take_profit_orders || [] : explicit_input.take_profit_orders;
|
|
74574
|
+
const requested_input = {
|
|
74575
|
+
symbol: payload.symbol,
|
|
74576
|
+
kind: payload.kind,
|
|
74577
|
+
entry_orders,
|
|
74578
|
+
stop_orders,
|
|
74579
|
+
take_profit_orders
|
|
74580
|
+
};
|
|
74581
|
+
const plan = buildFuturesReplayPlan({
|
|
74582
|
+
...requested_input,
|
|
74583
|
+
snapshot
|
|
74584
|
+
});
|
|
74585
|
+
const comparison = compareFuturesReplayOrders({
|
|
74586
|
+
...requested_input,
|
|
74587
|
+
snapshot
|
|
74588
|
+
});
|
|
74589
|
+
return {
|
|
74590
|
+
snapshot,
|
|
74591
|
+
inferred_input,
|
|
74592
|
+
requested_input,
|
|
74593
|
+
plan,
|
|
74594
|
+
comparison
|
|
74595
|
+
};
|
|
74596
|
+
}
|
|
74597
|
+
async placeFuturesReplay(payload) {
|
|
74598
|
+
const preview = await this.previewFuturesReplay(payload);
|
|
74599
|
+
if (!payload.place) {
|
|
74600
|
+
return preview;
|
|
74601
|
+
}
|
|
74602
|
+
const symbol = payload.symbol.toUpperCase();
|
|
74603
|
+
await cancelAllOrders(this.client, symbol, {
|
|
74604
|
+
kind: payload.kind
|
|
74605
|
+
});
|
|
74606
|
+
const refreshed_snapshot = await this.getFuturesReplaySnapshot({
|
|
74607
|
+
symbol,
|
|
74608
|
+
kind: payload.kind
|
|
74609
|
+
});
|
|
74610
|
+
const execution_plan = buildFuturesReplayPlan({
|
|
74611
|
+
symbol,
|
|
74612
|
+
kind: payload.kind,
|
|
74613
|
+
entry_orders: preview.requested_input.entry_orders,
|
|
74614
|
+
stop_orders: preview.requested_input.stop_orders,
|
|
74615
|
+
take_profit_orders: preview.requested_input.take_profit_orders,
|
|
74616
|
+
snapshot: refreshed_snapshot
|
|
74617
|
+
});
|
|
74618
|
+
const { price_places, decimal_places } = await this.getFuturesSymbolFormats(symbol);
|
|
74619
|
+
const entry_orders = execution_plan.orders_to_create.entry.map((order) => ({
|
|
74620
|
+
...order,
|
|
74621
|
+
side: payload.kind === "long" ? "buy" : "sell",
|
|
74622
|
+
kind: payload.kind
|
|
74623
|
+
}));
|
|
74624
|
+
const stop_orders = execution_plan.orders_to_create.stop.map((order) => ({
|
|
74625
|
+
...order,
|
|
74626
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
74627
|
+
kind: payload.kind,
|
|
74628
|
+
type: order.type || "STOP"
|
|
74629
|
+
}));
|
|
74630
|
+
const take_profit_orders = execution_plan.orders_to_create.take_profit.map((order) => ({
|
|
74631
|
+
...order,
|
|
74632
|
+
side: payload.kind === "long" ? "sell" : "buy",
|
|
74633
|
+
kind: payload.kind,
|
|
74634
|
+
...order.stop ? { type: order.type || "TAKE_PROFIT" } : {}
|
|
74635
|
+
}));
|
|
74636
|
+
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]) : [];
|
|
74637
|
+
return {
|
|
74638
|
+
...preview,
|
|
74639
|
+
execution_plan,
|
|
74640
|
+
refreshed_snapshot,
|
|
74641
|
+
execution
|
|
74642
|
+
};
|
|
74643
|
+
}
|
|
74644
|
+
async placeFuturesExactTrade(payload) {
|
|
74645
|
+
return await this.placeFuturesReplay(payload);
|
|
74646
|
+
}
|
|
74318
74647
|
async getSpotMarginHedgeSnapshot(payload) {
|
|
74319
74648
|
if (!this.main_client) {
|
|
74320
74649
|
throw new Error("Main client not available for spot and margin trading");
|
|
74321
74650
|
}
|
|
74322
|
-
if (payload.margin_type !== "isolated") {
|
|
74323
|
-
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
|
|
74324
|
-
}
|
|
74325
74651
|
const symbol = payload.symbol.toUpperCase();
|
|
74326
74652
|
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
74327
74653
|
const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
74328
74654
|
const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
|
|
74329
|
-
const
|
|
74655
|
+
const is_isolated = payload.margin_type === "isolated";
|
|
74656
|
+
const [spot_orders, margin_orders, spot_balances, margin_account] = await Promise.all([
|
|
74330
74657
|
this.getSpotOpenOrders(symbol),
|
|
74331
|
-
this.getMarginOpenOrders(symbol,
|
|
74658
|
+
this.getMarginOpenOrders(symbol, is_isolated),
|
|
74332
74659
|
this.getSpotBalances([baseAsset, quoteAsset]),
|
|
74333
|
-
this.getIsolatedMarginPosition(symbol)
|
|
74660
|
+
is_isolated ? this.getIsolatedMarginPosition(symbol) : this.getCrossMarginAccount()
|
|
74334
74661
|
]);
|
|
74335
74662
|
const current_price = await this.getSpotCurrentPrice(symbol);
|
|
74336
74663
|
const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
|
|
74337
74664
|
const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
|
|
74338
|
-
const
|
|
74339
|
-
const
|
|
74665
|
+
const isolated_margin_account = is_isolated ? margin_account : undefined;
|
|
74666
|
+
const cross_margin_account = !is_isolated ? margin_account : undefined;
|
|
74667
|
+
const isolated_asset = isolated_margin_account?.assets?.[0];
|
|
74668
|
+
const cross_quote_asset = !is_isolated ? cross_margin_account?.userAssets?.find((asset) => asset.asset?.toUpperCase?.() === quoteAsset) : undefined;
|
|
74669
|
+
const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || cross_quote_asset?.borrowed?.toString?.() || "0");
|
|
74340
74670
|
return {
|
|
74341
74671
|
symbol,
|
|
74342
74672
|
margin_type: payload.margin_type,
|
|
@@ -74349,7 +74679,8 @@ class BinanceExchange extends BaseExchange {
|
|
|
74349
74679
|
margin_orders,
|
|
74350
74680
|
current_price,
|
|
74351
74681
|
spot_balances,
|
|
74352
|
-
isolated_margin_position
|
|
74682
|
+
isolated_margin_position: isolated_margin_account,
|
|
74683
|
+
cross_margin_account
|
|
74353
74684
|
};
|
|
74354
74685
|
}
|
|
74355
74686
|
async previewSpotMarginHedge(payload) {
|
|
@@ -74406,14 +74737,12 @@ class BinanceExchange extends BaseExchange {
|
|
|
74406
74737
|
if (!this.main_client) {
|
|
74407
74738
|
throw new Error("Main client not available for spot and margin trading");
|
|
74408
74739
|
}
|
|
74409
|
-
if (payload.margin_type !== "isolated") {
|
|
74410
|
-
throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
|
|
74411
|
-
}
|
|
74412
74740
|
const symbol = payload.symbol.toUpperCase();
|
|
74413
74741
|
const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
|
|
74414
74742
|
const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
|
|
74415
74743
|
const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
|
|
74416
74744
|
const requested_input = preview.requested_input;
|
|
74745
|
+
const is_isolated = payload.margin_type === "isolated";
|
|
74417
74746
|
if (preview.snapshot.spot_orders.length > 0) {
|
|
74418
74747
|
await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
|
|
74419
74748
|
orderId: order.orderId || order.order_id || order.id,
|
|
@@ -74421,7 +74750,7 @@ class BinanceExchange extends BaseExchange {
|
|
|
74421
74750
|
})));
|
|
74422
74751
|
}
|
|
74423
74752
|
if (preview.snapshot.margin_orders.length > 0) {
|
|
74424
|
-
await this.cancelMarginOrders(symbol,
|
|
74753
|
+
await this.cancelMarginOrders(symbol, is_isolated, preview.snapshot.margin_orders.map((order) => ({
|
|
74425
74754
|
orderId: order.orderId || order.order_id || order.id,
|
|
74426
74755
|
origClientOrderId: order.origClientOrderId
|
|
74427
74756
|
})));
|
|
@@ -74443,50 +74772,120 @@ class BinanceExchange extends BaseExchange {
|
|
|
74443
74772
|
});
|
|
74444
74773
|
let borrow_result = null;
|
|
74445
74774
|
if (execution_plan.borrow_delta > 0) {
|
|
74446
|
-
|
|
74775
|
+
const borrow_payload = {
|
|
74447
74776
|
asset: quote_asset,
|
|
74448
74777
|
amount: execution_plan.borrow_delta,
|
|
74449
|
-
isIsolated: "TRUE",
|
|
74450
|
-
symbol,
|
|
74451
74778
|
type: "BORROW"
|
|
74779
|
+
};
|
|
74780
|
+
if (is_isolated) {
|
|
74781
|
+
borrow_payload.isIsolated = "TRUE";
|
|
74782
|
+
borrow_payload.symbol = symbol;
|
|
74783
|
+
}
|
|
74784
|
+
borrow_result = await this.main_client.submitMarginAccountBorrowRepay(borrow_payload);
|
|
74785
|
+
}
|
|
74786
|
+
async function getCrossTransferAmount(asset, amount) {
|
|
74787
|
+
if (is_isolated) {
|
|
74788
|
+
return amount;
|
|
74789
|
+
}
|
|
74790
|
+
if (!this.main_client || typeof this.main_client.queryMaxTransferOutAmount !== "function") {
|
|
74791
|
+
return amount;
|
|
74792
|
+
}
|
|
74793
|
+
const response = await this.main_client.queryMaxTransferOutAmount({
|
|
74794
|
+
asset
|
|
74452
74795
|
});
|
|
74796
|
+
const allowed_amount = parseFloat(response?.amount?.toString?.() || "0");
|
|
74797
|
+
if (!Number.isFinite(allowed_amount)) {
|
|
74798
|
+
return amount;
|
|
74799
|
+
}
|
|
74800
|
+
return Math.max(0, Math.min(amount, allowed_amount));
|
|
74453
74801
|
}
|
|
74454
74802
|
let quote_transfer_result = null;
|
|
74803
|
+
let quote_transfer_amount = execution_plan.transfer_to_spot_amount;
|
|
74455
74804
|
if (execution_plan.transfer_to_spot_amount > 0) {
|
|
74456
|
-
|
|
74457
|
-
|
|
74458
|
-
|
|
74459
|
-
|
|
74460
|
-
|
|
74461
|
-
|
|
74462
|
-
|
|
74805
|
+
quote_transfer_amount = await getCrossTransferAmount.call(this, quote_asset, execution_plan.transfer_to_spot_amount);
|
|
74806
|
+
if (quote_transfer_amount > 0) {
|
|
74807
|
+
quote_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
|
|
74808
|
+
asset: quote_asset,
|
|
74809
|
+
amount: quote_transfer_amount,
|
|
74810
|
+
symbol,
|
|
74811
|
+
transFrom: "ISOLATED_MARGIN",
|
|
74812
|
+
transTo: "SPOT"
|
|
74813
|
+
}) : await this.main_client.submitUniversalTransfer({
|
|
74814
|
+
type: "MARGIN_MAIN",
|
|
74815
|
+
asset: quote_asset,
|
|
74816
|
+
amount: quote_transfer_amount
|
|
74817
|
+
});
|
|
74818
|
+
}
|
|
74463
74819
|
}
|
|
74464
74820
|
let base_transfer_result = null;
|
|
74821
|
+
let base_transfer_amount = execution_plan.transfer_base_to_spot_amount;
|
|
74465
74822
|
if (execution_plan.transfer_base_to_spot_amount > 0) {
|
|
74466
|
-
|
|
74467
|
-
|
|
74468
|
-
|
|
74469
|
-
|
|
74470
|
-
|
|
74471
|
-
|
|
74472
|
-
|
|
74823
|
+
base_transfer_amount = await getCrossTransferAmount.call(this, base_asset, execution_plan.transfer_base_to_spot_amount);
|
|
74824
|
+
if (base_transfer_amount > 0) {
|
|
74825
|
+
base_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
|
|
74826
|
+
asset: base_asset,
|
|
74827
|
+
amount: base_transfer_amount,
|
|
74828
|
+
symbol,
|
|
74829
|
+
transFrom: "ISOLATED_MARGIN",
|
|
74830
|
+
transTo: "SPOT"
|
|
74831
|
+
}) : await this.main_client.submitUniversalTransfer({
|
|
74832
|
+
type: "MARGIN_MAIN",
|
|
74833
|
+
asset: base_asset,
|
|
74834
|
+
amount: base_transfer_amount
|
|
74835
|
+
});
|
|
74836
|
+
}
|
|
74473
74837
|
}
|
|
74474
|
-
const
|
|
74838
|
+
const spot_quote_available = refreshed_snapshot.spot_quote_free + quote_transfer_amount;
|
|
74839
|
+
const requested_max_spot_buy_orders = requested_input.max_spot_buy_orders ?? 5;
|
|
74840
|
+
const sorted_long_orders = [...requested_input.long_orders].sort((a, b) => a.price - b.price);
|
|
74841
|
+
let affordable_spot_buy_orders = 0;
|
|
74842
|
+
let affordable_spot_buy_notional = 0;
|
|
74843
|
+
for (const order of sorted_long_orders.slice(0, requested_max_spot_buy_orders)) {
|
|
74844
|
+
const next_notional = affordable_spot_buy_notional + order.price * order.quantity;
|
|
74845
|
+
if (next_notional <= spot_quote_available + 0.00000001) {
|
|
74846
|
+
affordable_spot_buy_orders += 1;
|
|
74847
|
+
affordable_spot_buy_notional = next_notional;
|
|
74848
|
+
} else {
|
|
74849
|
+
break;
|
|
74850
|
+
}
|
|
74851
|
+
}
|
|
74852
|
+
const placement_plan = buildSpotMarginHedgePlan({
|
|
74853
|
+
borrow_amount: requested_input.borrow_amount,
|
|
74854
|
+
symbol: requested_input.symbol,
|
|
74855
|
+
margin_type: requested_input.margin_type,
|
|
74856
|
+
long_orders: requested_input.long_orders,
|
|
74857
|
+
short_orders: requested_input.short_orders,
|
|
74858
|
+
current_price: refreshed_snapshot.current_price,
|
|
74859
|
+
max_spot_buy_orders: affordable_spot_buy_orders,
|
|
74860
|
+
snapshot: {
|
|
74861
|
+
...refreshed_snapshot,
|
|
74862
|
+
borrowed_quote_amount: refreshed_snapshot.borrowed_quote_amount + execution_plan.borrow_delta,
|
|
74863
|
+
spot_quote_free: spot_quote_available,
|
|
74864
|
+
spot_base_free: refreshed_snapshot.spot_base_free + base_transfer_amount
|
|
74865
|
+
}
|
|
74866
|
+
});
|
|
74867
|
+
const spot_result = placement_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
|
|
74475
74868
|
symbol,
|
|
74476
|
-
orders:
|
|
74869
|
+
orders: placement_plan.orders_to_create.spot,
|
|
74477
74870
|
price_places: symbol_formats.price_places,
|
|
74478
74871
|
decimal_places: symbol_formats.decimal_places
|
|
74479
74872
|
}) : [];
|
|
74480
|
-
const margin_result =
|
|
74873
|
+
const margin_result = placement_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
|
|
74481
74874
|
symbol,
|
|
74482
|
-
orders:
|
|
74483
|
-
isIsolated:
|
|
74875
|
+
orders: placement_plan.orders_to_create.margin,
|
|
74876
|
+
isIsolated: is_isolated,
|
|
74484
74877
|
price_places: symbol_formats.price_places,
|
|
74485
74878
|
decimal_places: symbol_formats.decimal_places
|
|
74486
74879
|
}) : [];
|
|
74487
74880
|
return {
|
|
74488
74881
|
...preview,
|
|
74489
|
-
execution_plan
|
|
74882
|
+
execution_plan: {
|
|
74883
|
+
...placement_plan,
|
|
74884
|
+
borrow_amount: execution_plan.borrow_amount,
|
|
74885
|
+
borrow_delta: execution_plan.borrow_delta,
|
|
74886
|
+
transfer_to_spot_amount: quote_transfer_amount,
|
|
74887
|
+
transfer_base_to_spot_amount: base_transfer_amount
|
|
74888
|
+
},
|
|
74490
74889
|
refreshed_snapshot,
|
|
74491
74890
|
execution: {
|
|
74492
74891
|
borrow: borrow_result ? {
|
|
@@ -74496,12 +74895,12 @@ class BinanceExchange extends BaseExchange {
|
|
|
74496
74895
|
} : null,
|
|
74497
74896
|
quote_transfer: quote_transfer_result ? {
|
|
74498
74897
|
asset: quote_asset,
|
|
74499
|
-
amount:
|
|
74898
|
+
amount: quote_transfer_amount,
|
|
74500
74899
|
response: quote_transfer_result
|
|
74501
74900
|
} : null,
|
|
74502
74901
|
base_transfer: base_transfer_result ? {
|
|
74503
74902
|
asset: base_asset,
|
|
74504
|
-
amount:
|
|
74903
|
+
amount: base_transfer_amount,
|
|
74505
74904
|
response: base_transfer_result
|
|
74506
74905
|
} : null,
|
|
74507
74906
|
spot_orders: spot_result,
|