@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.
@@ -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;
76199
+ return {
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);
76180
76266
  return {
76181
- price: roundNumber(order.price),
76182
- quantity: roundNumber(order.quantity)
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, 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,
@@ -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,6 +78249,123 @@ 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");