@tradejs/connectors 1.0.0

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.mjs ADDED
@@ -0,0 +1,1293 @@
1
+ // src/ByBit/index.ts
2
+ import _ from "lodash";
3
+ import chalk from "chalk";
4
+ import {
5
+ MARKET_CATEGORY as MARKET_CATEGORY2,
6
+ PRELOAD_FALLBACK_DAYS
7
+ } from "@tradejs/core/constants";
8
+ import { mergeData } from "@tradejs/core/data";
9
+ import { toJson } from "@tradejs/core/json";
10
+ import { round } from "@tradejs/core/math";
11
+ import { normalizeTickerData } from "@tradejs/core/tickers";
12
+ import { formatUnix as formatUnix2, getItemTimestamp, getTimestamp } from "@tradejs/core/time";
13
+ import { logger as logger2 } from "@tradejs/infra/logger";
14
+ import {
15
+ getCandlesRange,
16
+ getDataEdges,
17
+ upsertCandles,
18
+ toRows
19
+ } from "@tradejs/infra/timescale";
20
+
21
+ // src/ByBit/client.ts
22
+ import { RestClientV5 } from "bybit-api";
23
+ import { logger } from "@tradejs/infra/logger";
24
+ import { getData, redisKeys } from "@tradejs/infra/redis";
25
+ var useTestnet = false;
26
+ var getClient = async ({ userName }) => {
27
+ const user = await getData(redisKeys.user(userName));
28
+ if (!user) {
29
+ logger.log("error", "connection config not found: %s", userName);
30
+ return null;
31
+ }
32
+ const client = new RestClientV5({
33
+ key: user.BYBIT_API_KEY,
34
+ secret: user.BYBIT_API_SECRET,
35
+ testnet: useTestnet
36
+ });
37
+ return client;
38
+ };
39
+
40
+ // src/ByBit/utils.ts
41
+ import { MARKET_CATEGORY } from "@tradejs/core/constants";
42
+ import { formatUnix } from "@tradejs/core/time";
43
+ var parseKlineItem = (item) => ({
44
+ dt: formatUnix(parseInt(item[0])),
45
+ timestamp: parseInt(item[0]),
46
+ open: parseFloat(item[1]),
47
+ high: parseFloat(item[2]),
48
+ low: parseFloat(item[3]),
49
+ close: parseFloat(item[4]),
50
+ volume: parseFloat(item[5]),
51
+ turnover: parseFloat(item[6])
52
+ });
53
+ var mapKlineToChartData = (data) => data.map(parseKlineItem);
54
+ var symbolMetaCache = /* @__PURE__ */ new Map();
55
+ var roundByStep = (value, step, mode = "floor") => {
56
+ if (!step || !Number.isFinite(value)) return value;
57
+ const ratio = value / step;
58
+ const fn = mode === "ceil" ? Math.ceil : mode === "round" ? Math.round : Math.floor;
59
+ const res = fn(ratio) * step;
60
+ return Number(res.toFixed(12));
61
+ };
62
+ var stepToPrecision = (raw) => {
63
+ const [, decimals = ""] = raw.split(".");
64
+ return decimals.length;
65
+ };
66
+ var getSymbolMeta = async (client, symbol) => {
67
+ const cached = symbolMetaCache.get(symbol);
68
+ if (cached) return cached;
69
+ const res = await client.getInstrumentsInfo({
70
+ category: MARKET_CATEGORY,
71
+ symbol
72
+ });
73
+ const item = res?.result?.list?.[0];
74
+ if (!item) {
75
+ throw new Error(`No instrument info for symbol ${symbol}`);
76
+ }
77
+ const tickSizeStr = item.priceFilter?.tickSize ?? "0.01";
78
+ const qtyStepStr = item.lotSizeFilter?.qtyStep ?? "0.001";
79
+ const minOrderQtyStr = item.lotSizeFilter?.minOrderQty ?? "0.001";
80
+ const meta = {
81
+ tickSize: Number(tickSizeStr),
82
+ qtyStep: Number(qtyStepStr),
83
+ minOrderQty: Number(minOrderQtyStr),
84
+ pricePrecision: stepToPrecision(tickSizeStr),
85
+ qtyPrecision: stepToPrecision(qtyStepStr)
86
+ };
87
+ symbolMetaCache.set(symbol, meta);
88
+ return meta;
89
+ };
90
+ var normalizeQty = (rawQty, meta) => {
91
+ const qtyNum = roundByStep(rawQty, meta.qtyStep, "floor");
92
+ const qtyStr = qtyNum.toFixed(meta.qtyPrecision);
93
+ return { qtyNum, qtyStr };
94
+ };
95
+ var normalizePrice = (rawPrice, role, meta) => {
96
+ let mode = "floor";
97
+ switch (role) {
98
+ case "TP_LONG":
99
+ mode = "ceil";
100
+ break;
101
+ case "TP_SHORT":
102
+ mode = "floor";
103
+ break;
104
+ case "SL_LONG":
105
+ mode = "floor";
106
+ break;
107
+ case "SL_SHORT":
108
+ mode = "ceil";
109
+ break;
110
+ case "ENTRY":
111
+ default:
112
+ mode = "floor";
113
+ }
114
+ const priceNum = roundByStep(rawPrice, meta.tickSize, mode);
115
+ const priceStr = priceNum.toFixed(meta.pricePrecision);
116
+ return { priceNum, priceStr };
117
+ };
118
+ var mapPositionData = (data) => {
119
+ if (!data) {
120
+ return [];
121
+ }
122
+ return data.filter((item) => parseFloat(item.size) > 0).map((item) => ({
123
+ symbol: item.symbol,
124
+ price: parseFloat(item.avgPrice),
125
+ slPrice: parseFloat(item.stopLoss || ""),
126
+ qty: parseFloat(item.size),
127
+ direction: item.side === "Buy" ? "LONG" : "SHORT"
128
+ }));
129
+ };
130
+
131
+ // src/ByBit/index.ts
132
+ var LIMIT = 1e3;
133
+ var CACHE_FALLBACK_WINDOW = 1e3;
134
+ var INTERVAL_TO_MINUTES = {
135
+ "1": 1,
136
+ "3": 3,
137
+ "5": 5,
138
+ "15": 15,
139
+ "30": 30,
140
+ "60": 60,
141
+ "120": 120,
142
+ "240": 240,
143
+ "360": 360,
144
+ "720": 720,
145
+ D: 1440,
146
+ W: 10080,
147
+ M: 43200
148
+ };
149
+ var getLogLevel = (res) => res.retCode === 0 ? "info" : "error";
150
+ var ByBitConnectorCreator = async (config) => {
151
+ let state = {};
152
+ let isTimescaleFallbackMode = false;
153
+ const request = async ({
154
+ symbol,
155
+ interval,
156
+ start,
157
+ end,
158
+ silent
159
+ }) => {
160
+ const normalizedStart = round(
161
+ start || getTimestamp(PRELOAD_FALLBACK_DAYS),
162
+ 0
163
+ );
164
+ const normalizedEnd = round(end || Date.now());
165
+ try {
166
+ const client = await getClient(config);
167
+ if (!client) return [];
168
+ if (normalizedEnd <= normalizedStart) {
169
+ return [];
170
+ }
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
+ );
192
+ }
193
+ return mapKlineToChartData(kline.result.list.reverse());
194
+ } catch (error) {
195
+ logger2.log(
196
+ "error",
197
+ "request kline: %s %s %s",
198
+ normalizedStart,
199
+ normalizedEnd,
200
+ error
201
+ );
202
+ return [];
203
+ }
204
+ };
205
+ const loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs) => {
206
+ if (pointer === void 0) return [];
207
+ let accumulated = [];
208
+ let fulfilled = false;
209
+ while (!fulfilled) {
210
+ const currentPointer = pointer;
211
+ const params = {
212
+ symbol: requestParams.symbol,
213
+ interval: requestParams.interval,
214
+ silent: requestParams.silent
215
+ };
216
+ if (direction === "older") {
217
+ params.end = pointer;
218
+ if (limitBoundary !== void 0) params.start = limitBoundary;
219
+ } else {
220
+ params.start = pointer;
221
+ if (limitBoundary !== void 0) params.end = limitBoundary;
222
+ }
223
+ const partData = await request(params);
224
+ if (_.isEmpty(partData)) {
225
+ fulfilled = true;
226
+ break;
227
+ }
228
+ accumulated = direction === "older" ? mergeData(partData, accumulated) : mergeData(accumulated, partData);
229
+ const boundaryReached = limitBoundary !== void 0 && (direction === "older" && currentPointer <= limitBoundary || direction === "newer" && currentPointer >= limitBoundary);
230
+ if (partData.length < LIMIT || boundaryReached) {
231
+ fulfilled = true;
232
+ break;
233
+ }
234
+ const nextPointer = direction === "older" ? getItemTimestamp(partData[0]) - intervalMs : getItemTimestamp(partData[partData.length - 1]) + intervalMs;
235
+ if (!Number.isFinite(nextPointer) || nextPointer === currentPointer) {
236
+ fulfilled = true;
237
+ break;
238
+ }
239
+ pointer = nextPointer;
240
+ }
241
+ return accumulated;
242
+ };
243
+ const intervalMsOf = (interval) => interval * 6e4;
244
+ const intervalToMinutes = (interval) => {
245
+ return INTERVAL_TO_MINUTES[String(interval)] ?? null;
246
+ };
247
+ const clampToClosedCandle = (value, intervalMs) => Math.floor(value / intervalMs) * intervalMs;
248
+ const normalizeRangeToClosed = (intervalMs, start, end) => {
249
+ const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
250
+ const normStart = start !== void 0 ? clampToClosedCandle(start, intervalMs) : 0;
251
+ const cappedEnd = Math.min(end ?? Date.now(), lastClosed);
252
+ const normEnd = clampToClosedCandle(cappedEnd, intervalMs);
253
+ return { normStart, normEnd, lastClosed };
254
+ };
255
+ const rowsToKline = (rows) => rows.map(({ ts, ...data }) => ({
256
+ timestamp: new Date(ts).getTime(),
257
+ ...data
258
+ }));
259
+ const refreshTail = async ({
260
+ symbol,
261
+ interval,
262
+ silent,
263
+ tailCount = 2
264
+ }) => {
265
+ const intMinutes = intervalToMinutes(interval);
266
+ if (!intMinutes) {
267
+ logger2.log("error", "refreshTail: invalid interval %s", interval);
268
+ return;
269
+ }
270
+ const intervalMs = intervalMsOf(intMinutes);
271
+ const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
272
+ const tailEnd = lastClosed + intervalMs;
273
+ const tailStart = tailEnd - tailCount * intervalMs;
274
+ const part = await request({
275
+ symbol,
276
+ interval,
277
+ start: tailStart,
278
+ end: tailEnd,
279
+ silent
280
+ });
281
+ if (part.length) {
282
+ await upsertCandles(toRows(symbol, intMinutes, part));
283
+ }
284
+ };
285
+ return {
286
+ getState: async () => state,
287
+ setState: async (newState) => {
288
+ state = { ...state, ...newState };
289
+ },
290
+ kline: async ({
291
+ symbol,
292
+ interval,
293
+ start: defaultStart,
294
+ end: defaultEnd,
295
+ silent = false,
296
+ cacheOnly = false
297
+ }) => {
298
+ const intMinutes = intervalToMinutes(interval);
299
+ if (!intMinutes) {
300
+ logger2.log("error", "kline: invalid interval %s", interval);
301
+ return [];
302
+ }
303
+ if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
304
+ return [];
305
+ }
306
+ const intervalMs = intervalMsOf(intMinutes);
307
+ try {
308
+ const edges = await getDataEdges(symbol, intMinutes);
309
+ let dataStart = edges.min;
310
+ let dataEnd = edges.max;
311
+ const { normStart, normEnd } = normalizeRangeToClosed(
312
+ intervalMs,
313
+ defaultStart,
314
+ defaultEnd
315
+ );
316
+ if (cacheOnly) {
317
+ const base = edges.max ?? Date.now();
318
+ const s = Math.max(
319
+ defaultStart ?? base - CACHE_FALLBACK_WINDOW * intervalMs,
320
+ 0
321
+ );
322
+ const e = defaultEnd ?? base;
323
+ const dbData2 = await getCandlesRange(symbol, intMinutes, s, e);
324
+ return rowsToKline(dbData2);
325
+ }
326
+ const needOlderData = defaultStart !== void 0 && (dataStart === void 0 || normStart < dataStart);
327
+ const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
328
+ if (needOlderData) {
329
+ const pointerForOlder = dataStart ?? normEnd ?? Date.now();
330
+ const olderData = await loadData(
331
+ "older",
332
+ pointerForOlder,
333
+ normStart,
334
+ {
335
+ symbol,
336
+ interval,
337
+ silent,
338
+ start: normStart,
339
+ end: pointerForOlder
340
+ },
341
+ intervalMs
342
+ );
343
+ if (olderData.length) {
344
+ await upsertCandles(toRows(symbol, intMinutes, olderData));
345
+ dataStart = normStart;
346
+ }
347
+ }
348
+ if (needNewerData) {
349
+ const pointerForNewer = dataEnd ?? normStart ?? 0;
350
+ const newerData = await loadData(
351
+ "newer",
352
+ pointerForNewer,
353
+ normEnd,
354
+ {
355
+ symbol,
356
+ interval,
357
+ silent,
358
+ start: pointerForNewer,
359
+ end: normEnd
360
+ },
361
+ intervalMs
362
+ );
363
+ if (newerData.length) {
364
+ await upsertCandles(toRows(symbol, intMinutes, newerData));
365
+ dataEnd = normEnd;
366
+ }
367
+ }
368
+ const isRightEdgeQuery = defaultEnd === void 0 || defaultEnd && defaultEnd >= Date.now() - intervalMs;
369
+ if (!cacheOnly && isRightEdgeQuery) {
370
+ await refreshTail({ symbol, interval, silent });
371
+ }
372
+ const rangeStart = defaultStart ?? dataStart ?? 0;
373
+ const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
374
+ const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
375
+ const dbData = await getCandlesRange(
376
+ symbol,
377
+ intMinutes,
378
+ finalStart,
379
+ finalEnd
380
+ );
381
+ if (isTimescaleFallbackMode) {
382
+ isTimescaleFallbackMode = false;
383
+ logger2.log("info", "TimescaleDB connection restored for kline cache");
384
+ }
385
+ return rowsToKline(dbData);
386
+ } catch (error) {
387
+ if (!isTimescaleFallbackMode) {
388
+ isTimescaleFallbackMode = true;
389
+ logger2.log(
390
+ "warn",
391
+ "TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
392
+ symbol,
393
+ interval,
394
+ String(error)
395
+ );
396
+ }
397
+ if (cacheOnly) {
398
+ return [];
399
+ }
400
+ return request({
401
+ symbol,
402
+ interval,
403
+ start: defaultStart,
404
+ end: defaultEnd,
405
+ silent
406
+ });
407
+ }
408
+ },
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
+ },
442
+ getPositions: async () => {
443
+ const client = await getClient(config);
444
+ if (!client) {
445
+ return [];
446
+ }
447
+ const positionRes = await client.getPositionInfo({
448
+ category: MARKET_CATEGORY2,
449
+ settleCoin: "USDT"
450
+ });
451
+ logger2.log(
452
+ getLogLevel(positionRes),
453
+ "positions retCode: %s, %s",
454
+ positionRes.retCode
455
+ );
456
+ if (positionRes.retCode !== 0) {
457
+ return [];
458
+ }
459
+ const positions = mapPositionData(positionRes.result.list);
460
+ if (!positions || _.isEmpty(positions)) {
461
+ return [];
462
+ }
463
+ return positions;
464
+ },
465
+ placeOrder: async ({ symbol, price, qty, direction, isLimit }, TP = [], slPrice) => {
466
+ const client = await getClient(config);
467
+ if (!client) {
468
+ return false;
469
+ }
470
+ const isLong = direction === "LONG";
471
+ const meta = await getSymbolMeta(client, symbol);
472
+ const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
473
+ if (orderQty < meta.minOrderQty) {
474
+ logger2.log(
475
+ "warn",
476
+ "placeOrder: qty too small: %s",
477
+ toJson(
478
+ { symbol, qty, orderQty, minOrderQty: meta.minOrderQty },
479
+ true
480
+ )
481
+ );
482
+ return false;
483
+ }
484
+ 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
+ logger2.log(
489
+ "info",
490
+ "placeOrder: %s",
491
+ toJson(
492
+ {
493
+ symbol,
494
+ price,
495
+ qty,
496
+ direction,
497
+ orderQty,
498
+ orderQtyStr,
499
+ slPrice,
500
+ slPriceNorm: slNormalized?.priceStr,
501
+ TP
502
+ },
503
+ true
504
+ )
505
+ );
506
+ await client.setLeverage({
507
+ category: MARKET_CATEGORY2,
508
+ symbol,
509
+ buyLeverage: "10",
510
+ sellLeverage: "10"
511
+ });
512
+ const orderRes = await client.submitOrder({
513
+ category: MARKET_CATEGORY2,
514
+ symbol,
515
+ price: entryNormalized?.priceStr || void 0,
516
+ takeProfit: tpNormalized?.priceStr || void 0,
517
+ tpTriggerBy: "MarkPrice",
518
+ stopLoss: slNormalized?.priceStr || void 0,
519
+ slTriggerBy: "LastPrice",
520
+ side: isLong ? "Buy" : "Sell",
521
+ orderType: isLimit ? "Limit" : "Market",
522
+ qty: orderQtyStr,
523
+ orderFilter: "Order"
524
+ });
525
+ logger2.log(
526
+ getLogLevel(orderRes),
527
+ "placeOrder:response: %s",
528
+ toJson(orderRes, true)
529
+ );
530
+ if (orderRes.retCode !== 0) {
531
+ return false;
532
+ }
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
+ return true;
577
+ },
578
+ closePosition: async ({ symbol, direction }) => {
579
+ const client = await getClient(config);
580
+ if (!client) {
581
+ return false;
582
+ }
583
+ const closeRes = await client.submitOrder({
584
+ category: MARKET_CATEGORY2,
585
+ symbol,
586
+ side: direction === "LONG" ? "Sell" : "Buy",
587
+ orderType: "Market",
588
+ qty: "0",
589
+ reduceOnly: true
590
+ });
591
+ logger2.log(
592
+ getLogLevel(closeRes),
593
+ "closePosition: %s, %s, %s",
594
+ symbol,
595
+ direction,
596
+ toJson(closeRes, true)
597
+ );
598
+ if (closeRes.retCode !== 0) {
599
+ return false;
600
+ }
601
+ return true;
602
+ },
603
+ getTickers: async () => {
604
+ const client = await getClient(config);
605
+ if (!client) {
606
+ return [];
607
+ }
608
+ const data = await client.getTickers({
609
+ category: MARKET_CATEGORY2
610
+ });
611
+ return data.result.list.map((item) => normalizeTickerData(item));
612
+ }
613
+ };
614
+ };
615
+
616
+ // src/Binance/index.ts
617
+ import { fetchWithRetry } from "@tradejs/infra/http";
618
+ var INTERVAL_MAP = {
619
+ "1": "1m",
620
+ "3": "3m",
621
+ "5": "5m",
622
+ "15": "15m",
623
+ "30": "30m",
624
+ "60": "1h",
625
+ "120": "2h",
626
+ "240": "4h",
627
+ "360": "6h",
628
+ "720": "12h",
629
+ D: "1d",
630
+ W: "1w",
631
+ M: "1M"
632
+ };
633
+ var INTERVAL_MS = {
634
+ "1": 6e4,
635
+ "3": 18e4,
636
+ "5": 3e5,
637
+ "15": 9e5,
638
+ "30": 18e5,
639
+ "60": 36e5,
640
+ "120": 72e5,
641
+ "240": 144e5,
642
+ "360": 216e5,
643
+ "720": 432e5,
644
+ D: 864e5,
645
+ W: 6048e5,
646
+ M: 2592e6
647
+ };
648
+ var toNum = (value, fallback = 0) => {
649
+ const num = Number(value);
650
+ return Number.isFinite(num) ? num : fallback;
651
+ };
652
+ var BinanceConnectorCreator = async () => {
653
+ let state = {};
654
+ return {
655
+ getState: async () => state,
656
+ setState: async (newState) => {
657
+ state = { ...state, ...newState };
658
+ },
659
+ kline: async ({ symbol, interval, start, end }) => {
660
+ const intervalToken = INTERVAL_MAP[String(interval)];
661
+ if (!intervalToken) return [];
662
+ const intervalMs = INTERVAL_MS[String(interval)] ?? 9e5;
663
+ const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
664
+ let cursor = start ?? Math.max(0, end - intervalMs * 1e3);
665
+ const rows = [];
666
+ while (cursor <= end) {
667
+ const url = new URL(`${baseUrl}/api/v3/klines`);
668
+ url.searchParams.set("symbol", symbol);
669
+ url.searchParams.set("interval", intervalToken);
670
+ url.searchParams.set("startTime", String(cursor));
671
+ url.searchParams.set("endTime", String(end));
672
+ url.searchParams.set("limit", "1000");
673
+ const response = await fetchWithRetry(url.toString(), {
674
+ headers: { "User-Agent": "tradejs/binance-connector" }
675
+ });
676
+ if (!response.ok) break;
677
+ const payload = await response.json();
678
+ if (!Array.isArray(payload) || !payload.length) break;
679
+ let lastTs = cursor;
680
+ for (const item of payload) {
681
+ if (!Array.isArray(item)) continue;
682
+ const ts = toNum(item[0], 0);
683
+ if (!ts) continue;
684
+ rows.push({
685
+ timestamp: ts,
686
+ open: toNum(item[1]),
687
+ high: toNum(item[2]),
688
+ low: toNum(item[3]),
689
+ close: toNum(item[4]),
690
+ volume: toNum(item[5]),
691
+ turnover: toNum(item[7]),
692
+ dt: new Date(ts).toISOString()
693
+ });
694
+ lastTs = ts;
695
+ }
696
+ if (payload.length < 1e3) break;
697
+ cursor = lastTs + intervalMs;
698
+ }
699
+ rows.sort((a, b) => a.timestamp - b.timestamp);
700
+ return rows;
701
+ },
702
+ getPosition: async () => null,
703
+ getPositions: async () => [],
704
+ placeOrder: async () => false,
705
+ closePosition: async () => false,
706
+ getTickers: async () => {
707
+ const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
708
+ const response = await fetchWithRetry(`${baseUrl}/api/v3/ticker/24hr`, {
709
+ headers: { "User-Agent": "tradejs/binance-connector" }
710
+ });
711
+ if (!response.ok) return [];
712
+ const payload = await response.json();
713
+ if (!Array.isArray(payload)) return [];
714
+ return payload.map((row) => {
715
+ const symbol = String(row.symbol ?? "");
716
+ return {
717
+ symbol,
718
+ lastPrice: toNum(row.lastPrice),
719
+ indexPrice: toNum(row.lastPrice),
720
+ markPrice: toNum(row.lastPrice),
721
+ prevPrice24h: toNum(row.openPrice),
722
+ price24hPcnt: toNum(row.priceChangePercent) / 100,
723
+ highPrice24h: toNum(row.highPrice),
724
+ lowPrice24h: toNum(row.lowPrice),
725
+ prevPrice1h: 0,
726
+ openInterest: 0,
727
+ openInterestValue: 0,
728
+ turnover24h: toNum(row.quoteVolume),
729
+ volume24h: toNum(row.volume),
730
+ fundingRate: 0,
731
+ nextFundingTime: 0,
732
+ predictedDeliveryPrice: "",
733
+ basisRate: "",
734
+ deliveryFeeRate: "",
735
+ deliveryTime: 0,
736
+ ask1Size: toNum(row.askQty),
737
+ bid1Price: toNum(row.bidPrice),
738
+ ask1Price: toNum(row.askPrice),
739
+ bid1Size: toNum(row.bidQty),
740
+ basis: "",
741
+ preOpenPrice: "",
742
+ preQty: ""
743
+ };
744
+ });
745
+ }
746
+ };
747
+ };
748
+
749
+ // src/Coinbase/index.ts
750
+ import { fetchWithRetry as fetchWithRetry2 } from "@tradejs/infra/http";
751
+ var INTERVAL_MS2 = {
752
+ "1": 6e4,
753
+ "3": 18e4,
754
+ "5": 3e5,
755
+ "15": 9e5,
756
+ "30": 18e5,
757
+ "60": 36e5,
758
+ "120": 72e5,
759
+ "240": 144e5,
760
+ "360": 216e5,
761
+ "720": 432e5,
762
+ D: 864e5,
763
+ W: 6048e5,
764
+ M: 2592e6
765
+ };
766
+ var GRANULARITY_MAP = {
767
+ "1": 60,
768
+ "3": 300,
769
+ "5": 300,
770
+ "15": 900,
771
+ "30": 1800,
772
+ "60": 3600,
773
+ "120": 21600,
774
+ "240": 21600,
775
+ "360": 21600,
776
+ "720": 21600,
777
+ D: 86400,
778
+ W: 86400,
779
+ M: 86400
780
+ };
781
+ var toNum2 = (value, fallback = 0) => {
782
+ const num = Number(value);
783
+ return Number.isFinite(num) ? num : fallback;
784
+ };
785
+ var toCoinbaseProduct = (symbol) => {
786
+ const upper = symbol.toUpperCase();
787
+ const quoteSuffixes = ["USDT", "USDC", "BUSD", "USD"];
788
+ for (const quote of quoteSuffixes) {
789
+ if (upper.endsWith(quote)) {
790
+ const base = upper.slice(0, -quote.length);
791
+ if (!base) return null;
792
+ return `${base}-USD`;
793
+ }
794
+ }
795
+ return null;
796
+ };
797
+ var MAJOR_PRODUCTS = [
798
+ "BTC-USD",
799
+ "ETH-USD",
800
+ "SOL-USD",
801
+ "XRP-USD",
802
+ "ADA-USD",
803
+ "DOGE-USD",
804
+ "LTC-USD",
805
+ "BCH-USD",
806
+ "LINK-USD",
807
+ "AVAX-USD"
808
+ ];
809
+ var CoinbaseConnectorCreator = async () => {
810
+ let state = {};
811
+ return {
812
+ getState: async () => state,
813
+ setState: async (newState) => {
814
+ state = { ...state, ...newState };
815
+ },
816
+ kline: async ({ symbol, interval, start, end }) => {
817
+ const granularity = GRANULARITY_MAP[String(interval)];
818
+ if (!granularity) return [];
819
+ const product = toCoinbaseProduct(symbol);
820
+ if (!product) return [];
821
+ const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
822
+ const stepMs = granularity * 1e3 * 300;
823
+ const fromMs = start ?? Math.max(0, end - INTERVAL_MS2[String(interval)] * 1e3);
824
+ let cursorEnd = end;
825
+ const rows = [];
826
+ while (cursorEnd >= fromMs) {
827
+ const chunkStart = Math.max(fromMs, cursorEnd - stepMs);
828
+ const url = new URL(`${baseUrl}/products/${product}/candles`);
829
+ url.searchParams.set("granularity", String(granularity));
830
+ url.searchParams.set("start", new Date(chunkStart).toISOString());
831
+ url.searchParams.set("end", new Date(cursorEnd).toISOString());
832
+ const response = await fetchWithRetry2(url.toString(), {
833
+ headers: { "User-Agent": "tradejs/coinbase-connector" }
834
+ });
835
+ if (!response.ok) break;
836
+ const payload = await response.json();
837
+ if (Array.isArray(payload)) {
838
+ for (const item of payload) {
839
+ if (!Array.isArray(item)) continue;
840
+ const ts = toNum2(item[0]) * 1e3;
841
+ rows.push({
842
+ timestamp: ts,
843
+ low: toNum2(item[1]),
844
+ high: toNum2(item[2]),
845
+ open: toNum2(item[3]),
846
+ close: toNum2(item[4]),
847
+ volume: toNum2(item[5]),
848
+ turnover: 0,
849
+ dt: new Date(ts).toISOString()
850
+ });
851
+ }
852
+ }
853
+ cursorEnd = chunkStart - 1;
854
+ }
855
+ const dedup = /* @__PURE__ */ new Map();
856
+ for (const row of rows) {
857
+ dedup.set(row.timestamp, row);
858
+ }
859
+ return [...dedup.values()].sort((a, b) => a.timestamp - b.timestamp);
860
+ },
861
+ getPosition: async () => null,
862
+ getPositions: async () => [],
863
+ placeOrder: async () => false,
864
+ closePosition: async () => false,
865
+ getTickers: async () => {
866
+ const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
867
+ const entries = await Promise.all(
868
+ MAJOR_PRODUCTS.map(async (product) => {
869
+ const [tickerRes, statsRes] = await Promise.all([
870
+ fetchWithRetry2(`${baseUrl}/products/${product}/ticker`, {
871
+ headers: { "User-Agent": "tradejs/coinbase-connector" }
872
+ }),
873
+ fetchWithRetry2(`${baseUrl}/products/${product}/stats`, {
874
+ headers: { "User-Agent": "tradejs/coinbase-connector" }
875
+ })
876
+ ]);
877
+ if (!tickerRes.ok || !statsRes.ok) return null;
878
+ const ticker = await tickerRes.json();
879
+ const stats = await statsRes.json();
880
+ const base = product.replace("-USD", "");
881
+ const symbol = `${base}USDT`;
882
+ const open = toNum2(stats.open);
883
+ const last = toNum2(ticker.price);
884
+ const pct = open > 0 ? (last - open) / open : 0;
885
+ return {
886
+ symbol,
887
+ lastPrice: last,
888
+ indexPrice: last,
889
+ markPrice: last,
890
+ prevPrice24h: open,
891
+ price24hPcnt: pct,
892
+ highPrice24h: toNum2(stats.high),
893
+ lowPrice24h: toNum2(stats.low),
894
+ prevPrice1h: 0,
895
+ openInterest: 0,
896
+ openInterestValue: 0,
897
+ turnover24h: 0,
898
+ volume24h: toNum2(stats.volume),
899
+ fundingRate: 0,
900
+ nextFundingTime: 0,
901
+ predictedDeliveryPrice: "",
902
+ basisRate: "",
903
+ deliveryFeeRate: "",
904
+ deliveryTime: 0,
905
+ ask1Size: toNum2(ticker.ask_size),
906
+ bid1Price: toNum2(ticker.bid),
907
+ ask1Price: toNum2(ticker.ask),
908
+ bid1Size: toNum2(ticker.bid_size),
909
+ basis: "",
910
+ preOpenPrice: "",
911
+ preQty: ""
912
+ };
913
+ })
914
+ );
915
+ return entries.filter((item) => item != null);
916
+ }
917
+ };
918
+ };
919
+
920
+ // src/Test/index.ts
921
+ import { createTestConnector } from "@tradejs/node/backtest";
922
+ var TestConnectorCreator = (connector, context) => createTestConnector(connector, context);
923
+
924
+ // src/marketData/spotKlineProviders.ts
925
+ import { fetchWithRetry as fetchWithRetry3 } from "@tradejs/infra/http";
926
+ var toIntervalToken = (interval) => interval === "1h" ? "1h" : "15m";
927
+ var mapBinanceKline = (payload) => payload.map((item) => {
928
+ if (!Array.isArray(item)) return null;
929
+ const ts = Number(item[0]);
930
+ const open = Number(item[1]);
931
+ const high = Number(item[2]);
932
+ const low = Number(item[3]);
933
+ const close = Number(item[4]);
934
+ const volume = Number(item[5]);
935
+ if (![ts, open, high, low, close, volume].every(Number.isFinite))
936
+ return null;
937
+ return {
938
+ timestamp: ts,
939
+ open,
940
+ high,
941
+ low,
942
+ close,
943
+ volume,
944
+ turnover: Number(item[7]) || 0,
945
+ dt: new Date(ts).toISOString()
946
+ };
947
+ }).filter((item) => item != null).sort((a, b) => a.timestamp - b.timestamp);
948
+ var mapCoinbaseKline = (payload) => payload.map((item) => {
949
+ if (!Array.isArray(item)) return null;
950
+ const tsSec = Number(item[0]);
951
+ const low = Number(item[1]);
952
+ const high = Number(item[2]);
953
+ const open = Number(item[3]);
954
+ const close = Number(item[4]);
955
+ const volume = Number(item[5]);
956
+ const ts = tsSec * 1e3;
957
+ if (![ts, open, high, low, close, volume].every(Number.isFinite))
958
+ return null;
959
+ return {
960
+ timestamp: ts,
961
+ open,
962
+ high,
963
+ low,
964
+ close,
965
+ volume,
966
+ turnover: 0,
967
+ dt: new Date(ts).toISOString()
968
+ };
969
+ }).filter((item) => item != null).sort((a, b) => a.timestamp - b.timestamp);
970
+ var spotKlineProviders = {
971
+ binance: {
972
+ kline: async ({ symbol, interval, start, end }) => {
973
+ const token = toIntervalToken(interval);
974
+ const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
975
+ const url = new URL(`${baseUrl}/api/v3/klines`);
976
+ url.searchParams.set("symbol", symbol);
977
+ url.searchParams.set("interval", token);
978
+ url.searchParams.set("startTime", String(start));
979
+ url.searchParams.set("endTime", String(end));
980
+ url.searchParams.set("limit", "1000");
981
+ const response = await fetchWithRetry3(url.toString(), {
982
+ headers: { "User-Agent": "tradejs/market-data-ingest" }
983
+ });
984
+ if (!response.ok) {
985
+ throw new Error(
986
+ `Binance kline ${response.status}: ${await response.text()}`
987
+ );
988
+ }
989
+ const payload = await response.json();
990
+ return Array.isArray(payload) ? mapBinanceKline(payload) : [];
991
+ }
992
+ },
993
+ coinbase: {
994
+ kline: async ({ symbol, interval, start, end }) => {
995
+ const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
996
+ const granularity = interval === "1h" ? 3600 : 900;
997
+ const url = new URL(`${baseUrl}/products/${symbol}/candles`);
998
+ url.searchParams.set("granularity", String(granularity));
999
+ url.searchParams.set("start", new Date(start).toISOString());
1000
+ url.searchParams.set("end", new Date(end).toISOString());
1001
+ const response = await fetchWithRetry3(url.toString(), {
1002
+ headers: {
1003
+ "User-Agent": "tradejs/market-data-ingest",
1004
+ Accept: "application/json"
1005
+ }
1006
+ });
1007
+ if (response.status === 404) return [];
1008
+ if (!response.ok) {
1009
+ throw new Error(
1010
+ `Coinbase kline ${response.status}: ${await response.text()}`
1011
+ );
1012
+ }
1013
+ const payload = await response.json();
1014
+ return Array.isArray(payload) ? mapCoinbaseKline(payload) : [];
1015
+ }
1016
+ }
1017
+ };
1018
+
1019
+ // src/marketData/providers/binanceCoinbaseSpread.ts
1020
+ import {
1021
+ alignSpreadRows,
1022
+ coinbaseProductFromSymbol,
1023
+ intervalToMs
1024
+ } from "@tradejs/core/indicators";
1025
+ var fetchBinanceKlines = async (params) => {
1026
+ const { symbol, interval, fromMs, toMs } = params;
1027
+ const rows = await spotKlineProviders.binance.kline({
1028
+ symbol,
1029
+ interval,
1030
+ start: fromMs,
1031
+ end: toMs
1032
+ });
1033
+ return rows.map((row) => ({ ts: row.timestamp, close: row.close }));
1034
+ };
1035
+ var fetchCoinbaseCandles = async (params) => {
1036
+ const { symbol, interval, fromMs, toMs } = params;
1037
+ const product = coinbaseProductFromSymbol(symbol);
1038
+ if (!product) return [];
1039
+ const stepMs = intervalToMs(interval) * 250;
1040
+ const rows = [];
1041
+ let cursor = fromMs;
1042
+ while (cursor <= toMs) {
1043
+ const endMs = Math.min(toMs, cursor + stepMs);
1044
+ const klineRows = await spotKlineProviders.coinbase.kline({
1045
+ symbol: product,
1046
+ interval,
1047
+ start: cursor,
1048
+ end: endMs
1049
+ });
1050
+ for (const row of klineRows) {
1051
+ rows.push({ ts: row.timestamp, close: row.close });
1052
+ }
1053
+ cursor = endMs + 1;
1054
+ }
1055
+ rows.sort((a, b) => a.ts - b.ts);
1056
+ const dedup = /* @__PURE__ */ new Map();
1057
+ for (const row of rows) dedup.set(row.ts, row);
1058
+ return [...dedup.values()].sort((a, b) => a.ts - b.ts);
1059
+ };
1060
+ var binanceCoinbaseSpreadProvider = {
1061
+ name: "binance_coinbase_spread",
1062
+ fetchWindow: async ({ symbol, interval, fromMs, toMs }) => {
1063
+ const [binance, coinbase] = await Promise.all([
1064
+ fetchBinanceKlines({ symbol, interval, fromMs, toMs }),
1065
+ fetchCoinbaseCandles({ symbol, interval, fromMs, toMs })
1066
+ ]);
1067
+ const spreadRows = alignSpreadRows({
1068
+ symbol,
1069
+ interval,
1070
+ binance,
1071
+ coinbase,
1072
+ source: "binance_coinbase_spread"
1073
+ });
1074
+ return { spreadRows };
1075
+ }
1076
+ };
1077
+
1078
+ // src/marketData/providers/coinalyze.ts
1079
+ import { delay } from "@tradejs/core/async";
1080
+ import {
1081
+ coinalyzePointsToRows,
1082
+ mergeCoinalyzeMetrics
1083
+ } from "@tradejs/core/indicators";
1084
+ var coinalyzeIntervalMap = {
1085
+ "15m": "15min",
1086
+ "1h": "1hour"
1087
+ };
1088
+ var coinalyzeMinRequestDelayMs = Number(
1089
+ process.env.COINALYZE_MIN_REQUEST_DELAY_MS ?? 300
1090
+ );
1091
+ var coinalyzeMaxRetries = Number(process.env.COINALYZE_MAX_RETRIES ?? 4);
1092
+ var lastRequestTs = 0;
1093
+ var flattenCoinalyzeHistory = (raw) => {
1094
+ if (!Array.isArray(raw)) return [];
1095
+ const out = [];
1096
+ for (const item of raw) {
1097
+ if (!item || typeof item !== "object") continue;
1098
+ const history = item.history;
1099
+ if (Array.isArray(history)) {
1100
+ for (const point of history) {
1101
+ if (point && typeof point === "object") {
1102
+ out.push(point);
1103
+ }
1104
+ }
1105
+ continue;
1106
+ }
1107
+ out.push(item);
1108
+ }
1109
+ return out;
1110
+ };
1111
+ var normalizeMetricPayload = (metric, raw) => {
1112
+ const points = flattenCoinalyzeHistory(raw);
1113
+ if (!points.length) return [];
1114
+ if (metric === "oi") {
1115
+ return points.map((point) => ({
1116
+ ...point,
1117
+ open_interest: point.open_interest ?? point.openInterest ?? point.oi ?? point.c
1118
+ }));
1119
+ }
1120
+ if (metric === "funding") {
1121
+ return points.map((point) => ({
1122
+ ...point,
1123
+ funding_rate: point.funding_rate ?? point.fundingRate ?? point.rate ?? point.c
1124
+ }));
1125
+ }
1126
+ return points.map((point) => ({
1127
+ ...point,
1128
+ liq_long: point.liq_long ?? point.long_liq ?? point.long ?? point.l,
1129
+ liq_short: point.liq_short ?? point.short_liq ?? point.short ?? point.s
1130
+ }));
1131
+ };
1132
+ var fetchCoinalyzeSeries = async (params) => {
1133
+ const { endpoint, metric, symbol, interval, fromMs, toMs } = params;
1134
+ 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
+ const url = new URL(`${baseUrl}${endpoint}`);
1140
+ url.searchParams.set("symbols", symbol);
1141
+ url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
1142
+ url.searchParams.set("from", String(Math.floor(fromMs / 1e3)));
1143
+ url.searchParams.set("to", String(Math.floor(toMs / 1e3)));
1144
+ const headers = {
1145
+ api_key: apiKey,
1146
+ "x-api-key": apiKey,
1147
+ Authorization: `Bearer ${apiKey}`
1148
+ };
1149
+ for (let attempt = 0; attempt <= coinalyzeMaxRetries; attempt += 1) {
1150
+ const now = Date.now();
1151
+ const waitMs = Math.max(
1152
+ 0,
1153
+ lastRequestTs + coinalyzeMinRequestDelayMs - now
1154
+ );
1155
+ if (waitMs > 0) {
1156
+ await delay(waitMs);
1157
+ }
1158
+ lastRequestTs = Date.now();
1159
+ const response = await fetch(url.toString(), { headers });
1160
+ if (response.ok) {
1161
+ const raw = await response.json();
1162
+ return normalizeMetricPayload(metric, raw);
1163
+ }
1164
+ const text = await response.text();
1165
+ const retryAfterRaw = Number(response.headers.get("retry-after") ?? "");
1166
+ const retryAfterMs = Number.isFinite(retryAfterRaw) && retryAfterRaw > 0 ? retryAfterRaw * 1e3 : null;
1167
+ const transient = response.status === 429 || response.status >= 500;
1168
+ if (attempt < coinalyzeMaxRetries && transient) {
1169
+ const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
1170
+ await delay(retryAfterMs ?? backoffMs);
1171
+ continue;
1172
+ }
1173
+ throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
1174
+ }
1175
+ return [];
1176
+ };
1177
+ var coinalyzeProvider = {
1178
+ name: "coinalyze",
1179
+ fetchWindow: async ({ symbol, marketSymbol, interval, fromMs, toMs }) => {
1180
+ const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
1181
+ const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
1182
+ const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
1183
+ const requestSymbol = (marketSymbol || symbol).trim().toUpperCase();
1184
+ const oiRaw = await fetchCoinalyzeSeries({
1185
+ endpoint: oiPath,
1186
+ metric: "oi",
1187
+ symbol: requestSymbol,
1188
+ interval,
1189
+ fromMs,
1190
+ toMs
1191
+ });
1192
+ const fundingRaw = await fetchCoinalyzeSeries({
1193
+ endpoint: fundingPath,
1194
+ metric: "funding",
1195
+ symbol: requestSymbol,
1196
+ interval,
1197
+ fromMs,
1198
+ toMs
1199
+ });
1200
+ const liqRaw = await fetchCoinalyzeSeries({
1201
+ endpoint: liqPath,
1202
+ metric: "liq",
1203
+ symbol: requestSymbol,
1204
+ interval,
1205
+ fromMs,
1206
+ toMs
1207
+ });
1208
+ const points = mergeCoinalyzeMetrics({
1209
+ symbol,
1210
+ oiRaw,
1211
+ fundingRaw,
1212
+ liqRaw
1213
+ });
1214
+ return {
1215
+ derivativesRows: coinalyzePointsToRows(points, interval, "coinalyze")
1216
+ };
1217
+ }
1218
+ };
1219
+
1220
+ // src/marketData/providers/index.ts
1221
+ var marketDataProviders = {
1222
+ coinalyze: coinalyzeProvider,
1223
+ binance_coinbase_spread: binanceCoinbaseSpreadProvider
1224
+ };
1225
+
1226
+ // src/index.ts
1227
+ var ConnectorNames = /* @__PURE__ */ ((ConnectorNames2) => {
1228
+ ConnectorNames2["ByBit"] = "ByBit";
1229
+ ConnectorNames2["Binance"] = "Binance";
1230
+ ConnectorNames2["Coinbase"] = "Coinbase";
1231
+ ConnectorNames2["Test"] = "Test";
1232
+ return ConnectorNames2;
1233
+ })(ConnectorNames || {});
1234
+ var ConnectorProviders = /* @__PURE__ */ ((ConnectorProviders2) => {
1235
+ ConnectorProviders2["bybit"] = "bybit";
1236
+ ConnectorProviders2["binance"] = "binance";
1237
+ ConnectorProviders2["coinbase"] = "coinbase";
1238
+ return ConnectorProviders2;
1239
+ })(ConnectorProviders || {});
1240
+ var providerToConnectorName = {
1241
+ ["bybit" /* bybit */]: "ByBit" /* ByBit */,
1242
+ ["binance" /* binance */]: "Binance" /* Binance */,
1243
+ ["coinbase" /* coinbase */]: "Coinbase" /* Coinbase */
1244
+ };
1245
+ var getConnectorProviders = () => Object.keys(providerToConnectorName);
1246
+ var resolveConnectorNameByProvider = (provider) => {
1247
+ const normalized = String(provider || "").trim().toLowerCase();
1248
+ return providerToConnectorName[normalized] || null;
1249
+ };
1250
+ var connectors = {
1251
+ ["ByBit" /* ByBit */]: ByBitConnectorCreator,
1252
+ ["Binance" /* Binance */]: BinanceConnectorCreator,
1253
+ ["Coinbase" /* Coinbase */]: CoinbaseConnectorCreator,
1254
+ ["Test" /* Test */]: TestConnectorCreator
1255
+ };
1256
+ var connectorEntries = [
1257
+ {
1258
+ name: "ByBit" /* ByBit */,
1259
+ creator: ByBitConnectorCreator,
1260
+ providers: ["bybit" /* bybit */]
1261
+ },
1262
+ {
1263
+ name: "Binance" /* Binance */,
1264
+ creator: BinanceConnectorCreator,
1265
+ providers: ["binance" /* binance */]
1266
+ },
1267
+ {
1268
+ name: "Coinbase" /* Coinbase */,
1269
+ creator: CoinbaseConnectorCreator,
1270
+ providers: ["coinbase" /* coinbase */]
1271
+ }
1272
+ ];
1273
+ var getConnectorCreatorByProvider = (provider) => {
1274
+ const connectorName = resolveConnectorNameByProvider(provider);
1275
+ if (!connectorName) {
1276
+ return null;
1277
+ }
1278
+ return connectors[connectorName];
1279
+ };
1280
+ var index_default = { connectorEntries };
1281
+ export {
1282
+ ConnectorNames,
1283
+ ConnectorProviders,
1284
+ connectorEntries,
1285
+ connectors,
1286
+ index_default as default,
1287
+ getConnectorCreatorByProvider,
1288
+ getConnectorProviders,
1289
+ marketDataProviders,
1290
+ providerToConnectorName,
1291
+ resolveConnectorNameByProvider,
1292
+ spotKlineProviders
1293
+ };