@tradejs/connectors 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +327 -139
- package/dist/index.mjs +327 -139
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -47,6 +47,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
47
47
|
// src/ByBit/index.ts
|
|
48
48
|
var import_lodash = __toESM(require("lodash"));
|
|
49
49
|
var import_chalk = __toESM(require("chalk"));
|
|
50
|
+
var import_async = require("@tradejs/core/async");
|
|
50
51
|
var import_constants2 = require("@tradejs/core/constants");
|
|
51
52
|
var import_data = require("@tradejs/core/data");
|
|
52
53
|
var import_data2 = require("@tradejs/core/data");
|
|
@@ -61,7 +62,13 @@ var import_bybit_api = require("bybit-api");
|
|
|
61
62
|
var import_logger = require("@tradejs/infra/logger");
|
|
62
63
|
var import_redis = require("@tradejs/infra/redis");
|
|
63
64
|
var useTestnet = false;
|
|
64
|
-
var getClient = async ({ userName }) => {
|
|
65
|
+
var getClient = async ({ userName }, access = "private") => {
|
|
66
|
+
if (access === "public") {
|
|
67
|
+
return new import_bybit_api.RestClientV5({
|
|
68
|
+
parseAPIRateLimits: true,
|
|
69
|
+
testnet: useTestnet
|
|
70
|
+
});
|
|
71
|
+
}
|
|
65
72
|
const user = await (0, import_redis.getData)(import_redis.redisKeys.user(userName));
|
|
66
73
|
if (!user) {
|
|
67
74
|
import_logger.logger.log("error", "connection config not found: %s", userName);
|
|
@@ -70,6 +77,7 @@ var getClient = async ({ userName }) => {
|
|
|
70
77
|
const client = new import_bybit_api.RestClientV5({
|
|
71
78
|
key: user.BYBIT_API_KEY,
|
|
72
79
|
secret: user.BYBIT_API_SECRET,
|
|
80
|
+
parseAPIRateLimits: true,
|
|
73
81
|
testnet: useTestnet
|
|
74
82
|
});
|
|
75
83
|
return client;
|
|
@@ -169,6 +177,12 @@ var mapPositionData = (data) => {
|
|
|
169
177
|
// src/ByBit/index.ts
|
|
170
178
|
var LIMIT = 1e3;
|
|
171
179
|
var CACHE_FALLBACK_WINDOW = 1e3;
|
|
180
|
+
var BYBIT_RATE_LIMIT_RETCODE = 10006;
|
|
181
|
+
var BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE = 34040;
|
|
182
|
+
var KLINE_RATE_LIMIT_MAX_ATTEMPTS = 3;
|
|
183
|
+
var KLINE_RATE_LIMIT_BASE_DELAY_MS = 800;
|
|
184
|
+
var KLINE_RATE_LIMIT_MAX_DELAY_MS = 1e4;
|
|
185
|
+
var KLINE_RATE_LIMIT_RESET_BUFFER_MS = 50;
|
|
172
186
|
var INTERVAL_TO_MINUTES = {
|
|
173
187
|
"1": 1,
|
|
174
188
|
"3": 3,
|
|
@@ -185,9 +199,37 @@ var INTERVAL_TO_MINUTES = {
|
|
|
185
199
|
M: 43200
|
|
186
200
|
};
|
|
187
201
|
var getLogLevel = (res) => res.retCode === 0 ? "info" : "error";
|
|
202
|
+
var isTradingStopNotModified = (res) => res?.retCode === BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE;
|
|
203
|
+
var isTradingStopAccepted = (res) => res?.retCode === 0 || isTradingStopNotModified(res);
|
|
204
|
+
var isKlineRateLimited = (res) => res?.retCode === BYBIT_RATE_LIMIT_RETCODE;
|
|
205
|
+
var resolveKlineRetryDelayMs = (res, attempt) => {
|
|
206
|
+
const resetAtRaw = Number(res?.rateLimitApi?.resetAtTimestamp);
|
|
207
|
+
if (Number.isFinite(resetAtRaw) && resetAtRaw > 0) {
|
|
208
|
+
return Math.max(
|
|
209
|
+
KLINE_RATE_LIMIT_RESET_BUFFER_MS,
|
|
210
|
+
resetAtRaw - Date.now() + KLINE_RATE_LIMIT_RESET_BUFFER_MS
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const jitterMs = Math.round(Math.random() * 250);
|
|
214
|
+
const backoffMs = Math.min(
|
|
215
|
+
KLINE_RATE_LIMIT_MAX_DELAY_MS,
|
|
216
|
+
KLINE_RATE_LIMIT_BASE_DELAY_MS * 2 ** (attempt - 1)
|
|
217
|
+
);
|
|
218
|
+
return backoffMs + jitterMs;
|
|
219
|
+
};
|
|
188
220
|
var ByBitConnectorCreator = async (config) => {
|
|
189
221
|
let state = {};
|
|
190
222
|
let isTimescaleFallbackMode = false;
|
|
223
|
+
let publicClientPromise = null;
|
|
224
|
+
let privateClientPromise = null;
|
|
225
|
+
const getPublicClient = async () => {
|
|
226
|
+
publicClientPromise ??= getClient(config, "public");
|
|
227
|
+
return publicClientPromise;
|
|
228
|
+
};
|
|
229
|
+
const getPrivateClient = async () => {
|
|
230
|
+
privateClientPromise ??= getClient(config, "private");
|
|
231
|
+
return privateClientPromise;
|
|
232
|
+
};
|
|
191
233
|
const request = async ({
|
|
192
234
|
symbol,
|
|
193
235
|
interval,
|
|
@@ -201,41 +243,60 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
201
243
|
);
|
|
202
244
|
const normalizedEnd = (0, import_math.round)(end || Date.now());
|
|
203
245
|
try {
|
|
204
|
-
const client = await
|
|
246
|
+
const client = await getPublicClient();
|
|
205
247
|
if (!client) return [];
|
|
206
248
|
if (normalizedEnd <= normalizedStart) {
|
|
207
249
|
return [];
|
|
208
250
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
interval,
|
|
213
|
-
start: normalizedStart,
|
|
214
|
-
end: normalizedEnd,
|
|
215
|
-
limit: LIMIT
|
|
216
|
-
});
|
|
217
|
-
if (!kline?.result?.list) {
|
|
218
|
-
const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
|
|
219
|
-
import_logger2.logger.log(
|
|
220
|
-
"error",
|
|
221
|
-
"empty kline.list for %s %s%s",
|
|
251
|
+
for (let attempt = 1; attempt <= KLINE_RATE_LIMIT_MAX_ATTEMPTS; attempt += 1) {
|
|
252
|
+
const kline = await client.getKline({
|
|
253
|
+
category: import_constants2.MARKET_CATEGORY,
|
|
222
254
|
symbol,
|
|
223
255
|
interval,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
256
|
+
start: normalizedStart,
|
|
257
|
+
end: normalizedEnd,
|
|
258
|
+
limit: LIMIT
|
|
259
|
+
});
|
|
260
|
+
if (isKlineRateLimited(kline)) {
|
|
261
|
+
if (attempt < KLINE_RATE_LIMIT_MAX_ATTEMPTS) {
|
|
262
|
+
const waitMs = resolveKlineRetryDelayMs(kline, attempt);
|
|
263
|
+
import_logger2.logger.log(
|
|
264
|
+
"warn",
|
|
265
|
+
"kline rate limited for %s %s: attempt=%s/%s waitMs=%s",
|
|
266
|
+
symbol,
|
|
267
|
+
interval,
|
|
268
|
+
attempt,
|
|
269
|
+
KLINE_RATE_LIMIT_MAX_ATTEMPTS,
|
|
270
|
+
waitMs
|
|
271
|
+
);
|
|
272
|
+
await (0, import_async.delay)(waitMs);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!kline?.result?.list) {
|
|
277
|
+
const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
|
|
278
|
+
import_logger2.logger.log(
|
|
279
|
+
"error",
|
|
280
|
+
"empty kline.list for %s %s%s",
|
|
281
|
+
symbol,
|
|
282
|
+
interval,
|
|
283
|
+
responseError ? `: ${responseError}` : ""
|
|
284
|
+
);
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
if (!silent) {
|
|
288
|
+
import_logger2.logger.log(
|
|
289
|
+
"info",
|
|
290
|
+
"%s %s %s %s",
|
|
291
|
+
import_chalk.default.yellow((0, import_time2.formatUnix)(normalizedEnd)),
|
|
292
|
+
import_chalk.default.cyan(symbol),
|
|
293
|
+
import_chalk.default.cyan(interval),
|
|
294
|
+
import_chalk.default.yellow(kline.result.list.length)
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
return mapKlineToChartData(kline.result.list.reverse());
|
|
237
298
|
}
|
|
238
|
-
return
|
|
299
|
+
return [];
|
|
239
300
|
} catch (error) {
|
|
240
301
|
import_logger2.logger.log(
|
|
241
302
|
"error",
|
|
@@ -327,6 +388,190 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
327
388
|
await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(symbol, intMinutes, part));
|
|
328
389
|
}
|
|
329
390
|
};
|
|
391
|
+
const getPositionSnapshot = async (symbol) => {
|
|
392
|
+
const client = await getPrivateClient();
|
|
393
|
+
if (!client) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
const positionRes = await client.getPositionInfo({
|
|
397
|
+
symbol,
|
|
398
|
+
category: import_constants2.MARKET_CATEGORY
|
|
399
|
+
});
|
|
400
|
+
if (positionRes.retCode !== 0) {
|
|
401
|
+
import_logger2.logger.log(
|
|
402
|
+
getLogLevel(positionRes),
|
|
403
|
+
"position retCode: %s, %s",
|
|
404
|
+
symbol,
|
|
405
|
+
positionRes.retCode
|
|
406
|
+
);
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
const positions = mapPositionData(positionRes.result.list);
|
|
410
|
+
if (!positions || import_lodash.default.isEmpty(positions)) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const position = positions[0];
|
|
414
|
+
import_logger2.logger.log(
|
|
415
|
+
"debug",
|
|
416
|
+
"position: %s %s qty=%s price=%s",
|
|
417
|
+
position.symbol,
|
|
418
|
+
position.direction,
|
|
419
|
+
position.qty,
|
|
420
|
+
position.price
|
|
421
|
+
);
|
|
422
|
+
return {
|
|
423
|
+
...position
|
|
424
|
+
};
|
|
425
|
+
};
|
|
426
|
+
const setTakeProfits = async ({
|
|
427
|
+
symbol,
|
|
428
|
+
direction,
|
|
429
|
+
qty,
|
|
430
|
+
takeProfits
|
|
431
|
+
}) => {
|
|
432
|
+
const client = await getPrivateClient();
|
|
433
|
+
const marketDataClient = await getPublicClient();
|
|
434
|
+
if (!client || !marketDataClient) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
if (!Array.isArray(takeProfits) || takeProfits.length === 0) {
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
441
|
+
const positionQty = qty ?? (await getPositionSnapshot(symbol))?.qty ?? 0;
|
|
442
|
+
if (!Number.isFinite(positionQty) || positionQty <= 0) {
|
|
443
|
+
import_logger2.logger.log(
|
|
444
|
+
"warn",
|
|
445
|
+
"setTakeProfits: missing position qty: %s",
|
|
446
|
+
(0, import_data2.toJson)({ symbol, qty, positionQty, takeProfits }, true)
|
|
447
|
+
);
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
const isLong = direction === "LONG";
|
|
451
|
+
for (const tp of takeProfits) {
|
|
452
|
+
const tpSizeRaw = positionQty * tp.rate;
|
|
453
|
+
const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
|
|
454
|
+
tpSizeRaw,
|
|
455
|
+
meta
|
|
456
|
+
);
|
|
457
|
+
if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
|
|
458
|
+
import_logger2.logger.log(
|
|
459
|
+
"warn",
|
|
460
|
+
"tp skipped: size too small %s",
|
|
461
|
+
(0, import_data2.toJson)(
|
|
462
|
+
{ symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
|
|
463
|
+
true
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const tpPriceNorm = normalizePrice(
|
|
469
|
+
tp.price,
|
|
470
|
+
isLong ? "TP_LONG" : "TP_SHORT",
|
|
471
|
+
meta
|
|
472
|
+
);
|
|
473
|
+
const isFullMode = takeProfits.length === 1 && tp.rate === 1;
|
|
474
|
+
const tpRes = await client.setTradingStop({
|
|
475
|
+
category: import_constants2.MARKET_CATEGORY,
|
|
476
|
+
symbol,
|
|
477
|
+
tpSize: isFullMode ? void 0 : tpSizeStr,
|
|
478
|
+
tpslMode: isFullMode ? "Full" : "Partial",
|
|
479
|
+
takeProfit: tpPriceNorm.priceStr,
|
|
480
|
+
tpTriggerBy: "MarkPrice",
|
|
481
|
+
tpOrderType: "Market",
|
|
482
|
+
positionIdx: 0
|
|
483
|
+
});
|
|
484
|
+
if (isTradingStopNotModified(tpRes)) {
|
|
485
|
+
import_logger2.logger.log(
|
|
486
|
+
"debug",
|
|
487
|
+
"tp unchanged: %s %s price=%s rate=%s",
|
|
488
|
+
symbol,
|
|
489
|
+
direction,
|
|
490
|
+
tpPriceNorm.priceStr,
|
|
491
|
+
tp.rate
|
|
492
|
+
);
|
|
493
|
+
} else if (tpRes.retCode === 0) {
|
|
494
|
+
import_logger2.logger.log(
|
|
495
|
+
"info",
|
|
496
|
+
"tp updated: %s %s price=%s rate=%s",
|
|
497
|
+
symbol,
|
|
498
|
+
direction,
|
|
499
|
+
tpPriceNorm.priceStr,
|
|
500
|
+
tp.rate
|
|
501
|
+
);
|
|
502
|
+
} else {
|
|
503
|
+
import_logger2.logger.log(
|
|
504
|
+
"error",
|
|
505
|
+
"tp failed: %s %s price=%s rate=%s %s",
|
|
506
|
+
symbol,
|
|
507
|
+
direction,
|
|
508
|
+
tpPriceNorm.priceStr,
|
|
509
|
+
tp.rate,
|
|
510
|
+
(0, import_data2.toJson)(tpRes, true)
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
if (!isTradingStopAccepted(tpRes)) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return true;
|
|
518
|
+
};
|
|
519
|
+
const setStopLoss = async ({
|
|
520
|
+
symbol,
|
|
521
|
+
direction,
|
|
522
|
+
stopLossPrice
|
|
523
|
+
}) => {
|
|
524
|
+
const client = await getPrivateClient();
|
|
525
|
+
const marketDataClient = await getPublicClient();
|
|
526
|
+
if (!client || !marketDataClient) {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
if (typeof stopLossPrice !== "number" || !Number.isFinite(stopLossPrice)) {
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
533
|
+
const isLong = direction === "LONG";
|
|
534
|
+
const slNormalized = normalizePrice(
|
|
535
|
+
stopLossPrice,
|
|
536
|
+
isLong ? "SL_LONG" : "SL_SHORT",
|
|
537
|
+
meta
|
|
538
|
+
);
|
|
539
|
+
const slRes = await client.setTradingStop({
|
|
540
|
+
category: import_constants2.MARKET_CATEGORY,
|
|
541
|
+
symbol,
|
|
542
|
+
tpslMode: "Full",
|
|
543
|
+
stopLoss: slNormalized.priceStr,
|
|
544
|
+
slTriggerBy: "LastPrice",
|
|
545
|
+
positionIdx: 0
|
|
546
|
+
});
|
|
547
|
+
if (isTradingStopNotModified(slRes)) {
|
|
548
|
+
import_logger2.logger.log(
|
|
549
|
+
"debug",
|
|
550
|
+
"sl unchanged: %s %s stopLoss=%s",
|
|
551
|
+
symbol,
|
|
552
|
+
direction,
|
|
553
|
+
slNormalized.priceStr
|
|
554
|
+
);
|
|
555
|
+
} else if (slRes.retCode === 0) {
|
|
556
|
+
import_logger2.logger.log(
|
|
557
|
+
"info",
|
|
558
|
+
"sl updated: %s %s stopLoss=%s",
|
|
559
|
+
symbol,
|
|
560
|
+
direction,
|
|
561
|
+
slNormalized.priceStr
|
|
562
|
+
);
|
|
563
|
+
} else {
|
|
564
|
+
import_logger2.logger.log(
|
|
565
|
+
"error",
|
|
566
|
+
"sl failed: %s %s stopLoss=%s %s",
|
|
567
|
+
symbol,
|
|
568
|
+
direction,
|
|
569
|
+
slNormalized.priceStr,
|
|
570
|
+
(0, import_data2.toJson)(slRes, true)
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
return isTradingStopAccepted(slRes);
|
|
574
|
+
};
|
|
330
575
|
return {
|
|
331
576
|
getState: async () => state,
|
|
332
577
|
setState: async (newState) => {
|
|
@@ -338,7 +583,8 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
338
583
|
start: defaultStart,
|
|
339
584
|
end: defaultEnd,
|
|
340
585
|
silent = false,
|
|
341
|
-
cacheOnly = false
|
|
586
|
+
cacheOnly = false,
|
|
587
|
+
warmOnly = false
|
|
342
588
|
}) => {
|
|
343
589
|
const intMinutes = intervalToMinutes(interval);
|
|
344
590
|
if (!intMinutes) {
|
|
@@ -414,6 +660,16 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
414
660
|
if (!cacheOnly && isRightEdgeQuery) {
|
|
415
661
|
await refreshTail({ symbol, interval, silent });
|
|
416
662
|
}
|
|
663
|
+
if (warmOnly) {
|
|
664
|
+
if (isTimescaleFallbackMode) {
|
|
665
|
+
isTimescaleFallbackMode = false;
|
|
666
|
+
import_logger2.logger.log(
|
|
667
|
+
"info",
|
|
668
|
+
"TimescaleDB connection restored for kline cache"
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
return [];
|
|
672
|
+
}
|
|
417
673
|
const rangeStart = defaultStart ?? dataStart ?? 0;
|
|
418
674
|
const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
|
|
419
675
|
const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
|
|
@@ -439,7 +695,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
439
695
|
String(error)
|
|
440
696
|
);
|
|
441
697
|
}
|
|
442
|
-
if (cacheOnly) {
|
|
698
|
+
if (cacheOnly || warmOnly) {
|
|
443
699
|
return [];
|
|
444
700
|
}
|
|
445
701
|
return request({
|
|
@@ -451,41 +707,9 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
451
707
|
});
|
|
452
708
|
}
|
|
453
709
|
},
|
|
454
|
-
getPosition: async (symbol) =>
|
|
455
|
-
const client = await getClient(config);
|
|
456
|
-
if (!client) {
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
const positionRes = await client.getPositionInfo({
|
|
460
|
-
symbol,
|
|
461
|
-
category: import_constants2.MARKET_CATEGORY
|
|
462
|
-
});
|
|
463
|
-
import_logger2.logger.log(
|
|
464
|
-
getLogLevel(positionRes),
|
|
465
|
-
"position retCode: %s, %s",
|
|
466
|
-
symbol,
|
|
467
|
-
positionRes.retCode
|
|
468
|
-
);
|
|
469
|
-
if (positionRes.retCode !== 0) {
|
|
470
|
-
return null;
|
|
471
|
-
}
|
|
472
|
-
const positions = mapPositionData(positionRes.result.list);
|
|
473
|
-
if (!positions || import_lodash.default.isEmpty(positions)) {
|
|
474
|
-
return null;
|
|
475
|
-
}
|
|
476
|
-
const position = positions[0];
|
|
477
|
-
import_logger2.logger.log(
|
|
478
|
-
getLogLevel(positionRes),
|
|
479
|
-
"position: %s, %s",
|
|
480
|
-
symbol,
|
|
481
|
-
(0, import_data2.toJson)(positionRes, true)
|
|
482
|
-
);
|
|
483
|
-
return {
|
|
484
|
-
...position
|
|
485
|
-
};
|
|
486
|
-
},
|
|
710
|
+
getPosition: async (symbol) => getPositionSnapshot(symbol),
|
|
487
711
|
getPositions: async () => {
|
|
488
|
-
const client = await
|
|
712
|
+
const client = await getPrivateClient();
|
|
489
713
|
if (!client) {
|
|
490
714
|
return [];
|
|
491
715
|
}
|
|
@@ -493,12 +717,12 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
493
717
|
category: import_constants2.MARKET_CATEGORY,
|
|
494
718
|
settleCoin: "USDT"
|
|
495
719
|
});
|
|
496
|
-
import_logger2.logger.log(
|
|
497
|
-
getLogLevel(positionRes),
|
|
498
|
-
"positions retCode: %s, %s",
|
|
499
|
-
positionRes.retCode
|
|
500
|
-
);
|
|
501
720
|
if (positionRes.retCode !== 0) {
|
|
721
|
+
import_logger2.logger.log(
|
|
722
|
+
getLogLevel(positionRes),
|
|
723
|
+
"positions retCode: %s, %s",
|
|
724
|
+
positionRes.retCode
|
|
725
|
+
);
|
|
502
726
|
return [];
|
|
503
727
|
}
|
|
504
728
|
const positions = mapPositionData(positionRes.result.list);
|
|
@@ -507,13 +731,14 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
507
731
|
}
|
|
508
732
|
return positions;
|
|
509
733
|
},
|
|
510
|
-
placeOrder: async ({ symbol, price, qty, direction, isLimit }
|
|
511
|
-
const client = await
|
|
512
|
-
|
|
734
|
+
placeOrder: async ({ symbol, price, qty, direction, isLimit }) => {
|
|
735
|
+
const client = await getPrivateClient();
|
|
736
|
+
const marketDataClient = await getPublicClient();
|
|
737
|
+
if (!client || !marketDataClient) {
|
|
513
738
|
return false;
|
|
514
739
|
}
|
|
515
740
|
const isLong = direction === "LONG";
|
|
516
|
-
const meta = await getSymbolMeta(
|
|
741
|
+
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
517
742
|
const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
|
|
518
743
|
if (orderQty < meta.minOrderQty) {
|
|
519
744
|
import_logger2.logger.log(
|
|
@@ -527,9 +752,6 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
527
752
|
return false;
|
|
528
753
|
}
|
|
529
754
|
const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
|
|
530
|
-
const slNormalized = slPrice ? normalizePrice(slPrice, isLong ? "SL_LONG" : "SL_SHORT", meta) : void 0;
|
|
531
|
-
const firstTP = TP?.[0];
|
|
532
|
-
const tpNormalized = firstTP && firstTP.rate === 1 ? normalizePrice(firstTP.price, isLong ? "TP_LONG" : "TP_SHORT", meta) : void 0;
|
|
533
755
|
import_logger2.logger.log(
|
|
534
756
|
"info",
|
|
535
757
|
"placeOrder: %s",
|
|
@@ -540,10 +762,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
540
762
|
qty,
|
|
541
763
|
direction,
|
|
542
764
|
orderQty,
|
|
543
|
-
orderQtyStr
|
|
544
|
-
slPrice,
|
|
545
|
-
slPriceNorm: slNormalized?.priceStr,
|
|
546
|
-
TP
|
|
765
|
+
orderQtyStr
|
|
547
766
|
},
|
|
548
767
|
true
|
|
549
768
|
)
|
|
@@ -558,10 +777,6 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
558
777
|
category: import_constants2.MARKET_CATEGORY,
|
|
559
778
|
symbol,
|
|
560
779
|
price: entryNormalized?.priceStr || void 0,
|
|
561
|
-
takeProfit: tpNormalized?.priceStr || void 0,
|
|
562
|
-
tpTriggerBy: "MarkPrice",
|
|
563
|
-
stopLoss: slNormalized?.priceStr || void 0,
|
|
564
|
-
slTriggerBy: "LastPrice",
|
|
565
780
|
side: isLong ? "Buy" : "Sell",
|
|
566
781
|
orderType: isLimit ? "Limit" : "Market",
|
|
567
782
|
qty: orderQtyStr,
|
|
@@ -575,53 +790,12 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
575
790
|
if (orderRes.retCode !== 0) {
|
|
576
791
|
return false;
|
|
577
792
|
}
|
|
578
|
-
if (!isLimit) {
|
|
579
|
-
for (const tp of TP) {
|
|
580
|
-
const tpSizeRaw = orderQty * tp.rate;
|
|
581
|
-
const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
|
|
582
|
-
tpSizeRaw,
|
|
583
|
-
meta
|
|
584
|
-
);
|
|
585
|
-
if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
|
|
586
|
-
import_logger2.logger.log(
|
|
587
|
-
"warn",
|
|
588
|
-
"tp skipped: size too small %s",
|
|
589
|
-
(0, import_data2.toJson)(
|
|
590
|
-
{ symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
|
|
591
|
-
true
|
|
592
|
-
)
|
|
593
|
-
);
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
const tpPriceNorm = normalizePrice(
|
|
597
|
-
tp.price,
|
|
598
|
-
isLong ? "TP_LONG" : "TP_SHORT",
|
|
599
|
-
meta
|
|
600
|
-
);
|
|
601
|
-
const isFullMode = TP.length === 1 && tp.rate === 1;
|
|
602
|
-
const tpRes = await client.setTradingStop({
|
|
603
|
-
category: import_constants2.MARKET_CATEGORY,
|
|
604
|
-
symbol,
|
|
605
|
-
tpSize: isFullMode ? void 0 : tpSizeStr,
|
|
606
|
-
tpslMode: isFullMode ? "Full" : "Partial",
|
|
607
|
-
takeProfit: tpPriceNorm.priceStr,
|
|
608
|
-
stopLoss: isFullMode && slNormalized ? slNormalized.priceStr : void 0,
|
|
609
|
-
slTriggerBy: "LastPrice",
|
|
610
|
-
tpOrderType: "Market",
|
|
611
|
-
positionIdx: 0
|
|
612
|
-
});
|
|
613
|
-
import_logger2.logger.log(
|
|
614
|
-
getLogLevel(tpRes),
|
|
615
|
-
"tp: %s %s",
|
|
616
|
-
(0, import_data2.toJson)(tp, true),
|
|
617
|
-
(0, import_data2.toJson)(tpRes, true)
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
793
|
return true;
|
|
622
794
|
},
|
|
795
|
+
setTakeProfits,
|
|
796
|
+
setStopLoss,
|
|
623
797
|
closePosition: async ({ symbol, direction }) => {
|
|
624
|
-
const client = await
|
|
798
|
+
const client = await getPrivateClient();
|
|
625
799
|
if (!client) {
|
|
626
800
|
return false;
|
|
627
801
|
}
|
|
@@ -646,7 +820,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
646
820
|
return true;
|
|
647
821
|
},
|
|
648
822
|
getTickers: async () => {
|
|
649
|
-
const client = await
|
|
823
|
+
const client = await getPublicClient();
|
|
650
824
|
if (!client) {
|
|
651
825
|
return [];
|
|
652
826
|
}
|
|
@@ -747,6 +921,8 @@ var BinanceConnectorCreator = async () => {
|
|
|
747
921
|
getPosition: async () => null,
|
|
748
922
|
getPositions: async () => [],
|
|
749
923
|
placeOrder: async () => false,
|
|
924
|
+
setTakeProfits: async () => false,
|
|
925
|
+
setStopLoss: async () => false,
|
|
750
926
|
closePosition: async () => false,
|
|
751
927
|
getTickers: async () => {
|
|
752
928
|
const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
|
|
@@ -906,6 +1082,8 @@ var CoinbaseConnectorCreator = async () => {
|
|
|
906
1082
|
getPosition: async () => null,
|
|
907
1083
|
getPositions: async () => [],
|
|
908
1084
|
placeOrder: async () => false,
|
|
1085
|
+
setTakeProfits: async () => false,
|
|
1086
|
+
setStopLoss: async () => false,
|
|
909
1087
|
closePosition: async () => false,
|
|
910
1088
|
getTickers: async () => {
|
|
911
1089
|
const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
|
|
@@ -1117,7 +1295,7 @@ var binanceCoinbaseSpreadProvider = {
|
|
|
1117
1295
|
};
|
|
1118
1296
|
|
|
1119
1297
|
// src/marketData/providers/coinalyze.ts
|
|
1120
|
-
var
|
|
1298
|
+
var import_async2 = require("@tradejs/core/async");
|
|
1121
1299
|
var import_indicators2 = require("@tradejs/core/indicators");
|
|
1122
1300
|
var coinalyzeIntervalMap = {
|
|
1123
1301
|
"15m": "15min",
|
|
@@ -1168,12 +1346,8 @@ var normalizeMetricPayload = (metric, raw) => {
|
|
|
1168
1346
|
}));
|
|
1169
1347
|
};
|
|
1170
1348
|
var fetchCoinalyzeSeries = async (params) => {
|
|
1171
|
-
const { endpoint, metric, symbol, interval, fromMs, toMs } = params;
|
|
1349
|
+
const { endpoint, metric, symbol, apiKey, interval, fromMs, toMs } = params;
|
|
1172
1350
|
const baseUrl = process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
|
|
1173
|
-
const apiKey = process.env.COINALYZE_API_KEY?.trim();
|
|
1174
|
-
if (!apiKey) {
|
|
1175
|
-
throw new Error("Missing COINALYZE_API_KEY");
|
|
1176
|
-
}
|
|
1177
1351
|
const url = new URL(`${baseUrl}${endpoint}`);
|
|
1178
1352
|
url.searchParams.set("symbols", symbol);
|
|
1179
1353
|
url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
|
|
@@ -1191,7 +1365,7 @@ var fetchCoinalyzeSeries = async (params) => {
|
|
|
1191
1365
|
lastRequestTs + coinalyzeMinRequestDelayMs - now
|
|
1192
1366
|
);
|
|
1193
1367
|
if (waitMs > 0) {
|
|
1194
|
-
await (0,
|
|
1368
|
+
await (0, import_async2.delay)(waitMs);
|
|
1195
1369
|
}
|
|
1196
1370
|
lastRequestTs = Date.now();
|
|
1197
1371
|
const response = await fetch(url.toString(), { headers });
|
|
@@ -1205,7 +1379,7 @@ var fetchCoinalyzeSeries = async (params) => {
|
|
|
1205
1379
|
const transient = response.status === 429 || response.status >= 500;
|
|
1206
1380
|
if (attempt < coinalyzeMaxRetries && transient) {
|
|
1207
1381
|
const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
|
|
1208
|
-
await (0,
|
|
1382
|
+
await (0, import_async2.delay)(retryAfterMs ?? backoffMs);
|
|
1209
1383
|
continue;
|
|
1210
1384
|
}
|
|
1211
1385
|
throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
|
|
@@ -1214,15 +1388,27 @@ var fetchCoinalyzeSeries = async (params) => {
|
|
|
1214
1388
|
};
|
|
1215
1389
|
var coinalyzeProvider = {
|
|
1216
1390
|
name: "coinalyze",
|
|
1217
|
-
fetchWindow: async ({
|
|
1391
|
+
fetchWindow: async ({
|
|
1392
|
+
symbol,
|
|
1393
|
+
marketSymbol,
|
|
1394
|
+
apiKey,
|
|
1395
|
+
interval,
|
|
1396
|
+
fromMs,
|
|
1397
|
+
toMs
|
|
1398
|
+
}) => {
|
|
1399
|
+
if (!apiKey?.trim()) {
|
|
1400
|
+
throw new Error("Missing COINALYZE_API_KEY in user settings");
|
|
1401
|
+
}
|
|
1218
1402
|
const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
|
|
1219
1403
|
const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
|
|
1220
1404
|
const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
|
|
1221
1405
|
const requestSymbol = (marketSymbol || symbol).trim().toUpperCase();
|
|
1406
|
+
const normalizedApiKey = apiKey.trim();
|
|
1222
1407
|
const oiRaw = await fetchCoinalyzeSeries({
|
|
1223
1408
|
endpoint: oiPath,
|
|
1224
1409
|
metric: "oi",
|
|
1225
1410
|
symbol: requestSymbol,
|
|
1411
|
+
apiKey: normalizedApiKey,
|
|
1226
1412
|
interval,
|
|
1227
1413
|
fromMs,
|
|
1228
1414
|
toMs
|
|
@@ -1231,6 +1417,7 @@ var coinalyzeProvider = {
|
|
|
1231
1417
|
endpoint: fundingPath,
|
|
1232
1418
|
metric: "funding",
|
|
1233
1419
|
symbol: requestSymbol,
|
|
1420
|
+
apiKey: normalizedApiKey,
|
|
1234
1421
|
interval,
|
|
1235
1422
|
fromMs,
|
|
1236
1423
|
toMs
|
|
@@ -1239,6 +1426,7 @@ var coinalyzeProvider = {
|
|
|
1239
1426
|
endpoint: liqPath,
|
|
1240
1427
|
metric: "liq",
|
|
1241
1428
|
symbol: requestSymbol,
|
|
1429
|
+
apiKey: normalizedApiKey,
|
|
1242
1430
|
interval,
|
|
1243
1431
|
fromMs,
|
|
1244
1432
|
toMs
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/ByBit/index.ts
|
|
2
2
|
import _ from "lodash";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
+
import { delay } from "@tradejs/core/async";
|
|
4
5
|
import {
|
|
5
6
|
MARKET_CATEGORY as MARKET_CATEGORY2,
|
|
6
7
|
PRELOAD_FALLBACK_DAYS
|
|
@@ -23,7 +24,13 @@ import { RestClientV5 } from "bybit-api";
|
|
|
23
24
|
import { logger } from "@tradejs/infra/logger";
|
|
24
25
|
import { getData, redisKeys } from "@tradejs/infra/redis";
|
|
25
26
|
var useTestnet = false;
|
|
26
|
-
var getClient = async ({ userName }) => {
|
|
27
|
+
var getClient = async ({ userName }, access = "private") => {
|
|
28
|
+
if (access === "public") {
|
|
29
|
+
return new RestClientV5({
|
|
30
|
+
parseAPIRateLimits: true,
|
|
31
|
+
testnet: useTestnet
|
|
32
|
+
});
|
|
33
|
+
}
|
|
27
34
|
const user = await getData(redisKeys.user(userName));
|
|
28
35
|
if (!user) {
|
|
29
36
|
logger.log("error", "connection config not found: %s", userName);
|
|
@@ -32,6 +39,7 @@ var getClient = async ({ userName }) => {
|
|
|
32
39
|
const client = new RestClientV5({
|
|
33
40
|
key: user.BYBIT_API_KEY,
|
|
34
41
|
secret: user.BYBIT_API_SECRET,
|
|
42
|
+
parseAPIRateLimits: true,
|
|
35
43
|
testnet: useTestnet
|
|
36
44
|
});
|
|
37
45
|
return client;
|
|
@@ -131,6 +139,12 @@ var mapPositionData = (data) => {
|
|
|
131
139
|
// src/ByBit/index.ts
|
|
132
140
|
var LIMIT = 1e3;
|
|
133
141
|
var CACHE_FALLBACK_WINDOW = 1e3;
|
|
142
|
+
var BYBIT_RATE_LIMIT_RETCODE = 10006;
|
|
143
|
+
var BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE = 34040;
|
|
144
|
+
var KLINE_RATE_LIMIT_MAX_ATTEMPTS = 3;
|
|
145
|
+
var KLINE_RATE_LIMIT_BASE_DELAY_MS = 800;
|
|
146
|
+
var KLINE_RATE_LIMIT_MAX_DELAY_MS = 1e4;
|
|
147
|
+
var KLINE_RATE_LIMIT_RESET_BUFFER_MS = 50;
|
|
134
148
|
var INTERVAL_TO_MINUTES = {
|
|
135
149
|
"1": 1,
|
|
136
150
|
"3": 3,
|
|
@@ -147,9 +161,37 @@ var INTERVAL_TO_MINUTES = {
|
|
|
147
161
|
M: 43200
|
|
148
162
|
};
|
|
149
163
|
var getLogLevel = (res) => res.retCode === 0 ? "info" : "error";
|
|
164
|
+
var isTradingStopNotModified = (res) => res?.retCode === BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE;
|
|
165
|
+
var isTradingStopAccepted = (res) => res?.retCode === 0 || isTradingStopNotModified(res);
|
|
166
|
+
var isKlineRateLimited = (res) => res?.retCode === BYBIT_RATE_LIMIT_RETCODE;
|
|
167
|
+
var resolveKlineRetryDelayMs = (res, attempt) => {
|
|
168
|
+
const resetAtRaw = Number(res?.rateLimitApi?.resetAtTimestamp);
|
|
169
|
+
if (Number.isFinite(resetAtRaw) && resetAtRaw > 0) {
|
|
170
|
+
return Math.max(
|
|
171
|
+
KLINE_RATE_LIMIT_RESET_BUFFER_MS,
|
|
172
|
+
resetAtRaw - Date.now() + KLINE_RATE_LIMIT_RESET_BUFFER_MS
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const jitterMs = Math.round(Math.random() * 250);
|
|
176
|
+
const backoffMs = Math.min(
|
|
177
|
+
KLINE_RATE_LIMIT_MAX_DELAY_MS,
|
|
178
|
+
KLINE_RATE_LIMIT_BASE_DELAY_MS * 2 ** (attempt - 1)
|
|
179
|
+
);
|
|
180
|
+
return backoffMs + jitterMs;
|
|
181
|
+
};
|
|
150
182
|
var ByBitConnectorCreator = async (config) => {
|
|
151
183
|
let state = {};
|
|
152
184
|
let isTimescaleFallbackMode = false;
|
|
185
|
+
let publicClientPromise = null;
|
|
186
|
+
let privateClientPromise = null;
|
|
187
|
+
const getPublicClient = async () => {
|
|
188
|
+
publicClientPromise ??= getClient(config, "public");
|
|
189
|
+
return publicClientPromise;
|
|
190
|
+
};
|
|
191
|
+
const getPrivateClient = async () => {
|
|
192
|
+
privateClientPromise ??= getClient(config, "private");
|
|
193
|
+
return privateClientPromise;
|
|
194
|
+
};
|
|
153
195
|
const request = async ({
|
|
154
196
|
symbol,
|
|
155
197
|
interval,
|
|
@@ -163,41 +205,60 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
163
205
|
);
|
|
164
206
|
const normalizedEnd = round(end || Date.now());
|
|
165
207
|
try {
|
|
166
|
-
const client = await
|
|
208
|
+
const client = await getPublicClient();
|
|
167
209
|
if (!client) return [];
|
|
168
210
|
if (normalizedEnd <= normalizedStart) {
|
|
169
211
|
return [];
|
|
170
212
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
interval,
|
|
175
|
-
start: normalizedStart,
|
|
176
|
-
end: normalizedEnd,
|
|
177
|
-
limit: LIMIT
|
|
178
|
-
});
|
|
179
|
-
if (!kline?.result?.list) {
|
|
180
|
-
const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
|
|
181
|
-
logger2.log(
|
|
182
|
-
"error",
|
|
183
|
-
"empty kline.list for %s %s%s",
|
|
213
|
+
for (let attempt = 1; attempt <= KLINE_RATE_LIMIT_MAX_ATTEMPTS; attempt += 1) {
|
|
214
|
+
const kline = await client.getKline({
|
|
215
|
+
category: MARKET_CATEGORY2,
|
|
184
216
|
symbol,
|
|
185
217
|
interval,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
218
|
+
start: normalizedStart,
|
|
219
|
+
end: normalizedEnd,
|
|
220
|
+
limit: LIMIT
|
|
221
|
+
});
|
|
222
|
+
if (isKlineRateLimited(kline)) {
|
|
223
|
+
if (attempt < KLINE_RATE_LIMIT_MAX_ATTEMPTS) {
|
|
224
|
+
const waitMs = resolveKlineRetryDelayMs(kline, attempt);
|
|
225
|
+
logger2.log(
|
|
226
|
+
"warn",
|
|
227
|
+
"kline rate limited for %s %s: attempt=%s/%s waitMs=%s",
|
|
228
|
+
symbol,
|
|
229
|
+
interval,
|
|
230
|
+
attempt,
|
|
231
|
+
KLINE_RATE_LIMIT_MAX_ATTEMPTS,
|
|
232
|
+
waitMs
|
|
233
|
+
);
|
|
234
|
+
await delay(waitMs);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (!kline?.result?.list) {
|
|
239
|
+
const responseError = typeof kline?.retMsg === "string" && kline.retMsg !== "OK" ? `${kline.retMsg}${typeof kline?.retCode === "number" ? ` (retCode: ${kline.retCode})` : ""}` : typeof kline?.retCode === "number" && kline.retCode !== 0 ? `retCode: ${kline.retCode}` : "";
|
|
240
|
+
logger2.log(
|
|
241
|
+
"error",
|
|
242
|
+
"empty kline.list for %s %s%s",
|
|
243
|
+
symbol,
|
|
244
|
+
interval,
|
|
245
|
+
responseError ? `: ${responseError}` : ""
|
|
246
|
+
);
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
if (!silent) {
|
|
250
|
+
logger2.log(
|
|
251
|
+
"info",
|
|
252
|
+
"%s %s %s %s",
|
|
253
|
+
chalk.yellow(formatUnix2(normalizedEnd)),
|
|
254
|
+
chalk.cyan(symbol),
|
|
255
|
+
chalk.cyan(interval),
|
|
256
|
+
chalk.yellow(kline.result.list.length)
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return mapKlineToChartData(kline.result.list.reverse());
|
|
199
260
|
}
|
|
200
|
-
return
|
|
261
|
+
return [];
|
|
201
262
|
} catch (error) {
|
|
202
263
|
logger2.log(
|
|
203
264
|
"error",
|
|
@@ -289,6 +350,190 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
289
350
|
await upsertCandles(toRows(symbol, intMinutes, part));
|
|
290
351
|
}
|
|
291
352
|
};
|
|
353
|
+
const getPositionSnapshot = async (symbol) => {
|
|
354
|
+
const client = await getPrivateClient();
|
|
355
|
+
if (!client) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
const positionRes = await client.getPositionInfo({
|
|
359
|
+
symbol,
|
|
360
|
+
category: MARKET_CATEGORY2
|
|
361
|
+
});
|
|
362
|
+
if (positionRes.retCode !== 0) {
|
|
363
|
+
logger2.log(
|
|
364
|
+
getLogLevel(positionRes),
|
|
365
|
+
"position retCode: %s, %s",
|
|
366
|
+
symbol,
|
|
367
|
+
positionRes.retCode
|
|
368
|
+
);
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const positions = mapPositionData(positionRes.result.list);
|
|
372
|
+
if (!positions || _.isEmpty(positions)) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
const position = positions[0];
|
|
376
|
+
logger2.log(
|
|
377
|
+
"debug",
|
|
378
|
+
"position: %s %s qty=%s price=%s",
|
|
379
|
+
position.symbol,
|
|
380
|
+
position.direction,
|
|
381
|
+
position.qty,
|
|
382
|
+
position.price
|
|
383
|
+
);
|
|
384
|
+
return {
|
|
385
|
+
...position
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
const setTakeProfits = async ({
|
|
389
|
+
symbol,
|
|
390
|
+
direction,
|
|
391
|
+
qty,
|
|
392
|
+
takeProfits
|
|
393
|
+
}) => {
|
|
394
|
+
const client = await getPrivateClient();
|
|
395
|
+
const marketDataClient = await getPublicClient();
|
|
396
|
+
if (!client || !marketDataClient) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
if (!Array.isArray(takeProfits) || takeProfits.length === 0) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
403
|
+
const positionQty = qty ?? (await getPositionSnapshot(symbol))?.qty ?? 0;
|
|
404
|
+
if (!Number.isFinite(positionQty) || positionQty <= 0) {
|
|
405
|
+
logger2.log(
|
|
406
|
+
"warn",
|
|
407
|
+
"setTakeProfits: missing position qty: %s",
|
|
408
|
+
toJson({ symbol, qty, positionQty, takeProfits }, true)
|
|
409
|
+
);
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
const isLong = direction === "LONG";
|
|
413
|
+
for (const tp of takeProfits) {
|
|
414
|
+
const tpSizeRaw = positionQty * tp.rate;
|
|
415
|
+
const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
|
|
416
|
+
tpSizeRaw,
|
|
417
|
+
meta
|
|
418
|
+
);
|
|
419
|
+
if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
|
|
420
|
+
logger2.log(
|
|
421
|
+
"warn",
|
|
422
|
+
"tp skipped: size too small %s",
|
|
423
|
+
toJson(
|
|
424
|
+
{ symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
|
|
425
|
+
true
|
|
426
|
+
)
|
|
427
|
+
);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const tpPriceNorm = normalizePrice(
|
|
431
|
+
tp.price,
|
|
432
|
+
isLong ? "TP_LONG" : "TP_SHORT",
|
|
433
|
+
meta
|
|
434
|
+
);
|
|
435
|
+
const isFullMode = takeProfits.length === 1 && tp.rate === 1;
|
|
436
|
+
const tpRes = await client.setTradingStop({
|
|
437
|
+
category: MARKET_CATEGORY2,
|
|
438
|
+
symbol,
|
|
439
|
+
tpSize: isFullMode ? void 0 : tpSizeStr,
|
|
440
|
+
tpslMode: isFullMode ? "Full" : "Partial",
|
|
441
|
+
takeProfit: tpPriceNorm.priceStr,
|
|
442
|
+
tpTriggerBy: "MarkPrice",
|
|
443
|
+
tpOrderType: "Market",
|
|
444
|
+
positionIdx: 0
|
|
445
|
+
});
|
|
446
|
+
if (isTradingStopNotModified(tpRes)) {
|
|
447
|
+
logger2.log(
|
|
448
|
+
"debug",
|
|
449
|
+
"tp unchanged: %s %s price=%s rate=%s",
|
|
450
|
+
symbol,
|
|
451
|
+
direction,
|
|
452
|
+
tpPriceNorm.priceStr,
|
|
453
|
+
tp.rate
|
|
454
|
+
);
|
|
455
|
+
} else if (tpRes.retCode === 0) {
|
|
456
|
+
logger2.log(
|
|
457
|
+
"info",
|
|
458
|
+
"tp updated: %s %s price=%s rate=%s",
|
|
459
|
+
symbol,
|
|
460
|
+
direction,
|
|
461
|
+
tpPriceNorm.priceStr,
|
|
462
|
+
tp.rate
|
|
463
|
+
);
|
|
464
|
+
} else {
|
|
465
|
+
logger2.log(
|
|
466
|
+
"error",
|
|
467
|
+
"tp failed: %s %s price=%s rate=%s %s",
|
|
468
|
+
symbol,
|
|
469
|
+
direction,
|
|
470
|
+
tpPriceNorm.priceStr,
|
|
471
|
+
tp.rate,
|
|
472
|
+
toJson(tpRes, true)
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
if (!isTradingStopAccepted(tpRes)) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return true;
|
|
480
|
+
};
|
|
481
|
+
const setStopLoss = async ({
|
|
482
|
+
symbol,
|
|
483
|
+
direction,
|
|
484
|
+
stopLossPrice
|
|
485
|
+
}) => {
|
|
486
|
+
const client = await getPrivateClient();
|
|
487
|
+
const marketDataClient = await getPublicClient();
|
|
488
|
+
if (!client || !marketDataClient) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
if (typeof stopLossPrice !== "number" || !Number.isFinite(stopLossPrice)) {
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
495
|
+
const isLong = direction === "LONG";
|
|
496
|
+
const slNormalized = normalizePrice(
|
|
497
|
+
stopLossPrice,
|
|
498
|
+
isLong ? "SL_LONG" : "SL_SHORT",
|
|
499
|
+
meta
|
|
500
|
+
);
|
|
501
|
+
const slRes = await client.setTradingStop({
|
|
502
|
+
category: MARKET_CATEGORY2,
|
|
503
|
+
symbol,
|
|
504
|
+
tpslMode: "Full",
|
|
505
|
+
stopLoss: slNormalized.priceStr,
|
|
506
|
+
slTriggerBy: "LastPrice",
|
|
507
|
+
positionIdx: 0
|
|
508
|
+
});
|
|
509
|
+
if (isTradingStopNotModified(slRes)) {
|
|
510
|
+
logger2.log(
|
|
511
|
+
"debug",
|
|
512
|
+
"sl unchanged: %s %s stopLoss=%s",
|
|
513
|
+
symbol,
|
|
514
|
+
direction,
|
|
515
|
+
slNormalized.priceStr
|
|
516
|
+
);
|
|
517
|
+
} else if (slRes.retCode === 0) {
|
|
518
|
+
logger2.log(
|
|
519
|
+
"info",
|
|
520
|
+
"sl updated: %s %s stopLoss=%s",
|
|
521
|
+
symbol,
|
|
522
|
+
direction,
|
|
523
|
+
slNormalized.priceStr
|
|
524
|
+
);
|
|
525
|
+
} else {
|
|
526
|
+
logger2.log(
|
|
527
|
+
"error",
|
|
528
|
+
"sl failed: %s %s stopLoss=%s %s",
|
|
529
|
+
symbol,
|
|
530
|
+
direction,
|
|
531
|
+
slNormalized.priceStr,
|
|
532
|
+
toJson(slRes, true)
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
return isTradingStopAccepted(slRes);
|
|
536
|
+
};
|
|
292
537
|
return {
|
|
293
538
|
getState: async () => state,
|
|
294
539
|
setState: async (newState) => {
|
|
@@ -300,7 +545,8 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
300
545
|
start: defaultStart,
|
|
301
546
|
end: defaultEnd,
|
|
302
547
|
silent = false,
|
|
303
|
-
cacheOnly = false
|
|
548
|
+
cacheOnly = false,
|
|
549
|
+
warmOnly = false
|
|
304
550
|
}) => {
|
|
305
551
|
const intMinutes = intervalToMinutes(interval);
|
|
306
552
|
if (!intMinutes) {
|
|
@@ -376,6 +622,16 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
376
622
|
if (!cacheOnly && isRightEdgeQuery) {
|
|
377
623
|
await refreshTail({ symbol, interval, silent });
|
|
378
624
|
}
|
|
625
|
+
if (warmOnly) {
|
|
626
|
+
if (isTimescaleFallbackMode) {
|
|
627
|
+
isTimescaleFallbackMode = false;
|
|
628
|
+
logger2.log(
|
|
629
|
+
"info",
|
|
630
|
+
"TimescaleDB connection restored for kline cache"
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
379
635
|
const rangeStart = defaultStart ?? dataStart ?? 0;
|
|
380
636
|
const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
|
|
381
637
|
const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
|
|
@@ -401,7 +657,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
401
657
|
String(error)
|
|
402
658
|
);
|
|
403
659
|
}
|
|
404
|
-
if (cacheOnly) {
|
|
660
|
+
if (cacheOnly || warmOnly) {
|
|
405
661
|
return [];
|
|
406
662
|
}
|
|
407
663
|
return request({
|
|
@@ -413,41 +669,9 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
413
669
|
});
|
|
414
670
|
}
|
|
415
671
|
},
|
|
416
|
-
getPosition: async (symbol) =>
|
|
417
|
-
const client = await getClient(config);
|
|
418
|
-
if (!client) {
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
const positionRes = await client.getPositionInfo({
|
|
422
|
-
symbol,
|
|
423
|
-
category: MARKET_CATEGORY2
|
|
424
|
-
});
|
|
425
|
-
logger2.log(
|
|
426
|
-
getLogLevel(positionRes),
|
|
427
|
-
"position retCode: %s, %s",
|
|
428
|
-
symbol,
|
|
429
|
-
positionRes.retCode
|
|
430
|
-
);
|
|
431
|
-
if (positionRes.retCode !== 0) {
|
|
432
|
-
return null;
|
|
433
|
-
}
|
|
434
|
-
const positions = mapPositionData(positionRes.result.list);
|
|
435
|
-
if (!positions || _.isEmpty(positions)) {
|
|
436
|
-
return null;
|
|
437
|
-
}
|
|
438
|
-
const position = positions[0];
|
|
439
|
-
logger2.log(
|
|
440
|
-
getLogLevel(positionRes),
|
|
441
|
-
"position: %s, %s",
|
|
442
|
-
symbol,
|
|
443
|
-
toJson(positionRes, true)
|
|
444
|
-
);
|
|
445
|
-
return {
|
|
446
|
-
...position
|
|
447
|
-
};
|
|
448
|
-
},
|
|
672
|
+
getPosition: async (symbol) => getPositionSnapshot(symbol),
|
|
449
673
|
getPositions: async () => {
|
|
450
|
-
const client = await
|
|
674
|
+
const client = await getPrivateClient();
|
|
451
675
|
if (!client) {
|
|
452
676
|
return [];
|
|
453
677
|
}
|
|
@@ -455,12 +679,12 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
455
679
|
category: MARKET_CATEGORY2,
|
|
456
680
|
settleCoin: "USDT"
|
|
457
681
|
});
|
|
458
|
-
logger2.log(
|
|
459
|
-
getLogLevel(positionRes),
|
|
460
|
-
"positions retCode: %s, %s",
|
|
461
|
-
positionRes.retCode
|
|
462
|
-
);
|
|
463
682
|
if (positionRes.retCode !== 0) {
|
|
683
|
+
logger2.log(
|
|
684
|
+
getLogLevel(positionRes),
|
|
685
|
+
"positions retCode: %s, %s",
|
|
686
|
+
positionRes.retCode
|
|
687
|
+
);
|
|
464
688
|
return [];
|
|
465
689
|
}
|
|
466
690
|
const positions = mapPositionData(positionRes.result.list);
|
|
@@ -469,13 +693,14 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
469
693
|
}
|
|
470
694
|
return positions;
|
|
471
695
|
},
|
|
472
|
-
placeOrder: async ({ symbol, price, qty, direction, isLimit }
|
|
473
|
-
const client = await
|
|
474
|
-
|
|
696
|
+
placeOrder: async ({ symbol, price, qty, direction, isLimit }) => {
|
|
697
|
+
const client = await getPrivateClient();
|
|
698
|
+
const marketDataClient = await getPublicClient();
|
|
699
|
+
if (!client || !marketDataClient) {
|
|
475
700
|
return false;
|
|
476
701
|
}
|
|
477
702
|
const isLong = direction === "LONG";
|
|
478
|
-
const meta = await getSymbolMeta(
|
|
703
|
+
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
479
704
|
const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
|
|
480
705
|
if (orderQty < meta.minOrderQty) {
|
|
481
706
|
logger2.log(
|
|
@@ -489,9 +714,6 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
489
714
|
return false;
|
|
490
715
|
}
|
|
491
716
|
const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
|
|
492
|
-
const slNormalized = slPrice ? normalizePrice(slPrice, isLong ? "SL_LONG" : "SL_SHORT", meta) : void 0;
|
|
493
|
-
const firstTP = TP?.[0];
|
|
494
|
-
const tpNormalized = firstTP && firstTP.rate === 1 ? normalizePrice(firstTP.price, isLong ? "TP_LONG" : "TP_SHORT", meta) : void 0;
|
|
495
717
|
logger2.log(
|
|
496
718
|
"info",
|
|
497
719
|
"placeOrder: %s",
|
|
@@ -502,10 +724,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
502
724
|
qty,
|
|
503
725
|
direction,
|
|
504
726
|
orderQty,
|
|
505
|
-
orderQtyStr
|
|
506
|
-
slPrice,
|
|
507
|
-
slPriceNorm: slNormalized?.priceStr,
|
|
508
|
-
TP
|
|
727
|
+
orderQtyStr
|
|
509
728
|
},
|
|
510
729
|
true
|
|
511
730
|
)
|
|
@@ -520,10 +739,6 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
520
739
|
category: MARKET_CATEGORY2,
|
|
521
740
|
symbol,
|
|
522
741
|
price: entryNormalized?.priceStr || void 0,
|
|
523
|
-
takeProfit: tpNormalized?.priceStr || void 0,
|
|
524
|
-
tpTriggerBy: "MarkPrice",
|
|
525
|
-
stopLoss: slNormalized?.priceStr || void 0,
|
|
526
|
-
slTriggerBy: "LastPrice",
|
|
527
742
|
side: isLong ? "Buy" : "Sell",
|
|
528
743
|
orderType: isLimit ? "Limit" : "Market",
|
|
529
744
|
qty: orderQtyStr,
|
|
@@ -537,53 +752,12 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
537
752
|
if (orderRes.retCode !== 0) {
|
|
538
753
|
return false;
|
|
539
754
|
}
|
|
540
|
-
if (!isLimit) {
|
|
541
|
-
for (const tp of TP) {
|
|
542
|
-
const tpSizeRaw = orderQty * tp.rate;
|
|
543
|
-
const { qtyNum: tpSizeNum, qtyStr: tpSizeStr } = normalizeQty(
|
|
544
|
-
tpSizeRaw,
|
|
545
|
-
meta
|
|
546
|
-
);
|
|
547
|
-
if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
|
|
548
|
-
logger2.log(
|
|
549
|
-
"warn",
|
|
550
|
-
"tp skipped: size too small %s",
|
|
551
|
-
toJson(
|
|
552
|
-
{ symbol, tp, tpSizeNum, minOrderQty: meta.minOrderQty },
|
|
553
|
-
true
|
|
554
|
-
)
|
|
555
|
-
);
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
const tpPriceNorm = normalizePrice(
|
|
559
|
-
tp.price,
|
|
560
|
-
isLong ? "TP_LONG" : "TP_SHORT",
|
|
561
|
-
meta
|
|
562
|
-
);
|
|
563
|
-
const isFullMode = TP.length === 1 && tp.rate === 1;
|
|
564
|
-
const tpRes = await client.setTradingStop({
|
|
565
|
-
category: MARKET_CATEGORY2,
|
|
566
|
-
symbol,
|
|
567
|
-
tpSize: isFullMode ? void 0 : tpSizeStr,
|
|
568
|
-
tpslMode: isFullMode ? "Full" : "Partial",
|
|
569
|
-
takeProfit: tpPriceNorm.priceStr,
|
|
570
|
-
stopLoss: isFullMode && slNormalized ? slNormalized.priceStr : void 0,
|
|
571
|
-
slTriggerBy: "LastPrice",
|
|
572
|
-
tpOrderType: "Market",
|
|
573
|
-
positionIdx: 0
|
|
574
|
-
});
|
|
575
|
-
logger2.log(
|
|
576
|
-
getLogLevel(tpRes),
|
|
577
|
-
"tp: %s %s",
|
|
578
|
-
toJson(tp, true),
|
|
579
|
-
toJson(tpRes, true)
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
755
|
return true;
|
|
584
756
|
},
|
|
757
|
+
setTakeProfits,
|
|
758
|
+
setStopLoss,
|
|
585
759
|
closePosition: async ({ symbol, direction }) => {
|
|
586
|
-
const client = await
|
|
760
|
+
const client = await getPrivateClient();
|
|
587
761
|
if (!client) {
|
|
588
762
|
return false;
|
|
589
763
|
}
|
|
@@ -608,7 +782,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
608
782
|
return true;
|
|
609
783
|
},
|
|
610
784
|
getTickers: async () => {
|
|
611
|
-
const client = await
|
|
785
|
+
const client = await getPublicClient();
|
|
612
786
|
if (!client) {
|
|
613
787
|
return [];
|
|
614
788
|
}
|
|
@@ -709,6 +883,8 @@ var BinanceConnectorCreator = async () => {
|
|
|
709
883
|
getPosition: async () => null,
|
|
710
884
|
getPositions: async () => [],
|
|
711
885
|
placeOrder: async () => false,
|
|
886
|
+
setTakeProfits: async () => false,
|
|
887
|
+
setStopLoss: async () => false,
|
|
712
888
|
closePosition: async () => false,
|
|
713
889
|
getTickers: async () => {
|
|
714
890
|
const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
|
|
@@ -868,6 +1044,8 @@ var CoinbaseConnectorCreator = async () => {
|
|
|
868
1044
|
getPosition: async () => null,
|
|
869
1045
|
getPositions: async () => [],
|
|
870
1046
|
placeOrder: async () => false,
|
|
1047
|
+
setTakeProfits: async () => false,
|
|
1048
|
+
setStopLoss: async () => false,
|
|
871
1049
|
closePosition: async () => false,
|
|
872
1050
|
getTickers: async () => {
|
|
873
1051
|
const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
|
|
@@ -1083,7 +1261,7 @@ var binanceCoinbaseSpreadProvider = {
|
|
|
1083
1261
|
};
|
|
1084
1262
|
|
|
1085
1263
|
// src/marketData/providers/coinalyze.ts
|
|
1086
|
-
import { delay } from "@tradejs/core/async";
|
|
1264
|
+
import { delay as delay2 } from "@tradejs/core/async";
|
|
1087
1265
|
import {
|
|
1088
1266
|
coinalyzePointsToRows,
|
|
1089
1267
|
mergeCoinalyzeMetrics
|
|
@@ -1137,12 +1315,8 @@ var normalizeMetricPayload = (metric, raw) => {
|
|
|
1137
1315
|
}));
|
|
1138
1316
|
};
|
|
1139
1317
|
var fetchCoinalyzeSeries = async (params) => {
|
|
1140
|
-
const { endpoint, metric, symbol, interval, fromMs, toMs } = params;
|
|
1318
|
+
const { endpoint, metric, symbol, apiKey, interval, fromMs, toMs } = params;
|
|
1141
1319
|
const baseUrl = process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
|
|
1142
|
-
const apiKey = process.env.COINALYZE_API_KEY?.trim();
|
|
1143
|
-
if (!apiKey) {
|
|
1144
|
-
throw new Error("Missing COINALYZE_API_KEY");
|
|
1145
|
-
}
|
|
1146
1320
|
const url = new URL(`${baseUrl}${endpoint}`);
|
|
1147
1321
|
url.searchParams.set("symbols", symbol);
|
|
1148
1322
|
url.searchParams.set("interval", coinalyzeIntervalMap[interval] || interval);
|
|
@@ -1160,7 +1334,7 @@ var fetchCoinalyzeSeries = async (params) => {
|
|
|
1160
1334
|
lastRequestTs + coinalyzeMinRequestDelayMs - now
|
|
1161
1335
|
);
|
|
1162
1336
|
if (waitMs > 0) {
|
|
1163
|
-
await
|
|
1337
|
+
await delay2(waitMs);
|
|
1164
1338
|
}
|
|
1165
1339
|
lastRequestTs = Date.now();
|
|
1166
1340
|
const response = await fetch(url.toString(), { headers });
|
|
@@ -1174,7 +1348,7 @@ var fetchCoinalyzeSeries = async (params) => {
|
|
|
1174
1348
|
const transient = response.status === 429 || response.status >= 500;
|
|
1175
1349
|
if (attempt < coinalyzeMaxRetries && transient) {
|
|
1176
1350
|
const backoffMs = Math.min(1e4, 750 * 2 ** attempt);
|
|
1177
|
-
await
|
|
1351
|
+
await delay2(retryAfterMs ?? backoffMs);
|
|
1178
1352
|
continue;
|
|
1179
1353
|
}
|
|
1180
1354
|
throw new Error(`Coinalyze ${endpoint} ${response.status}: ${text}`);
|
|
@@ -1183,15 +1357,27 @@ var fetchCoinalyzeSeries = async (params) => {
|
|
|
1183
1357
|
};
|
|
1184
1358
|
var coinalyzeProvider = {
|
|
1185
1359
|
name: "coinalyze",
|
|
1186
|
-
fetchWindow: async ({
|
|
1360
|
+
fetchWindow: async ({
|
|
1361
|
+
symbol,
|
|
1362
|
+
marketSymbol,
|
|
1363
|
+
apiKey,
|
|
1364
|
+
interval,
|
|
1365
|
+
fromMs,
|
|
1366
|
+
toMs
|
|
1367
|
+
}) => {
|
|
1368
|
+
if (!apiKey?.trim()) {
|
|
1369
|
+
throw new Error("Missing COINALYZE_API_KEY in user settings");
|
|
1370
|
+
}
|
|
1187
1371
|
const oiPath = process.env.COINALYZE_OI_PATH?.trim() || "/open-interest-history";
|
|
1188
1372
|
const fundingPath = process.env.COINALYZE_FUNDING_PATH?.trim() || "/funding-rate-history";
|
|
1189
1373
|
const liqPath = process.env.COINALYZE_LIQ_PATH?.trim() || "/liquidation-history";
|
|
1190
1374
|
const requestSymbol = (marketSymbol || symbol).trim().toUpperCase();
|
|
1375
|
+
const normalizedApiKey = apiKey.trim();
|
|
1191
1376
|
const oiRaw = await fetchCoinalyzeSeries({
|
|
1192
1377
|
endpoint: oiPath,
|
|
1193
1378
|
metric: "oi",
|
|
1194
1379
|
symbol: requestSymbol,
|
|
1380
|
+
apiKey: normalizedApiKey,
|
|
1195
1381
|
interval,
|
|
1196
1382
|
fromMs,
|
|
1197
1383
|
toMs
|
|
@@ -1200,6 +1386,7 @@ var coinalyzeProvider = {
|
|
|
1200
1386
|
endpoint: fundingPath,
|
|
1201
1387
|
metric: "funding",
|
|
1202
1388
|
symbol: requestSymbol,
|
|
1389
|
+
apiKey: normalizedApiKey,
|
|
1203
1390
|
interval,
|
|
1204
1391
|
fromMs,
|
|
1205
1392
|
toMs
|
|
@@ -1208,6 +1395,7 @@ var coinalyzeProvider = {
|
|
|
1208
1395
|
endpoint: liqPath,
|
|
1209
1396
|
metric: "liq",
|
|
1210
1397
|
symbol: requestSymbol,
|
|
1398
|
+
apiKey: normalizedApiKey,
|
|
1211
1399
|
interval,
|
|
1212
1400
|
fromMs,
|
|
1213
1401
|
toMs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/connectors",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Built-in exchange connectors and market data providers for the TradeJS open-source framework.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@tradejs/core": "^1.0.
|
|
37
|
-
"@tradejs/infra": "^1.0.
|
|
38
|
-
"@tradejs/node": "^1.0.
|
|
39
|
-
"@tradejs/types": "^1.0.
|
|
36
|
+
"@tradejs/core": "^1.0.6",
|
|
37
|
+
"@tradejs/infra": "^1.0.6",
|
|
38
|
+
"@tradejs/node": "^1.0.6",
|
|
39
|
+
"@tradejs/types": "^1.0.6",
|
|
40
40
|
"bybit-api": "^3.9.0",
|
|
41
41
|
"chalk": "4.1.2",
|
|
42
42
|
"lodash": "^4.17.21"
|