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

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.
@@ -76510,7 +76510,7 @@ function buildSpotMarginHedgePlan(options) {
76510
76510
  const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
76511
76511
  const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
76512
76512
  const spot_long_notional = sumNotional(spot_long_orders);
76513
- const transfer_to_spot_amount = roundNumber2(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));
76514
76514
  const short_quantity = sumQuantity(short_orders);
76515
76515
  const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
76516
76516
  return {
@@ -77014,17 +77014,17 @@ async function createMarginLimitOrdersParallel(client, symbol, priceFormat, quan
77014
77014
  return Object.fromEntries(Object.entries(v).filter(([, value2]) => value2 !== undefined));
77015
77015
  };
77016
77016
  const newOrders = orders.map(createMarginOrder);
77017
- const limit = import_p_limit.default(ORDERS_PER_SECOND);
77018
- const results = await Promise.all(newOrders.map((orderPayload) => limit(async () => {
77017
+ const results = [];
77018
+ for (const orderPayload of newOrders) {
77019
77019
  try {
77020
77020
  const result = await client.marginAccountNewOrder(orderPayload);
77021
77021
  console.log("Margin order result:", result);
77022
- return result;
77022
+ results.push(result);
77023
77023
  } catch (error) {
77024
77024
  console.error("Error processing margin order:", error);
77025
77025
  throw error;
77026
77026
  }
77027
- })));
77027
+ }
77028
77028
  return results;
77029
77029
  }
