@tradejs/connectors 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -32,12 +32,16 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ConnectorNames: () => ConnectorNames,
34
34
  ConnectorProviders: () => ConnectorProviders,
35
+ buildBybitKlineTopic: () => buildBybitKlineTopic,
35
36
  connectorEntries: () => connectorEntries,
36
37
  connectors: () => connectors,
38
+ createBybitKlineStream: () => createBybitKlineStream,
39
+ createBybitKlineStreamWithClient: () => createBybitKlineStreamWithClient,
37
40
  default: () => index_default,
38
41
  getConnectorCreatorByProvider: () => getConnectorCreatorByProvider,
39
42
  getConnectorProviders: () => getConnectorProviders,
40
43
  marketDataProviders: () => marketDataProviders,
44
+ parseBybitKlineEvent: () => parseBybitKlineEvent,
41
45
  providerToConnectorName: () => providerToConnectorName,
42
46
  resolveConnectorNameByProvider: () => resolveConnectorNameByProvider,
43
47
  spotKlineProviders: () => spotKlineProviders
@@ -47,8 +51,8 @@ module.exports = __toCommonJS(index_exports);
47
51
  // src/ByBit/index.ts
48
52
  var import_lodash = __toESM(require("lodash"));
49
53
  var import_chalk = __toESM(require("chalk"));
50
- var import_async = require("@tradejs/core/async");
51
- var import_constants3 = require("@tradejs/core/constants");
54
+ var import_async2 = require("@tradejs/core/async");
55
+ var import_constants2 = require("@tradejs/core/constants");
52
56
  var import_data2 = require("@tradejs/core/data");
53
57
  var import_math = require("@tradejs/core/math");
54
58
  var import_tickers = require("@tradejs/core/tickers");
@@ -58,24 +62,43 @@ var import_logger3 = require("@tradejs/infra/logger");
58
62
  // src/ByBit/client.ts
59
63
  var import_bybit_api = require("bybit-api");
60
64
  var import_logger = require("@tradejs/infra/logger");
