@tradejs/connectors 1.0.4 → 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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @tradejs/connectors
2
2
 
3
- Built-in TradeJS connector package.
3
+ Built-in connector package for the TradeJS open-source framework.
4
4
 
5
5
  - Homepage: https://tradejs.dev
6
6
  - Documentation: https://docs.tradejs.dev
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,34 +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
- import_logger2.logger.log("error", "empty kline.list for %s %s", symbol, interval);
219
- return [];
220
- }
221
- if (!silent) {
222
- import_logger2.logger.log(
223
- "info",
224
- "%s %s %s %s",
225
- import_chalk.default.yellow((0, import_time2.formatUnix)(normalizedEnd)),
226
- import_chalk.default.cyan(symbol),
227
- import_chalk.default.cyan(interval),
228
- import_chalk.default.yellow(kline.result.list.length)
229
- );
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,
254
+ symbol,
255
+ interval,
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());
230
298
  }
231
- return mapKlineToChartData(kline.result.list.reverse());
299
+ return [];
232
300
  } catch (error) {
233
301
  import_logger2.logger.log(
234
302
  "error",
@@ -320,6 +388,190 @@ var ByBitConnectorCreator = async (config) => {
320
388
  await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(symbol, intMinutes, part));
321
389
  }
322
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
+ };
323
575
  return {
324
576
  getState: async () => state,
325
577
  setState: async (newState) => {
@@ -331,7 +583,8 @@ var ByBitConnectorCreator = async (config) => {
331
583
  start: defaultStart,
332
584
  end: defaultEnd,
333
585
  silent = false,
334
- cacheOnly = false
586
+ cacheOnly = false,
587
+ warmOnly = false
335
588
  }) => {
336
589
  const intMinutes = intervalToMinutes(interval);
337
590
  if (!intMinutes) {
@@ -407,6 +660,16 @@ var ByBitConnectorCreator = async (config) => {
407
660
  if (!cacheOnly && isRightEdgeQuery) {
408
661
  await refreshTail({ symbol, interval, silent });
409
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
+ }
410
673
  const rangeStart = defaultStart ?? dataStart ?? 0;
411
674
  const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
412
675
  const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
@@ -432,7 +695,7 @@ var ByBitConnectorCreator = async (config) => {
432
695
  String(error)
433
696
  );
434
697
  }
435
- if (cacheOnly) {
698
+ if (cacheOnly || warmOnly) {
436
699
  return [];
437
700
  }
438
701
  return request({
@@ -444,41 +707,9 @@ var ByBitConnectorCreator = async (config) => {
444
707
  });
445
708
  }
446
709
  },
447
- getPosition: async (symbol) => {
448
- const client = await getClient(config);
449
- if (!client) {
450
- return null;
451
- }
452
- const positionRes = await client.getPositionInfo({
453
- symbol,
454
- category: import_constants2.MARKET_CATEGORY
455
- });
456
- import_logger2.logger.log(
457
- getLogLevel(positionRes),
458
- "position retCode: %s, %s",
459
- symbol,
460
- positionRes.retCode
461
- );
462
- if (positionRes.retCode !== 0) {
463
- return null;
464
- }
465
- const positions = mapPositionData(positionRes.result.list);
466
- if (!positions || import_lodash.default.isEmpty(positions)) {
467
- return null;
468
- }
469
- const position = positions[0];
470
- import_logger2.logger.log(
471
- getLogLevel(positionRes),
472
- "position: %s, %s",
473
- symbol,
474
- (0, import_data2.toJson)(positionRes, true)
475
- );
476
- return {
477
- ...position
478
- };
479
- },
710
+ getPosition: async (symbol) => getPositionSnapshot(symbol),
480
711
  getPositions: async () => {
481
- const client = await getClient(config);
712
+ const client = await getPrivateClient();
482
713
  if (!client) {
483
714
  return [];
484
715
  }
@@ -486,12 +717,12 @@ var ByBitConnectorCreator = async (config) => {
486
717
  category: import_constants2.MARKET_CATEGORY,
487
718
  settleCoin: "USDT"
488
719
  });
489
- import_logger2.logger.log(
490
- getLogLevel(positionRes),
491
- "positions retCode: %s, %s",
492
- positionRes.retCode
493
- );
494
720
  if (positionRes.retCode !== 0) {
721
+ import_logger2.logger.log(
722
+ getLogLevel(positionRes),
723
+ "positions retCode: %s, %s",
724
+ positionRes.retCode
725
+ );
495
726
  return [];
496
727
  }
497
728
  const positions = mapPositionData(positionRes.result.list);
@@ -500,13 +731,14 @@ var ByBitConnectorCreator = async (config) => {
500
731
  }
501
732
  return positions;
502
733
  },
503
- placeOrder: async ({ symbol, price, qty, direction, isLimit }, TP = [], slPrice) => {
504
- const client = await getClient(config);
505
- if (!client) {
734
+ placeOrder: async ({ symbol, price, qty, direction, isLimit }) => {
735
+ const client = await getPrivateClient();
736
+ const marketDataClient = await getPublicClient();
737
+ if (!client || !marketDataClient) {
506
738
  return false;
507
739
  }
508
740
  const isLong = direction === "LONG";
509
- const meta = await getSymbolMeta(client, symbol);
741
+ const meta = await getSymbolMeta(marketDataClient, symbol);
510
742
  const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
511
743
  if (orderQty < meta.minOrderQty) {
512
744
  import_logger2.logger.log(
@@ -520,9 +752,6 @@ var ByBitConnectorCreator = async (config) => {
520
752
  return false;
521
753
  }
522
754
  const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
523
- const slNormalized = slPrice ? normalizePrice(slPrice, isLong ? "SL_LONG" : "SL_SHORT", meta) : void 0;
524
- const firstTP = TP?.[0];
525
- const tpNormalized = firstTP && firstTP.rate === 1 ? normalizePrice(firstTP.price, isLong ? "TP_LONG" : "TP_SHORT", meta) : void 0;
526
755
  import_logger2.logger.log(
527
756
  "info",
528
757
  "placeOrder: %s",
@@ -533,10 +762,7 @@ var ByBitConnectorCreator = async (config) => {
533
762
  qty,
534
763
  direction,
535
764
  orderQty,
536
- orderQtyStr,
537
- slPrice,
538
- slPriceNorm: slNormalized?.priceStr,
539
- TP
765
+ orderQtyStr
540
766
  },
541
767
  true
542
768
  )
@@ -551,10 +777,6 @@ var ByBitConnectorCreator = async (config) => {
551
777
  category: import_constants2.MARKET_CATEGORY,
552
778
  symbol,
553
779
  price: entryNormalized?.priceStr || void 0,
554
- takeProfit: tpNormalized?.priceStr || void 0,
555
- tpTriggerBy: "MarkPrice",
556
- stopLoss: slNormalized?.priceStr || void 0,
557
- slTriggerBy: "LastPrice",
558
780
  side: isLong ? "Buy" : "Sell",
559
781
  orderType: isLimit ? "Limit" : "Market",
560
782
  qty: orderQtyStr,
@@ -568,53 +790,12 @@ var ByBitConnectorCreator = async (config) => {
568
790
  if (orderRes.retCode !== 0) {
569
791
  return false;
570
792
  }
571
- if (!isLimit) {
572
- for (const tp of TP) {
573
- const tpSizeRaw = orderQty * tp.rate;
574
- const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
575
- tpSizeRaw,
576
- meta
577
- );
578
- if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
579
- import_logger2.logger.log(
580
- "warn",
581
- "tp skipped: size too small %s",
582
- (0, import_data2.toJson)(
583
- { symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
584
- true
585
- )
586
- );
587
- continue;
588
- }
589
- const tpPriceNorm = normalizePrice(
590
- tp.price,
591
- isLong ? "TP_LONG" : "TP_SHORT",
592
- meta
593
- );
594
- const isFullMode = TP.length === 1 && tp.rate === 1;
595
- const tpRes = await client.setTradingStop({
596
- category: import_constants2.MARKET_CATEGORY,
597
- symbol,
598
- tpSize: isFullMode ? void 0 : tpSizeStr,
599
- tpslMode: isFullMode ? "Full" : "Partial",
600
- takeProfit: tpPriceNorm.priceStr,
601
- stopLoss: isFullMode && slNormalized ? slNormalized.priceStr : void 0,
602
- slTriggerBy: "LastPrice",
603
- tpOrderType: "Market",
604
- positionIdx: 0
605
- });
606
- import_logger2.logger.log(
607
- getLogLevel(tpRes),
608
- "tp: %s %s",
609
- (0, import_data2.toJson)(tp, true),
610
- (0, import_data2.toJson)(tpRes, true)
611
- );
612
- }
613
- }
614
793
  return true;
615
794
  },
795
+ setTakeProfits,
796
+ setStopLoss,
616
797
  closePosition: async ({ symbol, direction }) => {
617
- const client = await getClient(config);
798
+ const client = await getPrivateClient();
618
799
  if (!client) {
619
800
  return false;
620
801
  }
@@ -639,7 +820,7 @@ var ByBitConnectorCreator = async (config) => {
639
820
  return true;
640
821
  },
641
822
  getTickers: async () => {
642
- const client = await getClient(config);
823
+ const client = await getPublicClient();
643
824
  if (!client) {
644
825
  return [];
645
826
  }
@@ -740,6 +921,8 @@ var BinanceConnectorCreator = async () => {
740
921
  getPosition: async () => null,
741
922
  getPositions: async () => [],
742
923
  placeOrder: async () => false,
924
+ setTakeProfits: async () => false,
925
+ setStopLoss: async () => false,
743
926
  closePosition: async () => false,
744
927
  getTickers: async () => {
745
928
  const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
@@ -899,6 +1082,8 @@ var CoinbaseConnectorCreator = async () => {
899
1082
  getPosition: async () => null,
900
1083
  getPositions: async () => [],
901
1084
  placeOrder: async () => false,
1085
+ setTakeProfits: async () => false,
1086
+ setStopLoss: async () => false,
902
1087
  closePosition: async () => false,
903
1088
  getTickers: async () => {
904
1089
  const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
@@ -1110,7 +1295,7 @@ var binanceCoinbaseSpreadProvider = {
1110
1295
  };
1111
1296
 
1112
1297
  // src/marketData/providers/coinalyze.ts
1113
- var import_async = require("@tradejs/core/async");
1298
+ var import_async2 = require("@tradejs/core/async");
1114
1299
  var import_indicators2 = require("@tradejs/core/indicators");
1115
1300
  var coinalyzeIntervalMap = {
1116
1301
  "15m": "15min",
@@ -1161,12 +1346,8 @@ var normalizeMetricPayload = (metric, raw) => {
1161
1346
  }));
1162
1347
  };
1163
1348
  var fetchCoinalyzeSeries = async (params) => {
1164
- const { endpoint, metric, symbol, interval, fromMs, toMs } = params;
1349
+ const { endpoint, metric, symbol, apiKey, interval, fromMs, toMs } = params;
1165
1350
  const baseUrl = process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
1166
- const apiKey = process.env.COINALYZE_API_KEY?.trim();
1167
- if (!apiKey) {
1168
- throw new Error("Missing COINALYZE_API_KEY");
1169
- }
1170
1351
  const url = new URL(`${baseUrl}${endpoint}`);
1171
1352
  url.searchParams.set("symbols", symbol);
1172
1353
  url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
@@ -1184,7 +1365,7 @@ var fetchCoinalyzeSeries = async (params) => {
1184
1365
  lastRequestTs + coinalyzeMinRequestDelayMs - now
1185
1366
  );
1186
1367
  if (waitMs > 0) {
1187
- await (0, import_async.delay)(waitMs);
1368
+ await (0, import_async2.delay)(waitMs);
1188
1369
  }
1189
1370
  lastRequestTs = Date.now();
1190
1371
  const response = await fetch(url.toString(), { headers });
@@ -1198,7 +1379,7 @@ var fetchCoinalyzeSeries = async (params) => {
1198
1379
  const transient = response.status === 429 || response.status >= 500;
1199
1380
  if (attempt < coinalyzeMaxRetries && transient) {
1200
1381
  const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
1201
- await (0, import_async.delay)(retryAfterMs ?? backoffMs);
1382
+ await (0, import_async2.delay)(retryAfterMs ?? backoffMs);
1202
1383
  continue;
1203
1384
  }
1204
1385
  throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
@@ -1207,15 +1388,27 @@ var fetchCoinalyzeSeries = async (params) => {
1207
1388
  };
1208
1389
  var coinalyzeProvider = {
1209
1390
  name: "coinalyze",
1210
- 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
+ }
1211
1402
  const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
1212
1403
  const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
1213
1404
  const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
1214
1405
  const requestSymbol = (marketSymbol || symbol).trim().toUpperCase();
1406
+ const normalizedApiKey = apiKey.trim();
1215
1407
  const oiRaw = await fetchCoinalyzeSeries({
1216
1408
  endpoint: oiPath,
1217
1409
  metric: "oi",
1218
1410
  symbol: requestSymbol,
1411
+ apiKey: normalizedApiKey,
1219
1412
  interval,
1220
1413
  fromMs,
1221
1414
  toMs
@@ -1224,6 +1417,7 @@ var coinalyzeProvider = {
1224
1417
  endpoint: fundingPath,
1225
1418
  metric: "funding",
1226
1419
  symbol: requestSymbol,
1420
+ apiKey: normalizedApiKey,
1227
1421
  interval,
1228
1422
  fromMs,
1229
1423
  toMs
@@ -1232,6 +1426,7 @@ var coinalyzeProvider = {
1232
1426
  endpoint: liqPath,
1233
1427
  metric: "liq",
1234
1428
  symbol: requestSymbol,
1429
+ apiKey: normalizedApiKey,
1235
1430
  interval,
1236
1431
  fromMs,
1237
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,34 +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
- logger2.log("error", "empty kline.list for %s %s", symbol, interval);
181
- return [];
182
- }
183
- if (!silent) {
184
- logger2.log(
185
- "info",
186
- "%s %s %s %s",
187
- chalk.yellow(formatUnix2(normalizedEnd)),
188
- chalk.cyan(symbol),
189
- chalk.cyan(interval),
190
- chalk.yellow(kline.result.list.length)
191
- );
213
+ for (let attempt = 1; attempt <= KLINE_RATE_LIMIT_MAX_ATTEMPTS; attempt += 1) {
214
+ const kline = await client.getKline({
215
+ category: MARKET_CATEGORY2,
216
+ symbol,
217
+ interval,
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());
192
260
  }
193
- return mapKlineToChartData(kline.result.list.reverse());
261
+ return [];
194
262
  } catch (error) {
195
263
  logger2.log(
196
264
  "error",
@@ -282,6 +350,190 @@ var ByBitConnectorCreator = async (config) => {
282
350
  await upsertCandles(toRows(symbol, intMinutes, part));
283
351
  }
284
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
+ };
285
537
  return {
286
538
  getState: async () => state,
287
539
  setState: async (newState) => {
@@ -293,7 +545,8 @@ var ByBitConnectorCreator = async (config) => {
293
545
  start: defaultStart,
294
546
  end: defaultEnd,
295
547
  silent = false,
296
- cacheOnly = false
548
+ cacheOnly = false,
549
+ warmOnly = false
297
550
  }) => {
298
551
  const intMinutes = intervalToMinutes(interval);
299
552
  if (!intMinutes) {
@@ -369,6 +622,16 @@ var ByBitConnectorCreator = async (config) => {
369
622
  if (!cacheOnly && isRightEdgeQuery) {
370
623
  await refreshTail({ symbol, interval, silent });
371
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
+ }
372
635
  const rangeStart = defaultStart ?? dataStart ?? 0;
373
636
  const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
374
637
  const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
@@ -394,7 +657,7 @@ var ByBitConnectorCreator = async (config) => {
394
657
  String(error)
395
658
  );
396
659
  }
397
- if (cacheOnly) {
660
+ if (cacheOnly || warmOnly) {
398
661
  return [];
399
662
  }
400
663
  return request({
@@ -406,41 +669,9 @@ var ByBitConnectorCreator = async (config) => {
406
669
  });
407
670
  }
408
671
  },
409
- getPosition: async (symbol) => {
410
- const client = await getClient(config);
411
- if (!client) {
412
- return null;
413
- }
414
- const positionRes = await client.getPositionInfo({
415
- symbol,
416
- category: MARKET_CATEGORY2
417
- });
418
- logger2.log(
419
- getLogLevel(positionRes),
420
- "position retCode: %s, %s",
421
- symbol,
422
- positionRes.retCode
423
- );
424
- if (positionRes.retCode !== 0) {
425
- return null;
426
- }
427
- const positions = mapPositionData(positionRes.result.list);
428
- if (!positions || _.isEmpty(positions)) {
429
- return null;
430
- }
431
- const position = positions[0];
432
- logger2.log(
433
- getLogLevel(positionRes),
434
- "position: %s, %s",
435
- symbol,
436
- toJson(positionRes, true)
437
- );
438
- return {
439
- ...position
440
- };
441
- },
672
+ getPosition: async (symbol) => getPositionSnapshot(symbol),
442
673
  getPositions: async () => {
443
- const client = await getClient(config);
674
+ const client = await getPrivateClient();
444
675
  if (!client) {
445
676
  return [];
446
677
  }
@@ -448,12 +679,12 @@ var ByBitConnectorCreator = async (config) => {
448
679
  category: MARKET_CATEGORY2,
449
680
  settleCoin: "USDT"
450
681
  });
451
- logger2.log(
452
- getLogLevel(positionRes),
453
- "positions retCode: %s, %s",
454
- positionRes.retCode
455
- );
456
682
  if (positionRes.retCode !== 0) {
683
+ logger2.log(
684
+ getLogLevel(positionRes),
685
+ "positions retCode: %s, %s",
686
+ positionRes.retCode
687
+ );
457
688
  return [];
458
689
  }
459
690
  const positions = mapPositionData(positionRes.result.list);
@@ -462,13 +693,14 @@ var ByBitConnectorCreator = async (config) => {
462
693
  }
463
694
  return positions;
464
695
  },
465
- placeOrder: async ({ symbol, price, qty, direction, isLimit }, TP = [], slPrice) => {
466
- const client = await getClient(config);
467
- if (!client) {
696
+ placeOrder: async ({ symbol, price, qty, direction, isLimit }) => {
697
+ const client = await getPrivateClient();
698
+ const marketDataClient = await getPublicClient();
699
+ if (!client || !marketDataClient) {
468
700
  return false;
469
701
  }
470
702
  const isLong = direction === "LONG";
471
- const meta = await getSymbolMeta(client, symbol);
703
+ const meta = await getSymbolMeta(marketDataClient, symbol);
472
704
  const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
473
705
  if (orderQty < meta.minOrderQty) {
474
706
  logger2.log(
@@ -482,9 +714,6 @@ var ByBitConnectorCreator = async (config) => {
482
714
  return false;
483
715
  }
484
716
  const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
485
- const slNormalized = slPrice ? normalizePrice(slPrice, isLong ? "SL_LONG" : "SL_SHORT", meta) : void 0;
486
- const firstTP = TP?.[0];
487
- const tpNormalized = firstTP && firstTP.rate === 1 ? normalizePrice(firstTP.price, isLong ? "TP_LONG" : "TP_SHORT", meta) : void 0;
488
717
  logger2.log(
489
718
  "info",
490
719
  "placeOrder: %s",
@@ -495,10 +724,7 @@ var ByBitConnectorCreator = async (config) => {
495
724
  qty,
496
725
  direction,
497
726
  orderQty,
498
- orderQtyStr,
499
- slPrice,
500
- slPriceNorm: slNormalized?.priceStr,
501
- TP
727
+ orderQtyStr
502
728
  },
503
729
  true
504
730
  )
@@ -513,10 +739,6 @@ var ByBitConnectorCreator = async (config) => {
513
739
  category: MARKET_CATEGORY2,
514
740
  symbol,
515
741
  price: entryNormalized?.priceStr || void 0,
516
- takeProfit: tpNormalized?.priceStr || void 0,
517
- tpTriggerBy: "MarkPrice",
518
- stopLoss: slNormalized?.priceStr || void 0,
519
- slTriggerBy: "LastPrice",
520
742
  side: isLong ? "Buy" : "Sell",
521
743
  orderType: isLimit ? "Limit" : "Market",
522
744
  qty: orderQtyStr,
@@ -530,53 +752,12 @@ var ByBitConnectorCreator = async (config) => {
530
752
  if (orderRes.retCode !== 0) {
531
753
  return false;
532
754
  }
533
- if (!isLimit) {
534
- for (const tp of TP) {
535
- const tpSizeRaw = orderQty * tp.rate;
536
- const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
537
- tpSizeRaw,
538
- meta
539
- );
540
- if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
541
- logger2.log(
542
- "warn",
543
- "tp skipped: size too small %s",
544
- toJson(
545
- { symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
546
- true
547
- )
548
- );
549
- continue;
550
- }
551
- const tpPriceNorm = normalizePrice(
552
- tp.price,
553
- isLong ? "TP_LONG" : "TP_SHORT",
554
- meta
555
- );
556
- const isFullMode = TP.length === 1 && tp.rate === 1;
557
- const tpRes = await client.setTradingStop({
558
- category: MARKET_CATEGORY2,
559
- symbol,
560
- tpSize: isFullMode ? void 0 : tpSizeStr,
561
- tpslMode: isFullMode ? "Full" : "Partial",
562
- takeProfit: tpPriceNorm.priceStr,
563
- stopLoss: isFullMode && slNormalized ? slNormalized.priceStr : void 0,
564
- slTriggerBy: "LastPrice",
565
- tpOrderType: "Market",
566
- positionIdx: 0
567
- });
568
- logger2.log(
569
- getLogLevel(tpRes),
570
- "tp: %s %s",
571
- toJson(tp, true),
572
- toJson(tpRes, true)
573
- );
574
- }
575
- }
576
755
  return true;
577
756
  },
757
+ setTakeProfits,
758
+ setStopLoss,
578
759
  closePosition: async ({ symbol, direction }) => {
579
- const client = await getClient(config);
760
+ const client = await getPrivateClient();
580
761
  if (!client) {
581
762
  return false;
582
763
  }
@@ -601,7 +782,7 @@ var ByBitConnectorCreator = async (config) => {
601
782
  return true;
602
783
  },
603
784
  getTickers: async () => {
604
- const client = await getClient(config);
785
+ const client = await getPublicClient();
605
786
  if (!client) {
606
787
  return [];
607
788
  }
@@ -702,6 +883,8 @@ var BinanceConnectorCreator = async () => {
702
883
  getPosition: async () => null,
703
884
  getPositions: async () => [],
704
885
  placeOrder: async () => false,
886
+ setTakeProfits: async () => false,
887
+ setStopLoss: async () => false,
705
888
  closePosition: async () => false,
706
889
  getTickers: async () => {
707
890
  const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
@@ -861,6 +1044,8 @@ var CoinbaseConnectorCreator = async () => {
861
1044
  getPosition: async () => null,
862
1045
  getPositions: async () => [],
863
1046
  placeOrder: async () => false,
1047
+ setTakeProfits: async () => false,
1048
+ setStopLoss: async () => false,
864
1049
  closePosition: async () => false,
865
1050
  getTickers: async () => {
866
1051
  const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
@@ -1076,7 +1261,7 @@ var binanceCoinbaseSpreadProvider = {
1076
1261
  };
1077
1262
 
1078
1263
  // src/marketData/providers/coinalyze.ts
1079
- import { delay } from "@tradejs/core/async";
1264
+ import { delay as delay2 } from "@tradejs/core/async";
1080
1265
  import {
1081
1266
  coinalyzePointsToRows,
1082
1267
  mergeCoinalyzeMetrics
@@ -1130,12 +1315,8 @@ var normalizeMetricPayload = (metric, raw) => {
1130
1315
  }));
1131
1316
  };
1132
1317
  var fetchCoinalyzeSeries = async (params) => {
1133
- const { endpoint, metric, symbol, interval, fromMs, toMs } = params;
1318
+ const { endpoint, metric, symbol, apiKey, interval, fromMs, toMs } = params;
1134
1319
  const baseUrl = process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
1135
- const apiKey = process.env.COINALYZE_API_KEY?.trim();
1136
- if (!apiKey) {
1137
- throw new Error("Missing COINALYZE_API_KEY");
1138
- }
1139
1320
  const url = new URL(`${baseUrl}${endpoint}`);
1140
1321
  url.searchParams.set("symbols", symbol);
1141
1322
  url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
@@ -1153,7 +1334,7 @@ var fetchCoinalyzeSeries = async (params) => {
1153
1334
  lastRequestTs + coinalyzeMinRequestDelayMs - now
1154
1335
  );
1155
1336
  if (waitMs > 0) {
1156
- await delay(waitMs);
1337
+ await delay2(waitMs);
1157
1338
  }
1158
1339
  lastRequestTs = Date.now();
1159
1340
  const response = await fetch(url.toString(), { headers });
@@ -1167,7 +1348,7 @@ var fetchCoinalyzeSeries = async (params) => {
1167
1348
  const transient = response.status === 429 || response.status >= 500;
1168
1349
  if (attempt < coinalyzeMaxRetries && transient) {
1169
1350
  const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
1170
- await delay(retryAfterMs ?? backoffMs);
1351
+ await delay2(retryAfterMs ?? backoffMs);
1171
1352
  continue;
1172
1353
  }
1173
1354
  throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
@@ -1176,15 +1357,27 @@ var fetchCoinalyzeSeries = async (params) => {
1176
1357
  };
1177
1358
  var coinalyzeProvider = {
1178
1359
  name: "coinalyze",
1179
- 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
+ }
1180
1371
  const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
1181
1372
  const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
1182
1373
  const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
1183
1374
  const requestSymbol = (marketSymbol || symbol).trim().toUpperCase();
1375
+ const normalizedApiKey = apiKey.trim();
1184
1376
  const oiRaw = await fetchCoinalyzeSeries({
1185
1377
  endpoint: oiPath,
1186
1378
  metric: "oi",
1187
1379
  symbol: requestSymbol,
1380
+ apiKey: normalizedApiKey,
1188
1381
  interval,
1189
1382
  fromMs,
1190
1383
  toMs
@@ -1193,6 +1386,7 @@ var coinalyzeProvider = {
1193
1386
  endpoint: fundingPath,
1194
1387
  metric: "funding",
1195
1388
  symbol: requestSymbol,
1389
+ apiKey: normalizedApiKey,
1196
1390
  interval,
1197
1391
  fromMs,
1198
1392
  toMs
@@ -1201,6 +1395,7 @@ var coinalyzeProvider = {
1201
1395
  endpoint: liqPath,
1202
1396
  metric: "liq",
1203
1397
  symbol: requestSymbol,
1398
+ apiKey: normalizedApiKey,
1204
1399
  interval,
1205
1400
  fromMs,
1206
1401
  toMs
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tradejs/connectors",
3
- "version": "1.0.4",
4
- "description": "Built-in TradeJS exchange connectors and market data providers.",
3
+ "version": "1.0.6",
4
+ "description": "Built-in exchange connectors and market data providers for the TradeJS open-source framework.",
5
5
  "keywords": [
6
6
  "tradejs",
7
7
  "trading",
@@ -11,6 +11,14 @@
11
11
  "market-data",
12
12
  "bybit"
13
13
  ],
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/tradejs-dev/tradejs.git",
17
+ "directory": "packages/connectors"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/tradejs-dev/tradejs/issues"
21
+ },
14
22
  "main": "dist/index.js",
15
23
  "module": "dist/index.mjs",
16
24
  "types": "dist/index.d.ts",
@@ -25,10 +33,10 @@
25
33
  }
26
34
  },
27
35
  "dependencies": {
28
- "@tradejs/core": "^1.0.4",
29
- "@tradejs/infra": "^1.0.4",
30
- "@tradejs/node": "^1.0.4",
31
- "@tradejs/types": "^1.0.4",
36
+ "@tradejs/core": "^1.0.6",
37
+ "@tradejs/infra": "^1.0.6",
38
+ "@tradejs/node": "^1.0.6",
39
+ "@tradejs/types": "^1.0.6",
32
40
  "bybit-api": "^3.9.0",
33
41
  "chalk": "4.1.2",
34
42
  "lodash": "^4.17.21"