@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.
Files changed (3) hide show
  1. package/dist/index.js +485 -339
  2. package/dist/index.mjs +477 -331
  3. 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 import_constants2 = require("@tradejs/core/constants");
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 import_time2 = require("@tradejs/core/time");
57
- var import_logger2 = require("@tradejs/infra/logger");
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/ByBit/utils.ts
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, import_time.formatUnix)(parseInt(item[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: import_constants.MARKET_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, import_time2.getTimestamp)(import_constants2.PRELOAD_FALLBACK_DAYS),
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: import_constants2.MARKET_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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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, import_time2.formatUnix)(normalizedEnd)),
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
- import_logger2.logger.log(
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 loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs) => {
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: import_constants2.MARKET_CATEGORY
548
+ category: import_constants3.MARKET_CATEGORY
399
549
  });
400
550
  if (positionRes.retCode !== 0) {
401
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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: import_constants2.MARKET_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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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: import_constants2.MARKET_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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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: async ({
581
- symbol,
582
- interval,
583
- start: defaultStart,
584
- end: defaultEnd,
585
- silent = false,
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: import_constants2.MARKET_CATEGORY,
743
+ category: import_constants3.MARKET_CATEGORY,
718
744
  settleCoin: "USDT"
719
745
  });
720
746
  if (positionRes.retCode !== 0) {
721
- import_logger2.logger.log(
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
- placeOrder: async ({ symbol, price, qty, direction, isLimit }) => {
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
- import_logger2.logger.log(
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
- import_logger2.logger.log(
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: import_constants2.MARKET_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: import_constants2.MARKET_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
- import_logger2.logger.log(
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: import_constants2.MARKET_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
- import_logger2.logger.log(
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: import_constants2.MARKET_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: async ({ symbol, interval, start, end }) => {
879
- const intervalToken = INTERVAL_MAP[String(interval)];
880
- if (!intervalToken) return [];
881
- const intervalMs = INTERVAL_MS[String(interval)] ?? 9e5;
882
- const baseUrl = process.env.BINANCE_BASE_URL?.trim() || "https://api.binance.com";
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: async ({ symbol, interval, start, end }) => {
1038
- const granularity = GRANULARITY_MAP[String(interval)];
1039
- if (!granularity) return [];
1040
- const product = toCoinbaseProduct(symbol);
1041
- if (!product) return [];
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,