@tradejs/cli 2.0.14 → 2.0.16

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.
@@ -1655,112 +1655,8 @@ var import_constants3 = require("@tradejs/core/constants");
1655
1655
  var import_strategies6 = require("@tradejs/node/strategies");
1656
1656
  var import_time3 = require("@tradejs/core/time");
1657
1657
  var import_logger4 = require("@tradejs/infra/logger");
1658
- var import_redis5 = require("@tradejs/infra/redis");
1659
1658
  var import_tradingAccounts2 = require("@tradejs/infra/tradingAccounts");
1660
1659
 
1661
- // src/lib/runtimeSignalsStorage.ts
1662
- var import_constants = require("@tradejs/core/constants");
1663
- var import_time = require("@tradejs/core/time");
1664
- var SECONDS_PER_DAY = 86400;
1665
- var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
1666
- var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants.TTL_3D;
1667
- var RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS = import_constants.TTL_1M;
1668
- var parsePositiveNumber = (value) => {
1669
- if (value == null || value === "") {
1670
- return null;
1671
- }
1672
- const parsed = Number(value);
1673
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
1674
- };
1675
- var getRuntimeSignalRetentionTtlSeconds = () => {
1676
- const explicitDays = parsePositiveNumber(
1677
- process.env[RUNTIME_SIGNAL_RETENTION_DAYS_ENV]
1678
- );
1679
- if (explicitDays != null) {
1680
- return Math.ceil(explicitDays * SECONDS_PER_DAY);
1681
- }
1682
- return DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS;
1683
- };
1684
- var toRuntimeSignalBucketRef = (signal) => ({
1685
- signalId: signal.signalId,
1686
- symbol: signal.symbol,
1687
- strategy: signal.strategy,
1688
- timestamp: signal.timestamp,
1689
- ...signal.runtimeConfigId && signal.runtimeConfigId !== "config" ? { runtimeConfigId: signal.runtimeConfigId } : {}
1690
- });
1691
- var shouldStoreRuntimeSignalDiagnostics = (signal) => signal.orderStatus === "completed" || signal.orderStatus === "failed";
1692
- var toStoredRuntimeSignal = (signal) => {
1693
- if (shouldStoreRuntimeSignalDiagnostics(signal)) {
1694
- return signal;
1695
- }
1696
- const stored = { ...signal };
1697
- delete stored.figures;
1698
- delete stored.indicators;
1699
- delete stored.additionalIndicators;
1700
- return stored;
1701
- };
1702
- var normalizeRuntimeSignalSkipReason = (reason, fallbackSource) => {
1703
- let source = fallbackSource;
1704
- let normalizedReason = reason;
1705
- if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
1706
- source = "AI";
1707
- normalizedReason = "MIN_AI_QUALITY";
1708
- } else if (reason === "AI_QUALITY_UNAVAILABLE") {
1709
- source = "AI";
1710
- normalizedReason = "QUALITY_UNAVAILABLE";
1711
- } else if (reason === "ML_RESULT_UNAVAILABLE") {
1712
- source = "ML";
1713
- normalizedReason = "RESULT_UNAVAILABLE";
1714
- } else if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
1715
- source = "ML";
1716
- normalizedReason = "ML_THRESHOLD";
1717
- } else if (reason.startsWith("HOOK_BEFORE_ENTRY_GATE:")) {
1718
- source = "hook";
1719
- normalizedReason = `BEFORE_ENTRY_GATE:${reason.slice(
1720
- "HOOK_BEFORE_ENTRY_GATE:".length
1721
- )}`;
1722
- } else if (reason === "HOOK_BEFORE_ENTRY_GATE") {
1723
- source = "hook";
1724
- normalizedReason = "BEFORE_ENTRY_GATE";
1725
- } else if (reason === "MAKE_ORDERS_DISABLED" || reason === "ENTRY_POLICY_BLOCKED") {
1726
- source = "policy";
1727
- }
1728
- return {
1729
- source: `skip from ${source}`,
1730
- reason: normalizedReason
1731
- };
1732
- };
1733
- var buildStatsReasonField = (source, reason) => `reason:${source}:${reason}`;
1734
- var buildRuntimeSignalStatsIncrements = (evaluation) => {
1735
- const increments = {
1736
- evaluated: 1
1737
- };
1738
- if (evaluation.status === "signal") {
1739
- increments.signals = 1;
1740
- }
1741
- let skipReason = null;
1742
- let fallbackSource = "core";
1743
- if (evaluation.status === "skip") {
1744
- skipReason = evaluation.reason || "NO_SIGNAL";
1745
- fallbackSource = "core";
1746
- } else if (evaluation.status === "signal" && (evaluation.orderStatus === "skipped" || evaluation.orderStatus === "canceled")) {
1747
- skipReason = evaluation.orderSkipReason || evaluation.reason || `orderStatus=${evaluation.orderStatus}`;
1748
- fallbackSource = "runtime";
1749
- } else if (evaluation.status === "error") {
1750
- skipReason = evaluation.reason || "EVALUATION_ERROR";
1751
- fallbackSource = "runtime";
1752
- }
1753
- if (skipReason) {
1754
- const normalized = normalizeRuntimeSignalSkipReason(
1755
- skipReason,
1756
- fallbackSource
1757
- );
1758
- increments[buildStatsReasonField(normalized.source, normalized.reason)] = 1;
1759
- }
1760
- return increments;
1761
- };
1762
- var shouldStoreDetailedRuntimeSignalEvaluation = (evaluation) => evaluation.status !== "skip";
1763
-
1764
1660
  // src/lib/marketData/windows.ts
