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