@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.
@@ -75760,9 +75760,18 @@ class BaseExchange {
75760
75760
  async previewSpotMarginHedge(_payload) {
75761
75761
  throw new Error("Spot margin hedge preview is not supported on this exchange");
75762
75762
  }
75763
+ async previewFuturesReplay(_payload) {
75764
+ throw new Error("Futures replay preview is not supported on this exchange");
75765
+ }
75763
75766
  async placeSpotMarginHedge(_payload) {
75764
75767
  throw new Error("Spot margin hedge placement is not supported on this exchange");
75765
75768
  }
75769
+ async placeFuturesReplay(_payload) {
75770
+ throw new Error("Futures replay placement is not supported on this exchange");
75771
+ }
75772
+ async placeFuturesExactTrade(_payload) {
75773
+ throw new Error("Futures exact trade placement is not supported on this exchange");
75774
+ }
75766
75775
  async placeSpotLimitOrders(_payload) {}
75767
75776
  async buildCumulative(payload) {
75768
75777
  const {
@@ -76171,19 +76180,198 @@ class BaseExchange {
76171
76180
  // src/exchanges/binance/index.ts
76172
76181
  var import_p_limit = __toESM(require_p_limit(), 1);
76173
76182
 
76174
- // src/exchanges/binance/spot-margin-hedge.ts
76183
+ // src/exchanges/binance/futures-replay.ts
76175
76184
  function roundNumber(value2, digits = 8) {
76176
76185
  return Number(value2.toFixed(digits));
76177
76186
  }
76178
- var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
76187
+ function parseNumber2(value2) {
76188
+ if (value2 === undefined || value2 === null || value2 === "")
76189
+ return;
76190
+ const parsed = Number(value2);
76191
+ if (!Number.isFinite(parsed) || parsed <= 0)
76192
+ return;
76193
+ return roundNumber(parsed);
76194
+ }
76179
76195
  function normalizeOrderInput(order) {
76196
+ const stop = parseNumber2(order.stop) ?? parseNumber2(order.stopPrice) ?? parseNumber2(order.triggerPrice);
76197
+ const type = order.type ? order.type.toUpperCase() : undefined;
76198
+ const quantity = order.quantity ?? order.qty ?? 0;
76180
76199
  return {
76181
- price: roundNumber(order.price),
76182
- quantity: roundNumber(order.quantity)
76200
+ price: roundNumber(Number(order.price)),
76201
+ quantity: roundNumber(Number(quantity)),
76202
+ ...stop ? { stop } : {},
76203
+ ...type && type !== "LIMIT" ? { type } : {}
76204
+ };
76205
+ }
76206
+ function sortReplayOrders(orders) {
76207
+ return [...orders].sort((left, right) => {
76208
+ const leftTrigger = left.stop ?? left.price;
76209
+ const rightTrigger = right.stop ?? right.price;
76210
+ if (leftTrigger !== rightTrigger)
76211
+ return leftTrigger - rightTrigger;
76212
+ if (left.price !== right.price)
76213
+ return left.price - right.price;
76214
+ return left.quantity - right.quantity;
76215
+ });
76216
+ }
76217
+ function buildReplayStopLimitPrice(payload) {
76218
+ const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
76219
+ return roundNumber(payload.kind === "long" ? payload.stop * spread ** -1 : payload.stop * spread);
76220
+ }
76221
+ function normalizeRequestedEntryOrders(orders) {
76222
+ return sortReplayOrders(orders.map((order) => ({
76223
+ price: roundNumber(Number(order.price)),
76224
+ quantity: roundNumber(Number(order.quantity))
76225
+ })));
76226
+ }
76227
+ function normalizeRequestedStopOrders(payload) {
76228
+ return sortReplayOrders(payload.orders.map((order) => {
76229
+ const stop = parseNumber2(order.stop) ?? roundNumber(Number(order.price));
76230
+ return {
76231
+ price: order.price !== undefined ? roundNumber(Number(order.price)) : buildReplayStopLimitPrice({
76232
+ kind: payload.kind,
76233
+ stop
76234
+ }),
76235
+ quantity: roundNumber(Number(order.quantity)),
76236
+ stop,
76237
+ type: (order.type || "STOP").toUpperCase()
76238
+ };
76239
+ }));
76240
+ }
76241
+ function normalizeRequestedTakeProfitOrders(orders) {
76242
+ return sortReplayOrders(orders.map((order) => {
76243
+ const stop = parseNumber2(order.stop);
76244
+ const type = order.type?.toUpperCase();
76245
+ return {
76246
+ price: roundNumber(Number(order.price)),
76247
+ quantity: roundNumber(Number(order.quantity)),
76248
+ ...stop ? { stop } : {},
76249
+ ...type ? { type } : stop ? { type: "TAKE_PROFIT" } : {}
76250
+ };
76251
+ }));
76252
+ }
76253
+ function buildOrderKey(order) {
76254
+ return [
76255
+ order.type || "LIMIT",
76256
+ order.price,
76257
+ order.quantity,
76258
+ order.stop ?? ""
76259
+ ].join(":");
76260
+ }
76261
+ function buildComparisonBucket(options) {
76262
+ const expected = sortReplayOrders(options.expected);
76263
+ const actual = sortReplayOrders(options.actual);
76264
+ const expected_keys = expected.map(buildOrderKey);
76265
+ const actual_keys = actual.map(buildOrderKey);
76266
+ return {
76267
+ expected,
76268
+ actual,
76269
+ missing: expected_keys.filter((key) => !actual_keys.includes(key)),
76270
+ extra: actual_keys.filter((key) => !expected_keys.includes(key))
76271
+ };
76272
+ }
76273
+ function buildFuturesReplaySnapshot(payload) {
76274
+ const entry_side = payload.kind === "long" ? "buy" : "sell";
76275
+ const close_side = payload.kind === "long" ? "sell" : "buy";
76276
+ const entry_orders = [];
76277
+ const stop_orders = [];
76278
+ const take_profit_orders = [];
76279
+ for (const order of payload.open_orders) {
76280
+ if (order.kind !== payload.kind)
76281
+ continue;
76282
+ const normalized_order = normalizeOrderInput(order);
76283
+ const type = (normalized_order.type || "LIMIT").toUpperCase();
76284
+ if (order.side === entry_side) {
76285
+ entry_orders.push(normalized_order);
76286
+ continue;
76287
+ }
76288
+ if (order.side !== close_side)
76289
+ continue;
76290
+ if (type.includes("TAKE_PROFIT") || normalized_order.stop === undefined) {
76291
+ take_profit_orders.push(normalized_order);
76292
+ continue;
76293
+ }
76294
+ stop_orders.push(normalized_order);
76295
+ }
76296
+ return {
76297
+ symbol: payload.symbol,
76298
+ kind: payload.kind,
76299
+ current_price: payload.current_price,
76300
+ position: payload.position,
76301
+ open_orders: payload.open_orders.filter((order) => order.kind === payload.kind),
76302
+ entry_orders: sortReplayOrders(entry_orders),
76303
+ stop_orders: sortReplayOrders(stop_orders),
76304
+ take_profit_orders: sortReplayOrders(take_profit_orders)
76305
+ };
76306
+ }
76307
+ function inferFuturesReplayInputFromLiveOrders(payload) {
76308
+ const snapshot = buildFuturesReplaySnapshot(payload);
76309
+ return {
76310
+ symbol: payload.symbol,
76311
+ kind: payload.kind,
76312
+ current_price: payload.current_price,
76313
+ position: payload.position,
76314
+ entry_orders: snapshot.entry_orders,
76315
+ stop_orders: snapshot.stop_orders,
76316
+ take_profit_orders: snapshot.take_profit_orders
76317
+ };
76318
+ }
76319
+ function compareFuturesReplayOrders(payload) {
76320
+ const entry = buildComparisonBucket({
76321
+ expected: payload.entry_orders,
76322
+ actual: payload.snapshot.entry_orders
76323
+ });
76324
+ const stop = buildComparisonBucket({
76325
+ expected: payload.stop_orders,
76326
+ actual: payload.snapshot.stop_orders
76327
+ });
76328
+ const take_profit = buildComparisonBucket({
76329
+ expected: payload.take_profit_orders,
76330
+ actual: payload.snapshot.take_profit_orders
76331
+ });
76332
+ return {
76333
+ 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,
76334
+ entry,
76335
+ stop,
76336
+ take_profit
76337
+ };
76338
+ }
76339
+ function buildFuturesReplayPlan(payload) {
76340
+ return {
76341
+ symbol: payload.symbol,
76342
+ kind: payload.kind,
76343
+ orders_to_cancel: payload.snapshot.open_orders,
76344
+ orders_to_create: {
76345
+ entry: sortReplayOrders(payload.entry_orders),
76346
+ stop: sortReplayOrders(payload.stop_orders),
76347
+ take_profit: sortReplayOrders(payload.take_profit_orders)
76348
+ }
76349
+ };
76350
+ }
76351
+ function normalizeFuturesReplayInput(payload) {
76352
+ return {
76353
+ entry_orders: normalizeRequestedEntryOrders(payload.entry_orders),
76354
+ stop_orders: normalizeRequestedStopOrders({
76355
+ kind: payload.kind,
76356
+ orders: payload.stop_orders
76357
+ }),
76358
+ take_profit_orders: normalizeRequestedTakeProfitOrders(payload.take_profit_orders)
76359
+ };
76360
+ }
76361
+
76362
+ // src/exchanges/binance/spot-margin-hedge.ts
76363
+ function roundNumber2(value2, digits = 8) {
76364
+ return Number(value2.toFixed(digits));
76365
+ }
76366
+ var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
76367
+ function normalizeOrderInput2(order) {
76368
+ return {
76369
+ price: roundNumber2(order.price),
76370
+ quantity: roundNumber2(order.quantity)
76183
76371
  };
76184
76372
  }
76185
76373
  function sortOrderInputs(orders) {
76186
- return [...orders].map(normalizeOrderInput).sort((left, right) => left.price - right.price);
76374
+ return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
76187
76375
  }
76188
76376
  function normalizeOrderForComparison(order) {
76189
76377
  const type = (order.type || "limit").toLowerCase();
@@ -76192,9 +76380,9 @@ function normalizeOrderForComparison(order) {
76192
76380
  return {
76193
76381
  side: order.side,
76194
76382
  type,
76195
- price: roundNumber(order.price),
76196
- quantity: roundNumber(order.quantity),
76197
- stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber(parsed_stop) : undefined
76383
+ price: roundNumber2(order.price),
76384
+ quantity: roundNumber2(order.quantity),
76385
+ stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber2(parsed_stop) : undefined
76198
76386
  };
76199
76387
  }
76200
76388
  function normalizeOrders(orders) {
@@ -76217,7 +76405,7 @@ function toPlannedOrders(orders) {
76217
76405
  stop: order.stop
76218
76406
  }));
76219
76407
  }
76220
- function buildOrderKey(order) {
76408
+ function buildOrderKey2(order) {
76221
76409
  return [
76222
76410
  order.side,
76223
76411
  order.type,
@@ -76227,8 +76415,8 @@ function buildOrderKey(order) {
76227
76415
  ].join(":");
76228
76416
  }
76229
76417
  function buildDiff(options) {
76230
- const expectedKeys = options.expected.map(buildOrderKey);
76231
- const actualKeys = options.actual.map(buildOrderKey);
76418
+ const expectedKeys = options.expected.map(buildOrderKey2);
76419
+ const actualKeys = options.actual.map(buildOrderKey2);
76232
76420
  return {
76233
76421
  missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
76234
76422
  extra: actualKeys.filter((key) => !expectedKeys.includes(key))
@@ -76240,10 +76428,10 @@ function getQuoteAsset(symbol) {
76240
76428
  return target || symbol.slice(-4);
76241
76429
  }
76242
76430
  function sumNotional(orders) {
76243
- return roundNumber(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
76431
+ return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
76244
76432
  }
76245
76433
  function sumQuantity(orders) {
76246
- return roundNumber(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
76434
+ return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
76247
76435
  }
76248
76436
  function buildLongOrderPlacement(options) {
76249
76437
  const { order, current_price } = options;
@@ -76252,7 +76440,7 @@ function buildLongOrderPlacement(options) {
76252
76440
  side: "buy",
76253
76441
  type: "stop_loss_limit",
76254
76442
  price: order.price,
76255
- stop: roundNumber(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
76443
+ stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
76256
76444
  quantity: order.quantity
76257
76445
  };
76258
76446
  }
@@ -76320,11 +76508,11 @@ function buildSpotMarginHedgePlan(options) {
76320
76508
  }));
76321
76509
  const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
76322
76510
  const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
76323
- const borrow_delta = roundNumber(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
76511
+ const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
76324
76512
  const spot_long_notional = sumNotional(spot_long_orders);
76325
- const transfer_to_spot_amount = roundNumber(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
76513
+ const transfer_to_spot_amount = roundNumber2(Math.max(0, options.margin_type === "cross" ? options.borrow_amount : spot_long_notional - options.snapshot.spot_quote_free));
76326
76514
  const short_quantity = sumQuantity(short_orders);
76327
- const transfer_base_to_spot_amount = roundNumber(Math.max(0, short_quantity - options.snapshot.spot_base_free));
76515
+ const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
76328
76516
  return {
76329
76517
  borrow_amount: options.borrow_amount,
76330
76518
  borrow_delta,
@@ -76826,17 +77014,17 @@ async function createMarginLimitOrdersParallel(client, symbol, priceFormat, quan
76826
77014
  return Object.fromEntries(Object.entries(v).filter(([, value2]) => value2 !== undefined));
76827
77015
  };
76828
77016
  const newOrders = orders.map(createMarginOrder);
76829
- const limit = import_p_limit.default(ORDERS_PER_SECOND);
76830
- const results = await Promise.all(newOrders.map((orderPayload) => limit(async () => {
77017
+ const results = [];
77018
+ for (const orderPayload of newOrders) {
76831
77019
  try {
76832
77020
  const result = await client.marginAccountNewOrder(orderPayload);
76833
77021
  console.log("Margin order result:", result);
76834
- return result;
77022
+ results.push(result);
76835
77023
  } catch (error) {
76836
77024
  console.error("Error processing margin order:", error);
76837
77025
  throw error;
76838
77026
  }
76839
- })));
77027
+ }
76840
77028
  return results;
76841
77029
  }
76842
77030
  async function getIsolatedMarginAccountInfo(client, symbol) {
@@ -77983,6 +78171,30 @@ class BinanceExchange extends BaseExchange {
77983
78171
  throw error;
77984
78172
  }
77985
78173
  }
78174
+ async getFuturesSymbolFormats(symbol) {
78175
+ if (typeof this.client.getExchangeInfo !== "function") {
78176
+ return {
78177
+ price_places: "%.8f",
78178
+ decimal_places: "%.8f"
78179
+ };
78180
+ }
78181
+ try {
78182
+ const exchange_info = await this.client.getExchangeInfo();
78183
+ const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
78184
+ const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
78185
+ const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
78186
+ return {
78187
+ price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
78188
+ decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
78189
+ };
78190
+ } catch (error) {
78191
+ console.error("Error fetching futures symbol formats:", error);
78192
+ return {
78193
+ price_places: "%.8f",
78194
+ decimal_places: "%.8f"
78195
+ };
78196
+ }
78197
+ }
77986
78198
  async getSpotSymbolFormats(symbol) {
77987
78199
  if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
77988
78200
  return {
@@ -78037,28 +78249,146 @@ class BinanceExchange extends BaseExchange {
78037
78249
  }
78038
78250
  return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
78039
78251
  }
78252
+ async getFuturesReplaySnapshot(payload) {
78253
+ const symbol = payload.symbol.toUpperCase();
78254
+ const [open_orders, position_info, current_price] = await Promise.all([
78255
+ this.getOpenOrders({ symbol }),
78256
+ this.getPositionInfo(symbol),
78257
+ this.getCurrentPrice(symbol)
78258
+ ]);
78259
+ return buildFuturesReplaySnapshot({
78260
+ symbol,
78261
+ kind: payload.kind,
78262
+ current_price,
78263
+ position: {
78264
+ size: position_info[payload.kind]?.size || 0
78265
+ },
78266
+ open_orders
78267
+ });
78268
+ }
78269
+ async previewFuturesReplay(payload) {
78270
+ const snapshot = payload.snapshot ? {
78271
+ ...payload.snapshot,
78272
+ current_price: payload.snapshot.current_price ?? await this.getCurrentPrice(payload.symbol)
78273
+ } : await this.getFuturesReplaySnapshot({
78274
+ symbol: payload.symbol,
78275
+ kind: payload.kind
78276
+ });
78277
+ const shouldInferEntryOrders = !payload.entry_orders || payload.entry_orders.length === 0;
78278
+ const shouldInferStopOrders = !payload.stop_orders || payload.stop_orders.length === 0;
78279
+ const shouldInferTakeProfitOrders = !payload.take_profit_orders || payload.take_profit_orders.length === 0;
78280
+ const inferred_input = shouldInferEntryOrders || shouldInferStopOrders || shouldInferTakeProfitOrders ? inferFuturesReplayInputFromLiveOrders({
78281
+ symbol: payload.symbol,
78282
+ kind: payload.kind,
78283
+ current_price: snapshot.current_price,
78284
+ position: snapshot.position,
78285
+ open_orders: snapshot.open_orders
78286
+ }) : undefined;
78287
+ const explicit_input = normalizeFuturesReplayInput({
78288
+ kind: payload.kind,
78289
+ entry_orders: payload.entry_orders || [],
78290
+ stop_orders: payload.stop_orders || [],
78291
+ take_profit_orders: payload.take_profit_orders || []
78292
+ });
78293
+ const entry_orders = shouldInferEntryOrders ? inferred_input?.entry_orders || [] : explicit_input.entry_orders;
78294
+ const stop_orders = shouldInferStopOrders ? inferred_input?.stop_orders || [] : explicit_input.stop_orders;
78295
+ const take_profit_orders = shouldInferTakeProfitOrders ? inferred_input?.take_profit_orders || [] : explicit_input.take_profit_orders;
78296
+ const requested_input = {
78297
+ symbol: payload.symbol,
78298
+ kind: payload.kind,
78299
+ entry_orders,
78300
+ stop_orders,
78301
+ take_profit_orders
78302
+ };
78303
+ const plan = buildFuturesReplayPlan({
78304
+ ...requested_input,
78305
+ snapshot
78306
+ });
78307
+ const comparison = compareFuturesReplayOrders({
78308
+ ...requested_input,
78309
+ snapshot
78310
+ });
78311
+ return {
78312
+ snapshot,
78313
+ inferred_input,
78314
+ requested_input,
78315
+ plan,
78316
+ comparison
78317
+ };
78318
+ }
78319
+ async placeFuturesReplay(payload) {
78320
+ const preview = await this.previewFuturesReplay(payload);
78321
+ if (!payload.place) {
78322
+ return preview;
78323
+ }
78324
+ const symbol = payload.symbol.toUpperCase();
78325
+ await cancelAllOrders(this.client, symbol, {
78326
+ kind: payload.kind
78327
+ });
78328
+ const refreshed_snapshot = await this.getFuturesReplaySnapshot({
78329
+ symbol,
78330
+ kind: payload.kind
78331
+ });
78332
+ const execution_plan = buildFuturesReplayPlan({
78333
+ symbol,
78334
+ kind: payload.kind,
78335
+ entry_orders: preview.requested_input.entry_orders,
78336
+ stop_orders: preview.requested_input.stop_orders,
78337
+ take_profit_orders: preview.requested_input.take_profit_orders,
78338
+ snapshot: refreshed_snapshot
78339
+ });
78340
+ const { price_places, decimal_places } = await this.getFuturesSymbolFormats(symbol);
78341
+ const entry_orders = execution_plan.orders_to_create.entry.map((order) => ({
78342
+ ...order,
78343
+ side: payload.kind === "long" ? "buy" : "sell",
78344
+ kind: payload.kind
78345
+ }));
78346
+ const stop_orders = execution_plan.orders_to_create.stop.map((order) => ({
78347
+ ...order,
78348
+ side: payload.kind === "long" ? "sell" : "buy",
78349
+ kind: payload.kind,
78350
+ type: order.type || "STOP"
78351
+ }));
78352
+ const take_profit_orders = execution_plan.orders_to_create.take_profit.map((order) => ({
78353
+ ...order,
78354
+ side: payload.kind === "long" ? "sell" : "buy",
78355
+ kind: payload.kind,
78356
+ ...order.stop ? { type: order.type || "TAKE_PROFIT" } : {}
78357
+ }));
78358
+ 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]) : [];
78359
+ return {
78360
+ ...preview,
78361
+ execution_plan,
78362
+ refreshed_snapshot,
78363
+ execution
78364
+ };
78365
+ }
78366
+ async placeFuturesExactTrade(payload) {
78367
+ return await this.placeFuturesReplay(payload);
78368
+ }
78040
78369
  async getSpotMarginHedgeSnapshot(payload) {
78041
78370
  if (!this.main_client) {
78042
78371
  throw new Error("Main client not available for spot and margin trading");
78043
78372
  }
78044
- if (payload.margin_type !== "isolated") {
78045
- throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
78046
- }
78047
78373
  const symbol = payload.symbol.toUpperCase();
78048
78374
  const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78049
78375
  const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78050
78376
  const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
78051
- const [spot_orders, margin_orders, spot_balances, isolated_margin_position] = await Promise.all([
78377
+ const is_isolated = payload.margin_type === "isolated";
78378
+ const [spot_orders, margin_orders, spot_balances, margin_account] = await Promise.all([
78052
78379
  this.getSpotOpenOrders(symbol),
78053
- this.getMarginOpenOrders(symbol, true),
78380
+ this.getMarginOpenOrders(symbol, is_isolated),
78054
78381
  this.getSpotBalances([baseAsset, quoteAsset]),
78055
- this.getIsolatedMarginPosition(symbol)
78382
+ is_isolated ? this.getIsolatedMarginPosition(symbol) : this.getCrossMarginAccount()
78056
78383
  ]);
78057
78384
  const current_price = await this.getSpotCurrentPrice(symbol);
78058
78385
  const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
78059
78386
  const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
78060
- const isolated_asset = isolated_margin_position?.assets?.[0];
78061
- const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || "0");
78387
+ const isolated_margin_account = is_isolated ? margin_account : undefined;
78388
+ const cross_margin_account = !is_isolated ? margin_account : undefined;
78389
+ const isolated_asset = isolated_margin_account?.assets?.[0];
78390
+ const cross_quote_asset = !is_isolated ? cross_margin_account?.userAssets?.find((asset) => asset.asset?.toUpperCase?.() === quoteAsset) : undefined;
78391
+ const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || cross_quote_asset?.borrowed?.toString?.() || "0");
78062
78392
  return {
78063
78393
  symbol,
78064
78394
  margin_type: payload.margin_type,
@@ -78071,7 +78401,8 @@ class BinanceExchange extends BaseExchange {
78071
78401
  margin_orders,
78072
78402
  current_price,
78073
78403
  spot_balances,
78074
- isolated_margin_position
78404
+ isolated_margin_position: isolated_margin_account,
78405
+ cross_margin_account
78075
78406
  };
78076
78407
  }
78077
78408
  async previewSpotMarginHedge(payload) {
@@ -78128,14 +78459,12 @@ class BinanceExchange extends BaseExchange {
78128
78459
  if (!this.main_client) {
78129
78460
  throw new Error("Main client not available for spot and margin trading");
78130
78461
  }
78131
- if (payload.margin_type !== "isolated") {
78132
- throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
78133
- }
78134
78462
  const symbol = payload.symbol.toUpperCase();
78135
78463
  const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78136
78464
  const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78137
78465
  const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
78138
78466
  const requested_input = preview.requested_input;
78467
+ const is_isolated = payload.margin_type === "isolated";
78139
78468
  if (preview.snapshot.spot_orders.length > 0) {
78140
78469
  await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
78141
78470
  orderId: order.orderId || order.order_id || order.id,
@@ -78143,7 +78472,7 @@ class BinanceExchange extends BaseExchange {
78143
78472
  })));
78144
78473
  }
78145
78474
  if (preview.snapshot.margin_orders.length > 0) {
78146
- await this.cancelMarginOrders(symbol, true, preview.snapshot.margin_orders.map((order) => ({
78475
+ await this.cancelMarginOrders(symbol, is_isolated, preview.snapshot.margin_orders.map((order) => ({
78147
78476
  orderId: order.orderId || order.order_id || order.id,
78148
78477
  origClientOrderId: order.origClientOrderId
78149
78478
  })));
@@ -78165,50 +78494,120 @@ class BinanceExchange extends BaseExchange {
78165
78494
  });
78166
78495
  let borrow_result = null;
78167
78496
  if (execution_plan.borrow_delta > 0) {
78168
- borrow_result = await this.main_client.submitMarginAccountBorrowRepay({
78497
+ const borrow_payload = {
78169
78498
  asset: quote_asset,
78170
78499
  amount: execution_plan.borrow_delta,
78171
- isIsolated: "TRUE",
78172
- symbol,
78173
78500
  type: "BORROW"
78501
+ };
78502
+ if (is_isolated) {
78503
+ borrow_payload.isIsolated = "TRUE";
78504
+ borrow_payload.symbol = symbol;
78505
+ }
78506
+ borrow_result = await this.main_client.submitMarginAccountBorrowRepay(borrow_payload);
78507
+ }
78508
+ async function getCrossTransferAmount(asset, amount) {
78509
+ if (is_isolated) {
78510
+ return amount;
78511
+ }
78512
+ if (!this.main_client || typeof this.main_client.queryMaxTransferOutAmount !== "function") {
78513
+ return amount;
78514
+ }
78515
+ const response = await this.main_client.queryMaxTransferOutAmount({
78516
+ asset
78174
78517
  });
78518
+ const allowed_amount = parseFloat(response?.amount?.toString?.() || "0");
78519
+ if (!Number.isFinite(allowed_amount)) {
78520
+ return amount;
78521
+ }
78522
+ return Math.max(0, Math.min(amount, allowed_amount));
78175
78523
  }
78176
78524
  let quote_transfer_result = null;
78525
+ let quote_transfer_amount = execution_plan.transfer_to_spot_amount;
78177
78526
  if (execution_plan.transfer_to_spot_amount > 0) {
78178
- quote_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78179
- asset: quote_asset,
78180
- amount: execution_plan.transfer_to_spot_amount,
78181
- symbol,
78182
- transFrom: "ISOLATED_MARGIN",
78183
- transTo: "SPOT"
78184
- });
78527
+ quote_transfer_amount = await getCrossTransferAmount.call(this, quote_asset, execution_plan.transfer_to_spot_amount);
78528
+ if (quote_transfer_amount > 0) {
78529
+ quote_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
78530
+ asset: quote_asset,
78531
+ amount: quote_transfer_amount,
78532
+ symbol,
78533
+ transFrom: "ISOLATED_MARGIN",
78534
+ transTo: "SPOT"
78535
+ }) : await this.main_client.submitUniversalTransfer({
78536
+ type: "MARGIN_MAIN",
78537
+ asset: quote_asset,
78538
+ amount: quote_transfer_amount
78539
+ });
78540
+ }
78185
78541
  }
78186
78542
  let base_transfer_result = null;
78543
+ let base_transfer_amount = execution_plan.transfer_base_to_spot_amount;
78187
78544
  if (execution_plan.transfer_base_to_spot_amount > 0) {
78188
- base_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78189
- asset: base_asset,
78190
- amount: execution_plan.transfer_base_to_spot_amount,
78191
- symbol,
78192
- transFrom: "ISOLATED_MARGIN",
78193
- transTo: "SPOT"
78194
- });
78545
+ base_transfer_amount = await getCrossTransferAmount.call(this, base_asset, execution_plan.transfer_base_to_spot_amount);
78546
+ if (base_transfer_amount > 0) {
78547
+ base_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
78548
+ asset: base_asset,
78549
+ amount: base_transfer_amount,
78550
+ symbol,
78551
+ transFrom: "ISOLATED_MARGIN",
78552
+ transTo: "SPOT"
78553
+ }) : await this.main_client.submitUniversalTransfer({
78554
+ type: "MARGIN_MAIN",
78555
+ asset: base_asset,
78556
+ amount: base_transfer_amount
78557
+ });
78558
+ }
78195
78559
  }
78196
- const spot_result = execution_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
78560
+ const spot_quote_available = refreshed_snapshot.spot_quote_free + quote_transfer_amount;
78561
+ const requested_max_spot_buy_orders = requested_input.max_spot_buy_orders ?? 5;
78562
+ const sorted_long_orders = [...requested_input.long_orders].sort((a, b) => a.price - b.price);
78563
+ let affordable_spot_buy_orders = 0;
78564
+ let affordable_spot_buy_notional = 0;
78565
+ for (const order of sorted_long_orders.slice(0, requested_max_spot_buy_orders)) {
78566
+ const next_notional = affordable_spot_buy_notional + order.price * order.quantity;
78567
+ if (next_notional <= spot_quote_available + 0.00000001) {
78568
+ affordable_spot_buy_orders += 1;
78569
+ affordable_spot_buy_notional = next_notional;
78570
+ } else {
78571
+ break;
78572
+ }
78573
+ }
78574
+ const placement_plan = buildSpotMarginHedgePlan({
78575
+ borrow_amount: requested_input.borrow_amount,
78576
+ symbol: requested_input.symbol,
78577
+ margin_type: requested_input.margin_type,
78578
+ long_orders: requested_input.long_orders,
78579
+ short_orders: requested_input.short_orders,
78580
+ current_price: refreshed_snapshot.current_price,
78581
+ max_spot_buy_orders: affordable_spot_buy_orders,
78582
+ snapshot: {
78583
+ ...refreshed_snapshot,
78584
+ borrowed_quote_amount: refreshed_snapshot.borrowed_quote_amount + execution_plan.borrow_delta,
78585
+ spot_quote_free: spot_quote_available,
78586
+ spot_base_free: refreshed_snapshot.spot_base_free + base_transfer_amount
78587
+ }
78588
+ });
78589
+ const spot_result = placement_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
78197
78590
  symbol,
78198
- orders: execution_plan.orders_to_create.spot,
78591
+ orders: placement_plan.orders_to_create.spot,
78199
78592
  price_places: symbol_formats.price_places,
78200
78593
  decimal_places: symbol_formats.decimal_places
78201
78594
  }) : [];
78202
- const margin_result = execution_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
78595
+ const margin_result = placement_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
78203
78596
  symbol,
78204
- orders: execution_plan.orders_to_create.margin,
78205
- isIsolated: true,
78597
+ orders: placement_plan.orders_to_create.margin,
78598
+ isIsolated: is_isolated,
78206
78599
  price_places: symbol_formats.price_places,
78207
78600
  decimal_places: symbol_formats.decimal_places
78208
78601
  }) : [];
78209
78602
  return {
78210
78603
  ...preview,
78211
- execution_plan,
78604
+ execution_plan: {
78605
+ ...placement_plan,
78606
+ borrow_amount: execution_plan.borrow_amount,
78607
+ borrow_delta: execution_plan.borrow_delta,
78608
+ transfer_to_spot_amount: quote_transfer_amount,
78609
+ transfer_base_to_spot_amount: base_transfer_amount
78610
+ },
78212
78611
  refreshed_snapshot,
78213
78612
  execution: {
78214
78613
  borrow: borrow_result ? {
@@ -78218,12 +78617,12 @@ class BinanceExchange extends BaseExchange {
78218
78617
  } : null,
78219
78618
  quote_transfer: quote_transfer_result ? {
78220
78619
  asset: quote_asset,
78221
- amount: execution_plan.transfer_to_spot_amount,
78620
+ amount: quote_transfer_amount,
78222
78621
  response: quote_transfer_result
78223
78622
  } : null,
78224
78623
  base_transfer: base_transfer_result ? {
78225
78624
  asset: base_asset,
78226
- amount: execution_plan.transfer_base_to_spot_amount,
78625
+ amount: base_transfer_amount,
78227
78626
  response: base_transfer_result
78228
78627
  } : null,
78229
78628
  spot_orders: spot_result,