1765
1661
  var import_indicators = require("@tradejs/core/indicators");
1766
1662
  var resolveOpenTimestamp = (timestamp, intervalMs2) => Number.isFinite(intervalMs2) && intervalMs2 > 0 ? Math.floor(timestamp / intervalMs2) * intervalMs2 : timestamp;
@@ -1820,6 +1716,8 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
1820
1716
 
1821
1717
  // src/lib/marketContextPrepare.ts
1822
1718
  var import_chalk4 = __toESM(require("chalk"));
1719
+ var import_timescale5 = require("@tradejs/infra/timescale");
1720
+ var import_registry = require("@tradejs/node/registry");
1823
1721
 
1824
1722
  // src/lib/binanceMarketContextBackfill.ts
1825
1723
  var import_progress = __toESM(require("progress"));
@@ -2620,7 +2518,7 @@ var backfillBinanceMarketContextForReplay = (params) => backfillBinanceMarketCon
2620
2518
  var import_chalk2 = __toESM(require("chalk"));
2621
2519
  var import_progress2 = __toESM(require("progress"));
2622
2520
  var import_async = require("@tradejs/core/async");
2623
- var import_constants2 = require("@tradejs/core/constants");
2521
+ var import_constants = require("@tradejs/core/constants");
2624
2522
  var import_indicators2 = require("@tradejs/core/indicators");
2625
2523
  var import_timescale2 = require("@tradejs/infra/timescale");
2626
2524
  var import_userSettings = require("@tradejs/infra/userSettings");
@@ -2655,7 +2553,7 @@ var normalizeSymbols = (symbols) => [
2655
2553
  ];
2656
2554
  var isDerivativesTargetContextEnabled = () => parseBooleanFlag(process.env.DERIVATIVES_CONTEXT_TARGET_ENABLED, false);
