@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.
@@ -75719,9 +75719,18 @@ class BaseExchange {
75719
75719
  async previewSpotMarginHedge(_payload) {
75720
75720
  throw new Error("Spot margin hedge preview is not supported on this exchange");
75721
75721
  }
75722
+ async previewFuturesReplay(_payload) {
75723
+ throw new Error("Futures replay preview is not supported on this exchange");
75724
+ }
75722
75725
  async placeSpotMarginHedge(_payload) {
75723
75726
  throw new Error("Spot margin hedge placement is not supported on this exchange");
75724
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
+ }
75725
75734
  async placeSpotLimitOrders(_payload) {}
75726
75735
  async buildCumulative(payload) {
75727
75736
  const {
@@ -76130,19 +76139,198 @@ class BaseExchange {
76130
76139
  // src/exchanges/binance/index.ts
76131
76140
  var import_p_limit = __toESM(require_p_limit(), 1);
76132
76141
 
76133
- // src/exchanges/binance/spot-margin-hedge.ts
76142
+ // src/exchanges/binance/futures-replay.ts
76134
76143
  function roundNumber(value2, digits = 8) {
76135
76144
  return Number(value2.toFixed(digits));
76136
76145
  }
76137
- var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
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
+ }
76138
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;
76139
76158
  return {
76140
- price: roundNumber(order.price),
76141
- quantity: roundNumber(order.quantity)
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)
76142
76330
  };
76143
76331
  }
76144
76332
  function sortOrderInputs(orders) {
76145
- return [...orders].map(normalizeOrderInput).sort((left, right) => left.price - right.price);
76333
+ return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
76146
76334
  }
76147
76335
  function normalizeOrderForComparison(order) {
76148
76336
  const type = (order.type || "limit").toLowerCase();
@@ -76151,9 +76339,9 @@ function normalizeOrderForComparison(order) {
76151
76339
  return {
76152
76340
  side: order.side,
76153
76341
  type,
76154
- price: roundNumber(order.price),
76155
- quantity: roundNumber(order.quantity),
76156
- stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber(parsed_stop) : undefined
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
76157
76345
  };
76158
76346
  }
76159
76347
  function normalizeOrders(orders) {
@@ -76176,7 +76364,7 @@ function toPlannedOrders(orders) {
76176
76364
  stop: order.stop
76177
76365
  }));
76178
76366
  }
