@tradejs/node 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,6 +32,8 @@ var connectors_exports = {};
32
32
  __export(connectors_exports, {
33
33
  BUILTIN_CONNECTOR_NAMES: () => BUILTIN_CONNECTOR_NAMES,
34
34
  DEFAULT_CONNECTOR_NAME: () => DEFAULT_CONNECTOR_NAME,
35
+ bindConnectorRuntime: () => bindConnectorRuntime,
36
+ connectorRuntime: () => connectorRuntime,
35
37
  ensureConnectorPluginsLoaded: () => ensureConnectorPluginsLoaded,
36
38
  getAvailableConnectorNames: () => getAvailableConnectorNames,
37
39
  getAvailableConnectorProviders: () => getAvailableConnectorProviders,
@@ -45,7 +47,7 @@ __export(connectors_exports, {
45
47
  module.exports = __toCommonJS(connectors_exports);
46
48
 
47
49
  // src/connectorsRegistry.ts
48
- var import_logger2 = require("@tradejs/infra/logger");
50
+ var import_logger4 = require("@tradejs/infra/logger");
49
51
 
50
52
  // src/tradejsConfig.ts
51
53
  var import_fs = __toESM(require("fs"));
@@ -332,6 +334,320 @@ var loadTradejsConfig = async (cwd = getTradejsProjectCwd()) => {
332
334
  }
333
335
  };
334
336
 
337
+ // src/connectorRuntime.ts
338
+ var import_logger3 = require("@tradejs/infra/logger");
339
+ var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
340
+
341
+ // src/timescaleCachedKline.ts
342
+ var import_constants = require("@tradejs/core/constants");
343
+ var import_data = require("@tradejs/core/data");
344
+ var import_time = require("@tradejs/core/time");
345
+ var import_async = require("@tradejs/core/async");
346
+ var import_logger2 = require("@tradejs/infra/logger");
347
+ var import_candles = require("@tradejs/infra/timescale/candles");
348
+ var DEFAULT_LIMIT = 1e3;
349
+ var DEFAULT_CACHE_FALLBACK_WINDOW = 1e3;
350
+ var DEFAULT_TIMESCALE_RETRIES = 2;
351
+ var DEFAULT_TIMESCALE_RETRY_DELAY_MS = 1e3;
352
+ var resolveNonNegativeInt = (value, fallback) => {
353
+ const parsed = Number(value);
354
+ if (!Number.isFinite(parsed) || parsed < 0) {
355
+ return fallback;
356
+ }
357
+ return Math.floor(parsed);
358
+ };
359
+ var getTimescaleRetryCount = () => resolveNonNegativeInt(
360
+ process.env.TIMESCALE_KLINE_RETRIES,
361
+ DEFAULT_TIMESCALE_RETRIES
362
+ );
363
+ var getTimescaleRetryDelayMs = () => resolveNonNegativeInt(
364
+ process.env.TIMESCALE_KLINE_RETRY_DELAY_MS,
365
+ DEFAULT_TIMESCALE_RETRY_DELAY_MS
366
+ );
367
+ var intervalMsOf = (interval) => interval * 6e4;
368
+ var clampToClosedCandle = (value, intervalMs) => Math.floor(value / intervalMs) * intervalMs;
369
+ var normalizeRangeToClosed = (intervalMs, start, end) => {
370
+ const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
371
+ const normStart = start !== void 0 ? clampToClosedCandle(start, intervalMs) : 0;
372
+ const cappedEnd = Math.min(end ?? Date.now(), lastClosed);
373
+ const normEnd = clampToClosedCandle(cappedEnd, intervalMs);
374
+ return { normStart, normEnd };
375
+ };
376
+ var rowsToKline = (rows) => rows.map(({ ts, ...data }) => ({
377
+ timestamp: new Date(ts).getTime(),
378
+ ...data
379
+ }));
380
+ var createTimescaleCachedKline = ({
381
+ provider,
382
+ request,
383
+ intervalToMinutes,
384
+ limit = DEFAULT_LIMIT,
385
+ cacheFallbackWindow = DEFAULT_CACHE_FALLBACK_WINDOW
386
+ }) => {
387
+ let isTimescaleFallbackMode = false;
388
+ const runTimescaleOperation = async (operation) => {
389
+ const retries = getTimescaleRetryCount();
390
+ const retryDelayMs = getTimescaleRetryDelayMs();
391
+ for (let attempt = 0; ; attempt += 1) {
392
+ try {
393
+ return await operation();
394
+ } catch (error) {
395
+ if (attempt >= retries) {
396
+ throw error;
397
+ }
398
+ const waitMs = retryDelayMs * 2 ** attempt;
399
+ if (waitMs > 0) {
400
+ await (0, import_async.delay)(waitMs);
401
+ }
402
+ }
403
+ }
404
+ };
405
+ const loadData = async (direction, pointer, limitBoundary, requestParams, intervalMs, options = {}) => {
406
+ if (pointer === void 0) return { data: [], loaded: false };
407
+ let accumulated = [];
408
+ let fulfilled = false;
409
+ let loaded = false;
410
+ const shouldAccumulate = options.accumulate ?? true;
411
+ while (!fulfilled) {
412
+ const currentPointer = pointer;
413
+ const params = {
414
+ symbol: requestParams.symbol,
415
+ interval: requestParams.interval,
416
+ silent: requestParams.silent,
417
+ end: requestParams.end
418
+ };
419
+ if (direction === "older") {
420
+ params.end = pointer;
421
+ if (limitBoundary !== void 0) params.start = limitBoundary;
422
+ } else {
423
+ params.start = pointer;
424
+ if (limitBoundary !== void 0) params.end = limitBoundary;
425
+ }
426
+ const partData = await request(params);
427
+ if (!partData.length) {
428
+ fulfilled = true;
429
+ break;
430
+ }
431
+ loaded = true;
432
+ if (options.onPartData) {
433
+ await options.onPartData(partData);
434
+ }
435
+ if (shouldAccumulate) {
436
+ accumulated = direction === "older" ? (0, import_data.mergeData)(partData, accumulated) : (0, import_data.mergeData)(accumulated, partData);
437
+ }
438
+ const boundaryReached = limitBoundary !== void 0 && (direction === "older" && currentPointer <= limitBoundary || direction === "newer" && currentPointer >= limitBoundary);
439
+ if (partData.length < limit || boundaryReached) {
440
+ fulfilled = true;
441
+ break;
442
+ }
443
+ const nextPointer = direction === "older" ? (0, import_time.getItemTimestamp)(partData[0]) - intervalMs : (0, import_time.getItemTimestamp)(partData[partData.length - 1]) + intervalMs;
444
+ if (!Number.isFinite(nextPointer) || nextPointer === currentPointer) {
445
+ fulfilled = true;
446
+ break;
447
+ }
448
+ pointer = nextPointer;
449
+ }
450
+ return { data: accumulated, loaded };
451
+ };
452
+ const refreshTail = async ({
453
+ symbol,
454
+ interval,
455
+ silent,
456
+ tailCount = 2
457
+ }) => {
458
+ const intMinutes = intervalToMinutes(interval);
459
+ if (!intMinutes) {
460
+ import_logger2.logger.log("error", "refreshTail: invalid interval %s", interval);
461
+ return;
462
+ }
463
+ const intervalMs = intervalMsOf(intMinutes);
464
+ const lastClosed = Math.floor(Date.now() / intervalMs) * intervalMs;
465
+ const tailEnd = lastClosed + intervalMs;
466
+ const tailStart = tailEnd - tailCount * intervalMs;
467
+ const part = await request({
468
+ symbol,
469
+ interval,
470
+ start: tailStart,
471
+ end: tailEnd,
472
+ silent
473
+ });
474
+ if (part.length) {
475
+ await runTimescaleOperation(
476
+ () => (0, import_candles.upsertCandles)((0, import_candles.toRows)(provider, symbol, intMinutes, part))
477
+ );
478
+ }
479
+ };
480
+ return async ({
481
+ symbol,
482
+ interval,
483
+ start: defaultStart,
484
+ end: defaultEnd,
485
+ silent = false,
486
+ cacheOnly = false,
487
+ warmOnly = false
488
+ }) => {
489
+ const intMinutes = intervalToMinutes(interval);
490
+ if (!intMinutes) {
491
+ import_logger2.logger.log("error", "kline: invalid interval %s", interval);
492
+ return [];
493
+ }
494
+ if (defaultStart !== void 0 && defaultEnd !== void 0 && defaultEnd <= defaultStart) {
495
+ return [];
496
+ }
497
+ const intervalMs = intervalMsOf(intMinutes);
498
+ try {
499
+ const edges = await runTimescaleOperation(
500
+ () => (0, import_candles.getDataEdges)(provider, symbol, intMinutes)
501
+ );
502
+ let dataStart = edges.min;
503
+ let dataEnd = edges.max;
504
+ const { normStart, normEnd } = normalizeRangeToClosed(
505
+ intervalMs,
506
+ defaultStart,
507
+ defaultEnd
508
+ );
509
+ if (cacheOnly) {
510
+ const base = edges.max ?? Date.now();
511
+ const s = Math.max(
512
+ defaultStart ?? base - cacheFallbackWindow * intervalMs,
513
+ 0
514
+ );
515
+ const e = defaultEnd ?? base;
516
+ const dbData2 = await runTimescaleOperation(
517
+ () => (0, import_candles.getCandlesRange)(provider, symbol, intMinutes, s, e)
518
+ );
519
+ return rowsToKline(dbData2);
520
+ }
521
+ const needOlderData = defaultStart !== void 0 && (dataStart === void 0 || normStart < dataStart);
522
+ const persistPartData = warmOnly ? (partData) => runTimescaleOperation(
523
+ () => (0, import_candles.upsertCandles)((0, import_candles.toRows)(provider, symbol, intMinutes, partData))
524
+ ) : void 0;
525
+ if (needOlderData) {
526
+ const pointerForOlder = dataStart ?? normEnd ?? Date.now();
527
+ const olderResult = await loadData(
528
+ "older",
529
+ pointerForOlder,
530
+ normStart,
531
+ {
532
+ symbol,
533
+ interval,
534
+ silent,
535
+ start: normStart,
536
+ end: pointerForOlder
537
+ },
538
+ intervalMs,
539
+ {
540
+ accumulate: !warmOnly,
541
+ onPartData: persistPartData
542
+ }
543
+ );
544
+ if (warmOnly && olderResult.loaded) {
545
+ dataStart = normStart;
546
+ if (dataEnd === void 0) {
547
+ dataEnd = normEnd;
548
+ }
549
+ } else if (olderResult.data.length) {
550
+ await runTimescaleOperation(
551
+ () => (0, import_candles.upsertCandles)(
552
+ (0, import_candles.toRows)(provider, symbol, intMinutes, olderResult.data)
553
+ )
554
+ );
555
+ dataStart = normStart;
556
+ if (dataEnd === void 0) {
557
+ dataEnd = normEnd;
558
+ }
559
+ }
560
+ }
561
+ const needNewerData = defaultEnd !== void 0 && (dataEnd === void 0 || normEnd > dataEnd);
562
+ if (needNewerData) {
563
+ const fallbackStart = (0, import_time.getTimestamp)(import_constants.PRELOAD_FALLBACK_DAYS);
564
+ const pointerForNewer = dataEnd ?? (defaultStart !== void 0 ? normStart : fallbackStart) ?? 0;
565
+ const newerResult = await loadData(
566
+ "newer",
567
+ pointerForNewer,
568
+ normEnd,
569
+ {
570
+ symbol,
571
+ interval,
572
+ silent,
573
+ start: pointerForNewer,
574
+ end: normEnd
575
+ },
576
+ intervalMs,
577
+ {
578
+ accumulate: !warmOnly,
579
+ onPartData: persistPartData
580
+ }
581
+ );
582
+ if (warmOnly && newerResult.loaded) {
583
+ dataEnd = normEnd;
584
+ } else if (newerResult.data.length) {
585
+ await runTimescaleOperation(
586
+ () => (0, import_candles.upsertCandles)(
587
+ (0, import_candles.toRows)(provider, symbol, intMinutes, newerResult.data)
588
+ )
589
+ );
590
+ dataEnd = normEnd;
591
+ }
592
+ }
593
+ const isRightEdgeQuery = defaultEnd === void 0 || defaultEnd && defaultEnd >= Date.now() - intervalMs;
594
+ if (!cacheOnly && isRightEdgeQuery) {
595
+ await refreshTail({ symbol, interval, silent });
596
+ }
597
+ if (warmOnly) {
598
+ if (isTimescaleFallbackMode) {
599
+ isTimescaleFallbackMode = false;
600
+ import_logger2.logger.log("info", "TimescaleDB connection restored for kline cache");
601
+ }
602
+ return [];
603
+ }
604
+ const rangeStart = defaultStart ?? dataStart ?? 0;
605
+ const rangeEnd = defaultEnd ?? dataEnd ?? Date.now();
606
+ const { normStart: finalStart, normEnd: finalEnd } = normalizeRangeToClosed(intervalMs, rangeStart, rangeEnd);
607
+ const dbData = await runTimescaleOperation(
608
+ () => (0, import_candles.getCandlesRange)(provider, symbol, intMinutes, finalStart, finalEnd)
609
+ );
610
+ if (isTimescaleFallbackMode) {
611
+ isTimescaleFallbackMode = false;
612
+ import_logger2.logger.log("info", "TimescaleDB connection restored for kline cache");
613
+ }
614
+ return rowsToKline(dbData);
615
+ } catch (error) {
616
+ if (!isTimescaleFallbackMode) {
617
+ isTimescaleFallbackMode = true;
618
+ import_logger2.logger.log(
619
+ "warn",
620
+ "TimescaleDB unavailable for %s %s: %s. Falling back to exchange API.",
621
+ symbol,
622
+ interval,
623
+ String(error)
624
+ );
625
+ }
626
+ if (cacheOnly || warmOnly) {
627
+ return [];
628
+ }
629
+ return request({
630
+ symbol,
631
+ interval,
632
+ start: defaultStart,
633
+ end: defaultEnd,
634
+ silent
635
+ });
636
+ }
637
+ };
638
+ };
639
+
640
+ // src/connectorRuntime.ts
641
+ var connectorRuntime = {
642
+ logger: import_logger3.logger,
643
+ resolveTradingAccount: import_tradingAccounts.resolveTradingAccount,
644
+ createCachedKline: createTimescaleCachedKline
645
+ };
646
+ var bindConnectorRuntime = (creator, runtime = connectorRuntime) => {
647
+ const boundCreator = (config) => creator(config, runtime);
648
+ return boundCreator;
649
+ };
650
+
335
651
  // src/connectorsRegistry.ts
336
652
  var createConnectorRegistryState = () => ({
337
653
  connectorCreators: /* @__PURE__ */ new Map(),
@@ -384,7 +700,7 @@ var normalizeProviders = (providers, connectorName) => {
384
700
  var registerProvider = (provider, connectorName, source, providerToConnectorName) => {
385
701
  const existing = providerToConnectorName.get(provider);
386
702
  if (existing && existing !== connectorName) {
387
- import_logger2.logger.warn(
703
+ import_logger4.logger.warn(
388
704
  'Skip duplicate connector provider "%s" from %s: already mapped to %s',
389
705
  provider,
390
706
  source,
@@ -397,11 +713,11 @@ var registerProvider = (provider, connectorName, source, providerToConnectorName
397
713
  var registerEntry = (entry, source, state) => {
398
714
  const connectorName = String(entry?.name ?? "").trim();
399
715
  if (!connectorName) {
400
- import_logger2.logger.warn("Skip connector entry without name from %s", source);
716
+ import_logger4.logger.warn("Skip connector entry without name from %s", source);
401
717
  return;
402
718
  }
403
719
  if (typeof entry.creator !== "function") {
404
- import_logger2.logger.warn(
720
+ import_logger4.logger.warn(
405
721
  'Skip connector entry "%s" from %s: creator must be a function',
406
722
  connectorName,
407
723
  source
@@ -413,7 +729,7 @@ var registerEntry = (entry, source, state) => {
413
729
  state.connectorCreators
414
730
  );
415
731
  if (existingByName) {
416
- import_logger2.logger.warn(
732
+ import_logger4.logger.warn(
417
733
  'Skip duplicate connector "%s" from %s: already registered as %s',
418
734
  connectorName,
419
735
  source,
@@ -421,7 +737,10 @@ var registerEntry = (entry, source, state) => {
421
737
  );
422
738
  return;
423
739
  }
424
- state.connectorCreators.set(connectorName, entry.creator);
740
+ state.connectorCreators.set(
741
+ connectorName,
742
+ bindConnectorRuntime(entry.creator)
743
+ );
425
744
  const providers = normalizeProviders(entry.providers, connectorName);
426
745
  for (const provider of providers) {
427
746
  registerProvider(
@@ -485,7 +804,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
485
804
  );
486
805
  const pluginDefinition = extractConnectorPluginDefinition(moduleExport);
487
806
  if (!pluginDefinition) {
488
- import_logger2.logger.warn(
807
+ import_logger4.logger.warn(
489
808
  'Skip connector plugin "%s": export { connectorEntries } is missing',
490
809
  moduleName
491
810
  );
@@ -493,7 +812,7 @@ var ensureConnectorPluginsLoaded = async (cwd = getTradejsProjectCwd()) => {
493
812
  }
494
813
  registerEntries(pluginDefinition.connectorEntries, moduleName, state);
495
814
  } catch (error) {
496
- import_logger2.logger.warn(
815
+ import_logger4.logger.warn(
497
816
  'Failed to load connector plugin "%s": %s',
498
817
  moduleName,
499
818
  String(error)
@@ -585,6 +904,8 @@ var DEFAULT_CONNECTOR_NAME = BUILTIN_CONNECTOR_NAMES.ByBit;
585
904
  0 && (module.exports = {
586
905
  BUILTIN_CONNECTOR_NAMES,
587
906
  DEFAULT_CONNECTOR_NAME,
907
+ bindConnectorRuntime,
908
+ connectorRuntime,
588
909
  ensureConnectorPluginsLoaded,
589
910
  getAvailableConnectorNames,
590
911
  getAvailableConnectorProviders,
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  BUILTIN_CONNECTOR_NAMES,
3
3
  DEFAULT_CONNECTOR_NAME,
4
+ bindConnectorRuntime,
5
+ connectorRuntime,
4
6
  ensureConnectorPluginsLoaded,
5
7
  getAvailableConnectorNames,
6
8
  getAvailableConnectorProviders,
@@ -10,12 +12,14 @@ import {
10
12
  registerConnectorEntries,
11
13
  resetConnectorRegistryCache,
12
14
  resolveConnectorName
13
- } from "./chunk-V3YMKE4I.mjs";
15
+ } from "./chunk-3TWULKHV.mjs";
14
16
  import "./chunk-WS5DYEVZ.mjs";
15
17
  import "./chunk-Y6FXYEAI.mjs";
16
18
  export {
17
19
  BUILTIN_CONNECTOR_NAMES,
18
20
  DEFAULT_CONNECTOR_NAME,
21
+ bindConnectorRuntime,
22
+ connectorRuntime,
19
23
  ensureConnectorPluginsLoaded,
20
24
  getAvailableConnectorNames,
21
25
  getAvailableConnectorProviders,
@@ -3,6 +3,7 @@ import { StrategyManifest, StrategyCreator, StrategyRegistryEntry } from '@trade
3
3
  declare const ensureStrategyPluginsLoaded: (cwd?: string) => Promise<void>;
4
4
  declare const ensureIndicatorPluginsLoaded: (cwd?: string) => Promise<void>;
5
5
  declare const getStrategyCreator: (name: string, cwd?: string) => Promise<StrategyCreator | undefined>;
6
+ declare const getStrategyDefaults: (name: string, cwd?: string) => Promise<StrategyRegistryEntry["defaults"] | undefined>;
6
7
  declare const getAvailableStrategyNames: (cwd?: string) => Promise<string[]>;
7
8
  declare const getRegisteredStrategies: (cwd?: string) => Record<string, StrategyCreator>;
8
9
  declare const getRegisteredManifests: (cwd?: string) => StrategyManifest[];
@@ -12,4 +13,4 @@ declare const registerStrategyEntries: (entries: readonly StrategyRegistryEntry[
12
13
  declare const resetStrategyRegistryCache: (cwd?: string) => void;
13
14
  declare const strategies: Record<string, StrategyCreator>;
14
15
 
15
- export { ensureIndicatorPluginsLoaded, ensureStrategyPluginsLoaded, getAvailableStrategyNames, getRegisteredManifests, getRegisteredStrategies, getStrategyCreator, getStrategyManifest, isKnownStrategy, registerStrategyEntries, resetStrategyRegistryCache, strategies };
16
+ export { ensureIndicatorPluginsLoaded, ensureStrategyPluginsLoaded, getAvailableStrategyNames, getRegisteredManifests, getRegisteredStrategies, getStrategyCreator, getStrategyDefaults, getStrategyManifest, isKnownStrategy, registerStrategyEntries, resetStrategyRegistryCache, strategies };
@@ -3,6 +3,7 @@ import { StrategyManifest, StrategyCreator, StrategyRegistryEntry } from '@trade
3
3
  declare const ensureStrategyPluginsLoaded: (cwd?: string) => Promise<void>;
4
4
  declare const ensureIndicatorPluginsLoaded: (cwd?: string) => Promise<void>;
5
5
  declare const getStrategyCreator: (name: string, cwd?: string) => Promise<StrategyCreator | undefined>;
6
+ declare const getStrategyDefaults: (name: string, cwd?: string) => Promise<StrategyRegistryEntry["defaults"] | undefined>;
6
7
  declare const getAvailableStrategyNames: (cwd?: string) => Promise<string[]>;
7
8
  declare const getRegisteredStrategies: (cwd?: string) => Record<string, StrategyCreator>;
8
9
  declare const getRegisteredManifests: (cwd?: string) => StrategyManifest[];
@@ -12,4 +13,4 @@ declare const registerStrategyEntries: (entries: readonly StrategyRegistryEntry[
12
13
  declare const resetStrategyRegistryCache: (cwd?: string) => void;
13
14
  declare const strategies: Record<string, StrategyCreator>;
14
15
 
15
- export { ensureIndicatorPluginsLoaded, ensureStrategyPluginsLoaded, getAvailableStrategyNames, getRegisteredManifests, getRegisteredStrategies, getStrategyCreator, getStrategyManifest, isKnownStrategy, registerStrategyEntries, resetStrategyRegistryCache, strategies };
16
+ export { ensureIndicatorPluginsLoaded, ensureStrategyPluginsLoaded, getAvailableStrategyNames, getRegisteredManifests, getRegisteredStrategies, getStrategyCreator, getStrategyDefaults, getStrategyManifest, isKnownStrategy, registerStrategyEntries, resetStrategyRegistryCache, strategies };