@tradejs/connectors 1.0.5 → 1.0.6

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.d.mts CHANGED
@@ -19,6 +19,7 @@ type MarketDataProviderName = 'coinalyze' | 'binance_coinbase_spread';
19
19
  type ProviderWindowParams = {
20
20
  symbol: string;
21
21
  marketSymbol?: string;
22
+ apiKey?: string;
22
23
  interval: DerivativesInterval;
23
24
  fromMs: number;
24
25
  toMs: number;
package/dist/index.d.ts CHANGED
@@ -19,6 +19,7 @@ type MarketDataProviderName = 'coinalyze' | 'binance_coinbase_spread';
19
19
  type ProviderWindowParams = {
20
20
  symbol: string;
21
21
  marketSymbol?: string;
22
+ apiKey?: string;
22
23
  interval: DerivativesInterval;
23
24
  fromMs: number;
24
25
  toMs: number;
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ module.exports = __toCommonJS(index_exports);
47
47
  // src/ByBit/index.ts
48
48
  var import_lodash = __toESM(require("lodash"));
49
49
  var import_chalk = __toESM(require("chalk"));
50
+ var import_async = require("@tradejs/core/async");
50
51
  var import_constants2 = require("@tradejs/core/constants");
51
52
  var import_data = require("@tradejs/core/data");
52
53
  var import_data2 = require("@tradejs/core/data");
@@ -61,7 +62,13 @@ var import_bybit_api = require("bybit-api");
61
62
  var import_logger = require("@tradejs/infra/logger");
62
63
  var import_redis = require("@tradejs/infra/redis");
63
64
  var useTestnet = false;
64
- var getClient = async ({ userName }) => {
65
+ var getClient = async ({ userName }, access = "private") => {
66
+ if (access === "public") {
67
+ return new import_bybit_api.RestClientV5({
68
+ parseAPIRateLimits: true,
69
+ testnet: useTestnet
70
+ });
71
+ }
65
72
  const user = await (0, import_redis.getData)(import_redis.redisKeys.user(userName));
66
73
  if (!user) {
67
74
  import_logger.logger.log("error", "connection config not found: %s", userName);
@@ -70,6 +77,7 @@ var getClient = async ({ userName }) => {
70
77
  const client = new import_bybit_api.RestClientV5({
71
78
  key: user.BYBIT_API_KEY,
72
79
  secret: user.BYBIT_API_SECRET,
80
+ parseAPIRateLimits: true,
73
81
  testnet: useTestnet
74
82
  });
75
83
  return client;
@@ -169,6 +177,12 @@ var mapPositionData = (data) => {
169
177
  // src/ByBit/index.ts
170
178
  var LIMIT = 1e3;
171
179
  var CACHE_FALLBACK_WINDOW = 1e3;
180
+ var BYBIT_RATE_LIMIT_RETCODE = 10006;
181
+ var BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE = 34040;
182
+ var KLINE_RATE_LIMIT_MAX_ATTEMPTS = 3;
183
+ var KLINE_RATE_LIMIT_BASE_DELAY_MS = 800;
184
+ var KLINE_RATE_LIMIT_MAX_DELAY_MS = 1e4;
185
+ var KLINE_RATE_LIMIT_RESET_BUFFER_MS = 50;
172
186
  var INTERVAL_TO_MINUTES = {
173
187
  "1": 1,
174
188
  "3": 3,
@@ -185,9 +199,37 @@ var INTERVAL_TO_MINUTES = {
185
199
  M: 43200
186
200
  };
187
201
  var getLogLevel = (res) => res.retCode === 0 ? "info" : "error";
202
+ var isTradingStopNotModified = (res) => res?.retCode === BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE;
203
+ var isTradingStopAccepted = (res) => res?.retCode === 0 || isTradingStopNotModified(res);
204
+ var isKlineRateLimited = (res) => res?.retCode === BYBIT_RATE_LIMIT_RETCODE;
205
+ var resolveKlineRetryDelayMs = (res, attempt) => {
206
+ const resetAtRaw = Number(res?.rateLimitApi?.resetAtTimestamp);
207
+ if (Number.isFinite(resetAtRaw) && resetAtRaw > 0) {
208
+ return Math.max(
209
+ KLINE_RATE_LIMIT_RESET_BUFFER_MS,
210
+ resetAtRaw - Date.now() + KLINE_RATE_LIMIT_RESET_BUFFER_MS
211
+ );
212
+ }
213
+ const jitterMs = Math.round(Math.random() * 250);
214
+ const backoffMs = Math.min(
215
+ KLINE_RATE_LIMIT_MAX_DELAY_MS,
216
+ KLINE_RATE_LIMIT_BASE_DELAY_MS * 2 ** (attempt - 1)
217
+ );
218
+ return backoffMs + jitterMs;
219
+ };
188
220
  var ByBitConnectorCreator = async (config) => {
189
221
  let state = {};
190
222
  let isTimescaleFallbackMode = false;
223
+ let publicClientPromise = null;
224
+ let privateClientPromise = null;
225
+ const getPublicClient = async () => {
226
+ publicClientPromise ??= getClient(config, "public");
227
+ return publicClientPromise;
228
+ };
229
+ const getPrivateClient = async () => {
230
+ privateClientPromise ??= getClient(config, "private");
231
+ return privateClientPromise;
232
+ };
191
233
  const request = async ({
192
234
  symbol,
193
235
  interval,
@@ -201,41 +243,60 @@ var ByBitConnectorCreator = async (config) => {
201
243
  );
202
244
  const normalizedEnd = (0, import_math.round)(end || Date.now());
203
245
  try {
204
- const client = await getClient(config);
246
+ const client = await getPublicClient();
205
247
  if (!client) return [];
206
248
  if (normalizedEnd <= normalizedStart) {
207
249
  return [];
208
250
  }
209
- const kline = await client.getKline({
210
- category: import_constants2.MARKET_CATEGORY,
211
- symbol,
212
- interval,
213
- start: normalizedStart,
214
- end: normalizedEnd,
215
- limit: LIMIT
216
- });
217
- if (!kline?.result?.list) {
218
- const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
219
- import_logger2.logger.log(
220
- "error",
221
- "empty kline.list for %s %s%s",
251
+ for (let attempt = 1; attempt <= KLINE_RATE_LIMIT_MAX_ATTEMPTS; attempt += 1) {
252
+ const kline = await client.getKline({
253
+ category: import_constants2.MARKET_CATEGORY,
222
254
  symbol,
223
255
  interval,
224
- responseError ? `: ${responseError}` : ""
225
- );
226
- return [];
227
- }
228
- if (!silent) {
229
- import_logger2.logger.log(
230
- "info",
231
- "%s %s %s %s",
232
- import_chalk.default.yellow((0, import_time2.formatUnix)(normalizedEnd)),
233
- import_chalk.default.cyan(symbol),
234
- import_chalk.default.cyan(interval),
235
- import_chalk.default.yellow(kline.result.list.length)
236
- );
256
+ start: normalizedStart,
257
+ end: normalizedEnd,
258
+ limit: LIMIT
259
+ });
260
+ if (isKlineRateLimited(kline)) {
261
+ if (attempt < KLINE_RATE_LIMIT_MAX_ATTEMPTS) {
262
+ const waitMs = resolveKlineRetryDelayMs(kline, attempt);
263
+ import_logger2.logger.log(
264
+ "warn",
265
+ "kline rate limited for %s %s: attempt=%s/%s waitMs=%s",
266
+ symbol,
267
+ interval,
268
+ attempt,
269
+ KLINE_RATE_LIMIT_MAX_ATTEMPTS,
270
+ waitMs
271
+ );
272
+ await (0, import_async.delay)(waitMs);
273
+ continue;
274
+ }
275
+ }
276
+ if (!kline?.result?.list) {
277
+ const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
278
+ import_logger2.logger.log(
279
+ "error",
280
+ "empty kline.list for %s %s%s",
281
+ symbol,
282
+ interval,
283
+ responseError ? `: ${responseError}` : ""
284
+ );
285
+ return [];
286
+ }
287
+ if (!silent) {
288
+ import_logger2.logger.log(
289
+ "info",
290
+ "%s %s %s %s",
291
+ import_chalk.default.yellow((0, import_time2.formatUnix)(normalizedEnd)),
292
+ import_chalk.default.cyan(symbol),
293
+ import_chalk.default.cyan(interval),
294
+ import_chalk.default.yellow(kline.result.list.length)
295
+ );
296
+ }
297
+ return mapKlineToChartData(kline.result.list.reverse());
237
298
  }
238
- return mapKlineToChartData(kline.result.list.reverse());
299
+ return [];
239
300
  } catch (error) {
240
301
  import_logger2.logger.log(
241
302
  "error",
@@ -327,6 +388,190 @@ var ByBitConnectorCreator = async (config) => {
327
388
  await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(symbol, intMinutes, part));
328
389
  }
329
390
  };
391
+ const getPositionSnapshot = async (symbol) => {
392
+ const client = await getPrivateClient();
393
+ if (!client) {
394
+ return null;
395
+ }
396
+ const positionRes = await client.getPositionInfo({
397
+ symbol,
398
+ category: import_constants2.MARKET_CATEGORY
399
+ });
400
+ if (positionRes.retCode !== 0) {
401
+ import_logger2.logger.log(
402
+ getLogLevel(positionRes),
403
+ "position retCode: %s, %s",
404
+ symbol,
405
+ positionRes.retCode
406
+ );
407
+ return null;
408
+ }
409
+ const positions = mapPositionData(positionRes.result.list);
410
+ if (!positions || import_lodash.default.isEmpty(positions)) {
411
+ return null;
412
+ }
413
+ const position = positions[0];
414
+ import_logger2.logger.log(
415
+ "debug",
416
+ "position: %s %s qty=%s price=%s",
417
+ position.symbol,
418
+ position.direction,
419
+ position.qty,
420
+ position.price
421
+ );
422
+ return {
423
+ ...position
424
+ };
425
+ };
426
+ const setTakeProfits = async ({
427
+ symbol,
428
+ direction,
429
+ qty,
430
+ takeProfits
431
+ }) => {
432
+ const client = await getPrivateClient();
433
+ const marketDataClient = await getPublicClient();
434
+ if (!client || !marketDataClient) {
435
+ return false;
436
+ }
437
+ if (!Array.isArray(takeProfits) || takeProfits.length === 0) {
438
+ return true;
439
+ }
440
+ const meta = await getSymbolMeta(marketDataClient, symbol);
441
+ const positionQty = qty ?? (await getPositionSnapshot(symbol))?.qty ?? 0;
442
+ if (!Number.isFinite(positionQty) || positionQty <= 0) {
443
+ import_logger2.logger.log(
444
+ "warn",
445
+ "setTakeProfits: missing position qty: %s",
446
+ (0, import_data2.toJson)({ symbol, qty, positionQty, takeProfits }, true)
447
+ );
448
+ return false;
449
+ }
450
+ const isLong = direction === "LONG";
451
+ for (const tp of takeProfits) {
452
+ const tpSizeRaw = positionQty * tp.rate;
453
+ const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
454
+ tpSizeRaw,
455
+ meta
456
+ );
457
+ if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
458
+ import_logger2.logger.log(
459
+ "warn",
460
+ "tp skipped: size too small %s",
461
+ (0, import_data2.toJson)(
462
+ { symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
463
+ true
464
+ )
465
+ );
466
+ continue;
467
+ }
468
+ const tpPriceNorm = normalizePrice(
469
+ tp.price,
470
+ isLong ? "TP_LONG" : "TP_SHORT",
471
+ meta
472
+ );
473
+ const isFullMode = takeProfits.length === 1 && tp.rate === 1;
474
+ const tpRes = await client.setTradingStop({
475
+ category: import_constants2.MARKET_CATEGORY,
476
+ symbol,
477
+ tpSize: isFullMode ? void 0 : tpSizeStr,
478
+ tpslMode: isFullMode ? "Full" : "Partial",
479
+ takeProfit: tpPriceNorm.priceStr,
480
+ tpTriggerBy: "MarkPrice",
481
+ tpOrderType: "Market",
482
+ positionIdx: 0
483
+ });
484
+ if (isTradingStopNotModified(tpRes)) {
485
+ import_logger2.logger.log(
486
+ "debug",
487
+ "tp unchanged: %s %s price=%s rate=%s",
488
+ symbol,
489
+ direction,
490
+ tpPriceNorm.priceStr,
491
+ tp.rate
492
+ );
493
+ } else if (tpRes.retCode === 0) {
494
+ import_logger2.logger.log(
495
+ "info",
496
+ "tp updated: %s %s price=%s rate=%s",
497
+ symbol,
498
+ direction,
499
+ tpPriceNorm.priceStr,
500
+ tp.rate
501
+ );
502
+ } else {
503
+ import_logger2.logger.log(
504
+ "error",
505
+ "tp failed: %s %s price=%s rate=%s %s",
506
+ symbol,
507
+ direction,
508
+ tpPriceNorm.priceStr,
509
+ tp.rate,
510
+ (0, import_data2.toJson)(tpRes, true)
511
+ );
512
+ }
513
+ if (!isTradingStopAccepted(tpRes)) {
514
+ return false;
515
+ }
516
+ }
517
+ return true;
518
+ };
519
+ const setStopLoss = async ({
520
+ symbol,
521
+ direction,
522
+ stopLossPrice
523
+ }) => {
524
+ const client = await getPrivateClient();
525
+ const marketDataClient = await getPublicClient();
526
+ if (!client || !marketDataClient) {
527
+ return false;
528
+ }
529
+ if (typeof stopLossPrice !== "number" || !Number.isFinite(stopLossPrice)) {
530
+ return true;
531
+ }
532
+ const meta = await getSymbolMeta(marketDataClient, symbol);
533
+ const isLong = direction === "LONG";
534
+ const slNormalized = normalizePrice(
535
+ stopLossPrice,
536
+ isLong ? "SL_LONG" : "SL_SHORT",
537
+ meta
538
+ );
539
+ const slRes = await client.setTradingStop({
540
+ category: import_constants2.MARKET_CATEGORY,
541
+ symbol,
542
+ tpslMode: "Full",
543
+ stopLoss: slNormalized.priceStr,
544
+ slTriggerBy: "LastPrice",
545
+ positionIdx: 0
546
+ });
547
+ if (isTradingStopNotModified(slRes)) {
548
+ import_logger2.logger.log(
549
+ "debug",
550
+ "sl unchanged: %s %s stopLoss=%s",
551
+ symbol,
552
+ direction,
553
+ slNormalized.priceStr
554
+ );
555
+ } else if (slRes.retCode === 0) {
556
+ import_logger2.logger.log(
557
+ "info",
558
+ "sl updated: %s %s stopLoss=%s",
559
+ symbol,
560
+ direction,
561
+ slNormalized.priceStr
562
+ );
563
+ } else {
564
+ import_logger2.logger.log(
565
+ "error",
566
+ "sl failed: %s %s stopLoss=%s %s",
567
+ symbol,
568
+ direction,
569
+ slNormalized.priceStr,
570
+ (0, import_data2.toJson)(slRes, true)
571
+ );
572
+ }
573
+ return isTradingStopAccepted(slRes);
574
+ };
330
575
  return {
331
576
  getState: async () => state,
332
577
  setState: async (newState) => {
@@ -338,7 +583,8 @@ var ByBitConnectorCreator = async (config) => {
338
583
  start: defaultStart,
339
584
  end: defaultEnd,
340
585
  silent = false,
341
- cacheOnly = false
586
+ cacheOnly = false,
587
+ warmOnly = false
342
588
  }) => {
343
589
  const intMinutes = intervalToMinutes(interval);
344
590
  if (!intMinutes) {
@@ -414,6 +660,16 @@ var ByBitConnectorCreator = async (config) => {
414
660
  if (!cacheOnly && isRightEdgeQuery) {
415
661
  await refreshTail({ symbol, interval, silent });
416
662
  }
663
+ if (warmOnly) {
664
+ if (isTimescaleFallbackMode) {
665
+ isTimescaleFallbackMode = false;
666
+ import_logger2.logger.log(
667
+ "info",
668
+ "TimescaleDB connection restored for kline cache"
669
+ );
670
+ }
671
+ return [];
672
+ }
417
673
  const rangeStart = defaultStart ?? dataStart ?? 0;
418
674
  const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
419
675
  const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
@@ -439,7 +695,7 @@ var ByBitConnectorCreator = async (config) => {
439
695
  String(error)
440
696
  );
441
697
  }
442
- if (cacheOnly) {
698
+ if (cacheOnly || warmOnly) {
443
699
  return [];
444
700
  }
445
701
  return request({
@@ -451,41 +707,9 @@ var ByBitConnectorCreator = async (config) => {
451
707
  });
452
708
  }
453
709
  },
454
- getPosition: async (symbol) => {
455
- const client = await getClient(config);
456
- if (!client) {
457
- return null;
458
- }
459
- const positionRes = await client.getPositionInfo({
460
- symbol,
461
- category: import_constants2.MARKET_CATEGORY
462
- });
463
- import_logger2.logger.log(
464
- getLogLevel(positionRes),
465
- "position retCode: %s, %s",
466
- symbol,
467
- positionRes.retCode
468
- );
469
- if (positionRes.retCode !== 0) {
470
- return null;
471
- }
472
- const positions = mapPositionData(positionRes.result.list);
473
- if (!positions || import_lodash.default.isEmpty(positions)) {
474
- return null;
475
- }
476
- const position = positions[0];
477
- import_logger2.logger.log(
478
- getLogLevel(positionRes),
479
- "position: %s, %s",
480
- symbol,
481
- (0, import_data2.toJson)(positionRes, true)
482
- );
483
- return {
484
- ...position
485
- };
486
- },
710
+ getPosition: async (symbol) => getPositionSnapshot(symbol),
487
711
  getPositions: async () => {
488
- const client = await getClient(config);
712
+ const client = await getPrivateClient();
489
713
  if (!client) {
490
714
  return [];
491
715
  }
@@ -493,12 +717,12 @@ var ByBitConnectorCreator = async (config) => {
493
717
  category: import_constants2.MARKET_CATEGORY,
494
718
  settleCoin: "USDT"
495
719
  });
496
- import_logger2.logger.log(
497
- getLogLevel(positionRes),
498
- "positions retCode: %s, %s",
499
- positionRes.retCode
500
- );
501
720
  if (positionRes.retCode !== 0) {
721
+ import_logger2.logger.log(
722
+ getLogLevel(positionRes),
723
+ "positions retCode: %s, %s",
724
+ positionRes.retCode
725
+ );
502
726
  return [];
503
727
  }
504
728
  const positions = mapPositionData(positionRes.result.list);
@@ -507,13 +731,14 @@ var ByBitConnectorCreator = async (config) => {
507
731
  }
508
732
  return positions;
509
733
  },
510
- placeOrder: async ({ symbol, price, qty, direction, isLimit }, TP = [], slPrice) => {
511
- const client = await getClient(config);
512
- if (!client) {
734
+ placeOrder: async ({ symbol, price, qty, direction, isLimit }) => {
735
+ const client = await getPrivateClient();
736
+ const marketDataClient = await getPublicClient();
737
+ if (!client || !marketDataClient) {
513
738
  return false;
514
739
  }
515
740
  const isLong = direction === "LONG";
516
- const meta = await getSymbolMeta(client, symbol);
741
+ const meta = await getSymbolMeta(marketDataClient, symbol);
517
742
  const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
518
743
  if (orderQty < meta.minOrderQty) {
519
744
  import_logger2.logger.log(
@@ -527,9 +752,6 @@ var ByBitConnectorCreator = async (config) => {
527
752
  return false;
528
753
  }
529
754
  const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
530
- const slNormalized = slPrice ? normalizePrice(slPrice, isLong ? "SL_LONG" : "SL_SHORT", meta) : void 0;
531
- const firstTP = TP?.[0];
532
- const tpNormalized = firstTP && firstTP.rate === 1 ? normalizePrice(firstTP.price, isLong ? "TP_LONG" : "TP_SHORT", meta) : void 0;
533
755
  import_logger2.logger.log(
534
756
  "info",
535
757
  "placeOrder: %s",
@@ -540,10 +762,7 @@ var ByBitConnectorCreator = async (config) => {
540
762
  qty,
541
763
  direction,
542
764
  orderQty,
543
- orderQtyStr,
544
- slPrice,
545
- slPriceNorm: slNormalized?.priceStr,
546
- TP
765
+ orderQtyStr
547
766
  },
548
767
  true
549
768
  )
@@ -558,10 +777,6 @@ var ByBitConnectorCreator = async (config) => {
558
777
  category: import_constants2.MARKET_CATEGORY,
559
778
  symbol,
560
779
  price: entryNormalized?.priceStr || void 0,
561
- takeProfit: tpNormalized?.priceStr || void 0,
562
- tpTriggerBy: "MarkPrice",
563
- stopLoss: slNormalized?.priceStr || void 0,
564
- slTriggerBy: "LastPrice",
565
780
  side: isLong ? "Buy" : "Sell",
566
781
  orderType: isLimit ? "Limit" : "Market",
567
782
  qty: orderQtyStr,
@@ -575,53 +790,12 @@ var ByBitConnectorCreator = async (config) => {
575
790
  if (orderRes.retCode !== 0) {
576
791
  return false;
577
792
  }
578
- if (!isLimit) {
579
- for (const tp of TP) {
580
- const tpSizeRaw = orderQty * tp.rate;
581
- const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
582
- tpSizeRaw,
583
- meta
584
- );
585
- if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
586
- import_logger2.logger.log(
587
- "warn",
588
- "tp skipped: size too small %s",
589
- (0, import_data2.toJson)(
590
- { symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
591
- true
592
- )
593
- );
594
- continue;
595
- }
596
- const tpPriceNorm = normalizePrice(
597
- tp.price,
598
- isLong ? "TP_LONG" : "TP_SHORT",
599
- meta
600
- );
601
- const isFullMode = TP.length === 1 && tp.rate === 1;
602
- const tpRes = await client.setTradingStop({
603
- category: import_constants2.MARKET_CATEGORY,
604
- symbol,
605
- tpSize: isFullMode ? void 0 : tpSizeStr,
606
- tpslMode: isFullMode ? "Full" : "Partial",
607
- takeProfit: tpPriceNorm.priceStr,
608
- stopLoss: isFullMode && slNormalized ? slNormalized.priceStr : void 0,
609
- slTriggerBy: "LastPrice",
610
- tpOrderType: "Market",
611
- positionIdx: 0
612
- });
613
- import_logger2.logger.log(
614
- getLogLevel(tpRes),
615
- "tp: %s %s",
616
- (0, import_data2.toJson)(tp, true),
617
- (0, import_data2.toJson)(tpRes, true)
618
- );
619
- }
620
- }
621
793
  return true;
622
794
  },
795
+ setTakeProfits,
796
+ setStopLoss,
623
797
  closePosition: async ({ symbol, direction }) => {
624
- const client = await getClient(config);
798
+ const client = await getPrivateClient();
625
799
  if (!client) {
626
800
  return false;
627
801
  }
@@ -646,7 +820,7 @@ var ByBitConnectorCreator = async (config) => {
646
820
  return true;
647
821
  },
648
822
  getTickers: async () => {
649
- const client = await getClient(config);
823
+ const client = await getPublicClient();
650
824
  if (!client) {
651
825
  return [];
652
826
  }
@@ -747,6 +921,8 @@ var BinanceConnectorCreator = async () => {
747
921
  getPosition: async () => null,
748
922
  getPositions: async () => [],
749
923
  placeOrder: async () => false,
924
+ setTakeProfits: async () => false,
925
+ setStopLoss: async () => false,
750
926
  closePosition: async () => false,
751
927
  getTickers: async () => {
752
928
  const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
@@ -906,6 +1082,8 @@ var CoinbaseConnectorCreator = async () => {
906
1082
  getPosition: async () => null,
907
1083
  getPositions: async () => [],
908
1084
  placeOrder: async () => false,
1085
+ setTakeProfits: async () => false,
1086
+ setStopLoss: async () => false,
909
1087
  closePosition: async () => false,
910
1088
  getTickers: async () => {
911
1089
  const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
@@ -1117,7 +1295,7 @@ var binanceCoinbaseSpreadProvider = {
1117
1295
  };
1118
1296
 
1119
1297
  // src/marketData/providers/coinalyze.ts
1120
- var import_async = require("@tradejs/core/async");
1298
+ var import_async2 = require("@tradejs/core/async");
1121
1299
  var import_indicators2 = require("@tradejs/core/indicators");
1122
1300
  var coinalyzeIntervalMap = {
1123
1301
  "15m": "15min",
@@ -1168,12 +1346,8 @@ var normalizeMetricPayload = (metric, raw) => {
1168
1346
  }));
1169
1347
  };
1170
1348
  var fetchCoinalyzeSeries = async (params) => {
1171
- const { endpoint, metric, symbol, interval, fromMs, toMs } = params;
1349
+ const { endpoint, metric, symbol, apiKey, interval, fromMs, toMs } = params;
1172
1350
  const baseUrl = process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
1173
- const apiKey = process.env.COINALYZE_API_KEY?.trim();
1174
- if (!apiKey) {
1175
- throw new Error("Missing COINALYZE_API_KEY");
1176
- }
1177
1351
  const url = new URL(`${baseUrl}${endpoint}`);
1178
1352
  url.searchParams.set("symbols", symbol);
1179
1353
  url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
@@ -1191,7 +1365,7 @@ var fetchCoinalyzeSeries = async (params) => {
1191
1365
  lastRequestTs + coinalyzeMinRequestDelayMs - now
1192
1366
  );
1193
1367
  if (waitMs > 0) {
1194
- await (0, import_async.delay)(waitMs);
1368
+ await (0, import_async2.delay)(waitMs);
1195
1369
  }
1196
1370
  lastRequestTs = Date.now();
1197
1371
  const response = await fetch(url.toString(), { headers });
@@ -1205,7 +1379,7 @@ var fetchCoinalyzeSeries = async (params) => {
1205
1379
  const transient = response.status === 429 || response.status >= 500;
1206
1380
  if (attempt < coinalyzeMaxRetries && transient) {
1207
1381
  const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
1208
- await (0, import_async.delay)(retryAfterMs ?? backoffMs);
1382
+ await (0, import_async2.delay)(retryAfterMs ?? backoffMs);
1209
1383
  continue;
1210
1384
  }
1211
1385
  throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
@@ -1214,15 +1388,27 @@ var fetchCoinalyzeSeries = async (params) => {
1214
1388
  };
1215
1389
  var coinalyzeProvider = {
1216
1390
  name: "coinalyze",
1217
- fetchWindow: async ({ symbol, marketSymbol, interval, fromMs, toMs }) => {
1391
+ fetchWindow: async ({
1392
+ symbol,
1393
+ marketSymbol,
1394
+ apiKey,
1395
+ interval,
1396
+ fromMs,
1397
+ toMs
1398
+ }) => {
1399
+ if (!apiKey?.trim()) {
1400
+ throw new Error("Missing COINALYZE_API_KEY in user settings");
1401
+ }
1218
1402
  const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
1219
1403
  const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
1220
1404
  const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
1221
1405
  const requestSymbol = (marketSymbol || symbol).trim().toUpperCase();
1406
+ const normalizedApiKey = apiKey.trim();
1222
1407
  const oiRaw = await fetchCoinalyzeSeries({
1223
1408
  endpoint: oiPath,
1224
1409
  metric: "oi",
1225
1410
  symbol: requestSymbol,
1411
+ apiKey: normalizedApiKey,
1226
1412
  interval,
1227
1413
  fromMs,
1228
1414
  toMs
@@ -1231,6 +1417,7 @@ var coinalyzeProvider = {
1231
1417
  endpoint: fundingPath,
1232
1418
  metric: "funding",
1233
1419
  symbol: requestSymbol,
1420
+ apiKey: normalizedApiKey,
1234
1421
  interval,
1235
1422
  fromMs,
1236
1423
  toMs
@@ -1239,6 +1426,7 @@ var coinalyzeProvider = {
1239
1426
  endpoint: liqPath,
1240
1427
  metric: "liq",
1241
1428
  symbol: requestSymbol,
1429
+ apiKey: normalizedApiKey,
1242
1430
  interval,
1243
1431
  fromMs,
1244
1432
  toMs
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/ByBit/index.ts
2
2
  import _ from "lodash";
3
3
  import chalk from "chalk";
4
+ import { delay } from "@tradejs/core/async";
4
5
  import {
5
6
  MARKET_CATEGORY as MARKET_CATEGORY2,
6
7
  PRELOAD_FALLBACK_DAYS
@@ -23,7 +24,13 @@ import { RestClientV5 } from "bybit-api";
23
24
  import { logger } from "@tradejs/infra/logger";
24
25
  import { getData, redisKeys } from "@tradejs/infra/redis";
25
26
  var useTestnet = false;
26
- var getClient = async ({ userName }) => {
27
+ var getClient = async ({ userName }, access = "private") => {
28
+ if (access === "public") {
29
+ return new RestClientV5({
30
+ parseAPIRateLimits: true,
31
+ testnet: useTestnet
32
+ });
33
+ }
27
34
  const user = await getData(redisKeys.user(userName));
28
35
  if (!user) {
29
36
  logger.log("error", "connection config not found: %s", userName);
@@ -32,6 +39,7 @@ var getClient = async ({ userName }) => {
32
39
  const client = new RestClientV5({
33
40
  key: user.BYBIT_API_KEY,
34
41
  secret: user.BYBIT_API_SECRET,
42
+ parseAPIRateLimits: true,
35
43
  testnet: useTestnet
36
44
  });
37
45
  return client;
@@ -131,6 +139,12 @@ var mapPositionData = (data) => {
131
139
  // src/ByBit/index.ts
132
140
  var LIMIT = 1e3;
133
141
  var CACHE_FALLBACK_WINDOW = 1e3;
142
+ var BYBIT_RATE_LIMIT_RETCODE = 10006;
143
+ var BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE = 34040;
144
+ var KLINE_RATE_LIMIT_MAX_ATTEMPTS = 3;
145
+ var KLINE_RATE_LIMIT_BASE_DELAY_MS = 800;
146
+ var KLINE_RATE_LIMIT_MAX_DELAY_MS = 1e4;
147
+ var KLINE_RATE_LIMIT_RESET_BUFFER_MS = 50;
134
148
  var INTERVAL_TO_MINUTES = {
135
149
  "1": 1,
136
150
  "3": 3,
@@ -147,9 +161,37 @@ var INTERVAL_TO_MINUTES = {
147
161
  M: 43200
148
162
  };
149
163
  var getLogLevel = (res) => res.retCode === 0 ? "info" : "error";
164
+ var isTradingStopNotModified = (res) => res?.retCode === BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE;
165
+ var isTradingStopAccepted = (res) => res?.retCode === 0 || isTradingStopNotModified(res);
166
+ var isKlineRateLimited = (res) => res?.retCode === BYBIT_RATE_LIMIT_RETCODE;
167
+ var resolveKlineRetryDelayMs = (res, attempt) => {
168
+ const resetAtRaw = Number(res?.rateLimitApi?.resetAtTimestamp);
169
+ if (Number.isFinite(resetAtRaw) && resetAtRaw > 0) {
170
+ return Math.max(
171
+ KLINE_RATE_LIMIT_RESET_BUFFER_MS,
172
+ resetAtRaw - Date.now() + KLINE_RATE_LIMIT_RESET_BUFFER_MS
173
+ );
174
+ }
175
+ const jitterMs = Math.round(Math.random() * 250);
176
+ const backoffMs = Math.min(
177
+ KLINE_RATE_LIMIT_MAX_DELAY_MS,
178
+ KLINE_RATE_LIMIT_BASE_DELAY_MS * 2 ** (attempt - 1)
179
+ );
180
+ return backoffMs + jitterMs;
181
+ };
150
182
  var ByBitConnectorCreator = async (config) => {
151
183
  let state = {};
152
184
  let isTimescaleFallbackMode = false;
185
+ let publicClientPromise = null;
186
+ let privateClientPromise = null;
187
+ const getPublicClient = async () => {
188
+ publicClientPromise ??= getClient(config, "public");
189
+ return publicClientPromise;
190
+ };
191
+ const getPrivateClient = async () => {
192
+ privateClientPromise ??= getClient(config, "private");
193
+ return privateClientPromise;
194
+ };
153
195
  const request = async ({
154
196
  symbol,
155
197
  interval,
@@ -163,41 +205,60 @@ var ByBitConnectorCreator = async (config) => {
163
205
  );
164
206
  const normalizedEnd = round(end || Date.now());
165
207
  try {
166
- const client = await getClient(config);
208
+ const client = await getPublicClient();
167
209
  if (!client) return [];
168
210
  if (normalizedEnd <= normalizedStart) {
169
211
  return [];
170
212
  }
171
- const kline = await client.getKline({
172
- category: MARKET_CATEGORY2,
173
- symbol,
174
- interval,
175
- start: normalizedStart,
176
- end: normalizedEnd,
177
- limit: LIMIT
178
- });
179
- if (!kline?.result?.list) {
180
- const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
181
- logger2.log(
182
- "error",
183
- "empty kline.list for %s %s%s",
213
+ for (let attempt = 1; attempt <= KLINE_RATE_LIMIT_MAX_ATTEMPTS; attempt += 1) {
214
+ const kline = await client.getKline({
215
+ category: MARKET_CATEGORY2,
184
216
  symbol,
185
217
  interval,
186
- responseError ? `: ${responseError}` : ""
187
- );
188
- return [];
189
- }
190
- if (!silent) {
191
- logger2.log(
192
- "info",
193
- "%s %s %s %s",
194
- chalk.yellow(formatUnix2(normalizedEnd)),
195
- chalk.cyan(symbol),
196
- chalk.cyan(interval),
197
- chalk.yellow(kline.result.list.length)
198
- );
218
+ start: normalizedStart,
219
+ end: normalizedEnd,
220
+ limit: LIMIT
221
+ });
222
+ if (isKlineRateLimited(kline)) {
223
+ if (attempt < KLINE_RATE_LIMIT_MAX_ATTEMPTS) {
224
+ const waitMs = resolveKlineRetryDelayMs(kline, attempt);
225
+ logger2.log(
226
+ "warn",
227
+ "kline rate limited for %s %s: attempt=%s/%s waitMs=%s",
228
+ symbol,
229
+ interval,
230
+ attempt,
231
+ KLINE_RATE_LIMIT_MAX_ATTEMPTS,
232
+ waitMs
233
+ );
234
+ await delay(waitMs);
235
+ continue;
236
+ }
237
+ }
238
+ if (!kline?.result?.list) {
239
+ const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
240
+ logger2.log(
241
+ "error",
242
+ "empty kline.list for %s %s%s",
243
+ symbol,
244
+ interval,
245
+ responseError ? `: ${responseError}` : ""
246
+ );
247
+ return [];
248
+ }
249
+ if (!silent) {
250
+ logger2.log(
251
+ "info",
252
+ "%s %s %s %s",
253
+ chalk.yellow(formatUnix2(normalizedEnd)),
254
+ chalk.cyan(symbol),
255
+ chalk.cyan(interval),
256
+ chalk.yellow(kline.result.list.length)
257
+ );
258
+ }
259
+ return mapKlineToChartData(kline.result.list.reverse());
199
260
  }
200
- return mapKlineToChartData(kline.result.list.reverse());
261
+ return [];
201
262
  } catch (error) {
202
263
  logger2.log(
203
264
  "error",
@@ -289,6 +350,190 @@ var ByBitConnectorCreator = async (config) => {
289
350
  await upsertCandles(toRows(symbol, intMinutes, part));
290
351
  }
291
352
  };
353
+ const getPositionSnapshot = async (symbol) => {
354
+ const client = await getPrivateClient();
355
+ if (!client) {
356
+ return null;
357
+ }
358
+ const positionRes = await client.getPositionInfo({
359
+ symbol,
360
+ category: MARKET_CATEGORY2
361
+ });
362
+ if (positionRes.retCode !== 0) {
363
+ logger2.log(
364
+ getLogLevel(positionRes),
365
+ "position retCode: %s, %s",
366
+ symbol,
367
+ positionRes.retCode
368
+ );
369
+ return null;
370
+ }
371
+ const positions = mapPositionData(positionRes.result.list);
372
+ if (!positions || _.isEmpty(positions)) {
373
+ return null;
374
+ }
375
+ const position = positions[0];
376
+ logger2.log(
377
+ "debug",
378
+ "position: %s %s qty=%s price=%s",
379
+ position.symbol,
380
+ position.direction,
381
+ position.qty,
382
+ position.price
383
+ );
384
+ return {
385
+ ...position
386
+ };
387
+ };
388
+ const setTakeProfits = async ({
389
+ symbol,
390
+ direction,
391
+ qty,
392
+ takeProfits
393
+ }) => {
394
+ const client = await getPrivateClient();
395
+ const marketDataClient = await getPublicClient();
396
+ if (!client || !marketDataClient) {
397
+ return false;
398
+ }
399
+ if (!Array.isArray(takeProfits) || takeProfits.length === 0) {
400
+ return true;
401
+ }
402
+ const meta = await getSymbolMeta(marketDataClient, symbol);
403
+ const positionQty = qty ?? (await getPositionSnapshot(symbol))?.qty ?? 0;
404
+ if (!Number.isFinite(positionQty) || positionQty <= 0) {
405
+ logger2.log(
406
+ "warn",
407
+ "setTakeProfits: missing position qty: %s",
408
+ toJson({ symbol, qty, positionQty, takeProfits }, true)
409
+ );
410
+ return false;
411
+ }
412
+ const isLong = direction === "LONG";
413
+ for (const tp of takeProfits) {
414
+ const tpSizeRaw = positionQty * tp.rate;
415
+ const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
416
+ tpSizeRaw,
417
+ meta
418
+ );
419
+ if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
420
+ logger2.log(
421
+ "warn",
422
+ "tp skipped: size too small %s",
423
+ toJson(
424
+ { symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
425
+ true
426
+ )
427
+ );
428
+ continue;
429
+ }
430
+ const tpPriceNorm = normalizePrice(
431
+ tp.price,
432
+ isLong ? "TP_LONG" : "TP_SHORT",
433
+ meta
434
+ );
435
+ const isFullMode = takeProfits.length === 1 && tp.rate === 1;
436
+ const tpRes = await client.setTradingStop({
437
+ category: MARKET_CATEGORY2,
438
+ symbol,
439
+ tpSize: isFullMode ? void 0 : tpSizeStr,
440
+ tpslMode: isFullMode ? "Full" : "Partial",
441
+ takeProfit: tpPriceNorm.priceStr,
442
+ tpTriggerBy: "MarkPrice",
443
+ tpOrderType: "Market",
444
+ positionIdx: 0
445
+ });
446
+ if (isTradingStopNotModified(tpRes)) {
447
+ logger2.log(
448
+ "debug",
449
+ "tp unchanged: %s %s price=%s rate=%s",
450
+ symbol,
451
+ direction,
452
+ tpPriceNorm.priceStr,
453
+ tp.rate
454
+ );
455
+ } else if (tpRes.retCode === 0) {
456
+ logger2.log(
457
+ "info",
458
+ "tp updated: %s %s price=%s rate=%s",
459
+ symbol,
460
+ direction,
461
+ tpPriceNorm.priceStr,
462
+ tp.rate
463
+ );
464
+ } else {
465
+ logger2.log(
466
+ "error",
467
+ "tp failed: %s %s price=%s rate=%s %s",
468
+ symbol,
469
+ direction,
470
+ tpPriceNorm.priceStr,
471
+ tp.rate,
472
+ toJson(tpRes, true)
473
+ );
474
+ }
475
+ if (!isTradingStopAccepted(tpRes)) {
476
+ return false;
477
+ }
478
+ }
479
+ return true;
480
+ };
481
+ const setStopLoss = async ({
482
+ symbol,
483
+ direction,
484
+ stopLossPrice
485
+ }) => {
486
+ const client = await getPrivateClient();
487
+ const marketDataClient = await getPublicClient();
488
+ if (!client || !marketDataClient) {
489
+ return false;
490
+ }
491
+ if (typeof stopLossPrice !== "number" || !Number.isFinite(stopLossPrice)) {
492
+ return true;
493
+ }
494
+ const meta = await getSymbolMeta(marketDataClient, symbol);
495
+ const isLong = direction === "LONG";
496
+ const slNormalized = normalizePrice(
497
+ stopLossPrice,
498
+ isLong ? "SL_LONG" : "SL_SHORT",
499
+ meta
500
+ );
501
+ const slRes = await client.setTradingStop({
502
+ category: MARKET_CATEGORY2,
503
+ symbol,
504
+ tpslMode: "Full",
505
+ stopLoss: slNormalized.priceStr,
506
+ slTriggerBy: "LastPrice",
507
+ positionIdx: 0
508
+ });
509
+ if (isTradingStopNotModified(slRes)) {
510
+ logger2.log(
511
+ "debug",
512
+ "sl unchanged: %s %s stopLoss=%s",
513
+ symbol,
514
+ direction,
515
+ slNormalized.priceStr
516
+ );
517
+ } else if (slRes.retCode === 0) {
518
+ logger2.log(
519
+ "info",
520
+ "sl updated: %s %s stopLoss=%s",
521
+ symbol,
522
+ direction,
523
+ slNormalized.priceStr
524
+ );
525
+ } else {
526
+ logger2.log(
527
+ "error",
528
+ "sl failed: %s %s stopLoss=%s %s",
529
+ symbol,
530
+ direction,
531
+ slNormalized.priceStr,
532
+ toJson(slRes, true)
533
+ );
534
+ }
535
+ return isTradingStopAccepted(slRes);
536
+ };
292
537
  return {
293
538
  getState: async () => state,
294
539
  setState: async (newState) => {
@@ -300,7 +545,8 @@ var ByBitConnectorCreator = async (config) => {
300
545
  start: defaultStart,
301
546
  end: defaultEnd,
302
547
  silent = false,
303
- cacheOnly = false
548
+ cacheOnly = false,
549
+ warmOnly = false
304
550
  }) => {
305
551
  const intMinutes = intervalToMinutes(interval);
306
552
  if (!intMinutes) {
@@ -376,6 +622,16 @@ var ByBitConnectorCreator = async (config) => {
376
622
  if (!cacheOnly && isRightEdgeQuery) {
377
623
  await refreshTail({ symbol, interval, silent });
378
624
  }
625
+ if (warmOnly) {
626
+ if (isTimescaleFallbackMode) {
627
+ isTimescaleFallbackMode = false;
628
+ logger2.log(
629
+ "info",
630
+ "TimescaleDB connection restored for kline cache"
631
+ );
632
+ }
633
+ return [];
634
+ }
379
635
  const rangeStart = defaultStart ?? dataStart ?? 0;
380
636
  const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
381
637
  const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
@@ -401,7 +657,7 @@ var ByBitConnectorCreator = async (config) => {
401
657
  String(error)
402
658
  );
403
659
  }
404
- if (cacheOnly) {
660
+ if (cacheOnly || warmOnly) {
405
661
  return [];
406
662
  }
407
663
  return request({
@@ -413,41 +669,9 @@ var ByBitConnectorCreator = async (config) => {
413
669
  });
414
670
  }
415
671
  },
416
- getPosition: async (symbol) => {
417
- const client = await getClient(config);
418
- if (!client) {
419
- return null;
420
- }
421
- const positionRes = await client.getPositionInfo({
422
- symbol,
423
- category: MARKET_CATEGORY2
424
- });
425
- logger2.log(
426
- getLogLevel(positionRes),
427
- "position retCode: %s, %s",
428
- symbol,
429
- positionRes.retCode
430
- );
431
- if (positionRes.retCode !== 0) {
432
- return null;
433
- }
434
- const positions = mapPositionData(positionRes.result.list);
435
- if (!positions || _.isEmpty(positions)) {
436
- return null;
437
- }
438
- const position = positions[0];
439
- logger2.log(
440
- getLogLevel(positionRes),
441
- "position: %s, %s",
442
- symbol,
443
- toJson(positionRes, true)
444
- );
445
- return {
446
- ...position
447
- };
448
- },
672
+ getPosition: async (symbol) => getPositionSnapshot(symbol),
449
673
  getPositions: async () => {
450
- const client = await getClient(config);
674
+ const client = await getPrivateClient();
451
675
  if (!client) {
452
676
  return [];
453
677
  }
@@ -455,12 +679,12 @@ var ByBitConnectorCreator = async (config) => {
455
679
  category: MARKET_CATEGORY2,
456
680
  settleCoin: "USDT"
457
681
  });
458
- logger2.log(
459
- getLogLevel(positionRes),
460
- "positions retCode: %s, %s",
461
- positionRes.retCode
462
- );
463
682
  if (positionRes.retCode !== 0) {
683
+ logger2.log(
684
+ getLogLevel(positionRes),
685
+ "positions retCode: %s, %s",
686
+ positionRes.retCode
687
+ );
464
688
  return [];
465
689
  }
466
690
  const positions = mapPositionData(positionRes.result.list);
@@ -469,13 +693,14 @@ var ByBitConnectorCreator = async (config) => {
469
693
  }
470
694
  return positions;
471
695
  },
472
- placeOrder: async ({ symbol, price, qty, direction, isLimit }, TP = [], slPrice) => {
473
- const client = await getClient(config);
474
- if (!client) {
696
+ placeOrder: async ({ symbol, price, qty, direction, isLimit }) => {
697
+ const client = await getPrivateClient();
698
+ const marketDataClient = await getPublicClient();
699
+ if (!client || !marketDataClient) {
475
700
  return false;
476
701
  }
477
702
  const isLong = direction === "LONG";
478
- const meta = await getSymbolMeta(client, symbol);
703
+ const meta = await getSymbolMeta(marketDataClient, symbol);
479
704
  const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
480
705
  if (orderQty < meta.minOrderQty) {
481
706
  logger2.log(
@@ -489,9 +714,6 @@ var ByBitConnectorCreator = async (config) => {
489
714
  return false;
490
715
  }
491
716
  const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
492
- const slNormalized = slPrice ? normalizePrice(slPrice, isLong ? "SL_LONG" : "SL_SHORT", meta) : void 0;
493
- const firstTP = TP?.[0];
494
- const tpNormalized = firstTP && firstTP.rate === 1 ? normalizePrice(firstTP.price, isLong ? "TP_LONG" : "TP_SHORT", meta) : void 0;
495
717
  logger2.log(
496
718
  "info",
497
719
  "placeOrder: %s",
@@ -502,10 +724,7 @@ var ByBitConnectorCreator = async (config) => {
502
724
  qty,
503
725
  direction,
504
726
  orderQty,
505
- orderQtyStr,
506
- slPrice,
507
- slPriceNorm: slNormalized?.priceStr,
508
- TP
727
+ orderQtyStr
509
728
  },
510
729
  true
511
730
  )
@@ -520,10 +739,6 @@ var ByBitConnectorCreator = async (config) => {
520
739
  category: MARKET_CATEGORY2,
521
740
  symbol,
522
741
  price: entryNormalized?.priceStr || void 0,
523
- takeProfit: tpNormalized?.priceStr || void 0,
524
- tpTriggerBy: "MarkPrice",
525
- stopLoss: slNormalized?.priceStr || void 0,
526
- slTriggerBy: "LastPrice",
527
742
  side: isLong ? "Buy" : "Sell",
528
743
  orderType: isLimit ? "Limit" : "Market",
529
744
  qty: orderQtyStr,
@@ -537,53 +752,12 @@ var ByBitConnectorCreator = async (config) => {
537
752
  if (orderRes.retCode !== 0) {
538
753
  return false;
539
754
  }
540
- if (!isLimit) {
541
- for (const tp of TP) {
542
- const tpSizeRaw = orderQty * tp.rate;
543
- const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
544
- tpSizeRaw,
545
- meta
546
- );
547
- if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
548
- logger2.log(
549
- "warn",
550
- "tp skipped: size too small %s",
551
- toJson(
552
- { symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
553
- true
554
- )
555
- );
556
- continue;
557
- }
558
- const tpPriceNorm = normalizePrice(
559
- tp.price,
560
- isLong ? "TP_LONG" : "TP_SHORT",
561
- meta
562
- );
563
- const isFullMode = TP.length === 1 && tp.rate === 1;
564
- const tpRes = await client.setTradingStop({
565
- category: MARKET_CATEGORY2,
566
- symbol,
567
- tpSize: isFullMode ? void 0 : tpSizeStr,
568
- tpslMode: isFullMode ? "Full" : "Partial",
569
- takeProfit: tpPriceNorm.priceStr,
570
- stopLoss: isFullMode && slNormalized ? slNormalized.priceStr : void 0,
571
- slTriggerBy: "LastPrice",
572
- tpOrderType: "Market",
573
- positionIdx: 0
574
- });
575
- logger2.log(
576
- getLogLevel(tpRes),
577
- "tp: %s %s",
578
- toJson(tp, true),
579
- toJson(tpRes, true)
580
- );
581
- }
582
- }
583
755
  return true;
584
756
  },
757
+ setTakeProfits,
758
+ setStopLoss,
585
759
  closePosition: async ({ symbol, direction }) => {
586
- const client = await getClient(config);
760
+ const client = await getPrivateClient();
587
761
  if (!client) {
588
762
  return false;
589
763
  }
@@ -608,7 +782,7 @@ var ByBitConnectorCreator = async (config) => {
608
782
  return true;
609
783
  },
610
784
  getTickers: async () => {
611
- const client = await getClient(config);
785
+ const client = await getPublicClient();
612
786
  if (!client) {
613
787
  return [];
614
788
  }
@@ -709,6 +883,8 @@ var BinanceConnectorCreator = async () => {
709
883
  getPosition: async () => null,
710
884
  getPositions: async () => [],
711
885
  placeOrder: async () => false,
886
+ setTakeProfits: async () => false,
887
+ setStopLoss: async () => false,
712
888
  closePosition: async () => false,
713
889
  getTickers: async () => {
714
890
  const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
@@ -868,6 +1044,8 @@ var CoinbaseConnectorCreator = async () => {
868
1044
  getPosition: async () => null,
869
1045
  getPositions: async () => [],
870
1046
  placeOrder: async () => false,
1047
+ setTakeProfits: async () => false,
1048
+ setStopLoss: async () => false,
871
1049
  closePosition: async () => false,
872
1050
  getTickers: async () => {
873
1051
  const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
@@ -1083,7 +1261,7 @@ var binanceCoinbaseSpreadProvider = {
1083
1261
  };
1084
1262
 
1085
1263
  // src/marketData/providers/coinalyze.ts
1086
- import { delay } from "@tradejs/core/async";
1264
+ import { delay as delay2 } from "@tradejs/core/async";
1087
1265
  import {
1088
1266
  coinalyzePointsToRows,
1089
1267
  mergeCoinalyzeMetrics
@@ -1137,12 +1315,8 @@ var normalizeMetricPayload = (metric, raw) => {
1137
1315
  }));
1138
1316
  };
1139
1317
  var fetchCoinalyzeSeries = async (params) => {
1140
- const { endpoint, metric, symbol, interval, fromMs, toMs } = params;
1318
+ const { endpoint, metric, symbol, apiKey, interval, fromMs, toMs } = params;
1141
1319
  const baseUrl = process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
1142
- const apiKey = process.env.COINALYZE_API_KEY?.trim();
1143
- if (!apiKey) {
1144
- throw new Error("Missing COINALYZE_API_KEY");
1145
- }
1146
1320
  const url = new URL(`${baseUrl}${endpoint}`);
1147
1321
  url.searchParams.set("symbols", symbol);
1148
1322
  url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
@@ -1160,7 +1334,7 @@ var fetchCoinalyzeSeries = async (params) => {
1160
1334
  lastRequestTs + coinalyzeMinRequestDelayMs - now
1161
1335
  );
1162
1336
  if (waitMs > 0) {
1163
- await delay(waitMs);
1337
+ await delay2(waitMs);
1164
1338
  }
1165
1339
  lastRequestTs = Date.now();
1166
1340
  const response = await fetch(url.toString(), { headers });
@@ -1174,7 +1348,7 @@ var fetchCoinalyzeSeries = async (params) => {
1174
1348
  const transient = response.status === 429 || response.status >= 500;
1175
1349
  if (attempt < coinalyzeMaxRetries && transient) {
1176
1350
  const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
1177
- await delay(retryAfterMs ?? backoffMs);
1351
+ await delay2(retryAfterMs ?? backoffMs);
1178
1352
  continue;
1179
1353
  }
1180
1354
  throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
@@ -1183,15 +1357,27 @@ var fetchCoinalyzeSeries = async (params) => {
1183
1357
  };
1184
1358
  var coinalyzeProvider = {
1185
1359
  name: "coinalyze",
1186
- fetchWindow: async ({ symbol, marketSymbol, interval, fromMs, toMs }) => {
1360
+ fetchWindow: async ({
1361
+ symbol,
1362
+ marketSymbol,
1363
+ apiKey,
1364
+ interval,
1365
+ fromMs,
1366
+ toMs
1367
+ }) => {
1368
+ if (!apiKey?.trim()) {
1369
+ throw new Error("Missing COINALYZE_API_KEY in user settings");
1370
+ }
1187
1371
  const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
1188
1372
  const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
1189
1373
  const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
1190
1374
  const requestSymbol = (marketSymbol || symbol).trim().toUpperCase();
1375
+ const normalizedApiKey = apiKey.trim();
1191
1376
  const oiRaw = await fetchCoinalyzeSeries({
1192
1377
  endpoint: oiPath,
1193
1378
  metric: "oi",
1194
1379
  symbol: requestSymbol,
1380
+ apiKey: normalizedApiKey,
1195
1381
  interval,
1196
1382
  fromMs,
1197
1383
  toMs
@@ -1200,6 +1386,7 @@ var coinalyzeProvider = {
1200
1386
  endpoint: fundingPath,
1201
1387
  metric: "funding",
1202
1388
  symbol: requestSymbol,
1389
+ apiKey: normalizedApiKey,
1203
1390
  interval,
1204
1391
  fromMs,
1205
1392
  toMs
@@ -1208,6 +1395,7 @@ var coinalyzeProvider = {
1208
1395
  endpoint: liqPath,
1209
1396
  metric: "liq",
1210
1397
  symbol: requestSymbol,
1398
+ apiKey: normalizedApiKey,
1211
1399
  interval,
1212
1400
  fromMs,
1213
1401
  toMs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/connectors",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Built-in exchange connectors and market data providers for the TradeJS open-source framework.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -33,10 +33,10 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@tradejs/core": "^1.0.5",
37
- "@tradejs/infra": "^1.0.5",
38
- "@tradejs/node": "^1.0.5",
39
- "@tradejs/types": "^1.0.5",
36
+ "@tradejs/core": "^1.0.6",
37
+ "@tradejs/infra": "^1.0.6",
38
+ "@tradejs/node": "^1.0.6",
39
+ "@tradejs/types": "^1.0.6",
40
40
  "bybit-api": "^3.9.0",
41
41
  "chalk": "4.1.2",
42
42
  "lodash": "^4.17.21"