@gbozee/ultimate 0.0.2-next.72 → 0.0.2-next.74

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
@@ -72035,6 +72035,21 @@ class BaseExchange {
72035
72035
  });
72036
72036
  }
72037
72037
  }
72038
+ async previewSpotMarginHedge(_payload) {
72039
+ throw new Error("Spot margin hedge preview is not supported on this exchange");
72040
+ }
72041
+ async previewFuturesReplay(_payload) {
72042
+ throw new Error("Futures replay preview is not supported on this exchange");
72043
+ }
72044
+ async placeSpotMarginHedge(_payload) {
72045
+ throw new Error("Spot margin hedge placement is not supported on this exchange");
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
+ }
72038
72053
  async placeSpotLimitOrders(_payload) {}
72039
72054
  async buildCumulative(payload) {
72040
72055
  const {
@@ -72442,6 +72457,389 @@ class BaseExchange {
72442
72457
 
72443
72458
  // src/exchanges/binance/index.ts
72444
72459
  var import_p_limit = __toESM(require_p_limit(), 1);
72460
+
72461
+ // src/exchanges/binance/futures-replay.ts
72462
+ function roundNumber(value2, digits = 8) {
72463
+ return Number(value2.toFixed(digits));
72464
+ }
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
+ }
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;
72477
+ return {
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)
72649
+ };
72650
+ }
72651
+ function sortOrderInputs(orders) {
72652
+ return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
72653
+ }
72654
+ function normalizeOrderForComparison(order) {
72655
+ const type = (order.type || "limit").toLowerCase();
72656
+ const raw_stop = "stop" in order && order.stop !== undefined ? order.stop : ("stopPrice" in order) ? order.stopPrice : undefined;
72657
+ const parsed_stop = raw_stop === undefined || raw_stop === null || raw_stop === "" ? undefined : Number(raw_stop);
72658
+ return {
72659
+ side: order.side,
72660
+ type,
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
72664
+ };
72665
+ }
72666
+ function normalizeOrders(orders) {
72667
+ return [...orders].map(normalizeOrderForComparison).sort((left, right) => {
72668
+ if (left.side !== right.side)
72669
+ return left.side.localeCompare(right.side);
72670
+ if (left.type !== right.type)
72671
+ return left.type.localeCompare(right.type);
72672
+ if (left.price !== right.price)
72673
+ return left.price - right.price;
72674
+ return left.quantity - right.quantity;
72675
+ });
72676
+ }
72677
+ function toPlannedOrders(orders) {
72678
+ return normalizeOrders(orders).map((order) => ({
72679
+ side: order.side,
72680
+ type: order.type,
72681
+ price: order.price,
72682
+ quantity: order.quantity,
72683
+ stop: order.stop
72684
+ }));
72685
+ }
72686
+ function buildOrderKey2(order) {
72687
+ return [
72688
+ order.side,
72689
+ order.type,
72690
+ order.price,
72691
+ order.quantity,
72692
+ order.stop ?? ""
72693
+ ].join(":");
72694
+ }
72695
+ function buildDiff(options) {
72696
+ const expectedKeys = options.expected.map(buildOrderKey2);
72697
+ const actualKeys = options.actual.map(buildOrderKey2);
72698
+ return {
72699
+ missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
72700
+ extra: actualKeys.filter((key) => !expectedKeys.includes(key))
72701
+ };
72702
+ }
72703
+ function getQuoteAsset(symbol) {
72704
+ const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
72705
+ const target = quoteAssets.find((asset) => symbol.endsWith(asset));
72706
+ return target || symbol.slice(-4);
72707
+ }
72708
+ function sumNotional(orders) {
72709
+ return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
72710
+ }
72711
+ function sumQuantity(orders) {
72712
+ return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
72713
+ }
72714
+ function buildLongOrderPlacement(options) {
72715
+ const { order, current_price } = options;
72716
+ if (order.price > current_price) {
72717
+ return {
72718
+ side: "buy",
72719
+ type: "stop_loss_limit",
72720
+ price: order.price,
72721
+ stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
72722
+ quantity: order.quantity
72723
+ };
72724
+ }
72725
+ return {
72726
+ side: "buy",
72727
+ type: "limit",
72728
+ price: order.price,
72729
+ quantity: order.quantity
72730
+ };
72731
+ }
72732
+ function inferSpotMarginHedgeInputFromLiveOrders(options) {
72733
+ const long_orders = sortOrderInputs([
72734
+ ...options.spot_orders.filter((order) => order.side === "buy").map((order) => ({
72735
+ price: order.price,
72736
+ quantity: order.quantity
72737
+ })),
72738
+ ...options.margin_orders.filter((order) => order.side === "buy").map((order) => ({
72739
+ price: order.price,
72740
+ quantity: order.quantity
72741
+ }))
72742
+ ]);
72743
+ const short_orders = sortOrderInputs([
72744
+ ...options.spot_orders.filter((order) => order.side === "sell").map((order) => ({
72745
+ price: order.price,
72746
+ quantity: order.quantity
72747
+ })),
72748
+ ...options.margin_orders.filter((order) => order.side === "sell").map((order) => ({
72749
+ price: order.price,
72750
+ quantity: order.quantity
72751
+ }))
72752
+ ]);
72753
+ return {
72754
+ symbol: options.symbol,
72755
+ margin_type: options.margin_type,
72756
+ borrow_amount: sumNotional(long_orders),
72757
+ long_orders,
72758
+ short_orders,
72759
+ placement_overrides: {
72760
+ spot: toPlannedOrders(options.spot_orders),
72761
+ margin: toPlannedOrders(options.margin_orders)
72762
+ }
72763
+ };
72764
+ }
72765
+ function buildSpotMarginHedgePlan(options) {
72766
+ const max_spot_buy_orders = options.max_spot_buy_orders ?? 5;
72767
+ const long_orders = sortOrderInputs(options.long_orders);
72768
+ const short_orders = sortOrderInputs(options.short_orders);
72769
+ const spot_long_orders = long_orders.slice(0, max_spot_buy_orders);
72770
+ const margin_long_orders = long_orders.slice(max_spot_buy_orders);
72771
+ const default_spot_orders_to_create = [
72772
+ ...spot_long_orders.map((order) => buildLongOrderPlacement({
72773
+ order,
72774
+ current_price: options.current_price
72775
+ })),
72776
+ ...short_orders.map((order) => ({
72777
+ side: "sell",
72778
+ type: "limit",
72779
+ price: order.price,
72780
+ quantity: order.quantity
72781
+ }))
72782
+ ];
72783
+ const default_margin_orders_to_create = margin_long_orders.map((order) => buildLongOrderPlacement({
72784
+ order,
72785
+ current_price: options.current_price
72786
+ }));
72787
+ const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
72788
+ const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
72789
+ const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
72790
+ const spot_long_notional = sumNotional(spot_long_orders);
72791
+ const transfer_to_spot_amount = roundNumber2(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
72792
+ const short_quantity = sumQuantity(short_orders);
72793
+ const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
72794
+ return {
72795
+ borrow_amount: options.borrow_amount,
72796
+ borrow_delta,
72797
+ transfer_to_spot_amount,
72798
+ transfer_to_spot_asset: getQuoteAsset(options.symbol),
72799
+ transfer_base_to_spot_amount,
72800
+ orders_to_cancel: {
72801
+ spot: options.snapshot.spot_orders,
72802
+ margin: options.snapshot.margin_orders
72803
+ },
72804
+ orders_to_create: {
72805
+ spot: spot_orders_to_create,
72806
+ margin: margin_orders_to_create
72807
+ },
72808
+ normalized: {
72809
+ spot: normalizeOrders(spot_orders_to_create),
72810
+ margin: normalizeOrders(margin_orders_to_create)
72811
+ }
72812
+ };
72813
+ }
72814
+ function compareSpotMarginHedgeOrders(options) {
72815
+ const actualSpot = normalizeOrders(options.spot_orders);
72816
+ const actualMargin = normalizeOrders(options.margin_orders);
72817
+ const spotDiff = buildDiff({
72818
+ expected: options.plan.normalized.spot,
72819
+ actual: actualSpot
72820
+ });
72821
+ const marginDiff = buildDiff({
72822
+ expected: options.plan.normalized.margin,
72823
+ actual: actualMargin
72824
+ });
72825
+ return {
72826
+ matches: spotDiff.missing.length === 0 && spotDiff.extra.length === 0 && marginDiff.missing.length === 0 && marginDiff.extra.length === 0,
72827
+ spot: {
72828
+ expected: options.plan.normalized.spot,
72829
+ actual: actualSpot,
72830
+ missing: spotDiff.missing,
72831
+ extra: spotDiff.extra
72832
+ },
72833
+ margin: {
72834
+ expected: options.plan.normalized.margin,
72835
+ actual: actualMargin,
72836
+ missing: marginDiff.missing,
72837
+ extra: marginDiff.extra
72838
+ }
72839
+ };
72840
+ }
72841
+
72842
+ // src/exchanges/binance/index.ts
72445
72843
  var CONSTANTS = {
72446
72844
  SPOT_TO_FIAT: "MAIN_C2C",
72447
72845
  SPOT_TO_USDT_FUTURE: "MAIN_UMFUTURE",
@@ -72930,12 +73328,20 @@ async function getCrossMarginAccountDetails(client) {
72930
73328
  async function cancelMarginOrders(client, symbol, isIsolated = false, orders) {
72931
73329
  try {
72932
73330
  if (orders && orders.length > 0) {
72933
- const cancelPromises = orders.map((order) => client.marginAccountCancelOrder({
72934
- symbol,
72935
- orderId: order.orderId,
72936
- origClientOrderId: order.origClientOrderId,
72937
- isIsolated: isIsolated ? "TRUE" : "FALSE"
72938
- }));
73331
+ const cancelPromises = orders.map((order) => {
73332
+ const payload = {
73333
+ symbol,
73334
+ isIsolated: isIsolated ? "TRUE" : "FALSE"
73335
+ };
73336
+ if (order.orderId !== undefined) {
73337
+ payload.orderId = order.orderId;
73338
+ }
73339
+ const origClientOrderId = order.origClientOrderId ?? order.clientOrderId;
73340
+ if (origClientOrderId !== undefined) {
73341
+ payload.origClientOrderId = origClientOrderId;
73342
+ }
73343
+ return client.marginAccountCancelOrder(payload);
73344
+ });
72939
73345
  return await Promise.all(cancelPromises);
72940
73346
  } else {
72941
73347
  return await client.marginAccountCancelOpenOrders({
@@ -73190,7 +73596,7 @@ async function placeStopOrder(client, payload) {
73190
73596
  });
73191
73597
  }
73192
73598
  }
73193
- const spread = 1.00005;
73599
+ const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
73194
73600
  const order = {
73195
73601
  kind: payload.kind,
73196
73602
  side: payload.kind === "long" ? "sell" : "buy",
@@ -73597,6 +74003,16 @@ async function getAllOpenOrders(payload) {
73597
74003
  const response = await client.getAllOpenOrders();
73598
74004
  return response;
73599
74005
  }
74006
+ function getPrintfFormatFromStepSize(stepSize, fallback) {
74007
+ if (stepSize === undefined || stepSize === null) {
74008
+ return fallback;
74009
+ }
74010
+ const normalized = String(stepSize);
74011
+ const [, decimalPart = ""] = normalized.split(".");
74012
+ const trimmed = decimalPart.replace(/0+$/, "");
74013
+ const digits = trimmed.length;
74014
+ return `%.${digits}f`;
74015
+ }
73600
74016
 
73601
74017
  class BinanceExchange extends BaseExchange {
73602
74018
  main_client;
@@ -74033,6 +74449,54 @@ class BinanceExchange extends BaseExchange {
74033
74449
  throw error;
74034
74450
  }
74035
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
+ }
74476
+ async getSpotSymbolFormats(symbol) {
74477
+ if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
74478
+ return {
74479
+ price_places: "%.8f",
74480
+ decimal_places: "%.8f"
74481
+ };
74482
+ }
74483
+ try {
74484
+ const exchange_info = await this.main_client.getExchangeInfo();
74485
+ const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
74486
+ const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
74487
+ const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
74488
+ return {
74489
+ price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
74490
+ decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
74491
+ };
74492
+ } catch (error) {
74493
+ console.error("Error fetching spot symbol formats:", error);
74494
+ return {
74495
+ price_places: "%.8f",
74496
+ decimal_places: "%.8f"
74497
+ };
74498
+ }
74499
+ }
74036
74500
  async createMarginLimitOrders(payload) {
74037
74501
  if (!this.main_client) {
74038
74502
  throw new Error("Main client not available for margin trading");
@@ -74063,6 +74527,317 @@ class BinanceExchange extends BaseExchange {
74063
74527
  }
74064
74528
  return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
74065
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
+ }
74647
+ async getSpotMarginHedgeSnapshot(payload) {
74648
+ if (!this.main_client) {
74649
+ throw new Error("Main client not available for spot and margin trading");
74650
+ }
74651
+ if (payload.margin_type !== "isolated") {
74652
+ throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
74653
+ }
74654
+ const symbol = payload.symbol.toUpperCase();
74655
+ const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
74656
+ const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
74657
+ const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
74658
+ const [spot_orders, margin_orders, spot_balances, isolated_margin_position] = await Promise.all([
74659
+ this.getSpotOpenOrders(symbol),
74660
+ this.getMarginOpenOrders(symbol, true),
74661
+ this.getSpotBalances([baseAsset, quoteAsset]),
74662
+ this.getIsolatedMarginPosition(symbol)
74663
+ ]);
74664
+ const current_price = await this.getSpotCurrentPrice(symbol);
74665
+ const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
74666
+ const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
74667
+ const isolated_asset = isolated_margin_position?.assets?.[0];
74668
+ const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || "0");
74669
+ return {
74670
+ symbol,
74671
+ margin_type: payload.margin_type,
74672
+ quote_asset: quoteAsset,
74673
+ base_asset: baseAsset,
74674
+ borrowed_quote_amount,
74675
+ spot_quote_free: parseFloat(spot_quote_balance?.free?.toString?.() || "0"),
74676
+ spot_base_free: parseFloat(spot_base_balance?.free?.toString?.() || "0"),
74677
+ spot_orders,
74678
+ margin_orders,
74679
+ current_price,
74680
+ spot_balances,
74681
+ isolated_margin_position
74682
+ };
74683
+ }
74684
+ async previewSpotMarginHedge(payload) {
74685
+ const snapshot = payload.snapshot ? {
74686
+ ...payload.snapshot,
74687
+ current_price: payload.snapshot.current_price ?? await this.getSpotCurrentPrice(payload.symbol)
74688
+ } : await this.getSpotMarginHedgeSnapshot({
74689
+ symbol: payload.symbol,
74690
+ margin_type: payload.margin_type
74691
+ });
74692
+ const shouldInferLongOrders = !payload.long_orders || payload.long_orders.length === 0;
74693
+ const shouldInferShortOrders = !payload.short_orders || payload.short_orders.length === 0;
74694
+ const inferred_input = shouldInferLongOrders || shouldInferShortOrders ? inferSpotMarginHedgeInputFromLiveOrders({
74695
+ symbol: payload.symbol,
74696
+ margin_type: payload.margin_type,
74697
+ spot_orders: snapshot.spot_orders,
74698
+ margin_orders: snapshot.margin_orders
74699
+ }) : undefined;
74700
+ const long_orders = shouldInferLongOrders ? inferred_input?.long_orders || [] : payload.long_orders || [];
74701
+ const short_orders = shouldInferShortOrders ? inferred_input?.short_orders || [] : payload.short_orders || [];
74702
+ const borrow_amount = payload.borrow_amount ?? (long_orders.length > 0 ? sumNotional(long_orders) : inferred_input?.borrow_amount ?? 0);
74703
+ const requested_input = {
74704
+ borrow_amount,
74705
+ symbol: payload.symbol,
74706
+ margin_type: payload.margin_type,
74707
+ long_orders,
74708
+ short_orders,
74709
+ current_price: snapshot.current_price,
74710
+ max_spot_buy_orders: payload.max_spot_buy_orders,
74711
+ placement_overrides: inferred_input?.placement_overrides
74712
+ };
74713
+ const plan = buildSpotMarginHedgePlan({
74714
+ ...requested_input,
74715
+ snapshot
74716
+ });
74717
+ const comparison = compareSpotMarginHedgeOrders({
74718
+ plan,
74719
+ spot_orders: snapshot.spot_orders,
74720
+ margin_orders: snapshot.margin_orders
74721
+ });
74722
+ return {
74723
+ snapshot,
74724
+ inferred_input,
74725
+ requested_input,
74726
+ plan,
74727
+ comparison
74728
+ };
74729
+ }
74730
+ async placeSpotMarginHedge(payload) {
74731
+ const preview = await this.previewSpotMarginHedge(payload);
74732
+ if (!payload.place) {
74733
+ return preview;
74734
+ }
74735
+ if (!this.main_client) {
74736
+ throw new Error("Main client not available for spot and margin trading");
74737
+ }
74738
+ if (payload.margin_type !== "isolated") {
74739
+ throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
74740
+ }
74741
+ const symbol = payload.symbol.toUpperCase();
74742
+ const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
74743
+ const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
74744
+ const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
74745
+ const requested_input = preview.requested_input;
74746
+ if (preview.snapshot.spot_orders.length > 0) {
74747
+ await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
74748
+ orderId: order.orderId || order.order_id || order.id,
74749
+ origClientOrderId: order.origClientOrderId
74750
+ })));
74751
+ }
74752
+ if (preview.snapshot.margin_orders.length > 0) {
74753
+ await this.cancelMarginOrders(symbol, true, preview.snapshot.margin_orders.map((order) => ({
74754
+ orderId: order.orderId || order.order_id || order.id,
74755
+ origClientOrderId: order.origClientOrderId
74756
+ })));
74757
+ }
74758
+ const refreshed_snapshot = await this.getSpotMarginHedgeSnapshot({
74759
+ symbol,
74760
+ margin_type: payload.margin_type
74761
+ });
74762
+ const symbol_formats = await this.getSpotSymbolFormats(symbol);
74763
+ const execution_plan = buildSpotMarginHedgePlan({
74764
+ borrow_amount: requested_input.borrow_amount,
74765
+ symbol: requested_input.symbol,
74766
+ margin_type: requested_input.margin_type,
74767
+ long_orders: requested_input.long_orders,
74768
+ short_orders: requested_input.short_orders,
74769
+ current_price: refreshed_snapshot.current_price,
74770
+ max_spot_buy_orders: requested_input.max_spot_buy_orders,
74771
+ snapshot: refreshed_snapshot
74772
+ });
74773
+ let borrow_result = null;
74774
+ if (execution_plan.borrow_delta > 0) {
74775
+ borrow_result = await this.main_client.submitMarginAccountBorrowRepay({
74776
+ asset: quote_asset,
74777
+ amount: execution_plan.borrow_delta,
74778
+ isIsolated: "TRUE",
74779
+ symbol,
74780
+ type: "BORROW"
74781
+ });
74782
+ }
74783
+ let quote_transfer_result = null;
74784
+ if (execution_plan.transfer_to_spot_amount > 0) {
74785
+ quote_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
74786
+ asset: quote_asset,
74787
+ amount: execution_plan.transfer_to_spot_amount,
74788
+ symbol,
74789
+ transFrom: "ISOLATED_MARGIN",
74790
+ transTo: "SPOT"
74791
+ });
74792
+ }
74793
+ let base_transfer_result = null;
74794
+ if (execution_plan.transfer_base_to_spot_amount > 0) {
74795
+ base_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
74796
+ asset: base_asset,
74797
+ amount: execution_plan.transfer_base_to_spot_amount,
74798
+ symbol,
74799
+ transFrom: "ISOLATED_MARGIN",
74800
+ transTo: "SPOT"
74801
+ });
74802
+ }
74803
+ const spot_result = execution_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
74804
+ symbol,
74805
+ orders: execution_plan.orders_to_create.spot,
74806
+ price_places: symbol_formats.price_places,
74807
+ decimal_places: symbol_formats.decimal_places
74808
+ }) : [];
74809
+ const margin_result = execution_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
74810
+ symbol,
74811
+ orders: execution_plan.orders_to_create.margin,
74812
+ isIsolated: true,
74813
+ price_places: symbol_formats.price_places,
74814
+ decimal_places: symbol_formats.decimal_places
74815
+ }) : [];
74816
+ return {
74817
+ ...preview,
74818
+ execution_plan,
74819
+ refreshed_snapshot,
74820
+ execution: {
74821
+ borrow: borrow_result ? {
74822
+ asset: quote_asset,
74823
+ amount: execution_plan.borrow_delta,
74824
+ response: borrow_result
74825
+ } : null,
74826
+ quote_transfer: quote_transfer_result ? {
74827
+ asset: quote_asset,
74828
+ amount: execution_plan.transfer_to_spot_amount,
74829
+ response: quote_transfer_result
74830
+ } : null,
74831
+ base_transfer: base_transfer_result ? {
74832
+ asset: base_asset,
74833
+ amount: execution_plan.transfer_base_to_spot_amount,
74834
+ response: base_transfer_result
74835
+ } : null,
74836
+ spot_orders: spot_result,
74837
+ margin_orders: margin_result
74838
+ }
74839
+ };
74840
+ }
74066
74841
  async getLastUserTrade(payload) {
74067
74842
  return await getLastUserTrade(this.client, payload.symbol);
74068
74843
  }