2657
2555
  var resolveDerivativesContextBackfillSymbols = (requestedSymbols = []) => {
2658
- const referenceSymbols = (0, import_constants2.resolveDerivativesContextReferenceSymbols)(
2556
+ const referenceSymbols = (0, import_constants.resolveDerivativesContextReferenceSymbols)(
2659
2557
  process.env.DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS
2660
2558
  );
2661
2559
  return isDerivativesTargetContextEnabled() ? normalizeSymbols([...referenceSymbols, ...requestedSymbols]) : referenceSymbols;
@@ -5061,6 +4959,9 @@ var backfillHyperliquidWhaleContext = async (params) => {
5061
4959
  };
5062
4960
 
5063
4961
  // src/lib/marketContextPrepare.ts
4962
+ var uniqueReasons = (reasons) => [
4963
+ ...new Set(reasons)
4964
+ ];
5064
4965
  var shouldPrepareDerivativesContextForRun = (params) => {
5065
4966
  if (params.universe === "tradfi") return false;
5066
4967
  if (params.mode === "signals") {
@@ -5068,6 +4969,9 @@ var shouldPrepareDerivativesContextForRun = (params) => {
5068
4969
  cacheOnly: params.cacheOnly
5069
4970
  });
5070
4971
  }
4972
+ if (params.mode === "backtest" && !params.aiEnabled && !params.mlEnabled) {
4973
+ return false;
4974
+ }
5071
4975
  return shouldBackfillDerivativesContextForBacktest({
5072
4976
  aiEnabled: Boolean(params.aiEnabled),
5073
4977
  cacheOnly: params.cacheOnly,
@@ -5077,6 +4981,7 @@ var shouldPrepareDerivativesContextForRun = (params) => {
5077
4981
  var shouldPrepareBinanceMarketContextForRun = (params) => {
5078
4982
  if (params.universe === "tradfi") return false;
5079
4983
  if (params.mode === "backtest") {
4984
+ if (!params.aiEnabled && !params.mlEnabled) return false;
5080
4985
  return shouldBackfillBinanceMarketContextForBacktest({
5081
4986
  aiEnabled: Boolean(params.aiEnabled),
5082
4987
  cacheOnly: params.cacheOnly,
@@ -5095,6 +5000,7 @@ var shouldPrepareBinanceMarketContextForRun = (params) => {
5095
5000
  var shouldPrepareCoinMarketCapContextForRun = (params) => {
5096
5001
  if (params.universe === "tradfi") return false;
5097
5002
  if (params.mode === "backtest") {
5003
+ if (!params.aiEnabled && !params.mlEnabled) return false;
5098
5004
  return shouldBackfillCoinMarketCapContextForBacktest({
5099
5005
  aiEnabled: Boolean(params.aiEnabled),
5100
5006
  cacheOnly: params.cacheOnly,
@@ -5110,10 +5016,8 @@ var shouldPrepareCoinMarketCapContextForRun = (params) => {
5110
5016
  cacheOnly: params.cacheOnly
5111
5017
  });
5112
5018
  };
5113
- var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5114
- const strategyRequiresContext = params.strategyNames?.includes(
5115
- "HyperliquidConsensus"
5116
- );
5019
+ var shouldPrepareHyperliquidWhaleContextForRun = (params, coreContextSources = []) => {
5020
+ const strategyRequiresContext = coreContextSources.includes("hyperliquidWhales");
5117
5021
  if (params.universe === "tradfi" || params.mode === "signals" || params.cacheOnly || !params.aiEnabled && !params.mlEnabled && !strategyRequiresContext) {
5118
5022
  return false;
5119
5023
  }
@@ -5124,6 +5028,64 @@ var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5124
5028
  if (strategyRequiresContext) return true;
5125
5029
  return ["1", "true", "yes", "on"].includes(configured);
5126
5030
  };
5031
+ var resolveCoreContextSources = async (params) => {
5032
+ const strategyNames = [...new Set(params.strategyNames ?? [])];
5033
+ if (!strategyNames.length) return [];
5034
+ await (0, import_registry.ensureStrategyPluginsLoaded)(params.projectRoot);
5035
+ return [
5036
+ ...new Set(
5037
+ strategyNames.flatMap(
5038
+ (strategyName) => (0, import_registry.getStrategyManifest)(strategyName, params.projectRoot)?.contextRequirements?.core ?? []
5039
+ )
5040
+ )
5041
+ ];
5042
+ };
5043
+ var resolveMarketContextRunRequirements = async (params) => {
5044
+ const coreContextSources = await resolveCoreContextSources(params);
5045
+ const paramsWithCoreDemand = (source) => coreContextSources.includes(source) ? { ...params, aiEnabled: true } : params;
5046
+ const isCrypto = params.universe !== "tradfi";
5047
+ const capturesPayload = Boolean(params.aiEnabled || params.mlEnabled);
5048
+ const runtimeReadsContext = params.mode !== "backtest";
5049
+ const reasonsFor = (source) => uniqueReasons([
5050
+ ...coreContextSources.includes(source) ? ["core"] : [],
5051
+ ...params.aiEnabled ? ["ai"] : [],
5052
+ ...params.mlEnabled ? ["ml"] : [],
5053
+ ...runtimeReadsContext ? ["runtime"] : []
5054
+ ]);
5055
+ const readsStandardContext = isCrypto && (capturesPayload || runtimeReadsContext);
5056
+ const readsHyperliquid = isCrypto && (readsStandardContext || coreContextSources.includes("hyperliquidWhales"));
5057
+ return {
5058
+ binance: {
5059
+ read: readsStandardContext || coreContextSources.includes("binance"),
5060
+ backfill: shouldPrepareBinanceMarketContextForRun(
5061
+ paramsWithCoreDemand("binance")
5062
+ ),
5063
+ requiredBy: reasonsFor("binance")
5064
+ },
5065
+ coinmarketcap: {
5066
+ read: readsStandardContext || coreContextSources.includes("coinmarketcap"),
5067
+ backfill: shouldPrepareCoinMarketCapContextForRun(
5068
+ paramsWithCoreDemand("coinmarketcap")
5069
+ ),
5070
+ requiredBy: reasonsFor("coinmarketcap")
5071
+ },
5072
+ derivatives: {
5073
+ read: readsStandardContext || coreContextSources.includes("derivatives"),
5074
+ backfill: shouldPrepareDerivativesContextForRun(
5075
+ paramsWithCoreDemand("derivatives")
5076
+ ),
5077
+ requiredBy: reasonsFor("derivatives")
5078
+ },
5079
+ hyperliquidWhales: {
5080
+ read: readsHyperliquid,
5081
+ backfill: shouldPrepareHyperliquidWhaleContextForRun(
5082
+ params,
5083
+ coreContextSources
5084
+ ),
5085
+ requiredBy: reasonsFor("hyperliquidWhales")
5086
+ }
5087
+ };
5088
+ };
5127
5089
  var resolveCoinMarketCapBackfillForMode = (mode) => mode === "backtest" ? backfillCoinMarketCapContextForBacktest : mode === "signals" ? backfillCoinMarketCapContextForSignals : backfillCoinMarketCapContextForReplay;
5128
5090
  var buildCoinMarketCapBackfillParams = (params) => ({
5129
5091
  userName: params.userName,
@@ -5152,7 +5114,15 @@ var prepareMarketContextForRun = async (params) => {
5152
5114
  console.log(import_chalk4.default.gray(message));
5153
5115
  });
5154
5116
  const timeOperation3 = (label, operation) => timeOperation(label, operation, log);
5155
- if (shouldPrepareDerivativesContextForRun(params)) {
5117
+ const requirements = await resolveMarketContextRunRequirements(params);
5118
+ const readableSources = Object.entries(requirements).filter(([, requirement]) => requirement.read).map(([source]) => source);
5119
+ if (readableSources.length) {
5120
+ await timeOperation3(
5121
+ "market context schema ensure",
5122
+ () => (0, import_timescale5.ensureMarketContextSchemas)(readableSources)
5123
+ );
5124
+ }
5125
+ if (requirements.derivatives.backfill) {
5156
5126
  await timeOperation3(
5157
5127
  "derivatives context backfill",
5158
5128
  () => (params.mode === "signals" ? backfillDerivativesContextForSignals : backfillDerivativesContextForBacktest)(
@@ -5160,7 +5130,7 @@ var prepareMarketContextForRun = async (params) => {
5160
5130
  )
5161
5131
  );
5162
5132
  }
5163
- if (shouldPrepareHyperliquidWhaleContextForRun(params)) {
5133
+ if (requirements.hyperliquidWhales.backfill) {
5164
5134
  await timeOperation3(
5165
5135
  "hyperliquid whale context backfill",
5166
5136
  () => backfillHyperliquidWhaleContext({
@@ -5172,13 +5142,13 @@ var prepareMarketContextForRun = async (params) => {
5172
5142
  })
5173
5143
  );
5174
5144
  }
5175
- if (shouldPrepareBinanceMarketContextForRun(params)) {
5145
+ if (requirements.binance.backfill) {
5176
5146
  await timeOperation3("binance market context backfill", () => {
5177
5147
  const backfill = params.mode === "backtest" ? backfillBinanceMarketContextForBacktest : params.mode === "signals" ? backfillBinanceMarketContextForSignals : backfillBinanceMarketContextForReplay;
5178
5148
  return backfill(buildBinanceMarketBackfillParams(params));
5179
5149
  });
5180
5150
  }
5181
- if (shouldPrepareCoinMarketCapContextForRun(params)) {
5151
+ if (requirements.coinmarketcap.backfill) {
5182
5152
  await timeOperation3(
5183
5153
  "coinmarketcap historical context backfill",
5184
5154
  () => resolveCoinMarketCapBackfillForMode(params.mode)(
@@ -5186,6 +5156,7 @@ var prepareMarketContextForRun = async (params) => {
5186
5156
  )
5187
5157
  );
5188
5158
  }
5159
+ return requirements;
5189
5160
  };
5190
5161
 
5191
5162
  // src/lib/marketData/historyPrepare.ts
@@ -5349,7 +5320,7 @@ var import_strategies3 = require("@tradejs/node/strategies");
5349
5320
 
5350
5321
  // src/lib/runtimeRedis.ts
5351
5322
  var import_redis = require("@tradejs/infra/redis");
5352
- var import_time2 = require("@tradejs/core/time");
5323
+ var import_time = require("@tradejs/core/time");
5353
5324
  var RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
5354
5325
  var RESERVED_CONFIG_IDS = /* @__PURE__ */ new Set(["results"]);
5355
5326
  var resolveStrategyConfigIdentityByKey = (userName, key) => {
@@ -5493,6 +5464,111 @@ var loadRuntimeStrategies = async ({
5493
5464
  // src/lib/signals/skipStats.ts
5494
5465
  var import_chalk6 = __toESM(require("chalk"));
5495
5466
  var import_logger2 = require("@tradejs/infra/logger");
5467
+
5468
+ // src/lib/runtimeSignalsStorage.ts
5469
+ var import_constants2 = require("@tradejs/core/constants");
5470
+ var import_time2 = require("@tradejs/core/time");
5471
+ var SECONDS_PER_DAY = 86400;
5472
+ var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
5473
+ var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants2.TTL_3D;
5474
+ var RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS = import_constants2.TTL_1M;
5475
+ var parsePositiveNumber = (value) => {
5476
+ if (value == null || value === "") {
5477
+ return null;
5478
+ }
5479
+ const parsed = Number(value);
5480
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
5481
+ };
5482
+ var getRuntimeSignalRetentionTtlSeconds = () => {
5483
+ const explicitDays = parsePositiveNumber(
5484
+ process.env[RUNTIME_SIGNAL_RETENTION_DAYS_ENV]
5485
+ );
5486
+ if (explicitDays != null) {
5487
+ return Math.ceil(explicitDays * SECONDS_PER_DAY);
5488
+ }
5489
+ return DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS;
5490
+ };
5491
+ var toRuntimeSignalBucketRef = (signal) => ({
5492
+ signalId: signal.signalId,
5493
+ symbol: signal.symbol,
5494
+ strategy: signal.strategy,
5495
+ timestamp: signal.timestamp,
5496
+ ...signal.runtimeConfigId && signal.runtimeConfigId !== "config" ? { runtimeConfigId: signal.runtimeConfigId } : {}
5497
+ });
5498
+ var shouldStoreRuntimeSignalDiagnostics = (signal) => signal.orderStatus === "completed" || signal.orderStatus === "failed";
5499
+ var toStoredRuntimeSignal = (signal) => {
5500
+ if (shouldStoreRuntimeSignalDiagnostics(signal)) {
5501
+ return signal;
5502
+ }
5503
+ const stored = { ...signal };
5504
+ delete stored.figures;
5505
+ delete stored.indicators;
5506
+ delete stored.additionalIndicators;
5507
+ return stored;
5508
+ };
5509
+ var normalizeRuntimeSignalSkipReason = (reason, fallbackSource) => {
5510
+ let source = fallbackSource;
5511
+ let normalizedReason = reason;
5512
+ if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
5513
+ source = "AI";
5514
+ normalizedReason = "MIN_AI_QUALITY";
5515
+ } else if (reason === "AI_QUALITY_UNAVAILABLE") {
5516
+ source = "AI";
5517
+ normalizedReason = "QUALITY_UNAVAILABLE";
5518
+ } else if (reason === "ML_RESULT_UNAVAILABLE") {
5519
+ source = "ML";
5520
+ normalizedReason = "RESULT_UNAVAILABLE";
5521
+ } else if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
5522
+ source = "ML";
5523
+ normalizedReason = "ML_THRESHOLD";
5524
+ } else if (reason.startsWith("HOOK_BEFORE_ENTRY_GATE:")) {
5525
+ source = "hook";
5526
+ normalizedReason = `BEFORE_ENTRY_GATE:${reason.slice(
5527
+ "HOOK_BEFORE_ENTRY_GATE:".length
5528
+ )}`;
5529
+ } else if (reason === "HOOK_BEFORE_ENTRY_GATE") {
5530
+ source = "hook";
5531
+ normalizedReason = "BEFORE_ENTRY_GATE";
5532
+ } else if (reason === "MAKE_ORDERS_DISABLED" || reason === "ENTRY_POLICY_BLOCKED") {
5533
+ source = "policy";
5534
+ }
5535
+ return {
5536
+ source: `skip from ${source}`,
5537
+ reason: normalizedReason
5538
+ };
5539
+ };
5540
+ var buildStatsReasonField = (source, reason) => `reason:${source}:${reason}`;
5541
+ var buildRuntimeSignalStatsIncrements = (evaluation) => {
5542
+ const increments = {
5543
+ evaluated: 1
5544
+ };
5545
+ if (evaluation.status === "signal") {
5546
+ increments.signals = 1;
5547
+ }
5548
+ let skipReason = null;
5549
+ let fallbackSource = "core";
5550
+ if (evaluation.status === "skip") {
5551
+ skipReason = evaluation.reason || "NO_SIGNAL";
5552
+ fallbackSource = "core";
5553
+ } else if (evaluation.status === "signal" && (evaluation.orderStatus === "skipped" || evaluation.orderStatus === "canceled")) {
5554
+ skipReason = evaluation.orderSkipReason || evaluation.reason || `orderStatus=${evaluation.orderStatus}`;
5555
+ fallbackSource = "runtime";
5556
+ } else if (evaluation.status === "error") {
5557
+ skipReason = evaluation.reason || "EVALUATION_ERROR";
5558
+ fallbackSource = "runtime";
5559
+ }
5560
+ if (skipReason) {
5561
+ const normalized = normalizeRuntimeSignalSkipReason(
5562
+ skipReason,
5563
+ fallbackSource
5564
+ );
5565
+ increments[buildStatsReasonField(normalized.source, normalized.reason)] = 1;
5566
+ }
5567
+ return increments;
5568
+ };
5569
+ var shouldStoreDetailedRuntimeSignalEvaluation = (evaluation) => evaluation.status !== "skip";
5570
+
5571
+ // src/lib/signals/skipStats.ts
5496
5572
  var createStrategySkipStats = (runtimeStrategies) => new Map(
5497
5573
  runtimeStrategies.map(({ strategyName }) => [
5498
5574
  strategyName,
@@ -5820,7 +5896,7 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
5820
5896
  if (evaluations.length === 0) return;
5821
5897
  const evaluationsByDay = /* @__PURE__ */ new Map();
5822
5898
  for (const evaluation of evaluations) {
5823
- const dayKey = (0, import_time.getRuntimeStorageDayKey)(evaluation.timestamp);
5899
+ const dayKey = (0, import_time2.getRuntimeStorageDayKey)(evaluation.timestamp);
5824
5900
  const dayEvaluations = evaluationsByDay.get(dayKey) ?? [];
5825
5901
  dayEvaluations.push(evaluation);
5826
5902
  evaluationsByDay.set(dayKey, dayEvaluations);
@@ -5928,13 +6004,47 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
5928
6004
  };
5929
6005
  var createRuntimeSignalEvaluationBuffer = () => {
5930
6006
  const evaluations = [];
6007
+ const signals2 = [];
5931
6008
  return {
5932
6009
  save: async (evaluation) => {
5933
6010
  evaluations.push(evaluation);
5934
6011
  },
6012
+ saveSignal: (userName, signal) => {
6013
+ signals2.push({ userName, signal });
6014
+ },
5935
6015
  flush: async () => {
5936
6016
  const pending = evaluations.splice(0);
5937
- await flushRuntimeSignalEvaluations(pending);
6017
+ const pendingSignals = signals2.splice(0);
6018
+ const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
6019
+ const signalRefsByBucket = /* @__PURE__ */ new Map();
6020
+ for (const { userName, signal } of pendingSignals) {
6021
+ const bucket = import_redis3.redisKeys.runtimeSignalBucket(
6022
+ userName,
6023
+ (0, import_time2.getRuntimeStorageDayKey)(signal.timestamp),
6024
+ signal.strategy
6025
+ );
6026
+ const entries = signalRefsByBucket.get(bucket) ?? [];
6027
+ entries.push({
6028
+ field: signal.signalId,
6029
+ data: toRuntimeSignalBucketRef(signal)
6030
+ });
6031
+ signalRefsByBucket.set(bucket, entries);
6032
+ }
6033
+ await Promise.all([
6034
+ flushRuntimeSignalEvaluations(pending),
6035
+ ...pendingSignals.map(
6036
+ ({ signal }) => (0, import_redis3.setData)(
6037
+ import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
6038
+ toStoredRuntimeSignal(signal),
6039
+ { expire: runtimeSignalRetentionTtl }
6040
+ )
6041
+ ),
6042
+ ...[...signalRefsByBucket].map(
6043
+ ([bucket, entries]) => (0, import_redis3.setHashJsonFields)(bucket, entries, {
6044
+ expire: runtimeSignalRetentionTtl
6045
+ })
6046
+ )
6047
+ ]);
5938
6048
  }
5939
6049
  };
5940
6050
  };
@@ -6167,7 +6277,7 @@ var runSignalsDaemon = async ({
6167
6277
  // src/lib/signals/klineFeed.ts
6168
6278
  var import_connectors5 = require("@tradejs/connectors");
6169
6279
  var import_logger3 = require("@tradejs/infra/logger");
6170
- var import_timescale5 = require("@tradejs/infra/timescale");
6280
+ var import_timescale6 = require("@tradejs/infra/timescale");
6171
6281
 
6172
6282
  // src/lib/marketData/klineEvents.ts
6173
6283
  var import_ioredis = __toESM(require("ioredis"));
@@ -6187,9 +6297,9 @@ var createSignalsKlineFeed = async ({
6187
6297
  publish = publishMarketKlineEvent,
6188
6298
  persist = async (rows) => {
6189
6299
  const intervalMinutes = Number(interval2);
6190
- await (0, import_timescale5.upsertCandles)(
6300
+ await (0, import_timescale6.upsertCandles)(
6191
6301
  rows.flatMap(
6192
- ({ symbol, candle }) => (0, import_timescale5.toRows)("bybit", symbol, intervalMinutes, [candle])
6302
+ ({ symbol, candle }) => (0, import_timescale6.toRows)("bybit", symbol, intervalMinutes, [candle])
6193
6303
  )
6194
6304
  );
6195
6305
  },
@@ -6455,7 +6565,7 @@ var createSignalsSession = async (lifecycle, scope = {}) => {
6455
6565
  lifecycle
6456
6566
  };
6457
6567
  };
6458
- var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCoinbaseData, primaryBtcClosedData, primaryEthClosedData, primaryEthByTimestamp, runtimeStrategies, strategyStats, runtimeCloseNotifications, lifecycle, preloadStart, currentTimestamp, persistStrategyState, universe, interval2, intervalMs2, accountId, deploymentId, instrument, runtimeScopeUniverse, saveRuntimeSignalEvaluation) => {
6568
+ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCoinbaseData, primaryBtcClosedData, primaryEthClosedData, primaryEthByTimestamp, runtimeStrategies, strategyStats, runtimeCloseNotifications, lifecycle, preloadStart, currentTimestamp, persistStrategyState, universe, interval2, intervalMs2, accountId, deploymentId, instrument, runtimeScopeUniverse, saveRuntimeSignalEvaluation, saveRuntimeSignal) => {
6459
6569
  const strategySignals = [];
6460
6570
  const loadCached = (cachedSymbol) => connector.kline({
6461
6571
  symbol: cachedSymbol,
@@ -6648,26 +6758,7 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
6648
6758
  );
6649
6759
  }
6650
6760
  strategySignals.push(signal);
6651
- const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
6652
- await (0, import_redis5.setData)(
6653
- import_redis5.redisKeys.storeSignal(symbol, signal.signalId),
6654
- toStoredRuntimeSignal(signal),
6655
- {
6656
- expire: runtimeSignalRetentionTtl
6657
- }
6658
- );
6659
- await (0, import_redis5.setHashJsonField)(
6660
- import_redis5.redisKeys.runtimeSignalBucket(
6661
- flags.user,
6662
- (0, import_time.getRuntimeStorageDayKey)(signal.timestamp),
6663
- signal.strategy
6664
- ),
6665
- signal.signalId,
6666
- toRuntimeSignalBucketRef(signal),
6667
- {
6668
- expire: runtimeSignalRetentionTtl
6669
- }
6670
- );
6761
+ saveRuntimeSignal?.(flags.user, signal);
6671
6762
  await saveRuntimeSignalEvaluation?.({
6672
6763
  evaluationId: buildRuntimeSignalEvaluationId({
6673
6764
  strategyName,
@@ -6973,7 +7064,8 @@ var signals = async (options = {}) => {
6973
7064
  deployment?.id,
6974
7065
  instrumentsBySymbol.get(symbol),
6975
7066
  sessionUniverse,
6976
- runtimeSignalEvaluations.save
7067
+ runtimeSignalEvaluations.save,
7068
+ runtimeSignalEvaluations.saveSignal
6977
7069
  );
6978
7070
  if (strategySignals.length > 0) {
6979
7071
  signals2.push(...strategySignals);