76179
- function buildOrderKey(order) {
76367
+ function buildOrderKey2(order) {
76180
76368
  return [
76181
76369
  order.side,
76182
76370
  order.type,
@@ -76186,8 +76374,8 @@ function buildOrderKey(order) {
76186
76374
  ].join(":");
76187
76375
  }
76188
76376
  function buildDiff(options) {
76189
- const expectedKeys = options.expected.map(buildOrderKey);
76190
- const actualKeys = options.actual.map(buildOrderKey);
76377
+ const expectedKeys = options.expected.map(buildOrderKey2);
76378
+ const actualKeys = options.actual.map(buildOrderKey2);
76191
76379
  return {
76192
76380
  missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
76193
76381
  extra: actualKeys.filter((key) => !expectedKeys.includes(key))
@@ -76199,10 +76387,10 @@ function getQuoteAsset(symbol) {
76199
76387
  return target || symbol.slice(-4);
76200
76388
  }
76201
76389
  function sumNotional(orders) {
76202
- return roundNumber(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
76390
+ return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
76203
76391
  }
76204
76392
  function sumQuantity(orders) {
76205
- return roundNumber(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
76393
+ return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
76206
76394
  }
76207
76395
  function buildLongOrderPlacement(options) {
76208
76396
  const { order, current_price } = options;
@@ -76211,7 +76399,7 @@ function buildLongOrderPlacement(options) {
76211
76399
  side: "buy",
76212
76400
  type: "stop_loss_limit",
76213
76401
  price: order.price,
76214
- stop: roundNumber(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
76402
+ stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
76215
76403
  quantity: order.quantity
76216
76404
  };
76217
76405
  }
@@ -76279,11 +76467,11 @@ function buildSpotMarginHedgePlan(options) {
76279
76467
  }));
76280
76468
  const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
76281
76469
  const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
76282
- const borrow_delta = roundNumber(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
76470
+ const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
76283
76471
  const spot_long_notional = sumNotional(spot_long_orders);
76284
- const transfer_to_spot_amount = roundNumber(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
76472
+ const transfer_to_spot_amount = roundNumber2(Math.max(0, options.margin_type === "cross" ? options.borrow_amount : spot_long_notional - options.snapshot.spot_quote_free));
76285
76473
  const short_quantity = sumQuantity(short_orders);
76286
- const transfer_base_to_spot_amount = roundNumber(Math.max(0, short_quantity - options.snapshot.spot_base_free));
76474
+ const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
76287
76475
  return {
76288
76476
  borrow_amount: options.borrow_amount,
76289
76477
  borrow_delta,
@@ -76785,17 +76973,17 @@ async function createMarginLimitOrdersParallel(client, symbol, priceFormat, quan
76785
76973
  return Object.fromEntries(Object.entries(v).filter(([, value2]) => value2 !== undefined));
76786
76974
  };
76787
76975
  const newOrders = orders.map(createMarginOrder);
76788
- const limit = import_p_limit.default(ORDERS_PER_SECOND);
76789
- const results = await Promise.all(newOrders.map((orderPayload) => limit(async () => {
76976
+ const results = [];
76977
+ for (const orderPayload of newOrders) {
76790
76978
  try {
76791
76979
  const result = await client.marginAccountNewOrder(orderPayload);
76792
76980
  console.log("Margin order result:", result);
76793
- return result;
76981
+ results.push(result);
76794
76982
  } catch (error) {
76795
76983
  console.error("Error processing margin order:", error);
76796
76984
  throw error;
76797
76985
  }
76798
- })));
76986
+ }
76799
76987
  return results;
76800
76988
  }
76801
76989
  async function getIsolatedMarginAccountInfo(client, symbol) {
@@ -77942,6 +78130,30 @@ class BinanceExchange extends BaseExchange {
77942
78130
  throw error;
77943
78131
  }
77944
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
+ }
77945
78157
  async getSpotSymbolFormats(symbol) {
77946
78158
  if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
77947
78159
  return {
@@ -77996,28 +78208,146 @@ class BinanceExchange extends BaseExchange {
77996
78208
  }
77997
78209
  return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
77998
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
+ }
77999
78328
  async getSpotMarginHedgeSnapshot(payload) {
78000
78329
  if (!this.main_client) {
78001
78330
  throw new Error("Main client not available for spot and margin trading");
78002
78331
  }
78003
- if (payload.margin_type !== "isolated") {
78004
- throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
78005
- }
78006
78332
  const symbol = payload.symbol.toUpperCase();
78007
78333
  const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78008
78334
  const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78009
78335
  const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
78010
- const [spot_orders, margin_orders, spot_balances, isolated_margin_position] = await Promise.all([
78336
+ const is_isolated = payload.margin_type === "isolated";
78337
+ const [spot_orders, margin_orders, spot_balances, margin_account] = await Promise.all([
78011
78338
  this.getSpotOpenOrders(symbol),
78012
- this.getMarginOpenOrders(symbol, true),
78339
+ this.getMarginOpenOrders(symbol, is_isolated),
78013
78340
  this.getSpotBalances([baseAsset, quoteAsset]),
78014
- this.getIsolatedMarginPosition(symbol)
78341
+ is_isolated ? this.getIsolatedMarginPosition(symbol) : this.getCrossMarginAccount()
78015
78342
  ]);
78016
78343
  const current_price = await this.getSpotCurrentPrice(symbol);
78017
78344
  const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
78018
78345
  const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
78019
- const isolated_asset = isolated_margin_position?.assets?.[0];
78020
- const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || "0");
78346
+ const isolated_margin_account = is_isolated ? margin_account : undefined;
78347
+ const cross_margin_account = !is_isolated ? margin_account : undefined;
78348
+ const isolated_asset = isolated_margin_account?.assets?.[0];
78349
+ const cross_quote_asset = !is_isolated ? cross_margin_account?.userAssets?.find((asset) => asset.asset?.toUpperCase?.() === quoteAsset) : undefined;
78350
+ const borrowed_quote_amount = parseFloat(isolated_asset?.quoteAsset?.borrowed?.toString?.() || cross_quote_asset?.borrowed?.toString?.() || "0");
78021
78351
  return {
78022
78352
  symbol,
78023
78353
  margin_type: payload.margin_type,
@@ -78030,7 +78360,8 @@ class BinanceExchange extends BaseExchange {
78030
78360
  margin_orders,
78031
78361
  current_price,
78032
78362
  spot_balances,
78033
- isolated_margin_position
78363
+ isolated_margin_position: isolated_margin_account,
78364
+ cross_margin_account
78034
78365
  };
78035
78366
  }
78036
78367
  async previewSpotMarginHedge(payload) {
@@ -78087,14 +78418,12 @@ class BinanceExchange extends BaseExchange {
78087
78418
  if (!this.main_client) {
78088
78419
  throw new Error("Main client not available for spot and margin trading");
78089
78420
  }
78090
- if (payload.margin_type !== "isolated") {
78091
- throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
78092
- }
78093
78421
  const symbol = payload.symbol.toUpperCase();
78094
78422
  const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78095
78423
  const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78096
78424
  const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
78097
78425
  const requested_input = preview.requested_input;
78426
+ const is_isolated = payload.margin_type === "isolated";
78098
78427
  if (preview.snapshot.spot_orders.length > 0) {
78099
78428
  await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
78100
78429
  orderId: order.orderId || order.order_id || order.id,
@@ -78102,7 +78431,7 @@ class BinanceExchange extends BaseExchange {
78102
78431
  })));
78103
78432
  }
78104
78433
  if (preview.snapshot.margin_orders.length > 0) {
78105
- await this.cancelMarginOrders(symbol, true, preview.snapshot.margin_orders.map((order) => ({
78434
+ await this.cancelMarginOrders(symbol, is_isolated, preview.snapshot.margin_orders.map((order) => ({
78106
78435
  orderId: order.orderId || order.order_id || order.id,
78107
78436
  origClientOrderId: order.origClientOrderId
78108
78437
  })));
@@ -78124,50 +78453,120 @@ class BinanceExchange extends BaseExchange {
78124
78453
  });
78125
78454
  let borrow_result = null;
78126
78455
  if (execution_plan.borrow_delta > 0) {
78127
- borrow_result = await this.main_client.submitMarginAccountBorrowRepay({
78456
+ const borrow_payload = {
78128
78457
  asset: quote_asset,
78129
78458
  amount: execution_plan.borrow_delta,
78130
- isIsolated: "TRUE",
78131
- symbol,
78132
78459
  type: "BORROW"
78460
+ };
78461
+ if (is_isolated) {
78462
+ borrow_payload.isIsolated = "TRUE";
78463
+ borrow_payload.symbol = symbol;
78464
+ }
78465
+ borrow_result = await this.main_client.submitMarginAccountBorrowRepay(borrow_payload);
78466
+ }
78467
+ async function getCrossTransferAmount(asset, amount) {
78468
+ if (is_isolated) {
78469
+ return amount;
78470
+ }
78471
+ if (!this.main_client || typeof this.main_client.queryMaxTransferOutAmount !== "function") {
78472
+ return amount;
78473
+ }
78474
+ const response = await this.main_client.queryMaxTransferOutAmount({
78475
+ asset
78133
78476
  });
78477
+ const allowed_amount = parseFloat(response?.amount?.toString?.() || "0");
78478
+ if (!Number.isFinite(allowed_amount)) {
78479
+ return amount;
78480
+ }
78481
+ return Math.max(0, Math.min(amount, allowed_amount));
78134
78482
  }
78135
78483
  let quote_transfer_result = null;
78484
+ let quote_transfer_amount = execution_plan.transfer_to_spot_amount;
78136
78485
  if (execution_plan.transfer_to_spot_amount > 0) {
78137
- quote_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78138
- asset: quote_asset,
78139
- amount: execution_plan.transfer_to_spot_amount,
78140
- symbol,
78141
- transFrom: "ISOLATED_MARGIN",
78142
- transTo: "SPOT"
78143
- });
78486
+ quote_transfer_amount = await getCrossTransferAmount.call(this, quote_asset, execution_plan.transfer_to_spot_amount);
78487
+ if (quote_transfer_amount > 0) {
78488
+ quote_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
78489
+ asset: quote_asset,
78490
+ amount: quote_transfer_amount,
78491
+ symbol,
78492
+ transFrom: "ISOLATED_MARGIN",
78493
+ transTo: "SPOT"
78494
+ }) : await this.main_client.submitUniversalTransfer({
78495
+ type: "MARGIN_MAIN",
78496
+ asset: quote_asset,
78497
+ amount: quote_transfer_amount
78498
+ });
78499
+ }
78144
78500
  }
78145
78501
  let base_transfer_result = null;
78502
+ let base_transfer_amount = execution_plan.transfer_base_to_spot_amount;
78146
78503
  if (execution_plan.transfer_base_to_spot_amount > 0) {
78147
- base_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78148
- asset: base_asset,
78149
- amount: execution_plan.transfer_base_to_spot_amount,
78150
- symbol,
78151
- transFrom: "ISOLATED_MARGIN",
78152
- transTo: "SPOT"
78153
- });
78504
+ base_transfer_amount = await getCrossTransferAmount.call(this, base_asset, execution_plan.transfer_base_to_spot_amount);
78505
+ if (base_transfer_amount > 0) {
78506
+ base_transfer_result = is_isolated ? await this.main_client.isolatedMarginAccountTransfer({
78507
+ asset: base_asset,
78508
+ amount: base_transfer_amount,
78509
+ symbol,
78510
+ transFrom: "ISOLATED_MARGIN",
78511
+ transTo: "SPOT"
78512
+ }) : await this.main_client.submitUniversalTransfer({
78513
+ type: "MARGIN_MAIN",
78514
+ asset: base_asset,
78515
+ amount: base_transfer_amount
78516
+ });
78517
+ }
78154
78518
  }
78155
- const spot_result = execution_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
78519
+ const spot_quote_available = refreshed_snapshot.spot_quote_free + quote_transfer_amount;
78520
+ const requested_max_spot_buy_orders = requested_input.max_spot_buy_orders ?? 5;
78521
+ const sorted_long_orders = [...requested_input.long_orders].sort((a, b) => a.price - b.price);
78522
+ let affordable_spot_buy_orders = 0;
78523
+ let affordable_spot_buy_notional = 0;
78524
+ for (const order of sorted_long_orders.slice(0, requested_max_spot_buy_orders)) {
78525
+ const next_notional = affordable_spot_buy_notional + order.price * order.quantity;
78526
+ if (next_notional <= spot_quote_available + 0.00000001) {
78527
+ affordable_spot_buy_orders += 1;
78528
+ affordable_spot_buy_notional = next_notional;
78529
+ } else {
78530
+ break;
78531
+ }
78532
+ }
78533
+ const placement_plan = buildSpotMarginHedgePlan({
78534
+ borrow_amount: requested_input.borrow_amount,
78535
+ symbol: requested_input.symbol,
78536
+ margin_type: requested_input.margin_type,
78537
+ long_orders: requested_input.long_orders,
78538
+ short_orders: requested_input.short_orders,
78539
+ current_price: refreshed_snapshot.current_price,
78540
+ max_spot_buy_orders: affordable_spot_buy_orders,
78541
+ snapshot: {
78542
+ ...refreshed_snapshot,
78543
+ borrowed_quote_amount: refreshed_snapshot.borrowed_quote_amount + execution_plan.borrow_delta,
78544
+ spot_quote_free: spot_quote_available,
78545
+ spot_base_free: refreshed_snapshot.spot_base_free + base_transfer_amount
78546
+ }
78547
+ });
78548
+ const spot_result = placement_plan.orders_to_create.spot.length > 0 ? await this.createSpotLimitOrders({
78156
78549
  symbol,
78157
- orders: execution_plan.orders_to_create.spot,
78550
+ orders: placement_plan.orders_to_create.spot,
78158
78551
  price_places: symbol_formats.price_places,
78159
78552
  decimal_places: symbol_formats.decimal_places
78160
78553
  }) : [];
78161
- const margin_result = execution_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
78554
+ const margin_result = placement_plan.orders_to_create.margin.length > 0 ? await this.createMarginLimitOrders({
78162
78555
  symbol,
78163
- orders: execution_plan.orders_to_create.margin,
78164
- isIsolated: true,
78556
+ orders: placement_plan.orders_to_create.margin,
78557
+ isIsolated: is_isolated,
78165
78558
  price_places: symbol_formats.price_places,
78166
78559
  decimal_places: symbol_formats.decimal_places
78167
78560
  }) : [];
78168
78561
  return {
78169
78562
  ...preview,
78170
- execution_plan,
78563
+ execution_plan: {
78564
+ ...placement_plan,
78565
+ borrow_amount: execution_plan.borrow_amount,
78566
+ borrow_delta: execution_plan.borrow_delta,
78567
+ transfer_to_spot_amount: quote_transfer_amount,
78568
+ transfer_base_to_spot_amount: base_transfer_amount
78569
+ },
78171
78570
  refreshed_snapshot,
78172
78571
  execution: {
78173
78572
  borrow: borrow_result ? {
@@ -78177,12 +78576,12 @@ class BinanceExchange extends BaseExchange {
78177
78576
  } : null,
78178
78577
  quote_transfer: quote_transfer_result ? {
78179
78578
  asset: quote_asset,
78180
- amount: execution_plan.transfer_to_spot_amount,
78579
+ amount: quote_transfer_amount,
78181
78580
  response: quote_transfer_result
78182
78581
  } : null,
78183
78582
  base_transfer: base_transfer_result ? {
78184
78583
  asset: base_asset,
78185
- amount: execution_plan.transfer_base_to_spot_amount,
78584
+ amount: base_transfer_amount,
78186
78585
  response: base_transfer_result
78187
78586
  } : null,
78188
78587
  spot_orders: spot_result,