@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 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/spot-margin-hedge.ts
72461
+ // src/exchanges/binance/futures-replay.ts
72453
72462
  function roundNumber(value2, digits = 8) {
72454
72463
  return Number(value2.toFixed(digits));
72455
72464
  }
72456
- var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
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(order.quantity)
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(normalizeOrderInput).sort((left, right) => left.price - right.price);
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: roundNumber(order.price),
72474
- quantity: roundNumber(order.quantity),
72475
- stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber(parsed_stop) : undefined
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 buildOrderKey(order) {
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(buildOrderKey);
72509
- const actualKeys = options.actual.map(buildOrderKey);
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 roundNumber(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
72709
+ return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
72522
72710
  }
72523
72711
  function sumQuantity(orders) {
72524
- return roundNumber(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
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: roundNumber(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
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 = roundNumber(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
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 = roundNumber(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
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 = roundNumber(Math.max(0, short_quantity - options.snapshot.spot_base_free));
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 limit = import_p_limit.default(ORDERS_PER_SECOND);
73108
- const results = await Promise.all(newOrders.map((orderPayload) => limit(async () => {
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
- return result;
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 [spot_orders, margin_orders, spot_balances, isolated_margin_position] = await Promise.all([
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, true),
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 isolated_asset = isolated_margin_position?.assets?.[0];
74339
- const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || "0");
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, true, preview.snapshot.margin_orders.map((order) => ({
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
- borrow_result = await this.main_client.submitMarginAccountBorrowRepay({
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
- quote_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
74457
- asset: quote_asset,
74458
- amount: execution_plan.transfer_to_spot_amount,
74459
- symbol,
74460
- transFrom: "ISOLATED_MARGIN",
74461
- transTo: "SPOT"
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
- base_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
74467
- asset: base_asset,
74468
- amount: execution_plan.transfer_base_to_spot_amount,
74469
- symbol,
74470
- transFrom: "ISOLATED_MARGIN",
74471
- transTo: "SPOT"
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 spot_result = execution_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
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: execution_plan.orders_to_create.spot,
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 = execution_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
74873
+ const margin_result = placement_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
74481
74874
  symbol,
74482
- orders: execution_plan.orders_to_create.margin,
74483
- isIsolated: true,
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: execution_plan.transfer_to_spot_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: execution_plan.transfer_base_to_spot_amount,
74903
+ amount: base_transfer_amount,
74505
74904
  response: base_transfer_result
74506
74905
  } : null,
74507
74906
  spot_orders: spot_result,