77030
77030
  async function getIsolatedMarginAccountInfo(client, symbol) {
@@ -78370,24 +78370,25 @@ class BinanceExchange extends BaseExchange {
78370
78370
  if (!this.main_client) {
78371
78371
  throw new Error("Main client not available for spot and margin trading");
78372
78372
  }
78373
- if (payload.margin_type !== "isolated") {
78374
- throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated is supported for replay right now.`);
78375
- }
78376
78373
  const symbol = payload.symbol.toUpperCase();
78377
78374
  const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78378
78375
  const quoteAsset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78379
78376
  const baseAsset = symbol.slice(0, symbol.length - quoteAsset.length);
78380
- 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([
78381
78379
  this.getSpotOpenOrders(symbol),
78382
- this.getMarginOpenOrders(symbol, true),
78380
+ this.getMarginOpenOrders(symbol, is_isolated),
78383
78381
  this.getSpotBalances([baseAsset, quoteAsset]),
78384
- this.getIsolatedMarginPosition(symbol)
78382
+ is_isolated ? this.getIsolatedMarginPosition(symbol) : this.getCrossMarginAccount()
78385
78383
  ]);
78386
78384
  const current_price = await this.getSpotCurrentPrice(symbol);
78387
78385
  const spot_quote_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === quoteAsset);
78388
78386
  const spot_base_balance = spot_balances.find((balance) => balance.asset.toUpperCase() === baseAsset);
78389
- const isolated_asset = isolated_margin_position?.assets?.[0];
78390
- 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");
78391
78392
  return {
78392
78393
  symbol,
78393
78394
  margin_type: payload.margin_type,
@@ -78400,7 +78401,8 @@ class BinanceExchange extends BaseExchange {
78400
78401
  margin_orders,
78401
78402
  current_price,
78402
78403
  spot_balances,
78403
- isolated_margin_position
78404
+ isolated_margin_position: isolated_margin_account,
78405
+ cross_margin_account
78404
78406
  };
78405
78407
  }
78406
78408
  async previewSpotMarginHedge(payload) {
@@ -78457,14 +78459,12 @@ class BinanceExchange extends BaseExchange {
78457
78459
  if (!this.main_client) {
78458
78460
  throw new Error("Main client not available for spot and margin trading");
78459
78461
  }
78460
- if (payload.margin_type !== "isolated") {
78461
- throw new Error(`Unsupported margin type: ${payload.margin_type}. Only isolated placement is supported right now.`);
78462
- }
78463
78462
  const symbol = payload.symbol.toUpperCase();
78464
78463
  const quoteAssets = ["USDT", "USDC", "BUSD", "BTC", "ETH"];
78465
78464
  const quote_asset = quoteAssets.find((asset) => symbol.endsWith(asset)) || symbol.slice(-4);
78466
78465
  const base_asset = symbol.slice(0, symbol.length - quote_asset.length);
78467
78466
  const requested_input = preview.requested_input;
78467
+ const is_isolated = payload.margin_type === "isolated";
78468
78468
  if (preview.snapshot.spot_orders.length > 0) {
78469
78469
  await this.cancelSpotOrders(symbol, preview.snapshot.spot_orders.map((order) => ({
78470
78470
  orderId: order.orderId || order.order_id || order.id,
@@ -78472,7 +78472,7 @@ class BinanceExchange extends BaseExchange {
78472
78472
  })));
78473
78473
  }
78474
78474
  if (preview.snapshot.margin_orders.length > 0) {
78475
- await this.cancelMarginOrders(symbol, true, preview.snapshot.margin_orders.map((order) => ({
78475
+ await this.cancelMarginOrders(symbol, is_isolated, preview.snapshot.margin_orders.map((order) => ({
78476
78476
  orderId: order.orderId || order.order_id || order.id,
78477
78477
  origClientOrderId: order.origClientOrderId
78478
78478
  })));
@@ -78494,50 +78494,120 @@ class BinanceExchange extends BaseExchange {
78494
78494
  });
78495
78495
  let borrow_result = null;
78496
78496
  if (execution_plan.borrow_delta > 0) {
78497
- borrow_result = await this.main_client.submitMarginAccountBorrowRepay({
78497
+ const borrow_payload = {
78498
78498
  asset: quote_asset,
78499
78499
  amount: execution_plan.borrow_delta,
78500
- isIsolated: "TRUE",
78501
- symbol,
78502
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
78503
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));
78504
78523
  }
78505
78524
  let quote_transfer_result = null;
78525
+ let quote_transfer_amount = execution_plan.transfer_to_spot_amount;
78506
78526
  if (execution_plan.transfer_to_spot_amount > 0) {
78507
- quote_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78508
- asset: quote_asset,
78509
- amount: execution_plan.transfer_to_spot_amount,
78510
- symbol,
78511
- transFrom: "ISOLATED_MARGIN",
78512
- transTo: "SPOT"
78513
- });
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
+ }
78514
78541
  }
78515
78542
  let base_transfer_result = null;
78543
+ let base_transfer_amount = execution_plan.transfer_base_to_spot_amount;
78516
78544
  if (execution_plan.transfer_base_to_spot_amount > 0) {
78517
- base_transfer_result = await this.main_client.isolatedMarginAccountTransfer({
78518
- asset: base_asset,
78519
- amount: execution_plan.transfer_base_to_spot_amount,
78520
- symbol,
78521
- transFrom: "ISOLATED_MARGIN",
78522
- transTo: "SPOT"
78523
- });
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
+ }
78524
78559
  }
78525
- 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({
78526
78590
  symbol,
78527
- orders: execution_plan.orders_to_create.spot,
78591
+ orders: placement_plan.orders_to_create.spot,
78528
78592
  price_places: symbol_formats.price_places,
78529
78593
  decimal_places: symbol_formats.decimal_places
78530
78594
  }) : [];
78531
- 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({
78532
78596
  symbol,
78533
- orders: execution_plan.orders_to_create.margin,
78534
- isIsolated: true,
78597
+ orders: placement_plan.orders_to_create.margin,
78598
+ isIsolated: is_isolated,
78535
78599
  price_places: symbol_formats.price_places,
78536
78600
  decimal_places: symbol_formats.decimal_places
78537
78601
  }) : [];
78538
78602
  return {
78539
78603
  ...preview,
78540
- 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
+ },
78541
78611
  refreshed_snapshot,
78542
78612
  execution: {
78543
78613
  borrow: borrow_result ? {
@@ -78547,12 +78617,12 @@ class BinanceExchange extends BaseExchange {
78547
78617
  } : null,
78548
78618
  quote_transfer: quote_transfer_result ? {
78549
78619
  asset: quote_asset,
78550
- amount: execution_plan.transfer_to_spot_amount,
78620
+ amount: quote_transfer_amount,
78551
78621
  response: quote_transfer_result
78552
78622
  } : null,
78553
78623
  base_transfer: base_transfer_result ? {
78554
78624
  asset: base_asset,
78555
- amount: execution_plan.transfer_base_to_spot_amount,
78625
+ amount: base_transfer_amount,
78556
78626
  response: base_transfer_result
78557
78627
  } : null,
78558
78628
  spot_orders: spot_result,
@@ -82047,6 +82117,76 @@ async function forceClosePosition2(client, symbol, options) {
82047
82117
  }
82048
82118
 
82049
82119
  // src/exchange-account.ts
82120
+ function getFormatPrecision(places) {
82121
+ if (!places) {
82122
+ return 0;
82123
+ }
82124
+ const match = places.match(/%\.(\d+)f/);
82125
+ return match ? Number(match[1]) : 0;
82126
+ }
82127
+ function floorToFormat(value2, places) {
82128
+ if (!Number.isFinite(value2) || value2 <= 0) {
82129
+ return 0;
82130
+ }
82131
+ const precision = getFormatPrecision(places);
82132
+ const factor = 10 ** precision;
82133
+ return Math.floor((value2 + Number.EPSILON) * factor) / factor;
82134
+ }
82135
+
82136
+ class TakeProfitReplaceRollbackSucceededError extends Error {
82137
+ status = "rollback_succeeded";
82138
+ restored = true;
82139
+ symbol;
82140
+ kind;
82141
+ takeProfitPrice;
82142
+ quantity;
82143
+ previousTakeProfitPrice;
82144
+ previousQuantity;
82145
+ restoreAttempted;
82146
+ cause;
82147
+ rollbackError;
82148
+ constructor(metadata3) {
82149
+ super("Take profit replacement failed and previous take profit was restored");
82150
+ this.name = "TakeProfitReplaceRollbackSucceededError";
82151
+ this.symbol = metadata3.symbol;
82152
+ this.kind = metadata3.kind;
82153
+ this.takeProfitPrice = metadata3.takeProfitPrice;
82154
+ this.quantity = metadata3.quantity;
82155
+ this.previousTakeProfitPrice = metadata3.previousTakeProfitPrice;
82156
+ this.previousQuantity = metadata3.previousQuantity;
82157
+ this.restoreAttempted = metadata3.restoreAttempted;
82158
+ this.cause = metadata3.cause;
82159
+ this.rollbackError = metadata3.rollbackError;
82160
+ }
82161
+ }
82162
+
82163
+ class TakeProfitReplaceRollbackFailedError extends Error {
82164
+ status = "rollback_failed";
82165
+ restored = false;
82166
+ symbol;
82167
+ kind;
82168
+ takeProfitPrice;
82169
+ quantity;
82170
+ previousTakeProfitPrice;
82171
+ previousQuantity;
82172
+ restoreAttempted;
82173
+ cause;
82174
+ rollbackError;
82175
+ constructor(metadata3) {
82176
+ super("Take profit replacement failed and previous take profit could not be restored");
82177
+ this.name = "TakeProfitReplaceRollbackFailedError";
82178
+ this.symbol = metadata3.symbol;
82179
+ this.kind = metadata3.kind;
82180
+ this.takeProfitPrice = metadata3.takeProfitPrice;
82181
+ this.quantity = metadata3.quantity;
82182
+ this.previousTakeProfitPrice = metadata3.previousTakeProfitPrice;
82183
+ this.previousQuantity = metadata3.previousQuantity;
82184
+ this.restoreAttempted = metadata3.restoreAttempted;
82185
+ this.cause = metadata3.cause;
82186
+ this.rollbackError = metadata3.rollbackError;
82187
+ }
82188
+ }
82189
+
82050
82190
  class ExchangeAccount {
82051
82191
  instance;
82052
82192
  exchange;
@@ -82143,8 +82283,6 @@ class ExchangeAccount {
82143
82283
  refresh
82144
82284
  });
82145
82285
  const raw_active_account = live_exchange_instance.data;
82146
- console.log("raw_active", raw_active_account);
82147
- console.log("symbol_config", symbol_config);
82148
82286
  const _all = get_active_accounts({
82149
82287
  active_account: raw_active_account,
82150
82288
  symbol_config
@@ -82278,6 +82416,132 @@ class ExchangeAccount {
82278
82416
  });
82279
82417
  return await focus_position.cancelOrders(payload);
82280
82418
  }
82419
+ async replaceTakeProfitForPosition(payload) {
82420
+ const { symbol, kind, takeProfitPrice, quantity } = payload;
82421
+ const focusPosition = await this.getFocusPosition({
82422
+ symbol,
82423
+ kind,
82424
+ update: true
82425
+ });
82426
+ const position2 = focusPosition.getInstance();
82427
+ const symbol_config = focusPosition.symbol_config;
82428
+ if (!symbol_config) {
82429
+ throw new Error(`Missing symbol config for ${symbol}`);
82430
+ }
82431
+ const normalizedQuantity = floorToFormat(quantity, symbol_config.decimal_places);
82432
+ const minSize = Number(symbol_config.min_size || 0);
82433
+ const previousTakeProfitPrice = Number(position2?.take_profit || 0);
82434
+ const previousQuantity = floorToFormat(Number(position2?.tp_quantity || 0), symbol_config.decimal_places);
82435
+ if (normalizedQuantity <= 0) {
82436
+ return {
82437
+ status: "below_min_lot",
82438
+ symbol,
82439
+ kind,
82440
+ takeProfitPrice,
82441
+ quantity: 0,
82442
+ minSize,
82443
+ reason: "quantity_floored_to_zero"
82444
+ };
82445
+ }
82446
+ if (minSize > 0 && normalizedQuantity < minSize) {
82447
+ return {
82448
+ status: "below_min_lot",
82449
+ symbol,
82450
+ kind,
82451
+ takeProfitPrice,
82452
+ quantity: normalizedQuantity,
82453
+ minSize,
82454
+ reason: "below_venue_minimum"
82455
+ };
82456
+ }
82457
+ const exchangeSeams = this.exchange;
82458
+ if (exchangeSeams.replaceTakeProfitForPosition) {
82459
+ await exchangeSeams.replaceTakeProfitForPosition({
82460
+ symbol,
82461
+ kind,
82462
+ takeProfitPrice,
82463
+ quantity: normalizedQuantity,
82464
+ price_places: symbol_config.price_places,
82465
+ decimal_places: symbol_config.decimal_places,
82466
+ previousTakeProfitPrice,
82467
+ previousQuantity
82468
+ });
82469
+ return {
82470
+ status: "replaced",
82471
+ symbol,
82472
+ kind,
82473
+ takeProfitPrice,
82474
+ quantity: normalizedQuantity,
82475
+ previousTakeProfitPrice,
82476
+ previousQuantity,
82477
+ restoreAttempted: false,
82478
+ via: "native_replace"
82479
+ };
82480
+ }
82481
+ const placeTpWithoutCancelling = exchangeSeams._placeTpOrder;
82482
+ if (!placeTpWithoutCancelling) {
82483
+ throw new Error(`Exchange ${this.instance.exchange} does not expose a TP replace or raw TP placement path`);
82484
+ }
82485
+ if (previousTakeProfitPrice > 0) {
82486
+ await this.cancelOrders({
82487
+ symbol,
82488
+ kind,
82489
+ price: previousTakeProfitPrice
82490
+ });
82491
+ }
82492
+ try {
82493
+ await placeTpWithoutCancelling.call(this.exchange, {
82494
+ symbol,
82495
+ tp: takeProfitPrice,
82496
+ kind,
82497
+ quantity: normalizedQuantity,
82498
+ cancel: false,
82499
+ price_places: symbol_config.price_places,
82500
+ decimal_places: symbol_config.decimal_places
82501
+ });
82502
+ return {
82503
+ status: "replaced",
82504
+ symbol,
82505
+ kind,
82506
+ takeProfitPrice,
82507
+ quantity: normalizedQuantity,
82508
+ previousTakeProfitPrice,
82509
+ previousQuantity,
82510
+ restoreAttempted: false
82511
+ };
82512
+ } catch (cause) {
82513
+ const metadata3 = {
82514
+ symbol,
82515
+ kind,
82516
+ takeProfitPrice,
82517
+ quantity: normalizedQuantity,
82518
+ previousTakeProfitPrice,
82519
+ previousQuantity,
82520
+ restoreAttempted: previousTakeProfitPrice > 0 && previousQuantity > 0,
82521
+ cause
82522
+ };
82523
+ if (!metadata3.restoreAttempted) {
82524
+ throw new TakeProfitReplaceRollbackFailedError(metadata3);
82525
+ }
82526
+ try {
82527
+ await placeTpWithoutCancelling.call(this.exchange, {
82528
+ symbol,
82529
+ tp: previousTakeProfitPrice,
82530
+ kind,
82531
+ quantity: previousQuantity,
82532
+ cancel: false,
82533
+ price_places: symbol_config.price_places,
82534
+ decimal_places: symbol_config.decimal_places
82535
+ });
82536
+ } catch (rollbackError) {
82537
+ throw new TakeProfitReplaceRollbackFailedError({
82538
+ ...metadata3,
82539
+ rollbackError
82540
+ });
82541
+ }
82542
+ throw new TakeProfitReplaceRollbackSucceededError(metadata3);
82543
+ }
82544
+ }
82281
82545
  async cancelExchangeOrders(payload) {
82282
82546
  return this.exchange.cancelOrders(payload);
82283
82547
  }
@@ -82562,8 +82826,8 @@ class ExchangeAccount {
82562
82826
  if (payload.trigger && !long_pause_tp && !short_pause_tp && config2) {
82563
82827
  return await this.reduceMajorPositionEntry({
82564
82828
  symbol,
82565
- long: config2.long,
82566
- short: config2.short,
82829
+ long: kind === "long" ? config2.long : undefined,
82830
+ short: kind === "short" ? config2.short : undefined,
82567
82831
  trigger: config2.trigger
82568
82832
  });
82569
82833
  }