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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -71936,9 +71936,18 @@ class BaseExchange {
71936
71936
  async previewSpotMarginHedge(_payload) {
71937
71937
  throw new Error("Spot margin hedge preview is not supported on this exchange");
71938
71938
  }
71939
+ async previewFuturesReplay(_payload) {
71940
+ throw new Error("Futures replay preview is not supported on this exchange");
71941
+ }
71939
71942
  async placeSpotMarginHedge(_payload) {
71940
71943
  throw new Error("Spot margin hedge placement is not supported on this exchange");
71941
71944
  }
71945
+ async placeFuturesReplay(_payload) {
71946
+ throw new Error("Futures replay placement is not supported on this exchange");
71947
+ }
71948
+ async placeFuturesExactTrade(_payload) {
71949
+ throw new Error("Futures exact trade placement is not supported on this exchange");
71950
+ }
71942
71951
  async placeSpotLimitOrders(_payload) {}
71943
71952
  async buildCumulative(payload) {
71944
71953
  const {
@@ -72347,19 +72356,198 @@ class BaseExchange {
72347
72356
  // src/exchanges/binance/index.ts
72348
72357
  var import_p_limit = __toESM(require_p_limit(), 1);
72349
72358
 
72350
- // src/exchanges/binance/spot-margin-hedge.ts
72359
+ // src/exchanges/binance/futures-replay.ts
72351
72360
  function roundNumber(value2, digits = 8) {
72352
72361
  return Number(value2.toFixed(digits));
72353
72362
  }
72354
- var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
72363
+ function parseNumber2(value2) {
72364
+ if (value2 === undefined || value2 === null || value2 === "")
72365
+ return;
72366
+ const parsed = Number(value2);
72367
+ if (!Number.isFinite(parsed) || parsed <= 0)
72368
+ return;
72369
+ return roundNumber(parsed);
72370
+ }
72355
72371
  function normalizeOrderInput(order) {
72372
+ const stop = parseNumber2(order.stop) ?? parseNumber2(order.stopPrice) ?? parseNumber2(order.triggerPrice);
72373
+ const type = order.type ? order.type.toUpperCase() : undefined;
72374
+ const quantity = order.quantity ?? order.qty ?? 0;
72375
+ return {
72376
+ price: roundNumber(Number(order.price)),
72377
+ quantity: roundNumber(Number(quantity)),
72378
+ ...stop ? { stop } : {},
72379
+ ...type && type !== "LIMIT" ? { type } : {}
72380
+ };
72381
+ }
72382
+ function sortReplayOrders(orders) {
72383
+ return [...orders].sort((left, right) => {
72384
+ const leftTrigger = left.stop ?? left.price;
72385
+ const rightTrigger = right.stop ?? right.price;
72386
+ if (leftTrigger !== rightTrigger)
72387
+ return leftTrigger - rightTrigger;
72388
+ if (left.price !== right.price)
72389
+ return left.price - right.price;
72390
+ return left.quantity - right.quantity;
72391
+ });
72392
+ }
72393
+ function buildReplayStopLimitPrice(payload) {
72394
+ const spread = payload.stop > 30000 ? 1.00005 : 1.0002;
72395
+ return roundNumber(payload.kind === "long" ? payload.stop * spread ** -1 : payload.stop * spread);
72396
+ }
72397
+ function normalizeRequestedEntryOrders(orders) {
72398
+ return sortReplayOrders(orders.map((order) => ({
72399
+ price: roundNumber(Number(order.price)),
72400
+ quantity: roundNumber(Number(order.quantity))
72401
+ })));
72402
+ }
72403
+ function normalizeRequestedStopOrders(payload) {
72404
+ return sortReplayOrders(payload.orders.map((order) => {
72405
+ const stop = parseNumber2(order.stop) ?? roundNumber(Number(order.price));
72406
+ return {
72407
+ price: order.price !== undefined ? roundNumber(Number(order.price)) : buildReplayStopLimitPrice({
72408
+ kind: payload.kind,
72409
+ stop
72410
+ }),
72411
+ quantity: roundNumber(Number(order.quantity)),
72412
+ stop,
72413
+ type: (order.type || "STOP").toUpperCase()
72414
+ };
72415
+ }));
72416
+ }
72417
+ function normalizeRequestedTakeProfitOrders(orders) {
72418
+ return sortReplayOrders(orders.map((order) => {
72419
+ const stop = parseNumber2(order.stop);
72420
+ const type = order.type?.toUpperCase();
72421
+ return {
72422
+ price: roundNumber(Number(order.price)),
72423
+ quantity: roundNumber(Number(order.quantity)),
72424
+ ...stop ? { stop } : {},
72425
+ ...type ? { type } : stop ? { type: "TAKE_PROFIT" } : {}
72426
+ };
72427
+ }));
72428
+ }
72429
+ function buildOrderKey(order) {
72430
+ return [
72431
+ order.type || "LIMIT",
72432
+ order.price,
72433
+ order.quantity,
72434
+ order.stop ?? ""
72435
+ ].join(":");
72436
+ }
72437
+ function buildComparisonBucket(options) {
72438
+ const expected = sortReplayOrders(options.expected);
72439
+ const actual = sortReplayOrders(options.actual);
72440
+ const expected_keys = expected.map(buildOrderKey);
72441
+ const actual_keys = actual.map(buildOrderKey);
72356
72442
  return {
72357
- price: roundNumber(order.price),
72358
- quantity: roundNumber(order.quantity)
72443
+ expected,
72444
+ actual,
72445
+ missing: expected_keys.filter((key) => !actual_keys.includes(key)),
72446
+ extra: actual_keys.filter((key) => !expected_keys.includes(key))
72447
+ };
72448
+ }
72449
+ function buildFuturesReplaySnapshot(payload) {
72450
+ const entry_side = payload.kind === "long" ? "buy" : "sell";
72451
+ const close_side = payload.kind === "long" ? "sell" : "buy";
72452
+ const entry_orders = [];
72453
+ const stop_orders = [];
72454
+ const take_profit_orders = [];
72455
+ for (const order of payload.open_orders) {
72456
+ if (order.kind !== payload.kind)
72457
+ continue;
72458
+ const normalized_order = normalizeOrderInput(order);
72459
+ const type = (normalized_order.type || "LIMIT").toUpperCase();
72460
+ if (order.side === entry_side) {
72461
+ entry_orders.push(normalized_order);
72462
+ continue;
72463
+ }
72464
+ if (order.side !== close_side)
72465
+ continue;
72466
+ if (type.includes("TAKE_PROFIT") || normalized_order.stop === undefined) {
72467
+ take_profit_orders.push(normalized_order);
72468
+ continue;
72469
+ }
72470
+ stop_orders.push(normalized_order);
72471
+ }
72472
+ return {
72473
+ symbol: payload.symbol,
72474
+ kind: payload.kind,
72475
+ current_price: payload.current_price,
72476
+ position: payload.position,
72477
+ open_orders: payload.open_orders.filter((order) => order.kind === payload.kind),
72478
+ entry_orders: sortReplayOrders(entry_orders),
72479
+ stop_orders: sortReplayOrders(stop_orders),
72480
+ take_profit_orders: sortReplayOrders(take_profit_orders)
72481
+ };
72482
+ }
72483
+ function inferFuturesReplayInputFromLiveOrders(payload) {
72484
+ const snapshot = buildFuturesReplaySnapshot(payload);
72485
+ return {
72486
+ symbol: payload.symbol,
72487
+ kind: payload.kind,
72488
+ current_price: payload.current_price,
72489
+ position: payload.position,
72490
+ entry_orders: snapshot.entry_orders,
72491
+ stop_orders: snapshot.stop_orders,
72492
+ take_profit_orders: snapshot.take_profit_orders
72493
+ };
72494
+ }
72495
+ function compareFuturesReplayOrders(payload) {
72496
+ const entry = buildComparisonBucket({
72497
+ expected: payload.entry_orders,
72498
+ actual: payload.snapshot.entry_orders
72499
+ });
72500
+ const stop = buildComparisonBucket({
72501
+ expected: payload.stop_orders,
72502
+ actual: payload.snapshot.stop_orders
72503
+ });
72504
+ const take_profit = buildComparisonBucket({
72505
+ expected: payload.take_profit_orders,
72506
+ actual: payload.snapshot.take_profit_orders
72507
+ });
72508
+ return {
72509
+ 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,
72510
+ entry,
72511
+ stop,
72512
+ take_profit
72513
+ };
72514
+ }
72515
+ function buildFuturesReplayPlan(payload) {
72516
+ return {
72517
+ symbol: payload.symbol,
72518
+ kind: payload.kind,
72519
+ orders_to_cancel: payload.snapshot.open_orders,
72520
+ orders_to_create: {
72521
+ entry: sortReplayOrders(payload.entry_orders),
72522
+ stop: sortReplayOrders(payload.stop_orders),
72523
+ take_profit: sortReplayOrders(payload.take_profit_orders)
72524
+ }
72525
+ };
72526
+ }
72527
+ function normalizeFuturesReplayInput(payload) {
72528
+ return {
72529
+ entry_orders: normalizeRequestedEntryOrders(payload.entry_orders),
72530
+ stop_orders: normalizeRequestedStopOrders({
72531
+ kind: payload.kind,
72532
+ orders: payload.stop_orders
72533
+ }),
72534
+ take_profit_orders: normalizeRequestedTakeProfitOrders(payload.take_profit_orders)
72535
+ };
72536
+ }
72537
+
72538
+ // src/exchanges/binance/spot-margin-hedge.ts
72539
+ function roundNumber2(value2, digits = 8) {
72540
+ return Number(value2.toFixed(digits));
72541
+ }
72542
+ var STOP_LIMIT_SPREAD_PERCENT = 0.0005;
72543
+ function normalizeOrderInput2(order) {
72544
+ return {
72545
+ price: roundNumber2(order.price),
72546
+ quantity: roundNumber2(order.quantity)
72359
72547
  };
72360
72548
  }
72361
72549
  function sortOrderInputs(orders) {
72362
- return [...orders].map(normalizeOrderInput).sort((left, right) => left.price - right.price);
72550
+ return [...orders].map(normalizeOrderInput2).sort((left, right) => left.price - right.price);
72363
72551
  }
72364
72552
  function normalizeOrderForComparison(order) {
72365
72553
  const type = (order.type || "limit").toLowerCase();
@@ -72368,9 +72556,9 @@ function normalizeOrderForComparison(order) {
72368
72556
  return {
72369
72557
  side: order.side,
72370
72558
  type,
72371
- price: roundNumber(order.price),
72372
- quantity: roundNumber(order.quantity),
72373
- stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber(parsed_stop) : undefined
72559
+ price: roundNumber2(order.price),
72560
+ quantity: roundNumber2(order.quantity),
72561
+ stop: parsed_stop !== undefined && Number.isFinite(parsed_stop) && parsed_stop > 0 ? roundNumber2(parsed_stop) : undefined
72374
72562
  };
72375
72563
  }
72376
72564
  function normalizeOrders(orders) {
@@ -72393,7 +72581,7 @@ function toPlannedOrders(orders) {
72393
72581
  stop: order.stop
72394
72582
  }));
72395
72583
  }
72396
- function buildOrderKey(order) {
72584
+ function buildOrderKey2(order) {
72397
72585
  return [
72398
72586
  order.side,
72399
72587
  order.type,
@@ -72403,8 +72591,8 @@ function buildOrderKey(order) {
72403
72591
  ].join(":");
72404
72592
  }
72405
72593
  function buildDiff(options) {
72406
- const expectedKeys = options.expected.map(buildOrderKey);
72407
- const actualKeys = options.actual.map(buildOrderKey);
72594
+ const expectedKeys = options.expected.map(buildOrderKey2);
72595
+ const actualKeys = options.actual.map(buildOrderKey2);
72408
72596
  return {
72409
72597
  missing: expectedKeys.filter((key) => !actualKeys.includes(key)),
72410
72598
  extra: actualKeys.filter((key) => !expectedKeys.includes(key))
@@ -72416,10 +72604,10 @@ function getQuoteAsset(symbol) {
72416
72604
  return target || symbol.slice(-4);
72417
72605
  }
72418
72606
  function sumNotional(orders) {
72419
- return roundNumber(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
72607
+ return roundNumber2(orders.reduce((sum, order) => sum + order.price * order.quantity, 0), 8);
72420
72608
  }
72421
72609
  function sumQuantity(orders) {
72422
- return roundNumber(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
72610
+ return roundNumber2(orders.reduce((sum, order) => sum + order.quantity, 0), 8);
72423
72611
  }
72424
72612
  function buildLongOrderPlacement(options) {
72425
72613
  const { order, current_price } = options;
@@ -72428,7 +72616,7 @@ function buildLongOrderPlacement(options) {
72428
72616
  side: "buy",
72429
72617
  type: "stop_loss_limit",
72430
72618
  price: order.price,
72431
- stop: roundNumber(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
72619
+ stop: roundNumber2(order.price * (1 - STOP_LIMIT_SPREAD_PERCENT)),
72432
72620
  quantity: order.quantity
72433
72621
  };
72434
72622
  }
@@ -72496,11 +72684,11 @@ function buildSpotMarginHedgePlan(options) {
72496
72684
  }));
72497
72685
  const spot_orders_to_create = options.placement_overrides?.spot || default_spot_orders_to_create;
72498
72686
  const margin_orders_to_create = options.placement_overrides?.margin || default_margin_orders_to_create;
72499
- const borrow_delta = roundNumber(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
72687
+ const borrow_delta = roundNumber2(Math.max(0, options.borrow_amount - options.snapshot.borrowed_quote_amount));
72500
72688
  const spot_long_notional = sumNotional(spot_long_orders);
72501
- const transfer_to_spot_amount = roundNumber(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
72689
+ const transfer_to_spot_amount = roundNumber2(Math.max(0, spot_long_notional - options.snapshot.spot_quote_free));
72502
72690
  const short_quantity = sumQuantity(short_orders);
72503
- const transfer_base_to_spot_amount = roundNumber(Math.max(0, short_quantity - options.snapshot.spot_base_free));
72691
+ const transfer_base_to_spot_amount = roundNumber2(Math.max(0, short_quantity - options.snapshot.spot_base_free));
72504
72692
  return {
72505
72693
  borrow_amount: options.borrow_amount,
72506
72694
  borrow_delta,
@@ -74159,6 +74347,30 @@ class BinanceExchange extends BaseExchange {
74159
74347
  throw error;
74160
74348
  }
74161
74349
  }
74350
+ async getFuturesSymbolFormats(symbol) {
74351
+ if (typeof this.client.getExchangeInfo !== "function") {
74352
+ return {
74353
+ price_places: "%.8f",
74354
+ decimal_places: "%.8f"
74355
+ };
74356
+ }
74357
+ try {
74358
+ const exchange_info = await this.client.getExchangeInfo();
74359
+ const symbol_info = exchange_info?.symbols?.find((item) => item.symbol === symbol.toUpperCase());
74360
+ const price_filter = symbol_info?.filters?.find((filter) => filter.filterType === "PRICE_FILTER");
74361
+ const lot_size_filter = symbol_info?.filters?.find((filter) => filter.filterType === "LOT_SIZE");
74362
+ return {
74363
+ price_places: getPrintfFormatFromStepSize(price_filter?.tickSize, "%.8f"),
74364
+ decimal_places: getPrintfFormatFromStepSize(lot_size_filter?.stepSize, "%.8f")
74365
+ };
74366
+ } catch (error) {
74367
+ console.error("Error fetching futures symbol formats:", error);
74368
+ return {
74369
+ price_places: "%.8f",
74370
+ decimal_places: "%.8f"
74371
+ };
74372
+ }
74373
+ }
74162
74374
  async getSpotSymbolFormats(symbol) {
74163
74375
  if (!this.main_client || typeof this.main_client.getExchangeInfo !== "function") {
74164
74376
  return {
@@ -74213,6 +74425,123 @@ class BinanceExchange extends BaseExchange {
74213
74425
  }
74214
74426
  return await getMarginOpenOrders(this.main_client, symbol, isIsolated);
74215
74427
  }
74428
+ async getFuturesReplaySnapshot(payload) {
74429
+ const symbol = payload.symbol.toUpperCase();
74430
+ const [open_orders, position_info, current_price] = await Promise.all([
74431
+ this.getOpenOrders({ symbol }),
74432
+ this.getPositionInfo(symbol),
74433
+ this.getCurrentPrice(symbol)
74434
+ ]);
74435
+ return buildFuturesReplaySnapshot({
74436
+ symbol,
74437
+ kind: payload.kind,
74438
+ current_price,
74439
+ position: {
74440
+ size: position_info[payload.kind]?.size || 0
74441
+ },
74442
+ open_orders
74443
+ });
74444
+ }
74445
+ async previewFuturesReplay(payload) {
74446
+ const snapshot = payload.snapshot ? {
74447
+ ...payload.snapshot,
74448
+ current_price: payload.snapshot.current_price ?? await this.getCurrentPrice(payload.symbol)
74449
+ } : await this.getFuturesReplaySnapshot({
74450
+ symbol: payload.symbol,
74451
+ kind: payload.kind
74452
+ });
74453
+ const shouldInferEntryOrders = !payload.entry_orders || payload.entry_orders.length === 0;
74454
+ const shouldInferStopOrders = !payload.stop_orders || payload.stop_orders.length === 0;
74455
+ const shouldInferTakeProfitOrders = !payload.take_profit_orders || payload.take_profit_orders.length === 0;
74456
+ const inferred_input = shouldInferEntryOrders || shouldInferStopOrders || shouldInferTakeProfitOrders ? inferFuturesReplayInputFromLiveOrders({
74457
+ symbol: payload.symbol,
74458
+ kind: payload.kind,
74459
+ current_price: snapshot.current_price,
74460
+ position: snapshot.position,
74461
+ open_orders: snapshot.open_orders
74462
+ }) : undefined;
74463
+ const explicit_input = normalizeFuturesReplayInput({
74464
+ kind: payload.kind,
74465
+ entry_orders: payload.entry_orders || [],
74466
+ stop_orders: payload.stop_orders || [],
74467
+ take_profit_orders: payload.take_profit_orders || []
74468
+ });
74469
+ const entry_orders = shouldInferEntryOrders ? inferred_input?.entry_orders || [] : explicit_input.entry_orders;
74470
+ const stop_orders = shouldInferStopOrders ? inferred_input?.stop_orders || [] : explicit_input.stop_orders;
74471
+ const take_profit_orders = shouldInferTakeProfitOrders ? inferred_input?.take_profit_orders || [] : explicit_input.take_profit_orders;
74472
+ const requested_input = {
74473
+ symbol: payload.symbol,
74474
+ kind: payload.kind,
74475
+ entry_orders,
74476
+ stop_orders,
74477
+ take_profit_orders
74478
+ };
74479
+ const plan = buildFuturesReplayPlan({
74480
+ ...requested_input,
74481
+ snapshot
74482
+ });
74483
+ const comparison = compareFuturesReplayOrders({
74484
+ ...requested_input,
74485
+ snapshot
74486
+ });
74487
+ return {
74488
+ snapshot,
74489
+ inferred_input,
74490
+ requested_input,
74491
+ plan,
74492
+ comparison
74493
+ };
74494
+ }
74495
+ async placeFuturesReplay(payload) {
74496
+ const preview = await this.previewFuturesReplay(payload);
74497
+ if (!payload.place) {
74498
+ return preview;
74499
+ }
74500
+ const symbol = payload.symbol.toUpperCase();
74501
+ await cancelAllOrders(this.client, symbol, {
74502
+ kind: payload.kind
74503
+ });
74504
+ const refreshed_snapshot = await this.getFuturesReplaySnapshot({
74505
+ symbol,
74506
+ kind: payload.kind
74507
+ });
74508
+ const execution_plan = buildFuturesReplayPlan({
74509
+ symbol,
74510
+ kind: payload.kind,
74511
+ entry_orders: preview.requested_input.entry_orders,
74512
+ stop_orders: preview.requested_input.stop_orders,
74513
+ take_profit_orders: preview.requested_input.take_profit_orders,
74514
+ snapshot: refreshed_snapshot
74515
+ });
74516
+ const { price_places, decimal_places } = await this.getFuturesSymbolFormats(symbol);
74517
+ const entry_orders = execution_plan.orders_to_create.entry.map((order) => ({
74518
+ ...order,
74519
+ side: payload.kind === "long" ? "buy" : "sell",
74520
+ kind: payload.kind
74521
+ }));
74522
+ const stop_orders = execution_plan.orders_to_create.stop.map((order) => ({
74523
+ ...order,
74524
+ side: payload.kind === "long" ? "sell" : "buy",
74525
+ kind: payload.kind,
74526
+ type: order.type || "STOP"
74527
+ }));
74528
+ const take_profit_orders = execution_plan.orders_to_create.take_profit.map((order) => ({
74529
+ ...order,
74530
+ side: payload.kind === "long" ? "sell" : "buy",
74531
+ kind: payload.kind,
74532
+ ...order.stop ? { type: order.type || "TAKE_PROFIT" } : {}
74533
+ }));
74534
+ 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]) : [];
74535
+ return {
74536
+ ...preview,
74537
+ execution_plan,
74538
+ refreshed_snapshot,
74539
+ execution
74540
+ };
74541
+ }
74542
+ async placeFuturesExactTrade(payload) {
74543
+ return await this.placeFuturesReplay(payload);
74544
+ }
74216
74545
  async getSpotMarginHedgeSnapshot(payload) {
74217
74546
  if (!this.main_client) {
74218
74547
  throw new Error("Main client not available for spot and margin trading");