@tradejs/connectors 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +485 -339
- package/dist/index.mjs +477 -331
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -48,14 +48,12 @@ module.exports = __toCommonJS(index_exports);
|
|
|
48
48
|
var import_lodash = __toESM(require("lodash"));
|
|
49
49
|
var import_chalk = __toESM(require("chalk"));
|
|
50
50
|
var import_async = require("@tradejs/core/async");
|
|
51
|
-
var
|
|
52
|
-
var import_data = require("@tradejs/core/data");
|
|
51
|
+
var import_constants3 = require("@tradejs/core/constants");
|
|
53
52
|
var import_data2 = require("@tradejs/core/data");
|
|
54
53
|
var import_math = require("@tradejs/core/math");
|
|
55
54
|
var import_tickers = require("@tradejs/core/tickers");
|
|
56
|
-
var
|
|
57
|
-
var
|
|
58
|
-
var import_timescale = require("@tradejs/infra/timescale");
|
|
55
|
+
var import_time3 = require("@tradejs/core/time");
|
|
56
|
+
var import_logger3 = require("@tradejs/infra/logger");
|
|
59
57
|
|
|
60
58
|
// src/ByBit/client.ts
|
|
61
59
|
var import_bybit_api = require("bybit-api");
|
|
@@ -83,11 +81,242 @@ var getClient = async ({ userName }, access = "private") => {
|
|
|
83
81
|
return client;
|
|
84
82
|
};
|
|
85
83
|
|
|
86
|
-
// src/
|
|
84
|
+
// src/shared/timescaleKlineCache.ts
|
|
87
85
|
var import_constants = require("@tradejs/core/constants");
|
|
86
|
+
var import_data = require("@tradejs/core/data");
|
|
88
87
|
var import_time = require("@tradejs/core/time");
|
|
88
|
+
var import_logger2 = require("@tradejs/infra/logger");
|
|
89
|
+
var import_timescale = require("@tradejs/infra/timescale");
|
|
90
|
+
var DEFAULT_LIMIT = 1e3;
|
|
91
|
+
var DEFAULT_CACHE_FALLBACK_WINDOW = 1e3;
|
|
92
|
+
var intervalMsOf = (interval) => interval * 6e4;
|
|
93
|
+
var clampToClosedCandle = (value, intervalMs) => Math.floor(value / intervalMs) * intervalMs;
|
|
94
|
+
var normalizeRangeToClosed = (intervalMs, start, end) => {
|
|
95
|
+
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
96
|
+
const normStart = start !== void 0 ? clampToClosedCandle(start, intervalMs) : 0;
|
|
97
|
+
const cappedEnd = Math.min(end ?? Date.now(), lastClosed);
|
|
98
|
+
const normEnd = clampToClosedCandle(cappedEnd, intervalMs);
|
|
99
|
+
return { normStart, normEnd };
|
|
100
|
+
};
|
|
101
|
+
var rowsToKline = (rows) => rows.map(({ ts, ...data }) => ({
|
|
102
|
+
timestamp: new Date(ts).getTime(),
|
|
103
|
+
...data
|
|
104
|
+
}));
|
|
105
|
+
var createTimescaleCachedKline = ({
|
|
106
|
+
provider,
|
|
107
|
+
request,
|
|
108
|
+
intervalToMinutes: intervalToMinutes3,
|
|
109
|
+
limit = DEFAULT_LIMIT,
|
|
110
|
+
cacheFallbackWindow = DEFAULT_CACHE_FALLBACK_WINDOW
|
|
111
|
+
}) => {
|
|
112
|
+
let isTimescaleFallbackMode = false;
|
|
113
|
+
const loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs) => {
|
|
114
|
+
if (pointer === void 0) return [];
|
|
115
|
+
let accumulated = [];
|
|
116
|
+
let fulfilled = false;
|
|
117
|
+
while (!fulfilled) {
|
|
118
|
+
const currentPointer = pointer;
|
|
119
|
+
const params = {
|
|
120
|
+
symbol: requestParams.symbol,
|
|
121
|
+
interval: requestParams.interval,
|
|
122
|
+
silent: requestParams.silent,
|
|
123
|
+
end: requestParams.end
|
|
124
|
+
};
|
|
125
|
+
if (direction === "older") {
|
|
126
|
+
params.end = pointer;
|
|
127
|
+
if (limitBoundary !== void 0) params.start = limitBoundary;
|
|
128
|
+
} else {
|
|
129
|
+
params.start = pointer;
|
|
130
|
+
if (limitBoundary !== void 0) params.end = limitBoundary;
|
|
131
|
+
}
|
|
132
|
+
const partData = await request(params);
|
|
133
|
+
if (!partData.length) {
|
|
134
|
+
fulfilled = true;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
accumulated = direction === "older" ? (0, import_data.mergeData)(partData, accumulated) : (0, import_data.mergeData)(accumulated, partData);
|
|
138
|
+
const boundaryReached = limitBoundary !== void 0 && (direction === "older" && currentPointer <= limitBoundary || direction === "newer" && currentPointer >= limitBoundary);
|
|
139
|
+
if (partData.length < limit || boundaryReached) {
|
|
140
|
+
fulfilled = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
const nextPointer = direction === "older" ? (0, import_time.getItemTimestamp)(partData[0]) - intervalMs : (0, import_time.getItemTimestamp)(partData[partData.length - 1]) + intervalMs;
|
|
144
|
+
if (!Number.isFinite(nextPointer) || nextPointer === currentPointer) {
|
|
145
|
+
fulfilled = true;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
pointer = nextPointer;
|
|
149
|
+
}
|
|
150
|
+
return accumulated;
|
|
151
|
+
};
|
|
152
|
+
const refreshTail = async ({
|
|
153
|
+
symbol,
|
|
154
|
+
interval,
|
|
155
|
+
silent,
|
|
156
|
+
tailCount = 2
|
|
157
|
+
}) => {
|
|
158
|
+
const intMinutes = intervalToMinutes3(interval);
|
|
159
|
+
if (!intMinutes) {
|
|
160
|
+
import_logger2.logger.log("error", "refreshTail: invalid interval %s", interval);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const intervalMs = intervalMsOf(intMinutes);
|
|
164
|
+
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
165
|
+
const tailEnd = lastClosed + intervalMs;
|
|
166
|
+
const tailStart = tailEnd - tailCount * intervalMs;
|
|
167
|
+
const part = await request({
|
|
168
|
+
symbol,
|
|
169
|
+
interval,
|
|
170
|
+
start: tailStart,
|
|
171
|
+
end: tailEnd,
|
|
172
|
+
silent
|
|
173
|
+
});
|
|
174
|
+
if (part.length) {
|
|
175
|
+
await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, part));
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
return async ({
|
|
179
|
+
symbol,
|
|
180
|
+
interval,
|
|
181
|
+
start: defaultStart,
|
|
182
|
+
end: defaultEnd,
|
|
183
|
+
silent = false,
|
|
184
|
+
cacheOnly = false,
|
|
185
|
+
warmOnly = false
|
|
186
|
+
}) => {
|
|
187
|
+
const intMinutes = intervalToMinutes3(interval);
|
|
188
|
+
if (!intMinutes) {
|
|
189
|
+
import_logger2.logger.log("error", "kline: invalid interval %s", interval);
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
const intervalMs = intervalMsOf(intMinutes);
|
|
196
|
+
try {
|
|
197
|
+
const edges = await (0, import_timescale.getDataEdges)(provider, symbol, intMinutes);
|
|
198
|
+
let dataStart = edges.min;
|
|
199
|
+
let dataEnd = edges.max;
|
|
200
|
+
const { normStart, normEnd } = normalizeRangeToClosed(
|
|
201
|
+
intervalMs,
|
|
202
|
+
defaultStart,
|
|
203
|
+
defaultEnd
|
|
204
|
+
);
|
|
205
|
+
if (cacheOnly) {
|
|
206
|
+
const base = edges.max ?? Date.now();
|
|
207
|
+
const s = Math.max(
|
|
208
|
+
defaultStart ?? base - cacheFallbackWindow * intervalMs,
|
|
209
|
+
0
|
|
210
|
+
);
|
|
211
|
+
const e = defaultEnd ?? base;
|
|
212
|
+
const dbData2 = await (0, import_timescale.getCandlesRange)(
|
|
213
|
+
provider,
|
|
214
|
+
symbol,
|
|
215
|
+
intMinutes,
|
|
216
|
+
s,
|
|
217
|
+
e
|
|
218
|
+
);
|
|
219
|
+
return rowsToKline(dbData2);
|
|
220
|
+
}
|
|
221
|
+
const needOlderData = defaultStart !== void 0 && (dataStart === void 0 || normStart < dataStart);
|
|
222
|
+
const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
|
|
223
|
+
if (needOlderData) {
|
|
224
|
+
const pointerForOlder = dataStart ?? normEnd ?? Date.now();
|
|
225
|
+
const olderData = await loadData(
|
|
226
|
+
"older",
|
|
227
|
+
pointerForOlder,
|
|
228
|
+
normStart,
|
|
229
|
+
{
|
|
230
|
+
symbol,
|
|
231
|
+
interval,
|
|
232
|
+
silent,
|
|
233
|
+
start: normStart,
|
|
234
|
+
end: pointerForOlder
|
|
235
|
+
},
|
|
236
|
+
intervalMs
|
|
237
|
+
);
|
|
238
|
+
if (olderData.length) {
|
|
239
|
+
await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, olderData));
|
|
240
|
+
dataStart = normStart;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (needNewerData) {
|
|
244
|
+
const fallbackStart = (0, import_time.getTimestamp)(import_constants.PRELOAD_FALLBACK_DAYS);
|
|
245
|
+
const pointerForNewer = dataEnd ?? (defaultStart !== void 0 ? normStart : fallbackStart) ?? 0;
|
|
246
|
+
const newerData = await loadData(
|
|
247
|
+
"newer",
|
|
248
|
+
pointerForNewer,
|
|
249
|
+
normEnd,
|
|
250
|
+
{
|
|
251
|
+
symbol,
|
|
252
|
+
interval,
|
|
253
|
+
silent,
|
|
254
|
+
start: pointerForNewer,
|
|
255
|
+
end: normEnd
|
|
256
|
+
},
|
|
257
|
+
intervalMs
|
|
258
|
+
);
|
|
259
|
+
if (newerData.length) {
|
|
260
|
+
await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(provider, symbol, intMinutes, newerData));
|
|
261
|
+
dataEnd = normEnd;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const isRightEdgeQuery = defaultEnd === void 0 || defaultEnd && defaultEnd >= Date.now() - intervalMs;
|
|
265
|
+
if (!cacheOnly && isRightEdgeQuery) {
|
|
266
|
+
await refreshTail({ symbol, interval, silent });
|
|
267
|
+
}
|
|
268
|
+
if (warmOnly) {
|
|
269
|
+
if (isTimescaleFallbackMode) {
|
|
270
|
+
isTimescaleFallbackMode = false;
|
|
271
|
+
import_logger2.logger.log("info", "TimescaleDB connection restored for kline cache");
|
|
272
|
+
}
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
const rangeStart = defaultStart ?? dataStart ?? 0;
|
|
276
|
+
const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
|
|
277
|
+
const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
|
|
278
|
+
const dbData = await (0, import_timescale.getCandlesRange)(
|
|
279
|
+
provider,
|
|
280
|
+
symbol,
|
|
281
|
+
intMinutes,
|
|
282
|
+
finalStart,
|
|
283
|
+
finalEnd
|
|
284
|
+
);
|
|
285
|
+
if (isTimescaleFallbackMode) {
|
|
286
|
+
isTimescaleFallbackMode = false;
|
|
287
|
+
import_logger2.logger.log("info", "TimescaleDB connection restored for kline cache");
|
|
288
|
+
}
|
|
289
|
+
return rowsToKline(dbData);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (!isTimescaleFallbackMode) {
|
|
292
|
+
isTimescaleFallbackMode = true;
|
|
293
|
+
import_logger2.logger.log(
|
|
294
|
+
"warn",
|
|
295
|
+
"TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
|
|
296
|
+
symbol,
|
|
297
|
+
interval,
|
|
298
|
+
String(error)
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
if (cacheOnly || warmOnly) {
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
return request({
|
|
305
|
+
symbol,
|
|
306
|
+
interval,
|
|
307
|
+
start: defaultStart,
|
|
308
|
+
end: defaultEnd,
|
|
309
|
+
silent
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// src/ByBit/utils.ts
|
|
316
|
+
var import_constants2 = require("@tradejs/core/constants");
|
|
317
|
+
var import_time2 = require("@tradejs/core/time");
|
|
89
318
|
var parseKlineItem = (item) => ({
|
|
90
|
-
dt: (0,
|
|
319
|
+
dt: (0, import_time2.formatUnix)(parseInt(item[0])),
|
|
91
320
|
timestamp: parseInt(item[0]),
|
|
92
321
|
open: parseFloat(item[1]),
|
|
93
322
|
high: parseFloat(item[2]),
|
|
@@ -113,7 +342,7 @@ var getSymbolMeta = async (client, symbol) => {
|
|
|
113
342
|
const cached = symbolMetaCache.get(symbol);
|
|
114
343
|
if (cached) return cached;
|
|
115
344
|
const res = await client.getInstrumentsInfo({
|
|
116
|
-
category:
|
|
345
|
+
category: import_constants2.MARKET_CATEGORY,
|
|
117
346
|
symbol
|
|
118
347
|
});
|
|
119
348
|
const item = res?.result?.list?.[0];
|
|
@@ -176,7 +405,6 @@ var mapPositionData = (data) => {
|
|
|
176
405
|
|
|
177
406
|
// src/ByBit/index.ts
|
|
178
407
|
var LIMIT = 1e3;
|
|
179
|
-
var CACHE_FALLBACK_WINDOW = 1e3;
|
|
180
408
|
var BYBIT_RATE_LIMIT_RETCODE = 10006;
|
|
181
409
|
var BYBIT_TRADING_STOP_NOT_MODIFIED_RETCODE = 34040;
|
|
182
410
|
var KLINE_RATE_LIMIT_MAX_ATTEMPTS = 3;
|
|
@@ -219,7 +447,6 @@ var resolveKlineRetryDelayMs = (res, attempt) => {
|
|
|
219
447
|
};
|
|
220
448
|
var ByBitConnectorCreator = async (config) => {
|
|
221
449
|
let state = {};
|
|
222
|
-
let isTimescaleFallbackMode = false;
|
|
223
450
|
let publicClientPromise = null;
|
|
224
451
|
let privateClientPromise = null;
|
|
225
452
|
const getPublicClient = async () => {
|
|
@@ -238,7 +465,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
238
465
|
silent
|
|
239
466
|
}) => {
|
|
240
467
|
const normalizedStart = (0, import_math.round)(
|
|
241
|
-
start || (0,
|
|
468
|
+
start || (0, import_time3.getTimestamp)(import_constants3.PRELOAD_FALLBACK_DAYS),
|
|
242
469
|
0
|
|
243
470
|
);
|
|
244
471
|
const normalizedEnd = (0, import_math.round)(end || Date.now());
|
|
@@ -250,7 +477,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
250
477
|
}
|
|
251
478
|
for (let attempt = 1; attempt <= KLINE_RATE_LIMIT_MAX_ATTEMPTS; attempt += 1) {
|
|
252
479
|
const kline = await client.getKline({
|
|
253
|
-
category:
|
|
480
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
254
481
|
symbol,
|
|
255
482
|
interval,
|
|
256
483
|
start: normalizedStart,
|
|
@@ -260,7 +487,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
260
487
|
if (isKlineRateLimited(kline)) {
|
|
261
488
|
if (attempt < KLINE_RATE_LIMIT_MAX_ATTEMPTS) {
|
|
262
489
|
const waitMs = resolveKlineRetryDelayMs(kline, attempt);
|
|
263
|
-
|
|
490
|
+
import_logger3.logger.log(
|
|
264
491
|
"warn",
|
|
265
492
|
"kline rate limited for %s %s: attempt=%s/%s waitMs=%s",
|
|
266
493
|
symbol,
|
|
@@ -275,7 +502,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
275
502
|
}
|
|
276
503
|
if (!kline?.result?.list) {
|
|
277
504
|
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
|
-
|
|
505
|
+
import_logger3.logger.log(
|
|
279
506
|
"error",
|
|
280
507
|
"empty kline.list for %s %s%s",
|
|
281
508
|
symbol,
|
|
@@ -284,11 +511,11 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
284
511
|
);
|
|
285
512
|
return [];
|
|
286
513
|
}
|
|
287
|
-
if (!silent) {
|
|
288
|
-
|
|
514
|
+
if (!silent && (process.stdout.isTTY || process.env.TRADEJS_LOG_KLINE_REQUESTS === "1")) {
|
|
515
|
+
import_logger3.logger.log(
|
|
289
516
|
"info",
|
|
290
517
|
"%s %s %s %s",
|
|
291
|
-
import_chalk.default.yellow((0,
|
|
518
|
+
import_chalk.default.yellow((0, import_time3.formatUnix)(normalizedEnd)),
|
|
292
519
|
import_chalk.default.cyan(symbol),
|
|
293
520
|
import_chalk.default.cyan(interval),
|
|
294
521
|
import_chalk.default.yellow(kline.result.list.length)
|
|
@@ -298,7 +525,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
298
525
|
}
|
|
299
526
|
return [];
|
|
300
527
|
} catch (error) {
|
|
301
|
-
|
|
528
|
+
import_logger3.logger.log(
|
|
302
529
|
"error",
|
|
303
530
|
"request kline: %s %s %s",
|
|
304
531
|
normalizedStart,
|
|
@@ -308,86 +535,9 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
308
535
|
return [];
|
|
309
536
|
}
|
|
310
537
|
};
|
|
311
|
-
const
|
|
312
|
-
if (pointer === void 0) return [];
|
|
313
|
-
let accumulated = [];
|
|
314
|
-
let fulfilled = false;
|
|
315
|
-
while (!fulfilled) {
|
|
316
|
-
const currentPointer = pointer;
|
|
317
|
-
const params = {
|
|
318
|
-
symbol: requestParams.symbol,
|
|
319
|
-
interval: requestParams.interval,
|
|
320
|
-
silent: requestParams.silent
|
|
321
|
-
};
|
|
322
|
-
if (direction === "older") {
|
|
323
|
-
params.end = pointer;
|
|
324
|
-
if (limitBoundary !== void 0) params.start = limitBoundary;
|
|
325
|
-
} else {
|
|
326
|
-
params.start = pointer;
|
|
327
|
-
if (limitBoundary !== void 0) params.end = limitBoundary;
|
|
328
|
-
}
|
|
329
|
-
const partData = await request(params);
|
|
330
|
-
if (import_lodash.default.isEmpty(partData)) {
|
|
331
|
-
fulfilled = true;
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
accumulated = direction === "older" ? (0, import_data.mergeData)(partData, accumulated) : (0, import_data.mergeData)(accumulated, partData);
|
|
335
|
-
const boundaryReached = limitBoundary !== void 0 && (direction === "older" && currentPointer <= limitBoundary || direction === "newer" && currentPointer >= limitBoundary);
|
|
336
|
-
if (partData.length < LIMIT || boundaryReached) {
|
|
337
|
-
fulfilled = true;
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
const nextPointer = direction === "older" ? (0, import_time2.getItemTimestamp)(partData[0]) - intervalMs : (0, import_time2.getItemTimestamp)(partData[partData.length - 1]) + intervalMs;
|
|
341
|
-
if (!Number.isFinite(nextPointer) || nextPointer === currentPointer) {
|
|
342
|
-
fulfilled = true;
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
345
|
-
pointer = nextPointer;
|
|
346
|
-
}
|
|
347
|
-
return accumulated;
|
|
348
|
-
};
|
|
349
|
-
const intervalMsOf = (interval) => interval * 6e4;
|
|
350
|
-
const intervalToMinutes = (interval) => {
|
|
538
|
+
const intervalToMinutes3 = (interval) => {
|
|
351
539
|
return INTERVAL_TO_MINUTES[String(interval)] ?? null;
|
|
352
540
|
};
|
|
353
|
-
const clampToClosedCandle = (value, intervalMs) => Math.floor(value / intervalMs) * intervalMs;
|
|
354
|
-
const normalizeRangeToClosed = (intervalMs, start, end) => {
|
|
355
|
-
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
356
|
-
const normStart = start !== void 0 ? clampToClosedCandle(start, intervalMs) : 0;
|
|
357
|
-
const cappedEnd = Math.min(end ?? Date.now(), lastClosed);
|
|
358
|
-
const normEnd = clampToClosedCandle(cappedEnd, intervalMs);
|
|
359
|
-
return { normStart, normEnd, lastClosed };
|
|
360
|
-
};
|
|
361
|
-
const rowsToKline = (rows) => rows.map(({ ts, ...data }) => ({
|
|
362
|
-
timestamp: new Date(ts).getTime(),
|
|
363
|
-
...data
|
|
364
|
-
}));
|
|
365
|
-
const refreshTail = async ({
|
|
366
|
-
symbol,
|
|
367
|
-
interval,
|
|
368
|
-
silent,
|
|
369
|
-
tailCount = 2
|
|
370
|
-
}) => {
|
|
371
|
-
const intMinutes = intervalToMinutes(interval);
|
|
372
|
-
if (!intMinutes) {
|
|
373
|
-
import_logger2.logger.log("error", "refreshTail: invalid interval %s", interval);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
const intervalMs = intervalMsOf(intMinutes);
|
|
377
|
-
const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
|
|
378
|
-
const tailEnd = lastClosed + intervalMs;
|
|
379
|
-
const tailStart = tailEnd - tailCount * intervalMs;
|
|
380
|
-
const part = await request({
|
|
381
|
-
symbol,
|
|
382
|
-
interval,
|
|
383
|
-
start: tailStart,
|
|
384
|
-
end: tailEnd,
|
|
385
|
-
silent
|
|
386
|
-
});
|
|
387
|
-
if (part.length) {
|
|
388
|
-
await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(symbol, intMinutes, part));
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
541
|
const getPositionSnapshot = async (symbol) => {
|
|
392
542
|
const client = await getPrivateClient();
|
|
393
543
|
if (!client) {
|
|
@@ -395,10 +545,10 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
395
545
|
}
|
|
396
546
|
const positionRes = await client.getPositionInfo({
|
|
397
547
|
symbol,
|
|
398
|
-
category:
|
|
548
|
+
category: import_constants3.MARKET_CATEGORY
|
|
399
549
|
});
|
|
400
550
|
if (positionRes.retCode !== 0) {
|
|
401
|
-
|
|
551
|
+
import_logger3.logger.log(
|
|
402
552
|
getLogLevel(positionRes),
|
|
403
553
|
"position retCode: %s, %s",
|
|
404
554
|
symbol,
|
|
@@ -411,7 +561,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
411
561
|
return null;
|
|
412
562
|
}
|
|
413
563
|
const position = positions[0];
|
|
414
|
-
|
|
564
|
+
import_logger3.logger.log(
|
|
415
565
|
"debug",
|
|
416
566
|
"position: %s %s qty=%s price=%s",
|
|
417
567
|
position.symbol,
|
|
@@ -440,7 +590,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
440
590
|
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
441
591
|
const positionQty = qty ?? (await getPositionSnapshot(symbol))?.qty ?? 0;
|
|
442
592
|
if (!Number.isFinite(positionQty) || positionQty <= 0) {
|
|
443
|
-
|
|
593
|
+
import_logger3.logger.log(
|
|
444
594
|
"warn",
|
|
445
595
|
"setTakeProfits: missing position qty: %s",
|
|
446
596
|
(0, import_data2.toJson)({ symbol, qty, positionQty, takeProfits }, true)
|
|
@@ -455,7 +605,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
455
605
|
meta
|
|
456
606
|
);
|
|
457
607
|
if (!tpSizeNum || tpSizeNum < meta.minOrderQty) {
|
|
458
|
-
|
|
608
|
+
import_logger3.logger.log(
|
|
459
609
|
"warn",
|
|
460
610
|
"tp skipped: size too small %s",
|
|
461
611
|
(0, import_data2.toJson)(
|
|
@@ -472,7 +622,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
472
622
|
);
|
|
473
623
|
const isFullMode = takeProfits.length === 1 && tp.rate === 1;
|
|
474
624
|
const tpRes = await client.setTradingStop({
|
|
475
|
-
category:
|
|
625
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
476
626
|
symbol,
|
|
477
627
|
tpSize: isFullMode ? void 0 : tpSizeStr,
|
|
478
628
|
tpslMode: isFullMode ? "Full" : "Partial",
|
|
@@ -482,7 +632,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
482
632
|
positionIdx: 0
|
|
483
633
|
});
|
|
484
634
|
if (isTradingStopNotModified(tpRes)) {
|
|
485
|
-
|
|
635
|
+
import_logger3.logger.log(
|
|
486
636
|
"debug",
|
|
487
637
|
"tp unchanged: %s %s price=%s rate=%s",
|
|
488
638
|
symbol,
|
|
@@ -491,7 +641,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
491
641
|
tp.rate
|
|
492
642
|
);
|
|
493
643
|
} else if (tpRes.retCode === 0) {
|
|
494
|
-
|
|
644
|
+
import_logger3.logger.log(
|
|
495
645
|
"info",
|
|
496
646
|
"tp updated: %s %s price=%s rate=%s",
|
|
497
647
|
symbol,
|
|
@@ -500,7 +650,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
500
650
|
tp.rate
|
|
501
651
|
);
|
|
502
652
|
} else {
|
|
503
|
-
|
|
653
|
+
import_logger3.logger.log(
|
|
504
654
|
"error",
|
|
505
655
|
"tp failed: %s %s price=%s rate=%s %s",
|
|
506
656
|
symbol,
|
|
@@ -537,7 +687,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
537
687
|
meta
|
|
538
688
|
);
|
|
539
689
|
const slRes = await client.setTradingStop({
|
|
540
|
-
category:
|
|
690
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
541
691
|
symbol,
|
|
542
692
|
tpslMode: "Full",
|
|
543
693
|
stopLoss: slNormalized.priceStr,
|
|
@@ -545,7 +695,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
545
695
|
positionIdx: 0
|
|
546
696
|
});
|
|
547
697
|
if (isTradingStopNotModified(slRes)) {
|
|
548
|
-
|
|
698
|
+
import_logger3.logger.log(
|
|
549
699
|
"debug",
|
|
550
700
|
"sl unchanged: %s %s stopLoss=%s",
|
|
551
701
|
symbol,
|
|
@@ -553,7 +703,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
553
703
|
slNormalized.priceStr
|
|
554
704
|
);
|
|
555
705
|
} else if (slRes.retCode === 0) {
|
|
556
|
-
|
|
706
|
+
import_logger3.logger.log(
|
|
557
707
|
"info",
|
|
558
708
|
"sl updated: %s %s stopLoss=%s",
|
|
559
709
|
symbol,
|
|
@@ -561,7 +711,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
561
711
|
slNormalized.priceStr
|
|
562
712
|
);
|
|
563
713
|
} else {
|
|
564
|
-
|
|
714
|
+
import_logger3.logger.log(
|
|
565
715
|
"error",
|
|
566
716
|
"sl failed: %s %s stopLoss=%s %s",
|
|
567
717
|
symbol,
|
|
@@ -577,136 +727,12 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
577
727
|
setState: async (newState) => {
|
|
578
728
|
state = { ...state, ...newState };
|
|
579
729
|
},
|
|
580
|
-
kline:
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
cacheOnly = false,
|
|
587
|
-
warmOnly = false
|
|
588
|
-
}) => {
|
|
589
|
-
const intMinutes = intervalToMinutes(interval);
|
|
590
|
-
if (!intMinutes) {
|
|
591
|
-
import_logger2.logger.log("error", "kline: invalid interval %s", interval);
|
|
592
|
-
return [];
|
|
593
|
-
}
|
|
594
|
-
if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
|
|
595
|
-
return [];
|
|
596
|
-
}
|
|
597
|
-
const intervalMs = intervalMsOf(intMinutes);
|
|
598
|
-
try {
|
|
599
|
-
const edges = await (0, import_timescale.getDataEdges)(symbol, intMinutes);
|
|
600
|
-
let dataStart = edges.min;
|
|
601
|
-
let dataEnd = edges.max;
|
|
602
|
-
const { normStart, normEnd } = normalizeRangeToClosed(
|
|
603
|
-
intervalMs,
|
|
604
|
-
defaultStart,
|
|
605
|
-
defaultEnd
|
|
606
|
-
);
|
|
607
|
-
if (cacheOnly) {
|
|
608
|
-
const base = edges.max ?? Date.now();
|
|
609
|
-
const s = Math.max(
|
|
610
|
-
defaultStart ?? base - CACHE_FALLBACK_WINDOW * intervalMs,
|
|
611
|
-
0
|
|
612
|
-
);
|
|
613
|
-
const e = defaultEnd ?? base;
|
|
614
|
-
const dbData2 = await (0, import_timescale.getCandlesRange)(symbol, intMinutes, s, e);
|
|
615
|
-
return rowsToKline(dbData2);
|
|
616
|
-
}
|
|
617
|
-
const needOlderData = defaultStart !== void 0 && (dataStart === void 0 || normStart < dataStart);
|
|
618
|
-
const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
|
|
619
|
-
if (needOlderData) {
|
|
620
|
-
const pointerForOlder = dataStart ?? normEnd ?? Date.now();
|
|
621
|
-
const olderData = await loadData(
|
|
622
|
-
"older",
|
|
623
|
-
pointerForOlder,
|
|
624
|
-
normStart,
|
|
625
|
-
{
|
|
626
|
-
symbol,
|
|
627
|
-
interval,
|
|
628
|
-
silent,
|
|
629
|
-
start: normStart,
|
|
630
|
-
end: pointerForOlder
|
|
631
|
-
},
|
|
632
|
-
intervalMs
|
|
633
|
-
);
|
|
634
|
-
if (olderData.length) {
|
|
635
|
-
await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(symbol, intMinutes, olderData));
|
|
636
|
-
dataStart = normStart;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
if (needNewerData) {
|
|
640
|
-
const pointerForNewer = dataEnd ?? normStart ?? 0;
|
|
641
|
-
const newerData = await loadData(
|
|
642
|
-
"newer",
|
|
643
|
-
pointerForNewer,
|
|
644
|
-
normEnd,
|
|
645
|
-
{
|
|
646
|
-
symbol,
|
|
647
|
-
interval,
|
|
648
|
-
silent,
|
|
649
|
-
start: pointerForNewer,
|
|
650
|
-
end: normEnd
|
|
651
|
-
},
|
|
652
|
-
intervalMs
|
|
653
|
-
);
|
|
654
|
-
if (newerData.length) {
|
|
655
|
-
await (0, import_timescale.upsertCandles)((0, import_timescale.toRows)(symbol, intMinutes, newerData));
|
|
656
|
-
dataEnd = normEnd;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
const isRightEdgeQuery = defaultEnd === void 0 || defaultEnd && defaultEnd >= Date.now() - intervalMs;
|
|
660
|
-
if (!cacheOnly && isRightEdgeQuery) {
|
|
661
|
-
await refreshTail({ symbol, interval, silent });
|
|
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
|
-
}
|
|
673
|
-
const rangeStart = defaultStart ?? dataStart ?? 0;
|
|
674
|
-
const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
|
|
675
|
-
const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
|
|
676
|
-
const dbData = await (0, import_timescale.getCandlesRange)(
|
|
677
|
-
symbol,
|
|
678
|
-
intMinutes,
|
|
679
|
-
finalStart,
|
|
680
|
-
finalEnd
|
|
681
|
-
);
|
|
682
|
-
if (isTimescaleFallbackMode) {
|
|
683
|
-
isTimescaleFallbackMode = false;
|
|
684
|
-
import_logger2.logger.log("info", "TimescaleDB connection restored for kline cache");
|
|
685
|
-
}
|
|
686
|
-
return rowsToKline(dbData);
|
|
687
|
-
} catch (error) {
|
|
688
|
-
if (!isTimescaleFallbackMode) {
|
|
689
|
-
isTimescaleFallbackMode = true;
|
|
690
|
-
import_logger2.logger.log(
|
|
691
|
-
"warn",
|
|
692
|
-
"TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
|
|
693
|
-
symbol,
|
|
694
|
-
interval,
|
|
695
|
-
String(error)
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
|
-
if (cacheOnly || warmOnly) {
|
|
699
|
-
return [];
|
|
700
|
-
}
|
|
701
|
-
return request({
|
|
702
|
-
symbol,
|
|
703
|
-
interval,
|
|
704
|
-
start: defaultStart,
|
|
705
|
-
end: defaultEnd,
|
|
706
|
-
silent
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
},
|
|
730
|
+
kline: createTimescaleCachedKline({
|
|
731
|
+
provider: "bybit",
|
|
732
|
+
request,
|
|
733
|
+
intervalToMinutes: intervalToMinutes3,
|
|
734
|
+
limit: LIMIT
|
|
735
|
+
}),
|
|
710
736
|
getPosition: async (symbol) => getPositionSnapshot(symbol),
|
|
711
737
|
getPositions: async () => {
|
|
712
738
|
const client = await getPrivateClient();
|
|
@@ -714,11 +740,11 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
714
740
|
return [];
|
|
715
741
|
}
|
|
716
742
|
const positionRes = await client.getPositionInfo({
|
|
717
|
-
category:
|
|
743
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
718
744
|
settleCoin: "USDT"
|
|
719
745
|
});
|
|
720
746
|
if (positionRes.retCode !== 0) {
|
|
721
|
-
|
|
747
|
+
import_logger3.logger.log(
|
|
722
748
|
getLogLevel(positionRes),
|
|
723
749
|
"positions retCode: %s, %s",
|
|
724
750
|
positionRes.retCode
|
|
@@ -731,7 +757,94 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
731
757
|
}
|
|
732
758
|
return positions;
|
|
733
759
|
},
|
|
734
|
-
|
|
760
|
+
getOpenPositionPnl: async () => {
|
|
761
|
+
const client = await getPrivateClient();
|
|
762
|
+
if (!client) {
|
|
763
|
+
return [];
|
|
764
|
+
}
|
|
765
|
+
const positionRes = await client.getPositionInfo({
|
|
766
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
767
|
+
settleCoin: "USDT"
|
|
768
|
+
});
|
|
769
|
+
if (positionRes.retCode !== 0) {
|
|
770
|
+
import_logger3.logger.log(
|
|
771
|
+
getLogLevel(positionRes),
|
|
772
|
+
"positions pnl retCode: %s, %s",
|
|
773
|
+
positionRes.retCode
|
|
774
|
+
);
|
|
775
|
+
return [];
|
|
776
|
+
}
|
|
777
|
+
return (positionRes.result?.list ?? []).map((item) => {
|
|
778
|
+
const qty = Number(item.size ?? Number.NaN);
|
|
779
|
+
const entryPrice = Number(item.avgPrice ?? Number.NaN);
|
|
780
|
+
const currentPrice = Number(item.markPrice ?? Number.NaN);
|
|
781
|
+
const unrealizedPnl = Number(item.unrealisedPnl ?? Number.NaN);
|
|
782
|
+
const side = String(item.side ?? "");
|
|
783
|
+
if (!Number.isFinite(qty) || qty <= 0 || !Number.isFinite(entryPrice) || !Number.isFinite(currentPrice) || !Number.isFinite(unrealizedPnl) || side !== "Buy" && side !== "Sell") {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
symbol: String(item.symbol ?? ""),
|
|
788
|
+
qty,
|
|
789
|
+
price: entryPrice,
|
|
790
|
+
currentPrice,
|
|
791
|
+
unrealizedPnl,
|
|
792
|
+
direction: side === "Buy" ? "LONG" : "SHORT"
|
|
793
|
+
};
|
|
794
|
+
}).filter(
|
|
795
|
+
(item) => item != null && item.symbol.length > 0
|
|
796
|
+
);
|
|
797
|
+
},
|
|
798
|
+
getClosedPnl: async ({
|
|
799
|
+
startTime,
|
|
800
|
+
endTime,
|
|
801
|
+
symbol,
|
|
802
|
+
limit = 100
|
|
803
|
+
}) => {
|
|
804
|
+
const client = await getPrivateClient();
|
|
805
|
+
if (!client) {
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
const response = await client.getClosedPnL({
|
|
809
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
810
|
+
startTime,
|
|
811
|
+
endTime,
|
|
812
|
+
symbol,
|
|
813
|
+
limit: Math.min(Math.max(1, Math.trunc(limit)), 100)
|
|
814
|
+
});
|
|
815
|
+
if (response.retCode !== 0) {
|
|
816
|
+
import_logger3.logger.log(
|
|
817
|
+
"error",
|
|
818
|
+
"closedPnl retCode: %s, %s",
|
|
819
|
+
response.retCode,
|
|
820
|
+
response.retMsg
|
|
821
|
+
);
|
|
822
|
+
return [];
|
|
823
|
+
}
|
|
824
|
+
return (response.result?.list ?? []).map((item) => {
|
|
825
|
+
const qty = Number(item.qty ?? item.closedSize ?? Number.NaN);
|
|
826
|
+
const entryPrice = Number(item.avgEntryPrice ?? Number.NaN);
|
|
827
|
+
const exitPrice = Number(item.avgExitPrice ?? Number.NaN);
|
|
828
|
+
const closedPnl = Number(item.closedPnl ?? Number.NaN);
|
|
829
|
+
const closedAt = Number(
|
|
830
|
+
item.updatedTime ?? item.createdTime ?? Number.NaN
|
|
831
|
+
);
|
|
832
|
+
const orderId = typeof item.orderId === "string" && item.orderId.trim() ? item.orderId : null;
|
|
833
|
+
if (!String(item.symbol ?? "").trim() || !Number.isFinite(qty) || !Number.isFinite(closedPnl) || !Number.isFinite(closedAt)) {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
return {
|
|
837
|
+
symbol: String(item.symbol),
|
|
838
|
+
qty,
|
|
839
|
+
entryPrice: Number.isFinite(entryPrice) ? entryPrice : null,
|
|
840
|
+
exitPrice: Number.isFinite(exitPrice) ? exitPrice : null,
|
|
841
|
+
closedPnl,
|
|
842
|
+
closedAt,
|
|
843
|
+
...orderId ? { orderId } : {}
|
|
844
|
+
};
|
|
845
|
+
}).filter((item) => item != null);
|
|
846
|
+
},
|
|
847
|
+
placeOrder: async ({ symbol, price, qty, direction, isLimit, orderId }) => {
|
|
735
848
|
const client = await getPrivateClient();
|
|
736
849
|
const marketDataClient = await getPublicClient();
|
|
737
850
|
if (!client || !marketDataClient) {
|
|
@@ -741,7 +854,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
741
854
|
const meta = await getSymbolMeta(marketDataClient, symbol);
|
|
742
855
|
const { qtyNum: orderQty, qtyStr: orderQtyStr } = normalizeQty(qty, meta);
|
|
743
856
|
if (orderQty < meta.minOrderQty) {
|
|
744
|
-
|
|
857
|
+
import_logger3.logger.log(
|
|
745
858
|
"warn",
|
|
746
859
|
"placeOrder: qty too small: %s",
|
|
747
860
|
(0, import_data2.toJson)(
|
|
@@ -752,7 +865,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
752
865
|
return false;
|
|
753
866
|
}
|
|
754
867
|
const entryNormalized = isLimit ? normalizePrice(price, "ENTRY", meta) : void 0;
|
|
755
|
-
|
|
868
|
+
import_logger3.logger.log(
|
|
756
869
|
"info",
|
|
757
870
|
"placeOrder: %s",
|
|
758
871
|
(0, import_data2.toJson)(
|
|
@@ -768,21 +881,22 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
768
881
|
)
|
|
769
882
|
);
|
|
770
883
|
await client.setLeverage({
|
|
771
|
-
category:
|
|
884
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
772
885
|
symbol,
|
|
773
886
|
buyLeverage: "10",
|
|
774
887
|
sellLeverage: "10"
|
|
775
888
|
});
|
|
776
889
|
const orderRes = await client.submitOrder({
|
|
777
|
-
category:
|
|
890
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
778
891
|
symbol,
|
|
779
892
|
price: entryNormalized?.priceStr || void 0,
|
|
780
893
|
side: isLong ? "Buy" : "Sell",
|
|
781
894
|
orderType: isLimit ? "Limit" : "Market",
|
|
782
895
|
qty: orderQtyStr,
|
|
783
|
-
orderFilter: "Order"
|
|
896
|
+
orderFilter: "Order",
|
|
897
|
+
orderLinkId: typeof orderId === "string" && orderId.trim() ? orderId : void 0
|
|
784
898
|
});
|
|
785
|
-
|
|
899
|
+
import_logger3.logger.log(
|
|
786
900
|
getLogLevel(orderRes),
|
|
787
901
|
"placeOrder:response: %s",
|
|
788
902
|
(0, import_data2.toJson)(orderRes, true)
|
|
@@ -800,14 +914,14 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
800
914
|
return false;
|
|
801
915
|
}
|
|
802
916
|
const closeRes = await client.submitOrder({
|
|
803
|
-
category:
|
|
917
|
+
category: import_constants3.MARKET_CATEGORY,
|
|
804
918
|
symbol,
|
|
805
919
|
side: direction === "LONG" ? "Sell" : "Buy",
|
|
806
920
|
orderType: "Market",
|
|
807
921
|
qty: "0",
|
|
808
922
|
reduceOnly: true
|
|
809
923
|
});
|
|
810
|
-
|
|
924
|
+
import_logger3.logger.log(
|
|
811
925
|
getLogLevel(closeRes),
|
|
812
926
|
"closePosition: %s, %s, %s",
|
|
813
927
|
symbol,
|
|
@@ -825,7 +939,7 @@ var ByBitConnectorCreator = async (config) => {
|
|
|
825
939
|
return [];
|
|
826
940
|
}
|
|
827
941
|
const data = await client.getTickers({
|
|
828
|
-
category:
|
|
942
|
+
category: import_constants3.MARKET_CATEGORY
|
|
829
943
|
});
|
|
830
944
|
return data.result.list.map((item) => (0, import_tickers.normalizeTickerData)(item));
|
|
831
945
|
}
|
|
@@ -868,58 +982,74 @@ var toNum = (value, fallback = 0) => {
|
|
|
868
982
|
const num = Number(value);
|
|
869
983
|
return Number.isFinite(num) ? num : fallback;
|
|
870
984
|
};
|
|
985
|
+
var intervalToMinutes = (interval) => {
|
|
986
|
+
const intervalMs = INTERVAL_MS[String(interval)];
|
|
987
|
+
return intervalMs ? Math.floor(intervalMs / 6e4) : null;
|
|
988
|
+
};
|
|
871
989
|
var BinanceConnectorCreator = async () => {
|
|
872
990
|
let state = {};
|
|
991
|
+
const requestKline = async ({
|
|
992
|
+
symbol,
|
|
993
|
+
interval,
|
|
994
|
+
start,
|
|
995
|
+
end
|
|
996
|
+
}) => {
|
|
997
|
+
const intervalToken = INTERVAL_MAP[String(interval)];
|
|
998
|
+
if (!intervalToken) return [];
|
|
999
|
+
const intervalMs = INTERVAL_MS[String(interval)] ?? 9e5;
|
|
1000
|
+
const normalizedEnd = end ?? Date.now();
|
|
1001
|
+
const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
|
|
1002
|
+
let cursor = start ?? Math.max(0, normalizedEnd - intervalMs * 1e3);
|
|
1003
|
+
const rows = [];
|
|
1004
|
+
while (cursor <= normalizedEnd) {
|
|
1005
|
+
const url = new URL(`${baseUrl}/api/v3/klines`);
|
|
1006
|
+
url.searchParams.set("symbol", symbol);
|
|
1007
|
+
url.searchParams.set("interval", intervalToken);
|
|
1008
|
+
url.searchParams.set("startTime", String(cursor));
|
|
1009
|
+
url.searchParams.set("endTime", String(normalizedEnd));
|
|
1010
|
+
url.searchParams.set("limit", "1000");
|
|
1011
|
+
const response = await (0, import_http.fetchWithRetry)(url.toString(), {
|
|
1012
|
+
headers: { "User-Agent": "tradejs/binance-connector" }
|
|
1013
|
+
});
|
|
1014
|
+
if (!response.ok) break;
|
|
1015
|
+
const payload = await response.json();
|
|
1016
|
+
if (!Array.isArray(payload) || !payload.length) break;
|
|
1017
|
+
let lastTs = cursor;
|
|
1018
|
+
for (const item of payload) {
|
|
1019
|
+
if (!Array.isArray(item)) continue;
|
|
1020
|
+
const ts = toNum(item[0], 0);
|
|
1021
|
+
if (!ts) continue;
|
|
1022
|
+
rows.push({
|
|
1023
|
+
timestamp: ts,
|
|
1024
|
+
open: toNum(item[1]),
|
|
1025
|
+
high: toNum(item[2]),
|
|
1026
|
+
low: toNum(item[3]),
|
|
1027
|
+
close: toNum(item[4]),
|
|
1028
|
+
volume: toNum(item[5]),
|
|
1029
|
+
turnover: toNum(item[7]),
|
|
1030
|
+
dt: new Date(ts).toISOString()
|
|
1031
|
+
});
|
|
1032
|
+
lastTs = ts;
|
|
1033
|
+
}
|
|
1034
|
+
if (payload.length < 1e3) break;
|
|
1035
|
+
cursor = lastTs + intervalMs;
|
|
1036
|
+
}
|
|
1037
|
+
rows.sort((a, b) => a.timestamp - b.timestamp);
|
|
1038
|
+
return rows;
|
|
1039
|
+
};
|
|
873
1040
|
return {
|
|
874
1041
|
getState: async () => state,
|
|
875
1042
|
setState: async (newState) => {
|
|
876
1043
|
state = { ...state, ...newState };
|
|
877
1044
|
},
|
|
878
|
-
kline:
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
let cursor = start ?? Math.max(0, end - intervalMs * 1e3);
|
|
884
|
-
const rows = [];
|
|
885
|
-
while (cursor <= end) {
|
|
886
|
-
const url = new URL(`${baseUrl}/api/v3/klines`);
|
|
887
|
-
url.searchParams.set("symbol", symbol);
|
|
888
|
-
url.searchParams.set("interval", intervalToken);
|
|
889
|
-
url.searchParams.set("startTime", String(cursor));
|
|
890
|
-
url.searchParams.set("endTime", String(end));
|
|
891
|
-
url.searchParams.set("limit", "1000");
|
|
892
|
-
const response = await (0, import_http.fetchWithRetry)(url.toString(), {
|
|
893
|
-
headers: { "User-Agent": "tradejs/binance-connector" }
|
|
894
|
-
});
|
|
895
|
-
if (!response.ok) break;
|
|
896
|
-
const payload = await response.json();
|
|
897
|
-
if (!Array.isArray(payload) || !payload.length) break;
|
|
898
|
-
let lastTs = cursor;
|
|
899
|
-
for (const item of payload) {
|
|
900
|
-
if (!Array.isArray(item)) continue;
|
|
901
|
-
const ts = toNum(item[0], 0);
|
|
902
|
-
if (!ts) continue;
|
|
903
|
-
rows.push({
|
|
904
|
-
timestamp: ts,
|
|
905
|
-
open: toNum(item[1]),
|
|
906
|
-
high: toNum(item[2]),
|
|
907
|
-
low: toNum(item[3]),
|
|
908
|
-
close: toNum(item[4]),
|
|
909
|
-
volume: toNum(item[5]),
|
|
910
|
-
turnover: toNum(item[7]),
|
|
911
|
-
dt: new Date(ts).toISOString()
|
|
912
|
-
});
|
|
913
|
-
lastTs = ts;
|
|
914
|
-
}
|
|
915
|
-
if (payload.length < 1e3) break;
|
|
916
|
-
cursor = lastTs + intervalMs;
|
|
917
|
-
}
|
|
918
|
-
rows.sort((a, b) => a.timestamp - b.timestamp);
|
|
919
|
-
return rows;
|
|
920
|
-
},
|
|
1045
|
+
kline: createTimescaleCachedKline({
|
|
1046
|
+
provider: "binance",
|
|
1047
|
+
request: requestKline,
|
|
1048
|
+
intervalToMinutes
|
|
1049
|
+
}),
|
|
921
1050
|
getPosition: async () => null,
|
|
922
1051
|
getPositions: async () => [],
|
|
1052
|
+
getOpenPositionPnl: async () => [],
|
|
923
1053
|
placeOrder: async () => false,
|
|
924
1054
|
setTakeProfits: async () => false,
|
|
925
1055
|
setStopLoss: async () => false,
|
|
@@ -1003,6 +1133,10 @@ var toNum2 = (value, fallback = 0) => {
|
|
|
1003
1133
|
const num = Number(value);
|
|
1004
1134
|
return Number.isFinite(num) ? num : fallback;
|
|
1005
1135
|
};
|
|
1136
|
+
var intervalToMinutes2 = (interval) => {
|
|
1137
|
+
const intervalMs = INTERVAL_MS2[String(interval)];
|
|
1138
|
+
return intervalMs ? Math.floor(intervalMs / 6e4) : null;
|
|
1139
|
+
};
|
|
1006
1140
|
var toCoinbaseProduct = (symbol) => {
|
|
1007
1141
|
const upper = symbol.toUpperCase();
|
|
1008
1142
|
const quoteSuffixes = ["USDT", "USDC", "BUSD", "USD"];
|
|
@@ -1029,58 +1163,70 @@ var MAJOR_PRODUCTS = [
|
|
|
1029
1163
|
];
|
|
1030
1164
|
var CoinbaseConnectorCreator = async () => {
|
|
1031
1165
|
let state = {};
|
|
1166
|
+
const requestKline = async ({
|
|
1167
|
+
symbol,
|
|
1168
|
+
interval,
|
|
1169
|
+
start,
|
|
1170
|
+
end
|
|
1171
|
+
}) => {
|
|
1172
|
+
const granularity = GRANULARITY_MAP[String(interval)];
|
|
1173
|
+
if (!granularity) return [];
|
|
1174
|
+
const product = toCoinbaseProduct(symbol);
|
|
1175
|
+
if (!product) return [];
|
|
1176
|
+
const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
|
|
1177
|
+
const normalizedEnd = end ?? Date.now();
|
|
1178
|
+
const stepMs = granularity * 1e3 * 300;
|
|
1179
|
+
const fromMs = start ?? Math.max(0, normalizedEnd - INTERVAL_MS2[String(interval)] * 1e3);
|
|
1180
|
+
let cursorEnd = normalizedEnd;
|
|
1181
|
+
const rows = [];
|
|
1182
|
+
while (cursorEnd >= fromMs) {
|
|
1183
|
+
const chunkStart = Math.max(fromMs, cursorEnd - stepMs);
|
|
1184
|
+
const url = new URL(`${baseUrl}/products/${product}/candles`);
|
|
1185
|
+
url.searchParams.set("granularity", String(granularity));
|
|
1186
|
+
url.searchParams.set("start", new Date(chunkStart).toISOString());
|
|
1187
|
+
url.searchParams.set("end", new Date(cursorEnd).toISOString());
|
|
1188
|
+
const response = await (0, import_http2.fetchWithRetry)(url.toString(), {
|
|
1189
|
+
headers: { "User-Agent": "tradejs/coinbase-connector" }
|
|
1190
|
+
});
|
|
1191
|
+
if (!response.ok) break;
|
|
1192
|
+
const payload = await response.json();
|
|
1193
|
+
if (Array.isArray(payload)) {
|
|
1194
|
+
for (const item of payload) {
|
|
1195
|
+
if (!Array.isArray(item)) continue;
|
|
1196
|
+
const ts = toNum2(item[0]) * 1e3;
|
|
1197
|
+
rows.push({
|
|
1198
|
+
timestamp: ts,
|
|
1199
|
+
low: toNum2(item[1]),
|
|
1200
|
+
high: toNum2(item[2]),
|
|
1201
|
+
open: toNum2(item[3]),
|
|
1202
|
+
close: toNum2(item[4]),
|
|
1203
|
+
volume: toNum2(item[5]),
|
|
1204
|
+
turnover: 0,
|
|
1205
|
+
dt: new Date(ts).toISOString()
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
cursorEnd = chunkStart - 1;
|
|
1210
|
+
}
|
|
1211
|
+
const dedup = /* @__PURE__ */ new Map();
|
|
1212
|
+
for (const row of rows) {
|
|
1213
|
+
dedup.set(row.timestamp, row);
|
|
1214
|
+
}
|
|
1215
|
+
return [...dedup.values()].sort((a, b) => a.timestamp - b.timestamp);
|
|
1216
|
+
};
|
|
1032
1217
|
return {
|
|
1033
1218
|
getState: async () => state,
|
|
1034
1219
|
setState: async (newState) => {
|
|
1035
1220
|
state = { ...state, ...newState };
|
|
1036
1221
|
},
|
|
1037
|
-
kline:
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const baseUrl = process.env.COINBASE_BASE_URL?.trim() || "https://api.exchange.coinbase.com";
|
|
1043
|
-
const stepMs = granularity * 1e3 * 300;
|
|
1044
|
-
const fromMs = start ?? Math.max(0, end - INTERVAL_MS2[String(interval)] * 1e3);
|
|
1045
|
-
let cursorEnd = end;
|
|
1046
|
-
const rows = [];
|
|
1047
|
-
while (cursorEnd >= fromMs) {
|
|
1048
|
-
const chunkStart = Math.max(fromMs, cursorEnd - stepMs);
|
|
1049
|
-
const url = new URL(`${baseUrl}/products/${product}/candles`);
|
|
1050
|
-
url.searchParams.set("granularity", String(granularity));
|
|
1051
|
-
url.searchParams.set("start", new Date(chunkStart).toISOString());
|
|
1052
|
-
url.searchParams.set("end", new Date(cursorEnd).toISOString());
|
|
1053
|
-
const response = await (0, import_http2.fetchWithRetry)(url.toString(), {
|
|
1054
|
-
headers: { "User-Agent": "tradejs/coinbase-connector" }
|
|
1055
|
-
});
|
|
1056
|
-
if (!response.ok) break;
|
|
1057
|
-
const payload = await response.json();
|
|
1058
|
-
if (Array.isArray(payload)) {
|
|
1059
|
-
for (const item of payload) {
|
|
1060
|
-
if (!Array.isArray(item)) continue;
|
|
1061
|
-
const ts = toNum2(item[0]) * 1e3;
|
|
1062
|
-
rows.push({
|
|
1063
|
-
timestamp: ts,
|
|
1064
|
-
low: toNum2(item[1]),
|
|
1065
|
-
high: toNum2(item[2]),
|
|
1066
|
-
open: toNum2(item[3]),
|
|
1067
|
-
close: toNum2(item[4]),
|
|
1068
|
-
volume: toNum2(item[5]),
|
|
1069
|
-
turnover: 0,
|
|
1070
|
-
dt: new Date(ts).toISOString()
|
|
1071
|
-
});
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
cursorEnd = chunkStart - 1;
|
|
1075
|
-
}
|
|
1076
|
-
const dedup = /* @__PURE__ */ new Map();
|
|
1077
|
-
for (const row of rows) {
|
|
1078
|
-
dedup.set(row.timestamp, row);
|
|
1079
|
-
}
|
|
1080
|
-
return [...dedup.values()].sort((a, b) => a.timestamp - b.timestamp);
|
|
1081
|
-
},
|
|
1222
|
+
kline: createTimescaleCachedKline({
|
|
1223
|
+
provider: "coinbase",
|
|
1224
|
+
request: requestKline,
|
|
1225
|
+
intervalToMinutes: intervalToMinutes2
|
|
1226
|
+
}),
|
|
1082
1227
|
getPosition: async () => null,
|
|
1083
1228
|
getPositions: async () => [],
|
|
1229
|
+
getOpenPositionPnl: async () => [],
|
|
1084
1230
|
placeOrder: async () => false,
|
|
1085
1231
|
setTakeProfits: async () => false,
|
|
1086
1232
|
setStopLoss: async () => false,
|