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