@tradejs/cli 2.0.15 → 2.0.17

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.
@@ -1645,7 +1645,7 @@ __export(signals_exports, {
1645
1645
  module.exports = __toCommonJS(signals_exports);
1646
1646
  var import_config = require("dotenv/config");
1647
1647
  var import_args = __toESM(require("args"));
1648
- var import_progress3 = __toESM(require("progress"));
1648
+ var import_progress4 = __toESM(require("progress"));
1649
1649
  var import_chalk7 = __toESM(require("chalk"));
1650
1650
  var import_connectors6 = require("@tradejs/node/connectors");
1651
1651
  var import_cli2 = require("@tradejs/node/cli");
@@ -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;
@@ -4655,6 +4553,7 @@ var backfillCoinMarketCapContextForReplay = backfillCoinMarketCapContext;
4655
4553
  var backfillCoinMarketCapContextForSignals = (params) => backfillCoinMarketCapContext({ ...params, refreshStaleFearGreed: true });
4656
4554
 
4657
4555
  // src/lib/hyperliquidWhaleBackfill.ts
4556
+ var import_progress3 = __toESM(require("progress"));
4658
4557
  var import_timescale4 = require("@tradejs/infra/timescale");
4659
4558
  var import_strategies2 = require("@tradejs/node/strategies");
4660
4559
 
@@ -5029,11 +4928,36 @@ var backfillHyperliquidWhaleContext = async (params) => {
5029
4928
  toMs,
5030
4929
  ...identity
5031
4930
  });
4931
+ const totalCoverageBuckets = Math.ceil(
4932
+ (toMs - fromMs) / HYPERLIQUID_WHALE_BUCKET_MS
4933
+ );
4934
+ const coverageBar = new import_progress3.default(
4935
+ "Hyperliquid coverage :current/:total [:bar][:percent] :eta(s) rows=:rows chunk=:chunk",
4936
+ {
4937
+ total: Math.max(1, totalCoverageBuckets),
4938
+ width: 24
4939
+ }
4940
+ );
4941
+ let displayedCoverageBuckets = 0;
5032
4942
  const coverageBuckets = await (0, import_timescale4.rebuildHyperliquidWhaleCoverageRows)({
5033
4943
  fromMs,
5034
4944
  toMs,
5035
4945
  expectedWhales: whales.addresses.length,
5036
- ...identity
4946
+ ...identity,
4947
+ onProgress: (progress) => {
4948
+ const next = Math.max(
4949
+ displayedCoverageBuckets,
4950
+ Math.min(progress.completedBuckets, coverageBar.total)
4951
+ );
4952
+ const delta = next - displayedCoverageBuckets;
4953
+ if (delta > 0) {
4954
+ coverageBar.tick(delta, {
4955
+ rows: progress.rows,
4956
+ chunk: `${progress.chunkIndex}/${progress.totalChunks}`
4957
+ });
4958
+ displayedCoverageBuckets = next;
4959
+ }
4960
+ }
5037
4961
  });
5038
4962
  const result = {
5039
4963
  cached: false,
@@ -5061,6 +4985,9 @@ var backfillHyperliquidWhaleContext = async (params) => {
5061
4985
  };
5062
4986
 
5063
4987
  // src/lib/marketContextPrepare.ts
4988
+ var uniqueReasons = (reasons) => [
4989
+ ...new Set(reasons)
4990
+ ];
5064
4991
  var shouldPrepareDerivativesContextForRun = (params) => {
5065
4992
  if (params.universe === "tradfi") return false;
5066
4993
  if (params.mode === "signals") {
@@ -5068,6 +4995,9 @@ var shouldPrepareDerivativesContextForRun = (params) => {
5068
4995
  cacheOnly: params.cacheOnly
5069
4996
  });
5070
4997
  }
4998
+ if (params.mode === "backtest" && !params.aiEnabled && !params.mlEnabled) {
4999
+ return false;
5000
+ }
5071
5001
  return shouldBackfillDerivativesContextForBacktest({
5072
5002
  aiEnabled: Boolean(params.aiEnabled),
5073
5003
  cacheOnly: params.cacheOnly,
@@ -5077,6 +5007,7 @@ var shouldPrepareDerivativesContextForRun = (params) => {
5077
5007
  var shouldPrepareBinanceMarketContextForRun = (params) => {
5078
5008
  if (params.universe === "tradfi") return false;
5079
5009
  if (params.mode === "backtest") {
5010
+ if (!params.aiEnabled && !params.mlEnabled) return false;
5080
5011
  return shouldBackfillBinanceMarketContextForBacktest({
5081
5012
  aiEnabled: Boolean(params.aiEnabled),
5082
5013
  cacheOnly: params.cacheOnly,
@@ -5095,6 +5026,7 @@ var shouldPrepareBinanceMarketContextForRun = (params) => {
5095
5026
  var shouldPrepareCoinMarketCapContextForRun = (params) => {
5096
5027
  if (params.universe === "tradfi") return false;
5097
5028
  if (params.mode === "backtest") {
5029
+ if (!params.aiEnabled && !params.mlEnabled) return false;
5098
5030
  return shouldBackfillCoinMarketCapContextForBacktest({
5099
5031
  aiEnabled: Boolean(params.aiEnabled),
5100
5032
  cacheOnly: params.cacheOnly,
@@ -5110,10 +5042,8 @@ var shouldPrepareCoinMarketCapContextForRun = (params) => {
5110
5042
  cacheOnly: params.cacheOnly
5111
5043
  });
5112
5044
  };
5113
- var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5114
- const strategyRequiresContext = params.strategyNames?.includes(
5115
- "HyperliquidConsensus"
5116
- );
5045
+ var shouldPrepareHyperliquidWhaleContextForRun = (params, coreContextSources = []) => {
5046
+ const strategyRequiresContext = coreContextSources.includes("hyperliquidWhales");
5117
5047
  if (params.universe === "tradfi" || params.mode === "signals" || params.cacheOnly || !params.aiEnabled && !params.mlEnabled && !strategyRequiresContext) {
5118
5048
  return false;
5119
5049
  }
@@ -5124,6 +5054,64 @@ var shouldPrepareHyperliquidWhaleContextForRun = (params) => {
5124
5054
  if (strategyRequiresContext) return true;
5125
5055
  return ["1", "true", "yes", "on"].includes(configured);
5126
5056
  };
5057
+ var resolveCoreContextSources = async (params) => {
5058
+ const strategyNames = [...new Set(params.strategyNames ?? [])];
5059
+ if (!strategyNames.length) return [];
5060
+ await (0, import_registry.ensureStrategyPluginsLoaded)(params.projectRoot);
5061
+ return [
5062
+ ...new Set(
5063
+ strategyNames.flatMap(
5064
+ (strategyName) => (0, import_registry.getStrategyManifest)(strategyName, params.projectRoot)?.contextRequirements?.core ?? []
5065
+ )
5066
+ )
5067
+ ];
5068
+ };
5069
+ var resolveMarketContextRunRequirements = async (params) => {
5070
+ const coreContextSources = await resolveCoreContextSources(params);
5071
+ const paramsWithCoreDemand = (source) => coreContextSources.includes(source) ? { ...params, aiEnabled: true } : params;
5072
+ const isCrypto = params.universe !== "tradfi";
5073
+ const capturesPayload = Boolean(params.aiEnabled || params.mlEnabled);
5074
+ const runtimeReadsContext = params.mode !== "backtest";
5075
+ const reasonsFor = (source) => uniqueReasons([
5076
+ ...coreContextSources.includes(source) ? ["core"] : [],
5077
+ ...params.aiEnabled ? ["ai"] : [],
5078
+ ...params.mlEnabled ? ["ml"] : [],
5079
+ ...runtimeReadsContext ? ["runtime"] : []
5080
+ ]);
5081
+ const readsStandardContext = isCrypto && (capturesPayload || runtimeReadsContext);
5082
+ const readsHyperliquid = isCrypto && (readsStandardContext || coreContextSources.includes("hyperliquidWhales"));
5083
+ return {
5084
+ binance: {
5085
+ read: readsStandardContext || coreContextSources.includes("binance"),
5086
+ backfill: shouldPrepareBinanceMarketContextForRun(
5087
+ paramsWithCoreDemand("binance")
5088
+ ),
5089
+ requiredBy: reasonsFor("binance")
5090
+ },
5091
+ coinmarketcap: {
5092
+ read: readsStandardContext || coreContextSources.includes("coinmarketcap"),
5093
+ backfill: shouldPrepareCoinMarketCapContextForRun(
5094
+ paramsWithCoreDemand("coinmarketcap")
5095
+ ),
5096
+ requiredBy: reasonsFor("coinmarketcap")
5097
+ },
5098
+ derivatives: {
5099
+ read: readsStandardContext || coreContextSources.includes("derivatives"),
5100
+ backfill: shouldPrepareDerivativesContextForRun(
5101
+ paramsWithCoreDemand("derivatives")
5102
+ ),
5103
+ requiredBy: reasonsFor("derivatives")
5104
+ },
5105
+ hyperliquidWhales: {
5106
+ read: readsHyperliquid,
5107
+ backfill: shouldPrepareHyperliquidWhaleContextForRun(
5108
+ params,
5109
+ coreContextSources
5110
+ ),
5111
+ requiredBy: reasonsFor("hyperliquidWhales")
5112
+ }
5113
+ };
5114
+ };
5127
5115
  var resolveCoinMarketCapBackfillForMode = (mode) => mode === "backtest" ? backfillCoinMarketCapContextForBacktest : mode === "signals" ? backfillCoinMarketCapContextForSignals : backfillCoinMarketCapContextForReplay;
5128
5116
  var buildCoinMarketCapBackfillParams = (params) => ({
5129
5117
  userName: params.userName,
@@ -5152,7 +5140,15 @@ var prepareMarketContextForRun = async (params) => {
5152
5140
  console.log(import_chalk4.default.gray(message));
5153
5141
  });
5154
5142
  const timeOperation3 = (label, operation) => timeOperation(label, operation, log);
5155
- if (shouldPrepareDerivativesContextForRun(params)) {
5143
+ const requirements = await resolveMarketContextRunRequirements(params);
5144
+ const readableSources = Object.entries(requirements).filter(([, requirement]) => requirement.read).map(([source]) => source);
5145
+ if (readableSources.length) {
5146
+ await timeOperation3(
5147
+ "market context schema ensure",
5148
+ () => (0, import_timescale5.ensureMarketContextSchemas)(readableSources)
5149
+ );
5150
+ }
5151
+ if (requirements.derivatives.backfill) {
5156
5152
  await timeOperation3(
5157
5153
  "derivatives context backfill",
5158
5154
  () => (params.mode === "signals" ? backfillDerivativesContextForSignals : backfillDerivativesContextForBacktest)(
@@ -5160,7 +5156,7 @@ var prepareMarketContextForRun = async (params) => {
5160
5156
  )
5161
5157
  );
5162
5158
  }
5163
- if (shouldPrepareHyperliquidWhaleContextForRun(params)) {
5159
+ if (requirements.hyperliquidWhales.backfill) {
5164
5160
  await timeOperation3(
5165
5161
  "hyperliquid whale context backfill",
5166
5162
  () => backfillHyperliquidWhaleContext({
@@ -5172,13 +5168,13 @@ var prepareMarketContextForRun = async (params) => {
5172
5168
  })
5173
5169
  );
5174
5170
  }
5175
- if (shouldPrepareBinanceMarketContextForRun(params)) {
5171
+ if (requirements.binance.backfill) {
5176
5172
  await timeOperation3("binance market context backfill", () => {
5177
5173
  const backfill = params.mode === "backtest" ? backfillBinanceMarketContextForBacktest : params.mode === "signals" ? backfillBinanceMarketContextForSignals : backfillBinanceMarketContextForReplay;
5178
5174
  return backfill(buildBinanceMarketBackfillParams(params));
5179
5175
  });
5180
5176
  }
5181
- if (shouldPrepareCoinMarketCapContextForRun(params)) {
5177
+ if (requirements.coinmarketcap.backfill) {
5182
5178
  await timeOperation3(
5183
5179
  "coinmarketcap historical context backfill",
5184
5180
  () => resolveCoinMarketCapBackfillForMode(params.mode)(
@@ -5186,6 +5182,7 @@ var prepareMarketContextForRun = async (params) => {
5186
5182
  )
5187
5183
  );
5188
5184
  }
5185
+ return requirements;
5189
5186
  };
5190
5187
 
5191
5188
  // src/lib/marketData/historyPrepare.ts
@@ -5349,7 +5346,7 @@ var import_strategies3 = require("@tradejs/node/strategies");
5349
5346
 
5350
5347
  // src/lib/runtimeRedis.ts
5351
5348
  var import_redis = require("@tradejs/infra/redis");
5352
- var import_time2 = require("@tradejs/core/time");
5349
+ var import_time = require("@tradejs/core/time");
5353
5350
  var RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
5354
5351
  var RESERVED_CONFIG_IDS = /* @__PURE__ */ new Set(["results"]);
5355
5352
  var resolveStrategyConfigIdentityByKey = (userName, key) => {
@@ -5493,6 +5490,111 @@ var loadRuntimeStrategies = async ({
5493
5490
  // src/lib/signals/skipStats.ts
5494
5491
  var import_chalk6 = __toESM(require("chalk"));
5495
5492
  var import_logger2 = require("@tradejs/infra/logger");
5493
+
5494
+ // src/lib/runtimeSignalsStorage.ts
5495
+ var import_constants2 = require("@tradejs/core/constants");
5496
+ var import_time2 = require("@tradejs/core/time");
5497
+ var SECONDS_PER_DAY = 86400;
5498
+ var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
5499
+ var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants2.TTL_3D;
5500
+ var RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS = import_constants2.TTL_1M;
5501
+ var parsePositiveNumber = (value) => {
5502
+ if (value == null || value === "") {
5503
+ return null;
5504
+ }
5505
+ const parsed = Number(value);
5506
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
5507
+ };
5508
+ var getRuntimeSignalRetentionTtlSeconds = () => {
5509
+ const explicitDays = parsePositiveNumber(
5510
+ process.env[RUNTIME_SIGNAL_RETENTION_DAYS_ENV]
5511
+ );
5512
+ if (explicitDays != null) {
5513
+ return Math.ceil(explicitDays * SECONDS_PER_DAY);
5514
+ }
5515
+ return DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS;
5516
+ };
5517
+ var toRuntimeSignalBucketRef = (signal) => ({
5518
+ signalId: signal.signalId,
5519
+ symbol: signal.symbol,
5520
+ strategy: signal.strategy,
5521
+ timestamp: signal.timestamp,
5522
+ ...signal.runtimeConfigId && signal.runtimeConfigId !== "config" ? { runtimeConfigId: signal.runtimeConfigId } : {}
5523
+ });
5524
+ var shouldStoreRuntimeSignalDiagnostics = (signal) => signal.orderStatus === "completed" || signal.orderStatus === "failed";
5525
+ var toStoredRuntimeSignal = (signal) => {
5526
+ if (shouldStoreRuntimeSignalDiagnostics(signal)) {
5527
+ return signal;
5528
+ }
5529
+ const stored = { ...signal };
5530
+ delete stored.figures;
5531
+ delete stored.indicators;
5532
+ delete stored.additionalIndicators;
5533
+ return stored;
5534
+ };
5535
+ var normalizeRuntimeSignalSkipReason = (reason, fallbackSource) => {
5536
+ let source = fallbackSource;
5537
+ let normalizedReason = reason;
5538
+ if (reason.startsWith("AI_QUALITY_BELOW_MIN")) {
5539
+ source = "AI";
5540
+ normalizedReason = "MIN_AI_QUALITY";
5541
+ } else if (reason === "AI_QUALITY_UNAVAILABLE") {
5542
+ source = "AI";
5543
+ normalizedReason = "QUALITY_UNAVAILABLE";
5544
+ } else if (reason === "ML_RESULT_UNAVAILABLE") {
5545
+ source = "ML";
5546
+ normalizedReason = "RESULT_UNAVAILABLE";
5547
+ } else if (reason.startsWith("ML_THRESHOLD_NOT_MET")) {
5548
+ source = "ML";
5549
+ normalizedReason = "ML_THRESHOLD";
5550
+ } else if (reason.startsWith("HOOK_BEFORE_ENTRY_GATE:")) {
5551
+ source = "hook";
5552
+ normalizedReason = `BEFORE_ENTRY_GATE:${reason.slice(
5553
+ "HOOK_BEFORE_ENTRY_GATE:".length
5554
+ )}`;
5555
+ } else if (reason === "HOOK_BEFORE_ENTRY_GATE") {
5556
+ source = "hook";
5557
+ normalizedReason = "BEFORE_ENTRY_GATE";
5558
+ } else if (reason === "MAKE_ORDERS_DISABLED" || reason === "ENTRY_POLICY_BLOCKED") {
5559
+ source = "policy";
5560
+ }
5561
+ return {
5562
+ source: `skip from ${source}`,
5563
+ reason: normalizedReason
5564
+ };
5565
+ };
5566
+ var buildStatsReasonField = (source, reason) => `reason:${source}:${reason}`;
5567
+ var buildRuntimeSignalStatsIncrements = (evaluation) => {
5568
+ const increments = {
5569
+ evaluated: 1
5570
+ };
5571
+ if (evaluation.status === "signal") {
5572
+ increments.signals = 1;
5573
+ }
5574
+ let skipReason = null;
5575
+ let fallbackSource = "core";
5576
+ if (evaluation.status === "skip") {
5577
+ skipReason = evaluation.reason || "NO_SIGNAL";
5578
+ fallbackSource = "core";
5579
+ } else if (evaluation.status === "signal" && (evaluation.orderStatus === "skipped" || evaluation.orderStatus === "canceled")) {
5580
+ skipReason = evaluation.orderSkipReason || evaluation.reason || `orderStatus=${evaluation.orderStatus}`;
5581
+ fallbackSource = "runtime";
5582
+ } else if (evaluation.status === "error") {
5583
+ skipReason = evaluation.reason || "EVALUATION_ERROR";
5584
+ fallbackSource = "runtime";
5585
+ }
5586
+ if (skipReason) {
5587
+ const normalized = normalizeRuntimeSignalSkipReason(
5588
+ skipReason,
5589
+ fallbackSource
5590
+ );
5591
+ increments[buildStatsReasonField(normalized.source, normalized.reason)] = 1;
5592
+ }
5593
+ return increments;
5594
+ };
5595
+ var shouldStoreDetailedRuntimeSignalEvaluation = (evaluation) => evaluation.status !== "skip";
5596
+
5597
+ // src/lib/signals/skipStats.ts
5496
5598
  var createStrategySkipStats = (runtimeStrategies) => new Map(
5497
5599
  runtimeStrategies.map(({ strategyName }) => [
5498
5600
  strategyName,
@@ -5820,7 +5922,7 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
5820
5922
  if (evaluations.length === 0) return;
5821
5923
  const evaluationsByDay = /* @__PURE__ */ new Map();
5822
5924
  for (const evaluation of evaluations) {
5823
- const dayKey = (0, import_time.getRuntimeStorageDayKey)(evaluation.timestamp);
5925
+ const dayKey = (0, import_time2.getRuntimeStorageDayKey)(evaluation.timestamp);
5824
5926
  const dayEvaluations = evaluationsByDay.get(dayKey) ?? [];
5825
5927
  dayEvaluations.push(evaluation);
5826
5928
  evaluationsByDay.set(dayKey, dayEvaluations);
@@ -5928,13 +6030,47 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
5928
6030
  };
5929
6031
  var createRuntimeSignalEvaluationBuffer = () => {
5930
6032
  const evaluations = [];
6033
+ const signals2 = [];
5931
6034
  return {
5932
6035
  save: async (evaluation) => {
5933
6036
  evaluations.push(evaluation);
5934
6037
  },
6038
+ saveSignal: (userName, signal) => {
6039
+ signals2.push({ userName, signal });
6040
+ },
5935
6041
  flush: async () => {
5936
6042
  const pending = evaluations.splice(0);
5937
- await flushRuntimeSignalEvaluations(pending);
6043
+ const pendingSignals = signals2.splice(0);
6044
+ const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
6045
+ const signalRefsByBucket = /* @__PURE__ */ new Map();
6046
+ for (const { userName, signal } of pendingSignals) {
6047
+ const bucket = import_redis3.redisKeys.runtimeSignalBucket(
6048
+ userName,
6049
+ (0, import_time2.getRuntimeStorageDayKey)(signal.timestamp),
6050
+ signal.strategy
6051
+ );
6052
+ const entries = signalRefsByBucket.get(bucket) ?? [];
6053
+ entries.push({
6054
+ field: signal.signalId,
6055
+ data: toRuntimeSignalBucketRef(signal)
6056
+ });
6057
+ signalRefsByBucket.set(bucket, entries);
6058
+ }
6059
+ await Promise.all([
6060
+ flushRuntimeSignalEvaluations(pending),
6061
+ ...pendingSignals.map(
6062
+ ({ signal }) => (0, import_redis3.setData)(
6063
+ import_redis3.redisKeys.storeSignal(signal.symbol, signal.signalId),
6064
+ toStoredRuntimeSignal(signal),
6065
+ { expire: runtimeSignalRetentionTtl }
6066
+ )
6067
+ ),
6068
+ ...[...signalRefsByBucket].map(
6069
+ ([bucket, entries]) => (0, import_redis3.setHashJsonFields)(bucket, entries, {
6070
+ expire: runtimeSignalRetentionTtl
6071
+ })
6072
+ )
6073
+ ]);
5938
6074
  }
5939
6075
  };
5940
6076
  };
@@ -6167,7 +6303,7 @@ var runSignalsDaemon = async ({
6167
6303
  // src/lib/signals/klineFeed.ts
6168
6304
  var import_connectors5 = require("@tradejs/connectors");
6169
6305
  var import_logger3 = require("@tradejs/infra/logger");
6170
- var import_timescale5 = require("@tradejs/infra/timescale");
6306
+ var import_timescale6 = require("@tradejs/infra/timescale");
6171
6307
 
6172
6308
  // src/lib/marketData/klineEvents.ts
6173
6309
  var import_ioredis = __toESM(require("ioredis"));
@@ -6187,9 +6323,9 @@ var createSignalsKlineFeed = async ({
6187
6323
  publish = publishMarketKlineEvent,
6188
6324
  persist = async (rows) => {
6189
6325
  const intervalMinutes = Number(interval2);
6190
- await (0, import_timescale5.upsertCandles)(
6326
+ await (0, import_timescale6.upsertCandles)(
6191
6327
  rows.flatMap(
6192
- ({ symbol, candle }) => (0, import_timescale5.toRows)("bybit", symbol, intervalMinutes, [candle])
6328
+ ({ symbol, candle }) => (0, import_timescale6.toRows)("bybit", symbol, intervalMinutes, [candle])
6193
6329
  )
6194
6330
  );
6195
6331
  },
@@ -6455,7 +6591,7 @@ var createSignalsSession = async (lifecycle, scope = {}) => {
6455
6591
  lifecycle
6456
6592
  };
6457
6593
  };
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) => {
6594
+ 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
6595
  const strategySignals = [];
6460
6596
  const loadCached = (cachedSymbol) => connector.kline({
6461
6597
  symbol: cachedSymbol,
@@ -6648,26 +6784,7 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
6648
6784
  );
6649
6785
  }
6650
6786
  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
- );
6787
+ saveRuntimeSignal?.(flags.user, signal);
6671
6788
  await saveRuntimeSignalEvaluation?.({
6672
6789
  evaluationId: buildRuntimeSignalEvaluationId({
6673
6790
  strategyName,
@@ -6938,7 +7055,7 @@ var signals = async (options = {}) => {
6938
7055
  return;
6939
7056
  }
6940
7057
  const strategyStats = createStrategySkipStats(runtimeStrategies);
6941
- const bar = new import_progress3.default(
7058
+ const bar = new import_progress4.default(
6942
7059
  ":current/:total [:bar][:percent] :found :eta(s) :symbol",
6943
7060
  {
6944
7061
  total: tickers.length,
@@ -6973,7 +7090,8 @@ var signals = async (options = {}) => {
6973
7090
  deployment?.id,
6974
7091
  instrumentsBySymbol.get(symbol),
6975
7092
  sessionUniverse,
6976
- runtimeSignalEvaluations.save
7093
+ runtimeSignalEvaluations.save,
7094
+ runtimeSignalEvaluations.saveSignal
6977
7095
  );
6978
7096
  if (strategySignals.length > 0) {
6979
7097
  signals2.push(...strategySignals);