61
- var import_redis = require("@tradejs/infra/redis");
62
- var useTestnet = false;
63
- var getClient = async ({ userName }, access = "private") => {
65
+ var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
66
+ var PRIVATE_RECV_WINDOW_MS = 1e4;
67
+ var getClient = async (config, access = "private") => {
68
+ const account = access === "public" && !config.accountId ? null : await (0, import_tradingAccounts.resolveTradingAccount)({
69
+ userName: config.userName,
70
+ accountId: config.accountId,
71
+ provider: "bybit",
72
+ universe: config.universe
73
+ });
74
+ const useTestnet = account?.environment === "testnet";
64
75
  if (access === "public") {
65
76
  return new import_bybit_api.RestClientV5({
66
77
  parseAPIRateLimits: true,
67
78
  testnet: useTestnet
68
79
  });
69
80
  }
70
- const user = await (0, import_redis.getData)(import_redis.redisKeys.user(userName));
71
- if (!user) {
72
- import_logger.logger.log("error", "connection config not found: %s", userName);
81
+ if (!account?.apiKey || !account.apiSecret) {
82
+ if (config.accountId) {
83
+ import_logger.logger.log(
84
+ "error",
85
+ "Bybit trading account config not found: user=%s account=%s",
86
+ config.userName,
87
+ config.accountId
88
+ );
89
+ } else {
90
+ import_logger.logger.log("error", "connection config not found: %s", config.userName);
91
+ }
73
92
  return null;
74
93
  }
75
94
  const client = new import_bybit_api.RestClientV5({
76
- key: user.BYBIT_API_KEY,
77
- secret: user.BYBIT_API_SECRET,
95
+ key: account.apiKey,
96
+ secret: account.apiSecret,
78
97
  parseAPIRateLimits: true,
98
+ recv_window: PRIVATE_RECV_WINDOW_MS,
99
+ // Avoid noisy bybit-api console.error dumps when its internal
100
+ // /v5/market/time sync probe hits transient network resets.
101
+ syncTimeBeforePrivateRequests: false,
79
102
  testnet: useTestnet
80
103
  });
81
104
  return client;
@@ -85,10 +108,28 @@ var getClient = async ({ userName }, access = "private") => {
85
108
  var import_constants = require("@tradejs/core/constants");
86
109
  var import_data = require("@tradejs/core/data");
87
110
  var import_time = require("@tradejs/core/time");
111
+ var import_async = require("@tradejs/core/async");
88
112
  var import_logger2 = require("@tradejs/infra/logger");
89
113
  var import_timescale = require("@tradejs/infra/timescale");
90
114
  var DEFAULT_LIMIT = 1e3;
91
115
  var DEFAULT_CACHE_FALLBACK_WINDOW = 1e3;
116
+ var DEFAULT_TIMESCALE_RETRIES = 2;
117
+ var DEFAULT_TIMESCALE_RETRY_DELAY_MS = 1e3;
118
+ var resolveNonNegativeInt = (value, fallback) => {
119
+ const parsed = Number(value);
120
+ if (!Number.isFinite(parsed) || parsed < 0) {
121
+ return fallback;
122
+ }
123
+ return Math.floor(parsed);
124
+ };
125
+ var getTimescaleRetryCount = () => resolveNonNegativeInt(
126
+ process.env.TIMESCALE_KLINE_RETRIES,
127
+ DEFAULT_TIMESCALE_RETRIES
128
+ );
129
+ var getTimescaleRetryDelayMs = () => resolveNonNegativeInt(
130
+ process.env.TIMESCALE_KLINE_RETRY_DELAY_MS,
131
+ DEFAULT_TIMESCALE_RETRY_DELAY_MS
132
+ );
92
133
  var intervalMsOf = (interval) => interval * 6e4;
93
134
  var clampToClosedCandle = (value, intervalMs) => Math.floor(value / intervalMs) * intervalMs;
94
135
  var normalizeRangeToClosed = (intervalMs, start, end) => {
@@ -110,10 +151,29 @@ var createTimescaleCachedKline = ({
110
151
  cacheFallbackWindow = DEFAULT_CACHE_FALLBACK_WINDOW
111
152
  }) => {
112
153
  let isTimescaleFallbackMode = false;
113
- const loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs) => {
114
- if (pointer === void 0) return [];
154
+ const runTimescaleOperation = async (operation) => {
155
+ const retries = getTimescaleRetryCount();
156
+ const retryDelayMs = getTimescaleRetryDelayMs();
157
+ for (let attempt = 0; ; attempt += 1) {
158
+ try {
159
+ return await operation();
160
+ } catch (error) {
161
+ if (attempt >= retries) {
162
+ throw error;
163
+ }
164
+ const waitMs = retryDelayMs * 2 ** attempt;
165
+ if (waitMs > 0) {
166
+ await (0, import_async.delay)(waitMs);
167
+ }
168
+ }
169
+ }
170
+ };
171
+ const loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs, options = {}) => {
172
+ if (pointer === void 0) return { data: [], loaded: false };
115
173
  let accumulated = [];
116
174
  let fulfilled = false;
175
+ let loaded = false;
176
+ const shouldAccumulate = options.accumulate ?? true;
117
177
  while (!fulfilled) {
118
178
  const currentPointer = pointer;
119
179
  const params = {
@@ -134,7 +194,13 @@ var createTimescaleCachedKline = ({
134
194
  fulfilled = true;
135
195
  break;
136
196
  }
137
- accumulated = direction === "older" ? (0, import_data.mergeData)(partData, accumulated) : (0, import_data.mergeData)(accumulated, partData);
197
+ loaded = true;
198
+ if (options.onPartData) {
199
+ await options.onPartData(partData);
200
+ }
201
+ if (shouldAccumulate) {
202
+ accumulated = direction === "older" ? (0, import_data.mergeData)(partData, accumulated) : (0, import_data.mergeData)(accumulated, partData);
203
+ }
138
204
  const boundaryReached = limitBoundary !== void 0 && (direction === "older" && currentPointer <= limitBoundary || direction === "newer" && currentPointer >= limitBoundary);
139
205
  if (partData.length < limit || boundaryReached) {
140
206
  fulfilled = true;
@@ -147,7 +213,7 @@ var createTimescaleCachedKline = ({
147
213
  }
148
214
  pointer = nextPointer;
149
215
  }
150
- return accumulated;
216
+ return { data: accumulated, loaded };
151
217
  };
152
218
  const refreshTail = async ({
153
219
  symbol,
@@ -172,7 +238,9 @@ var createTimescaleCachedKline = ({
172
238
  silent
173
239
  });
174
240
  if (part.length) {
175
- await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, part));
241
+ await runTimescaleOperation(
242
+ () => (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, part))
243
+ );
176
244
  }
177
245
  };
178
246
  return async ({
@@ -194,7 +262,9 @@ var createTimescaleCachedKline = ({
194
262
  }
195
263
  const intervalMs = intervalMsOf(intMinutes);
196
264
  try {
197
- const edges = await (0, import_timescale.getDataEdges)(provider, symbol, intMinutes);
265
+ const edges = await runTimescaleOperation(
266
+ () => (0, import_timescale.getDataEdges)(provider, symbol, intMinutes)
267
+ );
198
268
  let dataStart = edges.min;
199
269
  let dataEnd = edges.max;
200
270
  const { normStart, normEnd } = normalizeRangeToClosed(
@@ -209,20 +279,18 @@ var createTimescaleCachedKline = ({
209
279
  0
210
280
  );
211
281
  const e = defaultEnd ?? base;
212
- const dbData2 = await (0, import_timescale.getCandlesRange)(
213
- provider,
214
- symbol,
215
- intMinutes,
216
- s,
217
- e
282
+ const dbData2 = await runTimescaleOperation(
283
+ () => (0, import_timescale.getCandlesRange)(provider, symbol, intMinutes, s, e)
218
284
  );
219
285
  return rowsToKline(dbData2);
220
286
  }
221
287
  const needOlderData = defaultStart !== void 0 && (dataStart === void 0 || normStart < dataStart);
222
- const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
288
+ const persistPartData = warmOnly ? (partData) => runTimescaleOperation(
289
+ () => (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, partData))
290
+ ) : void 0;
223
291
  if (needOlderData) {
224
292
  const pointerForOlder = dataStart ?? normEnd ?? Date.now();
225
- const olderData = await loadData(
293
+ const olderResult = await loadData(
226
294
  "older",
227
295
  pointerForOlder,
228
296
  normStart,
@@ -233,17 +301,34 @@ var createTimescaleCachedKline = ({
233
301
  start: normStart,
234
302
  end: pointerForOlder
235
303
  },
236
- intervalMs
304
+ intervalMs,
305
+ {
306
+ accumulate: !warmOnly,
307
+ onPartData: persistPartData
308
+ }
237
309
  );
238
- if (olderData.length) {
239
- await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, olderData));
310
+ if (warmOnly && olderResult.loaded) {
240
311
  dataStart = normStart;
312
+ if (dataEnd === void 0) {
313
+ dataEnd = normEnd;
314
+ }
315
+ } else if (olderResult.data.length) {
316
+ await runTimescaleOperation(
317
+ () => (0, import_timescale.upsertCandles)(
318
+ (0, import_timescale.toRows)(provider, symbol, intMinutes, olderResult.data)
319
+ )
320
+ );
321
+ dataStart = normStart;
322
+ if (dataEnd === void 0) {
323
+ dataEnd = normEnd;
324
+ }
241
325
  }
242
326
  }
327
+ const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
243
328
  if (needNewerData) {
244
329
  const fallbackStart = (0, import_time.getTimestamp)(import_constants.PRELOAD_FALLBACK_DAYS);
245
330
  const pointerForNewer = dataEnd ?? (defaultStart !== void 0 ? normStart : fallbackStart) ?? 0;
246
- const newerData = await loadData(
331
+ const newerResult = await loadData(
247
332
  "newer",
248
333
  pointerForNewer,
249
334
  normEnd,
@@ -254,10 +339,20 @@ var createTimescaleCachedKline = ({
254
339
  start: pointerForNewer,
255
340
  end: normEnd
256
341
  },
257
- intervalMs
342
+ intervalMs,
343
+ {
344
+ accumulate: !warmOnly,
345
+ onPartData: persistPartData
346
+ }
258
347
  );
259
- if (newerData.length) {
260
- await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, newerData));
348
+ if (warmOnly && newerResult.loaded) {
349
+ dataEnd = normEnd;
350
+ } else if (newerResult.data.length) {
351
+ await runTimescaleOperation(
352
+ () => (0, import_timescale.upsertCandles)(
353
+ (0, import_timescale.toRows)(provider, symbol, intMinutes, newerResult.data)
354
+ )
355
+ );
261
356
  dataEnd = normEnd;
262
357
  }
263
358
  }
@@ -275,12 +370,8 @@ var createTimescaleCachedKline = ({
275
370
  const rangeStart = defaultStart ?? dataStart ?? 0;
276
371
  const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
277
372
  const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
278
- const dbData = await (0, import_timescale.getCandlesRange)(
279
- provider,
280
- symbol,
281
- intMinutes,
282
- finalStart,
283
- finalEnd
373
+ const dbData = await runTimescaleOperation(
374
+ () => (0, import_timescale.getCandlesRange)(provider, symbol, intMinutes, finalStart, finalEnd)
284
375
  );
285
376
  if (isTimescaleFallbackMode) {
286
377
  isTimescaleFallbackMode = false;
@@ -313,8 +404,8 @@ var createTimescaleCachedKline = ({
313
404
  };
314
405
 
315
406
  // src/ByBit/utils.ts
316
- var import_constants2 = require("@tradejs/core/constants");
317
407
  var import_time2 = require("@tradejs/core/time");
408
+ var BYBIT_CATEGORY = "linear";
318
409
  var parseKlineItem = (item) => ({
319
410
  dt: (0, import_time2.formatUnix)(parseInt(item[0])),
320
411
  timestamp: parseInt(item[0]),
@@ -342,7 +433,7 @@ var getSymbolMeta = async (client, symbol) => {
342
433
  const cached = symbolMetaCache.get(symbol);
343
434
  if (cached) return cached;
344
435
  const res = await client.getInstrumentsInfo({
345
- category: import_constants2.MARKET_CATEGORY,
436
+ category: BYBIT_CATEGORY,
346
437
  symbol
347
438
  });
348
439
  const item = res?.result?.list?.[0];
@@ -357,7 +448,10 @@ var getSymbolMeta = async (client, symbol) => {
357
448
  qtyStep: Number(qtyStepStr),
358
449
  minOrderQty: Number(minOrderQtyStr),
359
450
  pricePrecision: stepToPrecision(tickSizeStr),
360
- qtyPrecision: stepToPrecision(qtyStepStr)
451
+ qtyPrecision: stepToPrecision(qtyStepStr),
452
+ maxLeverage: Number.isFinite(
453
+ Number(item.leverageFilter?.maxLeverage)
454
+ ) ? Number(item.leverageFilter.maxLeverage) : null
361
455
  };
362
456
  symbolMetaCache.set(symbol, meta);
363
457
  return meta;
@@ -404,13 +498,29 @@ var mapPositionData = (data) => {
404
498
  };
405
499
 
406
500
  // src/ByBit/index.ts
501
+ var import_types = require("@tradejs/types");
407
502
  var LIMIT = 1e3;
503
+ var BYBIT_CATEGORY2 = "linear";
504
+ var BYBIT_CAPABILITIES = {
505
+ supportedUniverses: ["crypto", "tradfi"],
506
+ defaultUniverse: "crypto"
507
+ };
508
+ var BYBIT_TRADFI_SYMBOL_TYPES = ["stock", "commodity", "forex"];
509
+ var FUNDING_HISTORY_LIMIT = 200;
408
510
  var BYBIT_RATE_LIMIT_RETCODE = 10006;
409
511
  var BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE = 34040;
410
512
  var KLINE_RATE_LIMIT_MAX_ATTEMPTS = 3;
411
513
  var KLINE_RATE_LIMIT_BASE_DELAY_MS = 800;
412
514
  var KLINE_RATE_LIMIT_MAX_DELAY_MS = 1e4;
413
515
  var KLINE_RATE_LIMIT_RESET_BUFFER_MS = 50;
516
+ var POSITION_SNAPSHOT_TIMEOUT_MS = Math.max(
517
+ 1e3,
518
+ Number(process.env.TRADEJS_BYBIT_POSITION_TIMEOUT_MS ?? 1e4)
519
+ );
520
+ var POSITION_SNAPSHOT_CACHE_TTL_MS = Math.max(
521
+ 250,
522
+ Number(process.env.TRADEJS_BYBIT_POSITION_CACHE_TTL_MS ?? 1500)
523
+ );
414
524
  var INTERVAL_TO_MINUTES = {
415
525
  "1": 1,
416
526
  "3": 3,
@@ -430,6 +540,144 @@ var getLogLevel = (res) => res.retCode === 0 ? "info" : "error";
430
540
  var isTradingStopNotModified = (res) => res?.retCode === BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE;
431
541
  var isTradingStopAccepted = (res) => res?.retCode === 0 || isTradingStopNotModified(res);
432
542
  var isKlineRateLimited = (res) => res?.retCode === BYBIT_RATE_LIMIT_RETCODE;
543
+ var mapBybitAssetClass = (symbolType) => {
544
+ if (symbolType === "stock") return "equity";
545
+ if (symbolType === "commodity") return "commodity";
546
+ if (symbolType === "forex") return "forex";
547
+ return "crypto";
548
+ };
549
+ var mapBybitInstrument = (item) => {
550
+ const assetClass = mapBybitAssetClass(item.symbolType);
551
+ return {
552
+ provider: "bybit",
553
+ symbol: String(item.symbol ?? "").trim().toUpperCase(),
554
+ kind: "perpetual",
555
+ assetClass,
556
+ universe: assetClass === "crypto" ? "crypto" : "tradfi",
557
+ status: String(item.status ?? "").toLowerCase() === "trading" ? "trading" : "inactive",
558
+ baseAsset: String(item.baseCoin ?? "").trim() || void 0,
559
+ quoteAsset: String(item.quoteCoin ?? "").trim() || void 0,
560
+ settleAsset: String(item.settleCoin ?? "").trim() || void 0,
561
+ displayName: String(item.displayName ?? "").trim() || void 0,
562
+ venueMetadata: {
563
+ category: BYBIT_CATEGORY2,
564
+ symbolType: String(item.symbolType ?? ""),
565
+ contractType: String(item.contractType ?? ""),
566
+ fundingInterval: Number(item.fundingInterval ?? Number.NaN),
567
+ maxLeverage: Number(item.leverageFilter?.maxLeverage ?? Number.NaN)
568
+ }
569
+ };
570
+ };
571
+ var getOptionalStringField = (source, key) => {
572
+ if (!source || typeof source !== "object") {
573
+ return null;
574
+ }
575
+ const value = source[key];
576
+ return typeof value === "string" && value.trim() ? value : null;
577
+ };
578
+ var toFiniteNumberOrNull = (value) => {
579
+ const parsed = Number(value ?? Number.NaN);
580
+ return Number.isFinite(parsed) ? parsed : null;
581
+ };
582
+ var sumOptionalFees = (values) => {
583
+ const finiteValues = values.filter(
584
+ (value) => typeof value === "number" && Number.isFinite(value)
585
+ );
586
+ return finiteValues.length ? Number(finiteValues.reduce((sum, value) => sum + value, 0).toFixed(12)) : null;
587
+ };
588
+ var FUNDING_TRANSACTION_LOG_PAGE_LIMIT = 100;
589
+ var FUNDING_TRANSACTION_LOG_MAX_PAGES = 20;
590
+ var CLOSED_PNL_PAGE_LIMIT = 100;
591
+ var CLOSED_PNL_MAX_PAGES = 20;
592
+ var EXECUTION_PAGE_LIMIT = 100;
593
+ var EXECUTION_MAX_PAGES = 20;
594
+ var loadFundingFeeRows = async ({
595
+ client,
596
+ startTime,
597
+ endTime,
598
+ symbol
599
+ }) => {
600
+ if (typeof client.getTransactionLog !== "function") {
601
+ return [];
602
+ }
603
+ try {
604
+ const rows = [];
605
+ let cursor;
606
+ for (let page = 0; page < FUNDING_TRANSACTION_LOG_MAX_PAGES; page += 1) {
607
+ const response = await client.getTransactionLog({
608
+ accountType: "UNIFIED",
609
+ category: BYBIT_CATEGORY2,
610
+ type: "SETTLEMENT",
611
+ startTime,
612
+ endTime,
613
+ limit: FUNDING_TRANSACTION_LOG_PAGE_LIMIT,
614
+ ...cursor ? { cursor } : {}
615
+ });
616
+ if (response?.retCode !== 0) {
617
+ import_logger3.logger.log(
618
+ "debug",
619
+ "funding transaction log retCode: %s, %s",
620
+ response?.retCode,
621
+ response?.retMsg
622
+ );
623
+ return rows;
624
+ }
625
+ const pageRows = (response.result?.list ?? []).map((item) => {
626
+ const rowSymbol = String(item.symbol ?? "").trim();
627
+ const timestamp = Number(item.transactionTime ?? Number.NaN);
628
+ const fundingFee = Number(item.funding ?? Number.NaN);
629
+ if (!rowSymbol || !Number.isFinite(timestamp) || !Number.isFinite(fundingFee) || symbol && rowSymbol !== symbol) {
630
+ return null;
631
+ }
632
+ return {
633
+ symbol: rowSymbol,
634
+ timestamp,
635
+ fundingFee
636
+ };
637
+ });
638
+ rows.push(
639
+ ...pageRows.filter((item) => item != null)
640
+ );
641
+ const nextCursor = String(response.result?.nextPageCursor ?? "").trim();
642
+ if (!nextCursor || nextCursor === cursor) {
643
+ break;
644
+ }
645
+ cursor = nextCursor;
646
+ }
647
+ return rows;
648
+ } catch (error) {
649
+ import_logger3.logger.log("debug", "funding transaction log failed: %s", error);
650
+ return [];
651
+ }
652
+ };
653
+ var sumFundingFeeForTrade = ({
654
+ rows,
655
+ symbol,
656
+ entryTimestamp,
657
+ closedAt
658
+ }) => {
659
+ if (entryTimestamp == null || !Number.isFinite(entryTimestamp)) {
660
+ return null;
661
+ }
662
+ const matched = rows.filter(
663
+ (row) => row.symbol === symbol && row.timestamp >= entryTimestamp && row.timestamp <= closedAt
664
+ );
665
+ return matched.length ? Number(matched.reduce((sum, row) => sum + row.fundingFee, 0).toFixed(12)) : null;
666
+ };
667
+ var inferClosedPnlDirection = ({
668
+ entryPrice,
669
+ exitPrice,
670
+ closedPnl
671
+ }) => {
672
+ const priceDelta = exitPrice - entryPrice;
673
+ if (!Number.isFinite(priceDelta) || priceDelta === 0) {
674
+ return null;
675
+ }
676
+ if (closedPnl === 0) {
677
+ return priceDelta > 0 ? "LONG" : "SHORT";
678
+ }
679
+ return priceDelta * closedPnl > 0 ? "LONG" : "SHORT";
680
+ };
433
681
  var resolveKlineRetryDelayMs = (res, attempt) => {
434
682
  const resetAtRaw = Number(res?.rateLimitApi?.resetAtTimestamp);
435
683
  if (Number.isFinite(resetAtRaw) && resetAtRaw > 0) {
@@ -447,8 +695,14 @@ var resolveKlineRetryDelayMs = (res, attempt) => {
447
695
  };
448
696
  var ByBitConnectorCreator = async (config) => {
449
697
  let state = {};
698
+ const universe = (0, import_types.resolveConnectorUniverse)(
699
+ BYBIT_CAPABILITIES,
700
+ config.universe
701
+ );
450
702
  let publicClientPromise = null;
451
703
  let privateClientPromise = null;
704
+ const positionSnapshotCache = /* @__PURE__ */ new Map();
705
+ const inflightPositionSnapshots = /* @__PURE__ */ new Map();
452
706
  const getPublicClient = async () => {
453
707
  publicClientPromise ??= getClient(config, "public");
454
708
  return publicClientPromise;
@@ -457,6 +711,123 @@ var ByBitConnectorCreator = async (config) => {
457
711
  privateClientPromise ??= getClient(config, "private");
458
712
  return privateClientPromise;
459
713
  };
714
+ const instrumentCache = /* @__PURE__ */ new Map();
715
+ const fetchInstrumentGroup = async (symbolType) => {
716
+ const client = await getPublicClient();
717
+ if (!client) return [];
718
+ if (typeof client.getInstrumentsInfo !== "function") {
719
+ if (universe === "tradfi" || symbolType) {
720
+ throw new Error("Bybit instrument discovery is unavailable");
721
+ }
722
+ return [];
723
+ }
724
+ const instruments = [];
725
+ let cursor;
726
+ for (; ; ) {
727
+ const response = await client.getInstrumentsInfo({
728
+ category: BYBIT_CATEGORY2,
729
+ limit: LIMIT,
730
+ ...symbolType ? { symbolType } : {},
731
+ ...cursor ? { cursor } : {}
732
+ });
733
+ if (response.retCode !== 0) {
734
+ throw new Error(
735
+ `Bybit instruments failed: ${response.retCode} ${response.retMsg}`
736
+ );
737
+ }
738
+ for (const raw of response.result?.list ?? []) {
739
+ const instrument = mapBybitInstrument(raw);
740
+ if (instrument.symbol) instruments.push(instrument);
741
+ }
742
+ const nextCursor = String(response.result?.nextPageCursor ?? "").trim();
743
+ if (!nextCursor || nextCursor === cursor) break;
744
+ cursor = nextCursor;
745
+ }
746
+ return instruments;
747
+ };
748
+ const loadInstrumentUniverse = (requestedUniverse) => {
749
+ const cached = instrumentCache.get(requestedUniverse);
750
+ if (cached && cached.expiresAt > Date.now()) {
751
+ return cached.promise;
752
+ }
753
+ const promise = requestedUniverse === "tradfi" ? Promise.all(
754
+ BYBIT_TRADFI_SYMBOL_TYPES.map(
755
+ (symbolType) => fetchInstrumentGroup(symbolType)
756
+ )
757
+ ).then((groups) => {
758
+ const bySymbol = /* @__PURE__ */ new Map();
759
+ for (const instrument of groups.flat()) {
760
+ bySymbol.set(instrument.symbol, instrument);
761
+ }
762
+ return [...bySymbol.values()];
763
+ }) : fetchInstrumentGroup().then(
764
+ (instruments) => instruments.filter(
765
+ (instrument) => instrument.universe === "crypto"
766
+ )
767
+ );
768
+ instrumentCache.set(requestedUniverse, {
769
+ expiresAt: Date.now() + 5 * 6e4,
770
+ promise
771
+ });
772
+ promise.catch(() => instrumentCache.delete(requestedUniverse));
773
+ return promise;
774
+ };
775
+ const listInstruments = async (query = {}) => {
776
+ const requestedUniverse = (0, import_types.resolveConnectorUniverse)(
777
+ BYBIT_CAPABILITIES,
778
+ query?.universe ?? universe
779
+ );
780
+ const [assetClasses, symbols] = [
781
+ query?.assetClasses?.length ? new Set(query.assetClasses) : null,
782
+ query?.symbols?.length ? new Set(query.symbols.map((symbol) => symbol.trim().toUpperCase())) : null
783
+ ];
784
+ return (await loadInstrumentUniverse(requestedUniverse)).filter(
785
+ (instrument) => (!assetClasses || assetClasses.has(instrument.assetClass)) && (!symbols || symbols.has(instrument.symbol))
786
+ );
787
+ };
788
+ const withTimeout = async (promise, timeoutMs, label) => {
789
+ let timeoutHandle = null;
790
+ try {
791
+ return await Promise.race([
792
+ promise,
793
+ new Promise((_2, reject) => {
794
+ timeoutHandle = setTimeout(() => {
795
+ reject(new Error(`${label} timed out after ${timeoutMs}ms`));
796
+ }, timeoutMs);
797
+ })
798
+ ]);
799
+ } finally {
800
+ if (timeoutHandle) {
801
+ clearTimeout(timeoutHandle);
802
+ }
803
+ }
804
+ };
805
+ const setCachedPositionSnapshot = (symbol, value) => {
806
+ positionSnapshotCache.set(symbol, {
807
+ value,
808
+ expiresAt: Date.now() + POSITION_SNAPSHOT_CACHE_TTL_MS
809
+ });
810
+ };
811
+ const getCachedPositionSnapshot = (symbol) => {
812
+ const cached = positionSnapshotCache.get(symbol);
813
+ if (!cached) {
814
+ return void 0;
815
+ }
816
+ if (cached.expiresAt <= Date.now()) {
817
+ positionSnapshotCache.delete(symbol);
818
+ return void 0;
819
+ }
820
+ return cached.value;
821
+ };
822
+ const invalidatePositionSnapshot = (symbol) => {
823
+ if (typeof symbol === "string" && symbol.length > 0) {
824
+ positionSnapshotCache.delete(symbol);
825
+ inflightPositionSnapshots.delete(symbol);
826
+ return;
827
+ }
828
+ positionSnapshotCache.clear();
829
+ inflightPositionSnapshots.clear();
830
+ };
460
831
  const request = async ({
461
832
  symbol,
462
833
  interval,
@@ -465,7 +836,7 @@ var ByBitConnectorCreator = async (config) => {
465
836
  silent
466
837
  }) => {
467
838
  const normalizedStart = (0, import_math.round)(
468
- start || (0, import_time3.getTimestamp)(import_constants3.PRELOAD_FALLBACK_DAYS),
839
+ start || (0, import_time3.getTimestamp)(import_constants2.PRELOAD_FALLBACK_DAYS),
469
840
  0
470
841
  );
471
842
  const normalizedEnd = (0, import_math.round)(end || Date.now());
@@ -477,7 +848,7 @@ var ByBitConnectorCreator = async (config) => {
477
848
  }
478
849
  for (let attempt = 1; attempt <= KLINE_RATE_LIMIT_MAX_ATTEMPTS; attempt += 1) {
479
850
  const kline = await client.getKline({
480
- category: import_constants3.MARKET_CATEGORY,
851
+ category: BYBIT_CATEGORY2,
481
852
  symbol,
482
853
  interval,
483
854
  start: normalizedStart,
@@ -496,7 +867,7 @@ var ByBitConnectorCreator = async (config) => {
496
867
  KLINE_RATE_LIMIT_MAX_ATTEMPTS,
497
868
  waitMs
498
869
  );
499
- await (0, import_async.delay)(waitMs);
870
+ await (0, import_async2.delay)(waitMs);
500
871
  continue;
501
872
  }
502
873
  }
@@ -538,40 +909,67 @@ var ByBitConnectorCreator = async (config) => {
538
909
  const intervalToMinutes3 = (interval) => {
539
910
  return INTERVAL_TO_MINUTES[String(interval)] ?? null;
540
911
  };
541
- const getPositionSnapshot = async (symbol) => {
542
- const client = await getPrivateClient();
543
- if (!client) {
544
- return null;
545
- }
546
- const positionRes = await client.getPositionInfo({
547
- symbol,
548
- category: import_constants3.MARKET_CATEGORY
549
- });
550
- if (positionRes.retCode !== 0) {
912
+ const fetchPositionSnapshot = async (symbol) => {
913
+ try {
914
+ const client = await getPrivateClient();
915
+ if (!client) {
916
+ return null;
917
+ }
918
+ const positionRes = await withTimeout(
919
+ client.getPositionInfo({
920
+ symbol,
921
+ category: BYBIT_CATEGORY2
922
+ }),
923
+ POSITION_SNAPSHOT_TIMEOUT_MS,
924
+ `bybit getPositionInfo ${symbol}`
925
+ );
926
+ if (positionRes.retCode !== 0) {
927
+ import_logger3.logger.log(
928
+ getLogLevel(positionRes),
929
+ "position retCode: %s, %s",
930
+ symbol,
931
+ positionRes.retCode
932
+ );
933
+ return null;
934
+ }
935
+ const positions = mapPositionData(positionRes.result.list);
936
+ if (!positions || import_lodash.default.isEmpty(positions)) {
937
+ return null;
938
+ }
939
+ const position = positions[0];
551
940
  import_logger3.logger.log(
552
- getLogLevel(positionRes),
553
- "position retCode: %s, %s",
554
- symbol,
555
- positionRes.retCode
941
+ "debug",
942
+ "position: %s %s qty=%s price=%s",
943
+ position.symbol,
944
+ position.direction,
945
+ position.qty,
946
+ position.price
556
947
  );
948
+ return {
949
+ ...position
950
+ };
951
+ } catch (error) {
952
+ import_logger3.logger.log("error", "getPositionSnapshot failed: %s %s", symbol, error);
557
953
  return null;
558
954
  }
559
- const positions = mapPositionData(positionRes.result.list);
560
- if (!positions || import_lodash.default.isEmpty(positions)) {
561
- return null;
955
+ };
956
+ const getPositionSnapshot = async (symbol) => {
957
+ const cached = getCachedPositionSnapshot(symbol);
958
+ if (cached !== void 0) {
959
+ return cached;
562
960
  }
563
- const position = positions[0];
564
- import_logger3.logger.log(
565
- "debug",
566
- "position: %s %s qty=%s price=%s",
567
- position.symbol,
568
- position.direction,
569
- position.qty,
570
- position.price
571
- );
572
- return {
573
- ...position
574
- };
961
+ const inflight = inflightPositionSnapshots.get(symbol);
962
+ if (inflight) {
963
+ return inflight;
964
+ }
965
+ const nextSnapshotPromise = fetchPositionSnapshot(symbol).then((position) => {
966
+ setCachedPositionSnapshot(symbol, position);
967
+ return position;
968
+ }).finally(() => {
969
+ inflightPositionSnapshots.delete(symbol);
970
+ });
971
+ inflightPositionSnapshots.set(symbol, nextSnapshotPromise);
972
+ return nextSnapshotPromise;
575
973
  };
576
974
  const setTakeProfits = async ({
577
975
  symbol,
@@ -622,7 +1020,7 @@ var ByBitConnectorCreator = async (config) => {
622
1020
  );
623
1021
  const isFullMode = takeProfits.length === 1 && tp.rate === 1;
624
1022
  const tpRes = await client.setTradingStop({
625
- category: import_constants3.MARKET_CATEGORY,
1023
+ category: BYBIT_CATEGORY2,
626
1024
  symbol,
627
1025
  tpSize: isFullMode ? void 0 : tpSizeStr,
628
1026
  tpslMode: isFullMode ? "Full" : "Partial",
@@ -687,7 +1085,7 @@ var ByBitConnectorCreator = async (config) => {
687
1085
  meta
688
1086
  );
689
1087
  const slRes = await client.setTradingStop({
690
- category: import_constants3.MARKET_CATEGORY,
1088
+ category: BYBIT_CATEGORY2,
691
1089
  symbol,
692
1090
  tpslMode: "Full",
693
1091
  stopLoss: slNormalized.priceStr,
@@ -723,6 +1121,11 @@ var ByBitConnectorCreator = async (config) => {
723
1121
  return isTradingStopAccepted(slRes);
724
1122
  };
725
1123
  return {
1124
+ capabilities: BYBIT_CAPABILITIES,
1125
+ universe,
1126
+ accountId: config.accountId,
1127
+ deploymentId: config.deploymentId,
1128
+ listInstruments,
726
1129
  getState: async () => state,
727
1130
  setState: async (newState) => {
728
1131
  state = { ...state, ...newState };
@@ -740,7 +1143,7 @@ var ByBitConnectorCreator = async (config) => {
740
1143
  return [];
741
1144
  }
742
1145
  const positionRes = await client.getPositionInfo({
743
- category: import_constants3.MARKET_CATEGORY,
1146
+ category: BYBIT_CATEGORY2,
744
1147
  settleCoin: "USDT"
745
1148
  });
746
1149
  if (positionRes.retCode !== 0) {
@@ -763,7 +1166,7 @@ var ByBitConnectorCreator = async (config) => {
763
1166
  return [];
764
1167
  }
765
1168
  const positionRes = await client.getPositionInfo({
766
- category: import_constants3.MARKET_CATEGORY,
1169
+ category: BYBIT_CATEGORY2,
767
1170
  settleCoin: "USDT"
768
1171
  });
769
1172
  if (positionRes.retCode !== 0) {
@@ -779,6 +1182,8 @@ var ByBitConnectorCreator = async (config) => {
779
1182
  const entryPrice = Number(item.avgPrice ?? Number.NaN);
780
1183
  const currentPrice = Number(item.markPrice ?? Number.NaN);
781
1184
  const unrealizedPnl = Number(item.unrealisedPnl ?? Number.NaN);
1185
+ const takeProfitPrice = toFiniteNumberOrNull(item.takeProfit);
1186
+ const stopLossPrice = toFiniteNumberOrNull(item.stopLoss);
782
1187
  const side = String(item.side ?? "");
783
1188
  if (!Number.isFinite(qty) || qty <= 0 || !Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || !Number.isFinite(unrealizedPnl) || side !== "Buy" && side !== "Sell") {
784
1189
  return null;
@@ -789,7 +1194,9 @@ var ByBitConnectorCreator = async (config) => {
789
1194
  price: entryPrice,
790
1195
  currentPrice,
791
1196
  unrealizedPnl,
792
- direction: side === "Buy" ? "LONG" : "SHORT"
1197
+ direction: side === "Buy" ? "LONG" : "SHORT",
1198
+ ...takeProfitPrice != null && takeProfitPrice > 0 ? { takeProfitPrice } : {},
1199
+ ...stopLossPrice != null && stopLossPrice > 0 ? { stopLossPrice } : {}
793
1200
  };
794
1201
  }).filter(
795
1202
  (item) => item != null && item.symbol.length > 0
@@ -805,23 +1212,47 @@ var ByBitConnectorCreator = async (config) => {
805
1212
  if (!client) {
806
1213
  return [];
807
1214
  }
808
- const response = await client.getClosedPnL({
809
- category: import_constants3.MARKET_CATEGORY,
1215
+ const closedPnlRows = [];
1216
+ let cursor;
1217
+ const pageLimit = Math.min(
1218
+ Math.max(1, Math.trunc(limit)),
1219
+ CLOSED_PNL_PAGE_LIMIT
1220
+ );
1221
+ for (let page = 0; page < CLOSED_PNL_MAX_PAGES; page += 1) {
1222
+ const response = await client.getClosedPnL({
1223
+ category: BYBIT_CATEGORY2,
1224
+ startTime,
1225
+ endTime,
1226
+ symbol,
1227
+ limit: pageLimit,
1228
+ ...cursor ? { cursor } : {}
1229
+ });
1230
+ if (response.retCode !== 0) {
1231
+ import_logger3.logger.log(
1232
+ "error",
1233
+ "closedPnl retCode: %s, %s",
1234
+ response.retCode,
1235
+ response.retMsg
1236
+ );
1237
+ return [];
1238
+ }
1239
+ closedPnlRows.push(...response.result?.list ?? []);
1240
+ const nextCursor = String(response.result?.nextPageCursor ?? "").trim();
1241
+ if (!nextCursor || nextCursor === cursor) {
1242
+ break;
1243
+ }
1244
+ cursor = nextCursor;
1245
+ }
1246
+ if (!closedPnlRows.length) {
1247
+ return [];
1248
+ }
1249
+ const fundingRows = await loadFundingFeeRows({
1250
+ client,
810
1251
  startTime,
811
1252
  endTime,
812
- symbol,
813
- limit: Math.min(Math.max(1, Math.trunc(limit)), 100)
1253
+ symbol
814
1254
  });
815
- if (response.retCode !== 0) {
816
- import_logger3.logger.log(
817
- "error",
818
- "closedPnl retCode: %s, %s",
819
- response.retCode,
820
- response.retMsg
821
- );
822
- return [];
823
- }
824
- return (response.result?.list ?? []).map((item) => {
1255
+ return closedPnlRows.map((item) => {
825
1256
  const qty = Number(item.qty ?? item.closedSize ?? Number.NaN);
826
1257
  const entryPrice = Number(item.avgEntryPrice ?? Number.NaN);
827
1258
  const exitPrice = Number(item.avgExitPrice ?? Number.NaN);
@@ -829,7 +1260,23 @@ var ByBitConnectorCreator = async (config) => {
829
1260
  const closedAt = Number(
830
1261
  item.updatedTime ?? item.createdTime ?? Number.NaN
831
1262
  );
1263
+ const entryTimestamp = Number(item.createdTime ?? Number.NaN);
832
1264
  const orderId = typeof item.orderId === "string" && item.orderId.trim() ? item.orderId : null;
1265
+ const orderLinkId = getOptionalStringField(item, "orderLinkId");
1266
+ const openFee = toFiniteNumberOrNull(item.openFee);
1267
+ const closeFee = toFiniteNumberOrNull(item.closeFee);
1268
+ const fundingFee = sumFundingFeeForTrade({
1269
+ rows: fundingRows,
1270
+ symbol: String(item.symbol ?? ""),
1271
+ entryTimestamp: Number.isFinite(entryTimestamp) ? entryTimestamp : null,
1272
+ closedAt
1273
+ });
1274
+ const totalFee = sumOptionalFees([openFee, closeFee, fundingFee]);
1275
+ const direction = Number.isFinite(entryPrice) && Number.isFinite(exitPrice) ? inferClosedPnlDirection({
1276
+ entryPrice,
1277
+ exitPrice,
1278
+ closedPnl
1279
+ }) : null;
833
1280
  if (!String(item.symbol ?? "").trim() || !Number.isFinite(qty) || !Number.isFinite(closedPnl) || !Number.isFinite(closedAt)) {
834
1281
  return null;
835
1282
  }
@@ -840,29 +1287,145 @@ var ByBitConnectorCreator = async (config) => {
840
1287
  exitPrice: Number.isFinite(exitPrice) ? exitPrice : null,
841
1288
  closedPnl,
842
1289
  closedAt,
843
- ...orderId ? { orderId } : {}
1290
+ ...direction ? { direction } : {},
1291
+ ...Number.isFinite(entryTimestamp) ? { entryTimestamp } : {},
1292
+ ...orderId ? { orderId } : {},
1293
+ ...orderLinkId ? { orderLinkId } : {},
1294
+ ...openFee != null ? { openFee } : {},
1295
+ ...closeFee != null ? { closeFee } : {},
1296
+ ...fundingFee != null ? { fundingFee } : {},
1297
+ ...totalFee != null ? { totalFee } : {}
844
1298
  };
845
1299
  }).filter((item) => item != null);
846
1300
  },
847
- placeOrder: async ({ symbol, price, qty, direction, isLimit, orderId }) => {
1301
+ getEntryExecutions: async ({
1302
+ startTime,
1303
+ endTime,
1304
+ symbol,
1305
+ limit = 100
1306
+ }) => {
1307
+ const client = await getPrivateClient();
1308
+ if (!client) {
1309
+ return [];
1310
+ }
1311
+ const executionRows = [];
1312
+ let cursor;
1313
+ const pageLimit = Math.min(
1314
+ Math.max(1, Math.trunc(limit)),
1315
+ EXECUTION_PAGE_LIMIT
1316
+ );
1317
+ for (let page = 0; page < EXECUTION_MAX_PAGES; page += 1) {
1318
+ const response = await client.getExecutionList({
1319
+ category: BYBIT_CATEGORY2,
1320
+ settleCoin: "USDT",
1321
+ startTime,
1322
+ endTime,
1323
+ symbol,
1324
+ limit: pageLimit,
1325
+ ...cursor ? { cursor } : {}
1326
+ });
1327
+ if (response.retCode !== 0) {
1328
+ import_logger3.logger.log(
1329
+ "error",
1330
+ "entryExecutions retCode: %s, %s",
1331
+ response.retCode,
1332
+ response.retMsg
1333
+ );
1334
+ return [];
1335
+ }
1336
+ executionRows.push(...response.result?.list ?? []);
1337
+ const nextCursor = String(response.result?.nextPageCursor ?? "").trim();
1338
+ if (!nextCursor || nextCursor === cursor) {
1339
+ break;
1340
+ }
1341
+ cursor = nextCursor;
1342
+ }
1343
+ if (!executionRows.length) {
1344
+ return [];
1345
+ }
1346
+ const fundingRows = await loadFundingFeeRows({
1347
+ client,
1348
+ startTime,
1349
+ endTime,
1350
+ symbol
1351
+ });
1352
+ return executionRows.map((item) => {
1353
+ const qty = Number(item.execQty ?? item.orderQty ?? Number.NaN);
1354
+ const entryPrice = Number(item.execPrice ?? Number.NaN);
1355
+ const entryTimestamp = Number(item.execTime ?? Number.NaN);
1356
+ const side = String(item.side ?? "");
1357
+ const orderId = typeof item.orderId === "string" && item.orderId.trim() ? item.orderId : null;
1358
+ const orderLinkId = typeof item.orderLinkId === "string" && item.orderLinkId.trim() ? item.orderLinkId : null;
1359
+ const openFee = toFiniteNumberOrNull(item.execFeeV2 ?? item.execFee);
1360
+ const fundingFee = sumFundingFeeForTrade({
1361
+ rows: fundingRows,
1362
+ symbol: String(item.symbol ?? ""),
1363
+ entryTimestamp,
1364
+ closedAt: endTime
1365
+ });
1366
+ const totalFee = sumOptionalFees([openFee, fundingFee]);
1367
+ if (!String(item.symbol ?? "").trim() || !Number.isFinite(qty) || !Number.isFinite(entryTimestamp) || !Number.isFinite(entryPrice) || side !== "Buy" && side !== "Sell" || !orderLinkId) {
1368
+ return null;
1369
+ }
1370
+ return {
1371
+ symbol: String(item.symbol),
1372
+ qty,
1373
+ entryPrice,
1374
+ entryTimestamp,
1375
+ direction: side === "Buy" ? "LONG" : "SHORT",
1376
+ ...orderId ? { orderId } : {},
1377
+ ...orderLinkId ? { orderLinkId } : {},
1378
+ ...openFee != null ? { openFee } : {},
1379
+ ...fundingFee != null ? { fundingFee } : {},
1380
+ ...totalFee != null ? { totalFee } : {}
1381
+ };
1382
+ }).filter((item) => item != null).sort((left, right) => left.entryTimestamp - right.entryTimestamp);
1383
+ },
1384
+ placeOrder: async ({
1385
+ symbol,
1386
+ price,
1387
+ qty,
1388
+ direction,
1389
+ isLimit,
1390
+ orderId,
1391
+ signal,
1392
+ leverage
1393
+ }) => {
1394
+ invalidatePositionSnapshot(symbol);
848
1395
  const client = await getPrivateClient();
849
1396
  const marketDataClient = await getPublicClient();
850
1397
  if (!client || !marketDataClient) {
1398
+ if (signal) {
1399
+ signal.orderFailureReason = "BYBIT_CLIENT_UNAVAILABLE";
1400
+ }
851
1401
  return false;
852
1402
  }
853
1403
  const isLong = direction === "LONG";
854
1404
  const meta = await getSymbolMeta(marketDataClient, symbol);
855
- const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
856
- if (orderQty < meta.minOrderQty) {
1405
+ const normalizedQty = normalizeQty(qty, meta);
1406
+ const minNormalizedQty = normalizeQty(meta.minOrderQty, meta);
1407
+ const isQtyBelowMin = normalizedQty.qtyNum < meta.minOrderQty;
1408
+ const orderQty = isQtyBelowMin ? Math.max(minNormalizedQty.qtyNum, meta.minOrderQty) : normalizedQty.qtyNum;
1409
+ const orderQtyStr = isQtyBelowMin ? orderQty.toFixed(meta.qtyPrecision) : normalizedQty.qtyStr;
1410
+ if (isQtyBelowMin) {
857
1411
  import_logger3.logger.log(
858
1412
  "warn",
859
- "placeOrder: qty too small: %s",
1413
+ "placeOrder: qty below min, using minOrderQty: %s",
860
1414
  (0, import_data2.toJson)(
861
- { symbol, qty, orderQty, minOrderQty: meta.minOrderQty },
1415
+ {
1416
+ symbol,
1417
+ qty,
1418
+ requestedOrderQty: normalizedQty.qtyNum,
1419
+ orderQty,
1420
+ minOrderQty: meta.minOrderQty
1421
+ },
862
1422
  true
863
1423
  )
864
1424
  );
865
- return false;
1425
+ }
1426
+ if (signal) {
1427
+ signal.orderQty = orderQty;
1428
+ signal.orderValue = orderQty * price;
866
1429
  }
867
1430
  const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
868
1431
  import_logger3.logger.log(
@@ -880,14 +1443,16 @@ var ByBitConnectorCreator = async (config) => {
880
1443
  true
881
1444
  )
882
1445
  );
1446
+ const requestedLeverage = typeof leverage === "number" && Number.isFinite(leverage) ? Math.max(1, leverage) : 10;
1447
+ const effectiveLeverage = meta.maxLeverage != null ? Math.min(requestedLeverage, meta.maxLeverage) : requestedLeverage;
883
1448
  await client.setLeverage({
884
- category: import_constants3.MARKET_CATEGORY,
1449
+ category: BYBIT_CATEGORY2,
885
1450
  symbol,
886
- buyLeverage: "10",
887
- sellLeverage: "10"
1451
+ buyLeverage: String(effectiveLeverage),
1452
+ sellLeverage: String(effectiveLeverage)
888
1453
  });
889
1454
  const orderRes = await client.submitOrder({
890
- category: import_constants3.MARKET_CATEGORY,
1455
+ category: BYBIT_CATEGORY2,
891
1456
  symbol,
892
1457
  price: entryNormalized?.priceStr || void 0,
893
1458
  side: isLong ? "Buy" : "Sell",
@@ -902,19 +1467,27 @@ var ByBitConnectorCreator = async (config) => {
902
1467
  (0, import_data2.toJson)(orderRes, true)
903
1468
  );
904
1469
  if (orderRes.retCode !== 0) {
1470
+ if (signal) {
1471
+ signal.orderFailureReason = typeof orderRes.retMsg === "string" && orderRes.retMsg.trim() ? orderRes.retMsg.trim() : `BYBIT_RETCODE_${orderRes.retCode}`;
1472
+ }
905
1473
  return false;
906
1474
  }
1475
+ if (signal) {
1476
+ signal.orderFailureReason = void 0;
1477
+ }
1478
+ invalidatePositionSnapshot(symbol);
907
1479
  return true;
908
1480
  },
909
1481
  setTakeProfits,
910
1482
  setStopLoss,
911
1483
  closePosition: async ({ symbol, direction }) => {
1484
+ invalidatePositionSnapshot(symbol);
912
1485
  const client = await getPrivateClient();
913
1486
  if (!client) {
914
1487
  return false;
915
1488
  }
916
1489
  const closeRes = await client.submitOrder({
917
- category: import_constants3.MARKET_CATEGORY,
1490
+ category: BYBIT_CATEGORY2,
918
1491
  symbol,
919
1492
  side: direction === "LONG" ? "Sell" : "Buy",
920
1493
  orderType: "Market",
@@ -931,23 +1504,109 @@ var ByBitConnectorCreator = async (config) => {
931
1504
  if (closeRes.retCode !== 0) {
932
1505
  return false;
933
1506
  }
1507
+ invalidatePositionSnapshot(symbol);
934
1508
  return true;
935
1509
  },
936
- getTickers: async () => {
1510
+ getFundingRateHistory: async (rateRequest) => {
1511
+ const client = await getPublicClient();
1512
+ if (!client) return [];
1513
+ const safeLimit = Math.min(
1514
+ FUNDING_HISTORY_LIMIT,
1515
+ Math.max(1, Math.trunc(rateRequest.limit ?? FUNDING_HISTORY_LIMIT))
1516
+ );
1517
+ const byTimestamp = /* @__PURE__ */ new Map();
1518
+ let cursorEnd = Math.trunc(rateRequest.endTime);
1519
+ while (Number.isFinite(cursorEnd) && cursorEnd >= (rateRequest.startTime ?? 0)) {
1520
+ const response = await client.getFundingRateHistory({
1521
+ category: BYBIT_CATEGORY2,
1522
+ symbol: rateRequest.symbol.trim().toUpperCase(),
1523
+ endTime: cursorEnd,
1524
+ limit: safeLimit
1525
+ });
1526
+ if (response.retCode !== 0) {
1527
+ throw new Error(
1528
+ `Bybit funding history failed: ${response.retCode} ${response.retMsg}`
1529
+ );
1530
+ }
1531
+ const page = response.result?.list ?? [];
1532
+ if (!page.length) break;
1533
+ let oldestTimestamp = Number.POSITIVE_INFINITY;
1534
+ for (const item of page) {
1535
+ const timestamp = Number(item.fundingRateTimestamp);
1536
+ const rate = Number(item.fundingRate);
1537
+ if (!Number.isFinite(timestamp) || !Number.isFinite(rate)) continue;
1538
+ oldestTimestamp = Math.min(oldestTimestamp, timestamp);
1539
+ if (timestamp >= (rateRequest.startTime ?? 0)) {
1540
+ byTimestamp.set(timestamp, {
1541
+ symbol: String(item.symbol ?? rateRequest.symbol),
1542
+ timestamp,
1543
+ rate
1544
+ });
1545
+ }
1546
+ }
1547
+ if (page.length < safeLimit || !Number.isFinite(oldestTimestamp) || oldestTimestamp <= (rateRequest.startTime ?? 0)) {
1548
+ break;
1549
+ }
1550
+ const nextEnd = oldestTimestamp - 1;
1551
+ if (nextEnd >= cursorEnd) break;
1552
+ cursorEnd = nextEnd;
1553
+ }
1554
+ return [...byTimestamp.values()].sort(
1555
+ (left, right) => left.timestamp - right.timestamp
1556
+ );
1557
+ },
1558
+ getTradingFeeRate: async (symbol) => {
1559
+ const client = await getPrivateClient();
1560
+ if (!client) return null;
1561
+ const normalizedSymbol = symbol.trim().toUpperCase();
1562
+ const response = await client.getFeeRate({
1563
+ category: BYBIT_CATEGORY2,
1564
+ symbol: normalizedSymbol
1565
+ });
1566
+ if (response.retCode !== 0) return null;
1567
+ const row = (response.result?.list ?? []).find(
1568
+ (item) => String(item.symbol ?? "").toUpperCase() === normalizedSymbol
1569
+ );
1570
+ const makerRate = Number(row?.makerFeeRate);
1571
+ const takerRate = Number(row?.takerFeeRate);
1572
+ if (!Number.isFinite(makerRate) || !Number.isFinite(takerRate)) {
1573
+ return null;
1574
+ }
1575
+ return {
1576
+ symbol: normalizedSymbol,
1577
+ makerRate,
1578
+ takerRate,
1579
+ source: "exchange-account",
1580
+ capturedAt: Date.now()
1581
+ };
1582
+ },
1583
+ getTickers: async (query = {}) => {
937
1584
  const client = await getPublicClient();
938
1585
  if (!client) {
939
1586
  return [];
940
1587
  }
1588
+ const requestedUniverse = (0, import_types.resolveConnectorUniverse)(
1589
+ BYBIT_CAPABILITIES,
1590
+ query.universe ?? universe
1591
+ );
1592
+ const instruments = await listInstruments({
1593
+ ...query,
1594
+ universe: requestedUniverse
1595
+ });
1596
+ const allowedSymbols = instruments.length ? new Set(instruments.map((instrument) => instrument.symbol)) : null;
941
1597
  const data = await client.getTickers({
942
- category: import_constants3.MARKET_CATEGORY
1598
+ category: BYBIT_CATEGORY2
943
1599
  });
944
- return data.result.list.map((item) => (0, import_tickers.normalizeTickerData)(item));
1600
+ return data.result.list.filter(
1601
+ (item) => allowedSymbols == null || allowedSymbols.has(String(item.symbol ?? ""))
1602
+ ).map((item) => (0, import_tickers.normalizeTickerData)(item));
945
1603
  }
946
1604
  };
947
1605
  };
948
1606
 
949
1607
  // src/Binance/index.ts
950
1608
  var import_http = require("@tradejs/infra/http");
1609
+ var import_types2 = require("@tradejs/types");
951
1610
  var INTERVAL_MAP = {
952
1611
  "1": "1m",
953
1612
  "3": "3m",
@@ -982,12 +1641,21 @@ var toNum = (value, fallback = 0) => {
982
1641
  const num = Number(value);
983
1642
  return Number.isFinite(num) ? num : fallback;
984
1643
  };
1644
+ var toNullableNum = (value) => {
1645
+ const num = Number(value);
1646
+ return Number.isFinite(num) ? num : null;
1647
+ };
985
1648
  var intervalToMinutes = (interval) => {
986
1649
  const intervalMs = INTERVAL_MS[String(interval)];
987
1650
  return intervalMs ? Math.floor(intervalMs / 6e4) : null;
988
1651
  };
989
- var BinanceConnectorCreator = async () => {
1652
+ var capabilities = {
1653
+ supportedUniverses: ["crypto"],
1654
+ defaultUniverse: "crypto"
1655
+ };
1656
+ var BinanceConnectorCreator = async (config) => {
990
1657
  let state = {};
1658
+ const universe = (0, import_types2.resolveConnectorUniverse)(capabilities, config.universe);
991
1659
  const requestKline = async ({
992
1660
  symbol,
993
1661
  interval,
@@ -1019,14 +1687,24 @@ var BinanceConnectorCreator = async () => {
1019
1687
  if (!Array.isArray(item)) continue;
1020
1688
  const ts = toNum(item[0], 0);
1021
1689
  if (!ts) continue;
1690
+ const volume = toNum(item[5]);
1691
+ const turnover = toNum(item[7]);
1692
+ const trades = toNullableNum(item[8]);
1693
+ const takerBuyBaseVolume = toNullableNum(item[9]);
1694
+ const takerBuyQuoteVolume = toNullableNum(item[10]);
1022
1695
  rows.push({
1023
1696
  timestamp: ts,
1024
1697
  open: toNum(item[1]),
1025
1698
  high: toNum(item[2]),
1026
1699
  low: toNum(item[3]),
1027
1700
  close: toNum(item[4]),
1028
- volume: toNum(item[5]),
1029
- turnover: toNum(item[7]),
1701
+ volume,
1702
+ turnover,
1703
+ trades,
1704
+ takerBuyBaseVolume,
1705
+ takerBuyQuoteVolume,
1706
+ takerSellBaseVolume: takerBuyBaseVolume == null ? null : Math.max(0, volume - takerBuyBaseVolume),
1707
+ takerSellQuoteVolume: takerBuyQuoteVolume == null ? null : Math.max(0, turnover - takerBuyQuoteVolume),
1030
1708
  dt: new Date(ts).toISOString()
1031
1709
  });
1032
1710
  lastTs = ts;
@@ -1037,7 +1715,64 @@ var BinanceConnectorCreator = async () => {
1037
1715
  rows.sort((a, b) => a.timestamp - b.timestamp);
1038
1716
  return rows;
1039
1717
  };
1718
+ const loadTickers = async () => {
1719
+ const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
1720
+ const response = await (0, import_http.fetchWithRetry)(`${baseUrl}/api/v3/ticker/24hr`, {
1721
+ headers: { "User-Agent": "tradejs/binance-connector" }
1722
+ });
1723
+ if (!response.ok) return [];
1724
+ const payload = await response.json();
1725
+ if (!Array.isArray(payload)) return [];
1726
+ return payload.map((row) => {
1727
+ const symbol = String(row.symbol ?? "");
1728
+ return {
1729
+ symbol,
1730
+ lastPrice: toNum(row.lastPrice),
1731
+ indexPrice: toNum(row.lastPrice),
1732
+ markPrice: toNum(row.lastPrice),
1733
+ prevPrice24h: toNum(row.openPrice),
1734
+ price24hPcnt: toNum(row.priceChangePercent) / 100,
1735
+ highPrice24h: toNum(row.highPrice),
1736
+ lowPrice24h: toNum(row.lowPrice),
1737
+ prevPrice1h: 0,
1738
+ openInterest: 0,
1739
+ openInterestValue: 0,
1740
+ turnover24h: toNum(row.quoteVolume),
1741
+ volume24h: toNum(row.volume),
1742
+ fundingRate: 0,
1743
+ nextFundingTime: 0,
1744
+ predictedDeliveryPrice: "",
1745
+ basisRate: "",
1746
+ deliveryFeeRate: "",
1747
+ deliveryTime: 0,
1748
+ ask1Size: toNum(row.askQty),
1749
+ bid1Price: toNum(row.bidPrice),
1750
+ ask1Price: toNum(row.askPrice),
1751
+ bid1Size: toNum(row.bidQty),
1752
+ basis: "",
1753
+ preOpenPrice: "",
1754
+ preQty: ""
1755
+ };
1756
+ });
1757
+ };
1040
1758
  return {
1759
+ capabilities,
1760
+ universe,
1761
+ accountId: config.accountId,
1762
+ deploymentId: config.deploymentId,
1763
+ listInstruments: async (query) => {
1764
+ (0, import_types2.resolveConnectorUniverse)(capabilities, query?.universe ?? universe);
1765
+ const symbols = query?.symbols?.length ? new Set(query.symbols.map((symbol) => symbol.trim().toUpperCase())) : null;
1766
+ const tickers = await loadTickers();
1767
+ return tickers.filter((ticker) => !symbols || symbols.has(ticker.symbol)).map((ticker) => ({
1768
+ provider: "binance",
1769
+ symbol: ticker.symbol,
1770
+ kind: "spot",
1771
+ assetClass: "crypto",
1772
+ universe: "crypto",
1773
+ status: "trading"
1774
+ }));
1775
+ },
1041
1776
  getState: async () => state,
1042
1777
  setState: async (newState) => {
1043
1778
  state = { ...state, ...newState };
@@ -1054,51 +1789,91 @@ var BinanceConnectorCreator = async () => {
1054
1789
  setTakeProfits: async () => false,
1055
1790
  setStopLoss: async () => false,
1056
1791
  closePosition: async () => false,
1057
- getTickers: async () => {
1792
+ getTickers: async (query) => {
1793
+ (0, import_types2.resolveConnectorUniverse)(capabilities, query?.universe ?? universe);
1794
+ return loadTickers();
1795
+ },
1796
+ getTopOfBookTicker: async (symbol) => {
1797
+ const normalizedSymbol = String(symbol || "").trim().toUpperCase();
1798
+ if (!normalizedSymbol) return null;
1058
1799
  const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
1059
- const response = await (0, import_http.fetchWithRetry)(`${baseUrl}/api/v3/ticker/24hr`, {
1800
+ const url = new URL(`${baseUrl}/api/v3/ticker/bookTicker`);
1801
+ url.searchParams.set("symbol", normalizedSymbol);
1802
+ const response = await (0, import_http.fetchWithRetry)(url.toString(), {
1803
+ headers: { "User-Agent": "tradejs/binance-connector" }
1804
+ });
1805
+ if (!response.ok) return null;
1806
+ const row = await response.json();
1807
+ return {
1808
+ symbol: String(row.symbol ?? normalizedSymbol),
1809
+ bidPrice: toNum(row.bidPrice),
1810
+ bidQty: toNum(row.bidQty),
1811
+ askPrice: toNum(row.askPrice),
1812
+ askQty: toNum(row.askQty),
1813
+ timestamp: Date.now()
1814
+ };
1815
+ },
1816
+ getAggTrades: async ({ symbol, startTime, endTime, limit = 1e3 }) => {
1817
+ const normalizedSymbol = String(symbol || "").trim().toUpperCase();
1818
+ if (!normalizedSymbol) return [];
1819
+ const safeLimit = Math.min(Math.max(Math.floor(limit), 1), 1e3);
1820
+ const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
1821
+ const url = new URL(`${baseUrl}/api/v3/aggTrades`);
1822
+ url.searchParams.set("symbol", normalizedSymbol);
1823
+ url.searchParams.set("startTime", String(Math.floor(startTime)));
1824
+ url.searchParams.set("endTime", String(Math.floor(endTime)));
1825
+ url.searchParams.set("limit", String(safeLimit));
1826
+ const response = await (0, import_http.fetchWithRetry)(url.toString(), {
1060
1827
  headers: { "User-Agent": "tradejs/binance-connector" }
1061
1828
  });
1062
1829
  if (!response.ok) return [];
1063
1830
  const payload = await response.json();
1064
1831
  if (!Array.isArray(payload)) return [];
1065
- return payload.map((row) => {
1066
- const symbol = String(row.symbol ?? "");
1067
- return {
1068
- symbol,
1069
- lastPrice: toNum(row.lastPrice),
1070
- indexPrice: toNum(row.lastPrice),
1071
- markPrice: toNum(row.lastPrice),
1072
- prevPrice24h: toNum(row.openPrice),
1073
- price24hPcnt: toNum(row.priceChangePercent) / 100,
1074
- highPrice24h: toNum(row.highPrice),
1075
- lowPrice24h: toNum(row.lowPrice),
1076
- prevPrice1h: 0,
1077
- openInterest: 0,
1078
- openInterestValue: 0,
1079
- turnover24h: toNum(row.quoteVolume),
1080
- volume24h: toNum(row.volume),
1081
- fundingRate: 0,
1082
- nextFundingTime: 0,
1083
- predictedDeliveryPrice: "",
1084
- basisRate: "",
1085
- deliveryFeeRate: "",
1086
- deliveryTime: 0,
1087
- ask1Size: toNum(row.askQty),
1088
- bid1Price: toNum(row.bidPrice),
1089
- ask1Price: toNum(row.askPrice),
1090
- bid1Size: toNum(row.bidQty),
1091
- basis: "",
1092
- preOpenPrice: "",
1093
- preQty: ""
1094
- };
1832
+ return payload.map((row) => ({
1833
+ aggregateTradeId: toNum(row.a, Number.NaN),
1834
+ price: toNum(row.p, Number.NaN),
1835
+ quantity: toNum(row.q, Number.NaN),
1836
+ firstTradeId: toNum(row.f, Number.NaN),
1837
+ lastTradeId: toNum(row.l, Number.NaN),
1838
+ timestamp: toNum(row.T, Number.NaN),
1839
+ isBuyerMaker: Boolean(row.m)
1840
+ })).filter(
1841
+ (row) => Number.isFinite(row.aggregateTradeId) && Number.isFinite(row.price) && Number.isFinite(row.quantity) && Number.isFinite(row.timestamp)
1842
+ );
1843
+ },
1844
+ getOrderBookDepth: async ({ symbol, limit = 100 }) => {
1845
+ const normalizedSymbol = String(symbol || "").trim().toUpperCase();
1846
+ if (!normalizedSymbol) return null;
1847
+ const allowedLimits = [5, 10, 20, 50, 100, 500, 1e3, 5e3];
1848
+ const safeLimit = allowedLimits.includes(limit) ? limit : 100;
1849
+ const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
1850
+ const url = new URL(`${baseUrl}/api/v3/depth`);
1851
+ url.searchParams.set("symbol", normalizedSymbol);
1852
+ url.searchParams.set("limit", String(safeLimit));
1853
+ const response = await (0, import_http.fetchWithRetry)(url.toString(), {
1854
+ headers: { "User-Agent": "tradejs/binance-connector" }
1095
1855
  });
1856
+ if (!response.ok) return null;
1857
+ const payload = await response.json();
1858
+ const parseLevels = (levels) => (Array.isArray(levels) ? levels : []).map(
1859
+ (level) => Array.isArray(level) ? [toNum(level[0], Number.NaN), toNum(level[1], Number.NaN)] : null
1860
+ ).filter(
1861
+ (level) => level != null && Number.isFinite(level[0]) && Number.isFinite(level[1])
1862
+ );
1863
+ return {
1864
+ symbol: normalizedSymbol,
1865
+ lastUpdateId: toNullableNum(payload.lastUpdateId),
1866
+ bids: parseLevels(payload.bids),
1867
+ asks: parseLevels(payload.asks),
1868
+ timestamp: Date.now()
1869
+ };
1096
1870
  }
1097
1871
  };
1098
1872
  };
1099
1873
 
1100
1874
  // src/Coinbase/index.ts
1101
1875
  var import_http2 = require("@tradejs/infra/http");
1876
+ var import_types3 = require("@tradejs/types");
1102
1877
  var INTERVAL_MS2 = {
1103
1878
  "1": 6e4,
1104
1879
  "3": 18e4,
@@ -1161,8 +1936,13 @@ var MAJOR_PRODUCTS = [
1161
1936
  "LINK-USD",
1162
1937
  "AVAX-USD"
1163
1938
  ];
1164
- var CoinbaseConnectorCreator = async () => {
1939
+ var capabilities2 = {
1940
+ supportedUniverses: ["crypto"],
1941
+ defaultUniverse: "crypto"
1942
+ };
1943
+ var CoinbaseConnectorCreator = async (config) => {
1165
1944
  let state = {};
1945
+ const universe = (0, import_types3.resolveConnectorUniverse)(capabilities2, config.universe);
1166
1946
  const requestKline = async ({
1167
1947
  symbol,
1168
1948
  interval,
@@ -1214,7 +1994,76 @@ var CoinbaseConnectorCreator = async () => {
1214
1994
  }
1215
1995
  return [...dedup.values()].sort((a, b) => a.timestamp - b.timestamp);
1216
1996
  };
1997
+ const loadTickers = async () => {
1998
+ const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
1999
+ const entries = await Promise.all(
2000
+ MAJOR_PRODUCTS.map(async (product) => {
2001
+ const [tickerRes, statsRes] = await Promise.all([
2002
+ (0, import_http2.fetchWithRetry)(`${baseUrl}/products/${product}/ticker`, {
2003
+ headers: { "User-Agent": "tradejs/coinbase-connector" }
2004
+ }),
2005
+ (0, import_http2.fetchWithRetry)(`${baseUrl}/products/${product}/stats`, {
2006
+ headers: { "User-Agent": "tradejs/coinbase-connector" }
2007
+ })
2008
+ ]);
2009
+ if (!tickerRes.ok || !statsRes.ok) return null;
2010
+ const ticker = await tickerRes.json();
2011
+ const stats = await statsRes.json();
2012
+ const base = product.replace("-USD", "");
2013
+ const symbol = `${base}USDT`;
2014
+ const open = toNum2(stats.open);
2015
+ const last = toNum2(ticker.price);
2016
+ const pct = open > 0 ? (last - open) / open : 0;
2017
+ return {
2018
+ symbol,
2019
+ lastPrice: last,
2020
+ indexPrice: last,
2021
+ markPrice: last,
2022
+ prevPrice24h: open,
2023
+ price24hPcnt: pct,
2024
+ highPrice24h: toNum2(stats.high),
2025
+ lowPrice24h: toNum2(stats.low),
2026
+ prevPrice1h: 0,
2027
+ openInterest: 0,
2028
+ openInterestValue: 0,
2029
+ turnover24h: 0,
2030
+ volume24h: toNum2(stats.volume),
2031
+ fundingRate: 0,
2032
+ nextFundingTime: 0,
2033
+ predictedDeliveryPrice: "",
2034
+ basisRate: "",
2035
+ deliveryFeeRate: "",
2036
+ deliveryTime: 0,
2037
+ ask1Size: toNum2(ticker.ask_size),
2038
+ bid1Price: toNum2(ticker.bid),
2039
+ ask1Price: toNum2(ticker.ask),
2040
+ bid1Size: toNum2(ticker.bid_size),
2041
+ basis: "",
2042
+ preOpenPrice: "",
2043
+ preQty: ""
2044
+ };
2045
+ })
2046
+ );
2047
+ return entries.filter((item) => item != null);
2048
+ };
1217
2049
  return {
2050
+ capabilities: capabilities2,
2051
+ universe,
2052
+ accountId: config.accountId,
2053
+ deploymentId: config.deploymentId,
2054
+ listInstruments: async (query) => {
2055
+ (0, import_types3.resolveConnectorUniverse)(capabilities2, query?.universe ?? universe);
2056
+ const symbols = query?.symbols?.length ? new Set(query.symbols.map((symbol) => symbol.trim().toUpperCase())) : null;
2057
+ const tickers = await loadTickers();
2058
+ return tickers.filter((ticker) => !symbols || symbols.has(ticker.symbol)).map((ticker) => ({
2059
+ provider: "coinbase",
2060
+ symbol: ticker.symbol,
2061
+ kind: "spot",
2062
+ assetClass: "crypto",
2063
+ universe: "crypto",
2064
+ status: "trading"
2065
+ }));
2066
+ },
1218
2067
  getState: async () => state,
1219
2068
  setState: async (newState) => {
1220
2069
  state = { ...state, ...newState };
@@ -1231,57 +2080,9 @@ var CoinbaseConnectorCreator = async () => {
1231
2080
  setTakeProfits: async () => false,
1232
2081
  setStopLoss: async () => false,
1233
2082
  closePosition: async () => false,
1234
- getTickers: async () => {
1235
- const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
1236
- const entries = await Promise.all(
1237
- MAJOR_PRODUCTS.map(async (product) => {
1238
- const [tickerRes, statsRes] = await Promise.all([
1239
- (0, import_http2.fetchWithRetry)(`${baseUrl}/products/${product}/ticker`, {
1240
- headers: { "User-Agent": "tradejs/coinbase-connector" }
1241
- }),
1242
- (0, import_http2.fetchWithRetry)(`${baseUrl}/products/${product}/stats`, {
1243
- headers: { "User-Agent": "tradejs/coinbase-connector" }
1244
- })
1245
- ]);
1246
- if (!tickerRes.ok || !statsRes.ok) return null;
1247
- const ticker = await tickerRes.json();
1248
- const stats = await statsRes.json();
1249
- const base = product.replace("-USD", "");
1250
- const symbol = `${base}USDT`;
1251
- const open = toNum2(stats.open);
1252
- const last = toNum2(ticker.price);
1253
- const pct = open > 0 ? (last - open) / open : 0;
1254
- return {
1255
- symbol,
1256
- lastPrice: last,
1257
- indexPrice: last,
1258
- markPrice: last,
1259
- prevPrice24h: open,
1260
- price24hPcnt: pct,
1261
- highPrice24h: toNum2(stats.high),
1262
- lowPrice24h: toNum2(stats.low),
1263
- prevPrice1h: 0,
1264
- openInterest: 0,
1265
- openInterestValue: 0,
1266
- turnover24h: 0,
1267
- volume24h: toNum2(stats.volume),
1268
- fundingRate: 0,
1269
- nextFundingTime: 0,
1270
- predictedDeliveryPrice: "",
1271
- basisRate: "",
1272
- deliveryFeeRate: "",
1273
- deliveryTime: 0,
1274
- ask1Size: toNum2(ticker.ask_size),
1275
- bid1Price: toNum2(ticker.bid),
1276
- ask1Price: toNum2(ticker.ask),
1277
- bid1Size: toNum2(ticker.bid_size),
1278
- basis: "",
1279
- preOpenPrice: "",
1280
- preQty: ""
1281
- };
1282
- })
1283
- );
1284
- return entries.filter((item) => item != null);
2083
+ getTickers: async (query) => {
2084
+ (0, import_types3.resolveConnectorUniverse)(capabilities2, query?.universe ?? universe);
2085
+ return loadTickers();
1285
2086
  }
1286
2087
  };
1287
2088
  };
@@ -1301,18 +2102,26 @@ var mapBinanceKline = (payload) => payload.map((item) => {
1301
2102
  const low = Number(item[3]);
1302
2103
  const close = Number(item[4]);
1303
2104
  const volume = Number(item[5]);
2105
+ const turnover = Number(item[7]) || 0;
2106
+ const takerBuyBaseVolume = Number(item[9]);
2107
+ const takerBuyQuoteVolume = Number(item[10]);
1304
2108
  if (![ts, open, high, low, close, volume].every(Number.isFinite))
1305
2109
  return null;
1306
- return {
2110
+ const row = {
1307
2111
  timestamp: ts,
1308
2112
  open,
1309
2113
  high,
1310
2114
  low,
1311
2115
  close,
1312
2116
  volume,
1313
- turnover: Number(item[7]) || 0,
2117
+ turnover,
2118
+ takerBuyBaseVolume: Number.isFinite(takerBuyBaseVolume) ? takerBuyBaseVolume : null,
2119
+ takerBuyQuoteVolume: Number.isFinite(takerBuyQuoteVolume) ? takerBuyQuoteVolume : null,
2120
+ takerSellBaseVolume: Number.isFinite(takerBuyBaseVolume) ? Math.max(0, volume - takerBuyBaseVolume) : null,
2121
+ takerSellQuoteVolume: Number.isFinite(takerBuyQuoteVolume) ? Math.max(0, turnover - takerBuyQuoteVolume) : null,
1314
2122
  dt: new Date(ts).toISOString()
1315
2123
  };
2124
+ return row;
1316
2125
  }).filter((item) => item != null).sort((a, b) => a.timestamp - b.timestamp);
1317
2126
  var mapCoinbaseKline = (payload) => payload.map((item) => {
1318
2127
  if (!Array.isArray(item)) return null;
@@ -1385,6 +2194,144 @@ var spotKlineProviders = {
1385
2194
  }
1386
2195
  };
1387
2196
 
2197
+ // src/ByBit/klineStream.ts
2198
+ var import_bybit_api2 = require("bybit-api");
2199
+ var import_time4 = require("@tradejs/core/time");
2200
+ var import_logger4 = require("@tradejs/infra/logger");
2201
+ var import_tradingAccounts2 = require("@tradejs/infra/tradingAccounts");
2202
+ var CATEGORY = "linear";
2203
+ var SUBSCRIPTION_BATCH_SIZE = 100;
2204
+ var KLINE_TOPIC_PATTERN = /^kline\.([^.]+)\.(.+)$/;
2205
+ var chunk = (items, size) => {
2206
+ const result = [];
2207
+ for (let index = 0; index < items.length; index += size) {
2208
+ result.push(items.slice(index, index + size));
2209
+ }
2210
+ return result;
2211
+ };
2212
+ var normalizeSymbols = (symbols) => [...new Set(symbols.map((symbol) => symbol.trim().toUpperCase()))].filter(
2213
+ Boolean
2214
+ );
2215
+ var buildBybitKlineTopic = (symbol, interval) => `kline.${interval}.${symbol.trim().toUpperCase()}`;
2216
+ var parseBybitKlineEvent = (value, receivedAt = Date.now()) => {
2217
+ if (!value || typeof value !== "object") return [];
2218
+ const event = value;
2219
+ const match = String(event.topic ?? "").match(KLINE_TOPIC_PATTERN);
2220
+ if (!match || !Array.isArray(event.data)) return [];
2221
+ const [, interval, symbol] = match;
2222
+ return event.data.flatMap((raw) => {
2223
+ const row = raw;
2224
+ const timestamp = Number(row.start);
2225
+ const open = Number(row.open);
2226
+ const high = Number(row.high);
2227
+ const low = Number(row.low);
2228
+ const close = Number(row.close);
2229
+ const volume = Number(row.volume);
2230
+ const turnover = Number(row.turnover);
2231
+ if (![timestamp, open, high, low, close, volume, turnover].every(
2232
+ Number.isFinite
2233
+ ) || timestamp < 0 || high < Math.max(open, close) || low > Math.min(open, close)) {
2234
+ return [];
2235
+ }
2236
+ return [
2237
+ {
2238
+ symbol: symbol.toUpperCase(),
2239
+ interval,
2240
+ candle: {
2241
+ dt: (0, import_time4.formatUnix)(timestamp),
2242
+ timestamp,
2243
+ open,
2244
+ high,
2245
+ low,
2246
+ close,
2247
+ volume,
2248
+ turnover
2249
+ },
2250
+ confirm: Boolean(row.confirm),
2251
+ receivedAt
2252
+ }
2253
+ ];
2254
+ });
2255
+ };
2256
+ var createBybitKlineStreamWithClient = ({
2257
+ client,
2258
+ onEvent
2259
+ }) => {
2260
+ let topics = /* @__PURE__ */ new Set();
2261
+ let closed = false;
2262
+ client.on("update", (value) => {
2263
+ for (const event of parseBybitKlineEvent(value)) {
2264
+ void Promise.resolve(onEvent(event)).catch((error) => {
2265
+ import_logger4.logger.error("Bybit kline stream handler failed: %s", String(error));
2266
+ });
2267
+ }
2268
+ });
2269
+ client.on("reconnected", ({ wsKey }) => {
2270
+ import_logger4.logger.info("Bybit kline stream reconnected: %s", wsKey);
2271
+ });
2272
+ client.on("exception", (error) => {
2273
+ import_logger4.logger.error("Bybit kline stream exception: %s", String(error));
2274
+ });
2275
+ return {
2276
+ setSubscriptions: (symbols, interval) => {
2277
+ if (closed) return;
2278
+ const nextTopics = new Set(
2279
+ normalizeSymbols(symbols).map(
2280
+ (symbol) => buildBybitKlineTopic(symbol, interval)
2281
+ )
2282
+ );
2283
+ const added = [...nextTopics].filter((topic) => !topics.has(topic));
2284
+ const removed = [...topics].filter((topic) => !nextTopics.has(topic));
2285
+ for (const batch of chunk(removed, SUBSCRIPTION_BATCH_SIZE)) {
2286
+ void Promise.all(client.unsubscribeV5(batch, CATEGORY)).catch(
2287
+ (error) => import_logger4.logger.error("Bybit kline unsubscribe failed: %s", String(error))
2288
+ );
2289
+ }
2290
+ for (const batch of chunk(added, SUBSCRIPTION_BATCH_SIZE)) {
2291
+ void Promise.all(client.subscribeV5(batch, CATEGORY)).catch(
2292
+ (error) => import_logger4.logger.error("Bybit kline subscribe failed: %s", String(error))
2293
+ );
2294
+ }
2295
+ topics = nextTopics;
2296
+ import_logger4.logger.info(
2297
+ "Bybit kline stream subscriptions: topics=%s added=%s removed=%s",
2298
+ topics.size,
2299
+ added.length,
2300
+ removed.length
2301
+ );
2302
+ },
2303
+ close: async () => {
2304
+ if (closed) return;
2305
+ closed = true;
2306
+ const currentTopics = [...topics];
2307
+ topics.clear();
2308
+ for (const batch of chunk(currentTopics, SUBSCRIPTION_BATCH_SIZE)) {
2309
+ await Promise.allSettled(client.unsubscribeV5(batch, CATEGORY));
2310
+ }
2311
+ client.closeAll();
2312
+ }
2313
+ };
2314
+ };
2315
+ var createBybitKlineStream = async ({
2316
+ config,
2317
+ onEvent
2318
+ }) => {
2319
+ const account = config.accountId ? await (0, import_tradingAccounts2.resolveTradingAccount)({
2320
+ userName: config.userName,
2321
+ accountId: config.accountId,
2322
+ provider: "bybit",
2323
+ universe: config.universe
2324
+ }) : null;
2325
+ const client = new import_bybit_api2.WebsocketClient({
2326
+ testnet: account?.environment === "testnet",
2327
+ reconnectTimeout: Math.max(
2328
+ 500,
2329
+ Number(process.env.BYBIT_WS_RECONNECT_TIMEOUT_MS ?? 1e3)
2330
+ )
2331
+ });
2332
+ return createBybitKlineStreamWithClient({ client, onEvent });
2333
+ };
2334
+
1388
2335
  // src/marketData/providers/binanceCoinbaseSpread.ts
1389
2336
  var import_indicators = require("@tradejs/core/indicators");
1390
2337
  var fetchBinanceKlines = async (params) => {
@@ -1441,7 +2388,7 @@ var binanceCoinbaseSpreadProvider = {
1441
2388
  };
1442
2389
 
1443
2390
  // src/marketData/providers/coinalyze.ts
1444
- var import_async2 = require("@tradejs/core/async");
2391
+ var import_async3 = require("@tradejs/core/async");
1445
2392
  var import_indicators2 = require("@tradejs/core/indicators");
1446
2393
  var coinalyzeIntervalMap = {
1447
2394
  "15m": "15min",
@@ -1511,7 +2458,7 @@ var fetchCoinalyzeSeries = async (params) => {
1511
2458
  lastRequestTs + coinalyzeMinRequestDelayMs - now
1512
2459
  );
1513
2460
  if (waitMs > 0) {
1514
- await (0, import_async2.delay)(waitMs);
2461
+ await (0, import_async3.delay)(waitMs);
1515
2462
  }
1516
2463
  lastRequestTs = Date.now();
1517
2464
  const response = await fetch(url.toString(), { headers });
@@ -1525,7 +2472,7 @@ var fetchCoinalyzeSeries = async (params) => {
1525
2472
  const transient = response.status === 429 || response.status >= 500;
1526
2473
  if (attempt < coinalyzeMaxRetries && transient) {
1527
2474
  const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
1528
- await (0, import_async2.delay)(retryAfterMs ?? backoffMs);
2475
+ await (0, import_async3.delay)(retryAfterMs ?? backoffMs);
1529
2476
  continue;
1530
2477
  }
1531
2478
  throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
@@ -1654,11 +2601,15 @@ var index_default = { connectorEntries };
1654
2601
  0 && (module.exports = {
1655
2602
  ConnectorNames,
1656
2603
  ConnectorProviders,
2604
+ buildBybitKlineTopic,
1657
2605
  connectorEntries,
1658
2606
  connectors,
2607
+ createBybitKlineStream,
2608
+ createBybitKlineStreamWithClient,
1659
2609
  getConnectorCreatorByProvider,
1660
2610
  getConnectorProviders,
1661
2611
  marketDataProviders,
2612
+ parseBybitKlineEvent,
1662
2613
  providerToConnectorName,
1663
2614
  resolveConnectorNameByProvider,
1664
2615
  spotKlineProviders