@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.
@@ -75716,6 +75716,21 @@ class BaseExchange {
75716
75716
  });
75717
75717
  }
75718
75718
  }
75719
+ async previewSpotMarginHedge(_payload) {
75720
+ throw new Error("Spot margin hedge preview is not supported on this exchange");
75721
+ }
75722
+ async previewFuturesReplay(_payload) {
75723
+ throw new Error("Futures replay preview is not supported on this exchange");
75724
+ }
75725
+ async placeSpotMarginHedge(_payload) {
75726
+ throw new Error("Spot margin hedge placement is not supported on this exchange");
75727
+ }
75728
+ async placeFuturesReplay(_payload) {
75729
+ throw new Error("Futures replay placement is not supported on this exchange");
75730
+ }
75731
+ async placeFuturesExactTrade(_payload) {
75732
+ throw new Error("Futures exact trade placement is not supported on this exchange");
75733
+ }
75719
75734
  async placeSpotLimitOrders(_payload) {}
75720
75735
  async buildCumulative(payload) {
75721
75736
  const {
@@ -76123,6 +76138,389 @@ class BaseExchange {
76123
76138
 
76124
76139
  // src/exchanges/binance/index.ts
76125
76140
  var import_p_limit = __toESM(require_p_limit(), 1);
76141
+
76142
+ // src/exchanges/binance/futures-replay.ts
76143
+ function roundNumber(value2, digits = 8) {
76144
+ return Number(value2.toFixed(digits));
76145
+ }
76146
+ function parseNumber2(value2) {
76147
+ if (value2 === undefined || value2 === null || value2 === "")
76148
+ return;
76149
+ const parsed = Number(value2);
76150
+ if (!Number.isFinite(parsed) || parsed <= 0)
76151
+ return;
76152
+ return roundNumber(parsed);
76153
+ }
76154
+ function normalizeOrderInput(order) {
76155
+ const stop = parseNumber2(order.stop) ?? parseNumber2(order.stopPrice) ?? parseNumber2(order.triggerPrice);
76156
+ const type = order.type ? order.type.toUpperCase() : undefined;
76157
+ const quantity = order.quantity ?? order.qty ?? 0;
76158
+ return {
76159
+ price: roundNumber(Number(order.price)),
76160
+ quantity: roundNumber(Number(quantity)),
76161
+ ...stop ? { stop } : {},
76162
+ ...type && type !== "LIMIT" ? { type } : {}
76163
+ };
76164
+ }
76165
+ function sortReplayOrders(orders) {
76166
+ return [...orders].sort((left, right) => {
76167
+ const leftTrigger = left.stop ?? left.price;
76168
+ const rightTrigger = right.stop ?? right.price;
76169
+ if (leftTrigger !== rightTrigger)
76170
+ return leftTrigger - rightTrigger;
76171
+ if (left.price !== right.price)
76172
+ return left.price - right.price;
76173
+ return left.quantity - right.quantity;
76174
+ });
76175
+ }
76176
+ function buildReplayStopLimitPrice(payload) {
76177
+ const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
76178
+ return roundNumber(payload.kind === "long" ? payload.stop * spread ** -1 : payload.stop * spread);
76179
+ }
76180
+ function normalizeRequestedEntryOrders(orders) {
76181
+ return sortReplayOrders(orders.map((order) => ({
76182
+ price: roundNumber(Number(order.price)),
76183
+ quantity: roundNumber(Number(order.quantity))
76184
+ })));
76185
+ }
76186
+ function normalizeRequestedStopOrders(payload) {
76187
+ return sortReplayOrders(payload.orders.map((order) => {
76188
+ const stop = parseNumber2(order.stop) ?? roundNumber(Number(order.price));
76189
+ return {
76190
+ price: order.price !== undefined ? roundNumber(Number(order.price)) : buildReplayStopLimitPrice({
76191
+ kind: payload.kind,
76192
+ stop
76193
+ }),
76194
+ quantity: roundNumber(Number(order.quantity)),
76195
+ stop,
76196
+ type: (order.type || "STOP").toUpperCase()
76197
+ };
76198
+ }));
76199
+ }
76200
+ function normalizeRequestedTakeProfitOrders(orders) {
76201
+ return sortReplayOrders(orders.map((order) => {
76202
+ const stop = parseNumber2(order.stop);
76203
+ const type = order.type?.toUpperCase();
76204
+ return {
76205
+ price: roundNumber(Number(order.price)),
76206
+ quantity: roundNumber(Number(order.quantity)),
76207
+ ...stop ? { stop } : {},
76208
+ ...type ? { type } : stop ? { type: "TAKE_PROFIT" } : {}
76209
+ };
76210
+ }));
76211
+ }
76212
+ function buildOrderKey(order) {
76213
+ return [
76214
+ order.type || "LIMIT",
76215
+ order.price,
76216
+ order.quantity,
76217
+ order.stop ?? ""
76218
+ ].join(":");
76219
+ }
76220
+ function buildComparisonBucket(options) {
76221
+ const expected = sortReplayOrders(options.expected);
76222
+ const actual = sortReplayOrders(options.actual);
76223
+ const expected_keys = expected.map(buildOrderKey);
76224
+ const actual_keys = actual.map(buildOrderKey);
76225
+ return {
76226
+ expected,
76227
+ actual,
76228
+ missing: expected_keys.filter((key) => !actual_keys.includes(key)),
76229
+ extra: actual_keys.filter((key) => !expected_keys.includes(key))
76230
+ };
76231
+ }
76232
+ function buildFuturesReplaySnapshot(payload) {
76233
+ const entry_side = payload.kind === "long" ? "buy" : "sell";
76234
+ const close_side = payload.kind === "long" ? "sell" : "buy";
76235
+ const entry_orders = [];
76236
+ const stop_orders = [];
76237
+ const take_profit_orders = [];
76238
+ for (const order of payload.open_orders) {
76239
+ if (order.kind !== payload.kind)
76240
+ continue;
76241
+ const normalized_order = normalizeOrderInput(order);
76242
+ const type = (normalized_order.type || "LIMIT").toUpperCase();
76243
+ if (order.side === entry_side) {
76244
+ entry_orders.push(normalized_order);
76245
+ continue;
76246
+ }
76247
+ if (order.side !== close_side)
76248
+ continue;
76249
+ if (type.includes("TAKE_PROFIT") || normalized_order.stop === undefined) {
76250
+ take_profit_orders.push(normalized_order);
76251
+ continue;
76252
+ }
76253
+ stop_orders.push(normalized_order);
76254
+ }
76255
+ return {
76256
+ symbol: payload.symbol,
76257
+ kind: payload.kind,
76258
+ current_price: payload.current_price,
76259
+ position: payload.position,
76260
+ open_orders: payload.open_orders.filter((order) => order.kind === payload.kind),
76261
+ entry_orders: sortReplayOrders(entry_orders),
76262
+ stop_orders: sortReplayOrders(stop_orders),
76263
+ take_profit_orders: sortReplayOrders(take_profit_orders)
76264
+ };
76265
+ }
76266
+ function inferFuturesReplayInputFromLiveOrders(payload) {
76267
+ const snapshot = buildFuturesReplaySnapshot(payload);
76268
+ return {
76269
+ symbol: payload.symbol,
76270
+ kind: payload.kind,
76271
+ current_price: payload.current_price,
76272
+ position: payload.position,
76273
+ entry_orders: snapshot.entry_orders,
76274
+ stop_orders: snapshot.stop_orders,
76275
+ take_profit_orders: snapshot.take_profit_orders
76276
+ };
76277
+ }
76278
+ function compareFuturesReplayOrders(payload) {
76279
+ const entry = buildComparisonBucket({
76280
+ expected: payload.entry_orders,
76281
+ actual: payload.snapshot.entry_orders
76282
+ });
76283
+ const stop = buildComparisonBucket({
76284
+ expected: payload.stop_orders,
76285
+ actual: payload.snapshot.stop_orders
76286
+ });
76287
+ const take_profit = buildComparisonBucket({
76288
+ expected: payload.take_profit_orders,
76289
+ actual: payload.snapshot.take_profit_orders
76290
+ });
76291
+ return {
76292
+ matches: entry.missing.length === 0 && entry.extra.length === 0 && stop.missing.length === 0 && stop.extra.length === 0 && take_profit.missing.length === 0 && take_profit.extra.length === 0,
76293
+ entry,
76294
+ stop,
76295
+ take_profit
76296
+ };
76297
+ }
76298
+ function buildFuturesReplayPlan(payload) {
76299
+ return {
76300
+ symbol: payload.symbol,
76301
+ kind: payload.kind,
76302
+ orders_to_cancel: payload.snapshot.open_orders,
76303
+ orders_to_create: {
76304
+ entry: sortReplayOrders(payload.entry_orders),
76305
+ stop: sortReplayOrders(payload.stop_orders),
76306
+ take_profit: sortReplayOrders(payload.take_profit_orders)
76307
+ }
76308
+ };
76309
+ }
76310
+ function normalizeFuturesReplayInput(payload) {
76311
+ return {
76312
+ entry_orders: normalizeRequestedEntryOrders(payload.entry_orders),
76313
+ stop_orders: normalizeRequestedStopOrders({
76314
+ kind: payload.kind,
76315
+ orders: payload.stop_orders
76316
+ }),
76317
+ take_profit_orders: normalizeRequestedTakeProfitOrders(payload.take_profit_orders)
76318
+ };
76319
+ }
76320
+
76321
+ // src/exchanges/binance/spot-margin-hedge.ts
76322
+ function roundNumber2(value2, digits = 8) {
76323
+ return Number(value2.toFixed(digits));
76324
+ }
76325
+ var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
76326
+ function normalizeOrderInput2(order) {
76327
+ return {
76328
+ price: roundNumber2(order.price),
76329
+ quantity: roundNumber2(order.quantity)
76330
+ };
76331
+ }
76332
+ function sortOrderInputs(orders) {
76333
+ return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
76334
+ }
76335
+ function normalizeOrderForComparison(order) {
76336
+ const type = (order.type || "limit").toLowerCase();
76337
+ const raw_stop = "stop" in order && order.stop !== undefined ? order.stop : ("stopPrice" in order) ? order.stopPrice : undefined;
76338
+ const parsed_stop = raw_stop === undefined || raw_stop === null || raw_stop === "" ? undefined : Number(raw_stop);
76339
+ return {
76340
+ side: order.side,
76341
+ type,
76342
+ price: roundNumber2(order.price),
76343
+ quantity: roundNumber2(order.quantity),
76344
+ stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber2(parsed_stop) : undefined
76345
+ };
76346
+ }
76347
+ function normalizeOrders(orders) {
76348
+ return [...orders].map(normalizeOrderForComparison).sort((left, right) => {
76349
+ if (left.side !== right.side)
76350
+ return left.side.localeCompare(right.side);
76351
+ if (left.type !== right.type)
76352
+ return left.type.localeCompare(right.type);
76353
+ if (left.price !== right.price)
76354
+ return left.price - right.price;
76355
+ return left.quantity - right.quantity;
76356
+ });
76357
+ }
76358
+ function toPlannedOrders(orders) {
76359
+ return normalizeOrders(orders).map((order) => ({
76360
+ side: order.side,
76361
+ type: order.type,
76362
+ price: order.price,
76363
+ quantity: order.quantity,
76364
+ stop: order.stop
76365
+ }));
76366
+ }
76367
+ function buildOrderKey2(order) {
76368
+ return [
76369
+ order.side,
76370
+ order.type,
76371
+ order.price,
76372
+ order.quantity,
76373
+ order.stop ?? ""
76374
+ ].join(":");
76375
+ }
76376
+ function buildDiff(options) {
76377
+ const expectedKeys = options.expected.map(buildOrderKey2);
76378
+ const actualKeys = options.actual.map(buildOrderKey2);
76379
+ return {
76380
+ missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
76381
+ extra: actualKeys.filter((key) => !expectedKeys.includes(key))
76382
+ };
76383
+ }
76384
+ function getQuoteAsset(symbol) {
76385
+ const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
76386
+ const target = quoteAssets.find((asset) => symbol.endsWith(asset));
76387
+ return target || symbol.slice(-4);
76388
+ }
76389
+ function sumNotional(orders) {
76390
+ return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
76391
+ }
76392
+ function sumQuantity(orders) {
76393
+ return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
76394
+ }
76395
+ function buildLongOrderPlacement(options) {
76396
+ const { order, current_price } = options;
76397
+ if (order.price > current_price) {
76398
+ return {
76399
+ side: "buy",
76400
+ type: "stop_loss_limit",
76401
+ price: order.price,
76402
+ stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
76403
+ quantity: order.quantity
76404
+ };
76405
+ }
76406
+ return {
76407
+ side: "buy",
76408
+ type: "limit",
76409
+ price: order.price,
76410
+ quantity: order.quantity
76411
+ };
76412
+ }
76413
+ function inferSpotMarginHedgeInputFromLiveOrders(options) {
76414
+ const long_orders = sortOrderInputs([
76415
+ ...options.spot_orders.filter((order) => order.side === "buy").map((order) => ({
76416
+ price: order.price,
76417
+ quantity: order.quantity
76418
+ })),
76419
+ ...options.margin_orders.filter((order) => order.side === "buy").map((order) => ({
76420
+ price: order.price,
76421
+ quantity: order.quantity
76422
+ }))
76423
+ ]);
76424
+ const short_orders = sortOrderInputs([
76425
+ ...options.spot_orders.filter((order) => order.side === "sell").map((order) => ({
76426
+ price: order.price,
76427
+ quantity: order.quantity
76428
+ })),
76429
+ ...options.margin_orders.filter((order) => order.side === "sell").map((order) => ({
76430
+ price: order.price,
76431
+ quantity: order.quantity
76432
+ }))
76433
+ ]);
76434
+ return {
76435
+ symbol: options.symbol,
76436
+ margin_type: options.margin_type,
76437
+ borrow_amount: sumNotional(long_orders),
76438
+ long_orders,
76439
+ short_orders,
76440
+ placement_overrides: {
76441
+ spot: toPlannedOrders(options.spot_orders),
76442
+ margin: toPlannedOrders(options.margin_orders)
76443
+ }
76444
+ };
76445
+ }
76446
+ function buildSpotMarginHedgePlan(options) {
76447
+ const max_spot_buy_orders = options.max_spot_buy_orders ?? 5;
76448
+ const long_orders = sortOrderInputs(options.long_orders);
76449
+ const short_orders = sortOrderInputs(options.short_orders);
76450
+ const spot_long_orders = long_orders.slice(0, max_spot_buy_orders);
76451
+ const margin_long_orders = long_orders.slice(max_spot_buy_orders);
76452
+ const default_spot_orders_to_create = [
76453
+ ...spot_long_orders.map((order) => buildLongOrderPlacement({
76454
+ order,
76455
+ current_price: options.current_price
76456
+ })),
76457
+ ...short_orders.map((order) => ({
76458
+ side: "sell",
76459
+ type: "limit",
76460
+ price: order.price,
76461
+ quantity: order.quantity
76462
+ }))
76463
+ ];
76464
+ const default_margin_orders_to_create = margin_long_orders.map((order) => buildLongOrderPlacement({
76465
+ order,
76466
+ current_price: options.current_price
76467
+ }));
76468
+ const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
76469
+ const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
76470
+ const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
76471
+ const spot_long_notional = sumNotional(spot_long_orders);
76472
+ const transfer_to_spot_amount = roundNumber2(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
76473
+ const short_quantity = sumQuantity(short_orders);
76474
+ const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
76475
+ return {
76476
+ borrow_amount: options.borrow_amount,
76477
+ borrow_delta,
76478
+ transfer_to_spot_amount,
76479
+ transfer_to_spot_asset: getQuoteAsset(options.symbol),
76480
+ transfer_base_to_spot_amount,
76481
+ orders_to_cancel: {
76482
+ spot: options.snapshot.spot_orders,
76483
+ margin: options.snapshot.margin_orders
76484
+ },
76485
+ orders_to_create: {
76486
+ spot: spot_orders_to_create,
76487
+ margin: margin_orders_to_create
76488
+ },
76489
+ normalized: {
76490
+ spot: normalizeOrders(spot_orders_to_create),
76491
+ margin: normalizeOrders(margin_orders_to_create)
76492
+ }
76493
+ };
76494
+ }
76495
+ function compareSpotMarginHedgeOrders(options) {
76496
+ const actualSpot = normalizeOrders(options.spot_orders);
76497
+ const actualMargin = normalizeOrders(options.margin_orders);
76498
+ const spotDiff = buildDiff({
76499
+ expected: options.plan.normalized.spot,
76500
+ actual: actualSpot
76501
+ });
76502
+ const marginDiff = buildDiff({
76503
+ expected: options.plan.normalized.margin,
76504
+ actual: actualMargin
76505
+ });
76506
+ return {
76507
+ matches: spotDiff.missing.length === 0 && spotDiff.extra.length === 0 && marginDiff.missing.length === 0 && marginDiff.extra.length === 0,
76508
+ spot: {
76509
+ expected: options.plan.normalized.spot,
76510
+ actual: actualSpot,
76511
+ missing: spotDiff.missing,
76512
+ extra: spotDiff.extra
76513
+ },
76514
+ margin: {
76515
+ expected: options.plan.normalized.margin,
76516
+ actual: actualMargin,
76517
+ missing: marginDiff.missing,
76518
+ extra: marginDiff.extra
76519
+ }
76520
+ };
76521
+ }
76522
+
76523
+ // src/exchanges/binance/index.ts
76126
76524
  var CONSTANTS = {
76127
76525
  SPOT_TO_FIAT: "MAIN_C2C",
76128
76526
  SPOT_TO_USDT_FUTURE: "MAIN_UMFUTURE",
@@ -76611,12 +77009,20 @@ async function getCrossMarginAccountDetails(client) {
76611
77009
  async function cancelMarginOrders(client, symbol, isIsolated = false, orders) {
76612
77010
  try {
76613
77011
  if (orders && orders.length > 0) {
76614
- const cancelPromises = orders.map((order) => client.marginAccountCancelOrder({
76615
- symbol,
76616
- orderId: order.orderId,
76617
- origClientOrderId: order.origClientOrderId,
76618
- isIsolated: isIsolated ? "TRUE" : "FALSE"
76619
- }));
77012
+ const cancelPromises = orders.map((order) => {
77013
+ const payload = {
77014
+ symbol,
77015
+ isIsolated: isIsolated ? "TRUE" : "FALSE"
77016
+ };
77017
+ if (order.orderId !== undefined) {
77018
+ payload.orderId = order.orderId;
77019
+ }
77020
+ const origClientOrderId = order.origClientOrderId ?? order.clientOrderId;
77021
+ if (origClientOrderId !== undefined) {
77022
+ payload.origClientOrderId = origClientOrderId;
77023
+ }
77024
+ return client.marginAccountCancelOrder(payload);
77025
+ });
76620
77026
  return await Promise.all(cancelPromises);
76621
77027
  } else {
76622
77028
  return await client.marginAccountCancelOpenOrders({
@@ -76871,7 +77277,7 @@ async function placeStopOrder(client, payload) {
76871
77277
  });
76872
77278
  }
76873
77279
  }
76874
- const spread = 1.00005;
77280
+ const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
76875
77281
  const order = {
76876
77282
  kind: payload.kind,
76877
77283
  side: payload.kind === "long" ? "sell" : "buy",
@@ -77278,6 +77684,16 @@ async function getAllOpenOrders(payload) {
77278
77684
  const response = await client.getAllOpenOrders();
77279
77685
  return response;
77280
77686
  }
77687
+ function getPrintfFormatFromStepSize(stepSize, fallback) {
77688
+ if (stepSize === undefined || stepSize === null) {
77689
+ return fallback;
77690
+ }
77691
+ const normalized = String(stepSize);
77692
+ const [, decimalPart = ""] = normalized.split(".");
77693
+ const trimmed = decimalPart.replace(/0+$/, "");
77694
+ const digits = trimmed.length;
77695
+ return `%.${digits}f`;
77696
+ }
77281
77697
 
77282
77698
  class BinanceExchange extends BaseExchange {
77283
77699
  main_client;
@@ -77714,6 +78130,54 @@ class BinanceExchange extends BaseExchange {
77714
78130
  throw error;
77715
78131
  }
77716
78132
  }
78133
+ async getFuturesSymbolFormats(symbol) {
78134
+ if (typeof this.client.getExchangeInfo !== "function") {
78135
+ return {
78136
+ price_places: "%.8f",
78137
+ decimal_places: "%.8f"
78138
+ };
78139
+ }
78140
+ try {
78141
+ const exchange_info = await this.client.getExchangeInfo();
78142
+ const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
78143
+ const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
78144
+ const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
78145
+ return {
78146
+ price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
78147
+ decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
78148
+ };
78149
+ } catch (error) {
78150
+ console.error("Error fetching futures symbol formats:", error);
78151
+ return {
78152
+ price_places: "%.8f",
78153
+ decimal_places: "%.8f"
78154
+ };
78155
+ }
78156
+ }
78157
+ async getSpotSymbolFormats(symbol) {
78158
+ if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
78159
+ return {
78160
+ price_places: "%.8f",
78161
+ decimal_places: "%.8f"
78162
+ };
78163
+ }
78164
+ try {
78165
+ const exchange_info = await this.main_client.getExchangeInfo();
78166
+ const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
78167
+ const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
78168
+ const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
78169
+ return {
78170
+ price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
78171
+ decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
78172
+ };
78173
+ } catch (error) {
78174
+ console.error("Error fetching spot symbol formats:", error);
78175
+ return {
78176
+ price_places: "%.8f",
78177
+ decimal_places: "%.8f"
78178
+ };
78179
+ }
78180
+ }
77717
78181
  async createMarginLimitOrders(payload) {
77718
78182
  if (!this.main_client) {
77719
78183
  throw new Error("Main client not available for margin trading");
@@ -77744,6 +78208,317 @@ class BinanceExchange extends BaseExchange {
77744
78208
  }
77745
78209
  return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
77746
78210
  }
78211
+ async getFuturesReplaySnapshot(payload) {
78212
+ const symbol = payload.symbol.toUpperCase();
78213
+ const [open_orders, position_info, current_price] = await Promise.all([
78214
+ this.getOpenOrders({ symbol }),
78215
+ this.getPositionInfo(symbol),
78216
+ this.getCurrentPrice(symbol)
78217
+ ]);
78218
+ return buildFuturesReplaySnapshot({
78219
+ symbol,
78220
+ kind: payload.kind,
78221
+ current_price,
78222
+ position: {
78223
+ size: position_info[payload.kind]?.size || 0
78224
+ },
78225
+ open_orders
78226
+ });
78227
+ }
78228
+ async previewFuturesReplay(payload) {
78229
+ const snapshot = payload.snapshot ? {
78230
+ ...payload.snapshot,
78231
+ current_price: payload.snapshot.current_price ?? await this.getCurrentPrice(payload.symbol)
78232
+ } : await this.getFuturesReplaySnapshot({
78233
+ symbol: payload.symbol,
78234
+ kind: payload.kind
78235
+ });
78236
+ const shouldInferEntryOrders = !payload.entry_orders || payload.entry_orders.length === 0;
78237
+ const shouldInferStopOrders = !payload.stop_orders || payload.stop_orders.length === 0;
78238
+ const shouldInferTakeProfitOrders = !payload.take_profit_orders || payload.take_profit_orders.length === 0;
78239
+ const inferred_input = shouldInferEntryOrders || shouldInferStopOrders || shouldInferTakeProfitOrders ? inferFuturesReplayInputFromLiveOrders({
78240
+ symbol: payload.symbol,
78241
+ kind: payload.kind,
78242
+ current_price: snapshot.current_price,
78243
+ position: snapshot.position,
78244
+ open_orders: snapshot.open_orders
78245
+ }) : undefined;
78246
+ const explicit_input = normalizeFuturesReplayInput({
78247
+ kind: payload.kind,
78248
+ entry_orders: payload.entry_orders || [],
78249
+ stop_orders: payload.stop_orders || [],
78250
+ take_profit_orders: payload.take_profit_orders || []
78251
+ });
78252
+ const entry_orders = shouldInferEntryOrders ? inferred_input?.entry_orders || [] : explicit_input.entry_orders;
78253
+ const stop_orders = shouldInferStopOrders ? inferred_input?.stop_orders || [] : explicit_input.stop_orders;
78254
+ const take_profit_orders = shouldInferTakeProfitOrders ? inferred_input?.take_profit_orders || [] : explicit_input.take_profit_orders;
78255
+ const requested_input = {
78256
+ symbol: payload.symbol,
78257
+ kind: payload.kind,
78258
+ entry_orders,
78259
+ stop_orders,
78260
+ take_profit_orders
78261
+ };
78262
+ const plan = buildFuturesReplayPlan({
78263
+ ...requested_input,
78264
+ snapshot
78265
+ });
78266
+ const comparison = compareFuturesReplayOrders({
78267
+ ...requested_input,
78268
+ snapshot
78269
+ });
78270
+ return {
78271
+ snapshot,
78272
+ inferred_input,
78273
+ requested_input,
78274
+ plan,
78275
+ comparison
78276
+ };
78277
+ }
78278
+ async placeFuturesReplay(payload) {
78279
+ const preview = await this.previewFuturesReplay(payload);
78280
+ if (!payload.place) {
78281
+ return preview;
78282
+ }
78283
+ const symbol = payload.symbol.toUpperCase();
78284
+ await cancelAllOrders(this.client, symbol, {
78285
+ kind: payload.kind
78286
+ });
78287
+ const refreshed_snapshot = await this.getFuturesReplaySnapshot({
78288
+ symbol,
78289
+ kind: payload.kind
78290
+ });
78291
+ const execution_plan = buildFuturesReplayPlan({
78292
+ symbol,
78293
+ kind: payload.kind,
78294
+ entry_orders: preview.requested_input.entry_orders,
78295
+ stop_orders: preview.requested_input.stop_orders,
78296
+ take_profit_orders: preview.requested_input.take_profit_orders,
78297
+ snapshot: refreshed_snapshot
78298
+ });
78299
+ const { price_places, decimal_places } = await this.getFuturesSymbolFormats(symbol);
78300
+ const entry_orders = execution_plan.orders_to_create.entry.map((order) => ({
78301
+ ...order,
78302
+ side: payload.kind === "long" ? "buy" : "sell",
78303
+ kind: payload.kind
78304
+ }));
78305
+ const stop_orders = execution_plan.orders_to_create.stop.map((order) => ({
78306
+ ...order,
78307
+ side: payload.kind === "long" ? "sell" : "buy",
78308
+ kind: payload.kind,
78309
+ type: order.type || "STOP"
78310
+ }));
78311
+ const take_profit_orders = execution_plan.orders_to_create.take_profit.map((order) => ({
78312
+ ...order,
78313
+ side: payload.kind === "long" ? "sell" : "buy",
78314
+ kind: payload.kind,
78315
+ ...order.stop ? { type: order.type || "TAKE_PROFIT" } : {}
78316
+ }));
78317
+ const execution = entry_orders.length > 0 || stop_orders.length > 0 || take_profit_orders.length > 0 ? await createLimitPurchaseOrdersParallel(this.client, symbol, price_places, decimal_places, [...entry_orders, ...stop_orders, ...take_profit_orders]) : [];
78318
+ return {
78319
+ ...preview,
78320
+ execution_plan,
78321
+ refreshed_snapshot,
78322
+ execution
78323
+ };
78324
+ }
78325
+ async placeFuturesExactTrade(payload) {
78326
+ return await this.placeFuturesReplay(payload);
78327
+ }
78328
+ async getSpotMarginHedgeSnapshot(payload) {
78329
+ if (!this.main_client) {
78330
+ throw new Error("Main client not available for spot and margin trading");
78331
+ }
78332
+ if (payload.margin_type !== "isolated") {
78333
+ throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
78334
+ }
78335
+ const symbol = payload.symbol.toUpperCase();
78336
+ const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78337
+ const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78338
+ const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
78339
+ const [spot_orders, margin_orders, spot_balances, isolated_margin_position] = await Promise.all([
78340
+ this.getSpotOpenOrders(symbol),
78341
+ this.getMarginOpenOrders(symbol, true),
78342
+ this.getSpotBalances([baseAsset, quoteAsset]),
78343
+ this.getIsolatedMarginPosition(symbol)
78344
+ ]);
78345
+ const current_price = await this.getSpotCurrentPrice(symbol);
78346
+ const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
78347
+ const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
78348
+ const isolated_asset = isolated_margin_position?.assets?.[0];
78349
+ const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || "0");
78350
+ return {
78351
+ symbol,
78352
+ margin_type: payload.margin_type,
78353
+ quote_asset: quoteAsset,
78354
+ base_asset: baseAsset,
78355
+ borrowed_quote_amount,
78356
+ spot_quote_free: parseFloat(spot_quote_balance?.free?.toString?.() || "0"),
78357
+ spot_base_free: parseFloat(spot_base_balance?.free?.toString?.() || "0"),
78358
+ spot_orders,
78359
+ margin_orders,
78360
+ current_price,
78361
+ spot_balances,
78362
+ isolated_margin_position
78363
+ };
78364
+ }
78365
+ async previewSpotMarginHedge(payload) {
78366
+ const snapshot = payload.snapshot ? {
78367
+ ...payload.snapshot,
78368
+ current_price: payload.snapshot.current_price ?? await this.getSpotCurrentPrice(payload.symbol)
78369
+ } : await this.getSpotMarginHedgeSnapshot({
78370
+ symbol: payload.symbol,
78371
+ margin_type: payload.margin_type
78372
+ });
78373
+ const shouldInferLongOrders = !payload.long_orders || payload.long_orders.length === 0;
78374
+ const shouldInferShortOrders = !payload.short_orders || payload.short_orders.length === 0;
78375
+ const inferred_input = shouldInferLongOrders || shouldInferShortOrders ? inferSpotMarginHedgeInputFromLiveOrders({
78376
+ symbol: payload.symbol,
78377
+ margin_type: payload.margin_type,
78378
+ spot_orders: snapshot.spot_orders,
78379
+ margin_orders: snapshot.margin_orders
78380
+ }) : undefined;
78381
+ const long_orders = shouldInferLongOrders ? inferred_input?.long_orders || [] : payload.long_orders || [];
78382
+ const short_orders = shouldInferShortOrders ? inferred_input?.short_orders || [] : payload.short_orders || [];
78383
+ const borrow_amount = payload.borrow_amount ?? (long_orders.length > 0 ? sumNotional(long_orders) : inferred_input?.borrow_amount ?? 0);
78384
+ const requested_input = {
78385
+ borrow_amount,
78386
+ symbol: payload.symbol,
78387
+ margin_type: payload.margin_type,
78388
+ long_orders,
78389
+ short_orders,
78390
+ current_price: snapshot.current_price,
78391
+ max_spot_buy_orders: payload.max_spot_buy_orders,
78392
+ placement_overrides: inferred_input?.placement_overrides
78393
+ };
78394
+ const plan = buildSpotMarginHedgePlan({
78395
+ ...requested_input,
78396
+ snapshot
78397
+ });
78398
+ const comparison = compareSpotMarginHedgeOrders({
78399
+ plan,
78400
+ spot_orders: snapshot.spot_orders,
78401
+ margin_orders: snapshot.margin_orders
78402
+ });
78403
+ return {
78404
+ snapshot,
78405
+ inferred_input,
78406
+ requested_input,
78407
+ plan,
78408
+ comparison
78409
+ };
78410
+ }
78411
+ async placeSpotMarginHedge(payload) {
78412
+ const preview = await this.previewSpotMarginHedge(payload);
78413
+ if (!payload.place) {
78414
+ return preview;
78415
+ }
78416
+ if (!this.main_client) {
78417
+ throw new Error("Main client not available for spot and margin trading");
78418
+ }
78419
+ if (payload.margin_type !== "isolated") {
78420
+ throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
78421
+ }
78422
+ const symbol = payload.symbol.toUpperCase();
78423
+ const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78424
+ const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78425
+ const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
78426
+ const requested_input = preview.requested_input;
78427
+ if (preview.snapshot.spot_orders.length > 0) {
78428
+ await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
78429
+ orderId: order.orderId || order.order_id || order.id,
78430
+ origClientOrderId: order.origClientOrderId
78431
+ })));
78432
+ }
78433
+ if (preview.snapshot.margin_orders.length > 0) {
78434
+ await this.cancelMarginOrders(symbol, true, preview.snapshot.margin_orders.map((order) => ({
78435
+ orderId: order.orderId || order.order_id || order.id,
78436
+ origClientOrderId: order.origClientOrderId
78437
+ })));
78438
+ }
78439
+ const refreshed_snapshot = await this.getSpotMarginHedgeSnapshot({
78440
+ symbol,
78441
+ margin_type: payload.margin_type
78442
+ });
78443
+ const symbol_formats = await this.getSpotSymbolFormats(symbol);
78444
+ const execution_plan = buildSpotMarginHedgePlan({
78445
+ borrow_amount: requested_input.borrow_amount,
78446
+ symbol: requested_input.symbol,
78447
+ margin_type: requested_input.margin_type,
78448
+ long_orders: requested_input.long_orders,
78449
+ short_orders: requested_input.short_orders,
78450
+ current_price: refreshed_snapshot.current_price,
78451
+ max_spot_buy_orders: requested_input.max_spot_buy_orders,
78452
+ snapshot: refreshed_snapshot
78453
+ });
78454
+ let borrow_result = null;
78455
+ if (execution_plan.borrow_delta > 0) {
78456
+ borrow_result = await this.main_client.submitMarginAccountBorrowRepay({
78457
+ asset: quote_asset,
78458
+ amount: execution_plan.borrow_delta,
78459
+ isIsolated: "TRUE",
78460
+ symbol,
78461
+ type: "BORROW"
78462
+ });
78463
+ }
78464
+ let quote_transfer_result = null;
78465
+ if (execution_plan.transfer_to_spot_amount > 0) {
78466
+ quote_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78467
+ asset: quote_asset,
78468
+ amount: execution_plan.transfer_to_spot_amount,
78469
+ symbol,
78470
+ transFrom: "ISOLATED_MARGIN",
78471
+ transTo: "SPOT"
78472
+ });
78473
+ }
78474
+ let base_transfer_result = null;
78475
+ if (execution_plan.transfer_base_to_spot_amount > 0) {
78476
+ base_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78477
+ asset: base_asset,
78478
+ amount: execution_plan.transfer_base_to_spot_amount,
78479
+ symbol,
78480
+ transFrom: "ISOLATED_MARGIN",
78481
+ transTo: "SPOT"
78482
+ });
78483
+ }
78484
+ const spot_result = execution_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
78485
+ symbol,
78486
+ orders: execution_plan.orders_to_create.spot,
78487
+ price_places: symbol_formats.price_places,
78488
+ decimal_places: symbol_formats.decimal_places
78489
+ }) : [];
78490
+ const margin_result = execution_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
78491
+ symbol,
78492
+ orders: execution_plan.orders_to_create.margin,
78493
+ isIsolated: true,
78494
+ price_places: symbol_formats.price_places,
78495
+ decimal_places: symbol_formats.decimal_places
78496
+ }) : [];
78497
+ return {
78498
+ ...preview,
78499
+ execution_plan,
78500
+ refreshed_snapshot,
78501
+ execution: {
78502
+ borrow: borrow_result ? {
78503
+ asset: quote_asset,
78504
+ amount: execution_plan.borrow_delta,
78505
+ response: borrow_result
78506
+ } : null,
78507
+ quote_transfer: quote_transfer_result ? {
78508
+ asset: quote_asset,
78509
+ amount: execution_plan.transfer_to_spot_amount,
78510
+ response: quote_transfer_result
78511
+ } : null,
78512
+ base_transfer: base_transfer_result ? {
78513
+ asset: base_asset,
78514
+ amount: execution_plan.transfer_base_to_spot_amount,
78515
+ response: base_transfer_result
78516
+ } : null,
78517
+ spot_orders: spot_result,
78518
+ margin_orders: margin_result
78519
+ }
78520
+ };
78521
+ }
77747
78522
  async getLastUserTrade(payload) {
77748
78523
  return await getLastUserTrade(this.client, payload.symbol);
77749
78524
  }