@tradejs/cli 2.0.6 → 2.0.7

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.
@@ -1650,9 +1650,9 @@ var import_chalk7 = __toESM(require("chalk"));
1650
1650
  var import_connectors6 = require("@tradejs/node/connectors");
1651
1651
  var import_cli2 = require("@tradejs/node/cli");
1652
1652
  var import_async3 = require("@tradejs/core/async");
1653
- var import_strategies2 = require("@tradejs/core/strategies");
1653
+ var import_strategies3 = require("@tradejs/core/strategies");
1654
1654
  var import_constants3 = require("@tradejs/core/constants");
1655
- var import_strategies3 = require("@tradejs/node/strategies");
1655
+ var import_strategies4 = require("@tradejs/node/strategies");
1656
1656
  var import_time3 = require("@tradejs/core/time");
1657
1657
  var import_logger4 = require("@tradejs/infra/logger");
1658
1658
  var import_redis5 = require("@tradejs/infra/redis");
@@ -1664,6 +1664,7 @@ var import_time = require("@tradejs/core/time");
1664
1664
  var SECONDS_PER_DAY = 86400;
1665
1665
  var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
1666
1666
  var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants.TTL_3D;
1667
+ var RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS = import_constants.TTL_1M;
1667
1668
  var parsePositiveNumber = (value) => {
1668
1669
  if (value == null || value === "") {
1669
1670
  return null;
@@ -1825,6 +1826,7 @@ var import_progress = __toESM(require("progress"));
1825
1826
  var import_chalk = __toESM(require("chalk"));
1826
1827
  var import_connectors = require("@tradejs/connectors");
1827
1828
  var import_connectors2 = require("@tradejs/node/connectors");
1829
+ var import_strategies = require("@tradejs/node/strategies");
1828
1830
  var import_timescale = require("@tradejs/infra/timescale");
1829
1831
 
1830
1832
  // src/lib/binanceMarketData.ts
@@ -1834,14 +1836,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
1834
1836
  "15m": 9e5,
1835
1837
  "1h": 36e5
1836
1838
  };
1837
- var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
1838
- "USDCUSDT",
1839
- "FDUSDUSDT",
1840
- "TUSDUSDT",
1841
- "BUSDUSDT",
1842
- "USDPUSDT",
1843
- "DAIUSDT"
1844
- ]);
1845
1839
  var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
1846
1840
  var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
1847
1841
  var standardDeviation = (values) => {
@@ -1885,9 +1879,6 @@ var classifyBtcAltRegime = ({
1885
1879
  if (btcVsAltReturn24h < -5e-3) return "alt_lead";
1886
1880
  return "neutral";
1887
1881
  };
1888
- var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
1889
- (ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
1890
- ).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
1891
1882
  var aggregateAggTradesToRows = ({
1892
1883
  symbol,
1893
1884
  interval: interval2,
@@ -2369,10 +2360,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2369
2360
  process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
2370
2361
  2
2371
2362
  );
2372
- const breadthLimit = asInt(
2373
- process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
2374
- 30
2375
- );
2376
2363
  const breadthChunkDays = asFloat(
2377
2364
  process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
2378
2365
  30
@@ -2495,88 +2482,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2495
2482
  }
2496
2483
  }
2497
2484
  if (includeBreadth) {
2498
- const tickers = await connector.getTickers();
2499
- const breadthSymbols = selectBreadthUniverseFromTickers(
2500
- tickers,
2501
- breadthLimit
2502
- );
2503
- const universe = `binance_top${breadthSymbols.length}_usdt`;
2504
- const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
2505
- universe,
2506
- interval: interval2,
2507
- startMs: breadthStartMs,
2508
- endMs
2509
- });
2510
- const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
2511
- if (!hasCoverage({
2512
- coverage,
2485
+ const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
2486
+ const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
2487
+ const connectorInterval = marketIntervalToConnectorInterval(interval2);
2488
+ const chunks = buildBreadthBackfillChunks({
2513
2489
  startMs: breadthStartMs,
2514
2490
  endMs,
2515
- intervalMs: intervalMs2
2516
- }) || !hasBtcAltMetrics) {
2517
- const connectorInterval = marketIntervalToConnectorInterval(interval2);
2518
- const chunks = buildBreadthBackfillChunks({
2519
- startMs: breadthStartMs,
2520
- endMs,
2521
- intervalMs: intervalMs2,
2522
- chunkDays: breadthChunkDays
2523
- });
2524
- const missingChunks = filterMissingBreadthBackfillChunks({
2525
- chunks,
2526
- coverage,
2527
- intervalMs: intervalMs2
2528
- });
2529
- const bar = new import_progress.default(
2530
- "breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
2531
- {
2532
- total: Math.max(1, missingChunks.length * breadthSymbols.length),
2533
- width: 24
2534
- }
2535
- );
2536
- let candlesRead = 0;
2537
- let skippedBreadthChunks = chunks.length - missingChunks.length;
2538
- if (!missingChunks.length) {
2539
- bar.tick(1, {
2540
- candles: 0,
2541
- skip: skippedBreadthChunks,
2542
- chunk: "cached",
2543
- symbol: universe
2491
+ intervalMs: intervalMs2,
2492
+ chunkDays: breadthChunkDays
2493
+ });
2494
+ const plans = await Promise.all(
2495
+ breadthUniverses.map(async (definition) => {
2496
+ const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
2497
+ universe: definition.universe,
2498
+ interval: interval2,
2499
+ startMs: breadthStartMs,
2500
+ endMs
2544
2501
  });
2545
- }
2546
- for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
2547
- const chunk = missingChunks[chunkIndex];
2548
- const originalChunkIndex = chunks.findIndex(
2549
- (item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
2550
- );
2551
- const candlesBySymbol = {};
2552
- for (const symbol of breadthSymbols) {
2553
- const candles = await connector.kline({
2554
- symbol,
2555
- interval: connectorInterval,
2556
- start: chunk.fetchStartMs,
2557
- end: chunk.endMs,
2558
- silent: true
2559
- });
2560
- candlesBySymbol[symbol] = candles;
2561
- candlesRead += candles.length;
2562
- bar.tick(1, {
2563
- candles: candlesRead,
2564
- skip: skippedBreadthChunks,
2565
- chunk: `${originalChunkIndex + 1}/${chunks.length}`,
2566
- symbol
2567
- });
2502
+ const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
2503
+ const missingChunks2 = !hasCoverage({
2504
+ coverage,
2505
+ startMs: breadthStartMs,
2506
+ endMs,
2507
+ intervalMs: intervalMs2
2508
+ }) || !hasBtcAltMetrics ? !hasBtcAltMetrics ? chunks : filterMissingBreadthBackfillChunks({
2509
+ chunks,
2510
+ coverage,
2511
+ intervalMs: intervalMs2
2512
+ }) : [];
2513
+ if (!missingChunks2.length) {
2514
+ console.log(
2515
+ import_chalk.default.gray(`breadth cached: universe=${definition.universe}`)
2516
+ );
2568
2517
  }
2569
- const btcCandles = await connector.kline({
2570
- symbol: "BTCUSDT",
2518
+ return {
2519
+ definition,
2520
+ missingChunkKeys: new Set(
2521
+ missingChunks2.map((chunk) => `${chunk.startMs}:${chunk.endMs}`)
2522
+ )
2523
+ };
2524
+ })
2525
+ );
2526
+ const missingChunks = chunks.filter((chunk) => {
2527
+ const key = `${chunk.startMs}:${chunk.endMs}`;
2528
+ return plans.some((plan) => plan.missingChunkKeys.has(key));
2529
+ });
2530
+ const bar = new import_progress.default(
2531
+ "breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
2532
+ {
2533
+ total: Math.max(1, missingChunks.length * breadthSymbols.length),
2534
+ width: 24
2535
+ }
2536
+ );
2537
+ let candlesRead = 0;
2538
+ const skippedBreadthChunks = chunks.length - missingChunks.length;
2539
+ if (!missingChunks.length) {
2540
+ bar.tick(1, {
2541
+ candles: 0,
2542
+ skip: skippedBreadthChunks,
2543
+ chunk: "cached",
2544
+ symbol: "top5/top10/top30/top50/top100"
2545
+ });
2546
+ }
2547
+ for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
2548
+ const chunk = missingChunks[chunkIndex];
2549
+ const originalChunkIndex = chunks.findIndex(
2550
+ (item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
2551
+ );
2552
+ const candlesBySymbol = {};
2553
+ for (const symbol of breadthSymbols) {
2554
+ const candles = await connector.kline({
2555
+ symbol,
2571
2556
  interval: connectorInterval,
2572
2557
  start: chunk.fetchStartMs,
2573
2558
  end: chunk.endMs,
2574
2559
  silent: true
2575
2560
  });
2561
+ candlesBySymbol[symbol] = candles;
2562
+ candlesRead += candles.length;
2563
+ bar.tick(1, {
2564
+ candles: candlesRead,
2565
+ skip: skippedBreadthChunks,
2566
+ chunk: `${originalChunkIndex + 1}/${chunks.length}`,
2567
+ symbol
2568
+ });
2569
+ }
2570
+ const btcCandles = await connector.kline({
2571
+ symbol: "BTCUSDT",
2572
+ interval: connectorInterval,
2573
+ start: chunk.fetchStartMs,
2574
+ end: chunk.endMs,
2575
+ silent: true
2576
+ });
2577
+ const chunkKey = `${chunk.startMs}:${chunk.endMs}`;
2578
+ for (const plan of plans) {
2579
+ if (!plan.missingChunkKeys.has(chunkKey)) {
2580
+ continue;
2581
+ }
2582
+ const symbolSet = new Set(plan.definition.symbols);
2583
+ const universeCandles = Object.fromEntries(
2584
+ Object.entries(candlesBySymbol).filter(
2585
+ ([symbol]) => symbolSet.has(symbol)
2586
+ )
2587
+ );
2576
2588
  const rows = buildMarketBreadthRows({
2577
- universe,
2589
+ universe: plan.definition.universe,
2578
2590
  interval: interval2,
2579
- candlesBySymbol,
2591
+ candlesBySymbol: universeCandles,
2580
2592
  btcCandles
2581
2593
  }).filter((row) => {
2582
2594
  const ts = row.ts.getTime();
@@ -2585,8 +2597,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2585
2597
  await (0, import_timescale.upsertMarketBreadthRows)(rows);
2586
2598
  breadthRows += rows.length;
2587
2599
  }
2588
- } else {
2589
- console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
2590
2600
  }
2591
2601
  }
2592
2602
  console.log(
@@ -3811,27 +3821,27 @@ var createCoinMarketCapFetchStats = () => ({
3811
3821
  totalCredits: 0,
3812
3822
  byPath: /* @__PURE__ */ new Map()
3813
3823
  });
3814
- var getCoinMarketCapPathLabel = (path) => {
3815
- if (path.includes("global-metrics")) return "global";
3816
- if (path.includes("cryptocurrency/quotes")) return "reference";
3817
- if (path.includes("exchange/quotes")) return "exchangeLiquidity";
3818
- if (path.includes("fear-and-greed")) return "fearGreed";
3819
- if (path.includes("cmc100")) return "cmc100";
3820
- if (path.includes("cmc20")) return "cmc20";
3821
- return path;
3822
- };
3823
- var recordCoinMarketCapFetchStats = (stats, path, creditCount) => {
3824
+ var getCoinMarketCapPathLabel = (path2) => {
3825
+ if (path2.includes("global-metrics")) return "global";
3826
+ if (path2.includes("cryptocurrency/quotes")) return "reference";
3827
+ if (path2.includes("exchange/quotes")) return "exchangeLiquidity";
3828
+ if (path2.includes("fear-and-greed")) return "fearGreed";
3829
+ if (path2.includes("cmc100")) return "cmc100";
3830
+ if (path2.includes("cmc20")) return "cmc20";
3831
+ return path2;
3832
+ };
3833
+ var recordCoinMarketCapFetchStats = (stats, path2, creditCount) => {
3824
3834
  if (!stats) return;
3825
- const existing = stats.byPath.get(path) ?? { requests: 0, credits: 0 };
3835
+ const existing = stats.byPath.get(path2) ?? { requests: 0, credits: 0 };
3826
3836
  existing.requests += 1;
3827
3837
  existing.credits += creditCount ?? 0;
3828
3838
  stats.requests += 1;
3829
3839
  stats.totalCredits += creditCount ?? 0;
3830
- stats.byPath.set(path, existing);
3840
+ stats.byPath.set(path2, existing);
3831
3841
  };
3832
3842
  var formatCoinMarketCapFetchStats = (stats) => {
3833
3843
  const byPath = [...stats.byPath.entries()].map(
3834
- ([path, item]) => `${getCoinMarketCapPathLabel(path)}=${item.requests}req/${item.credits}cr`
3844
+ ([path2, item]) => `${getCoinMarketCapPathLabel(path2)}=${item.requests}req/${item.credits}cr`
3835
3845
  ).join(" ");
3836
3846
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
3837
3847
  };
@@ -4903,7 +4913,7 @@ var updateBtcReferenceHistory = async ({
4903
4913
  var import_logger = require("@tradejs/infra/logger");
4904
4914
  var import_redis2 = require("@tradejs/infra/redis");
4905
4915
  var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
4906
- var import_strategies = require("@tradejs/node/strategies");
4916
+ var import_strategies2 = require("@tradejs/node/strategies");
4907
4917
 
4908
4918
  // src/lib/runtimeRedis.ts
4909
4919
  var import_redis = require("@tradejs/infra/redis");
@@ -5003,7 +5013,7 @@ var loadRuntimeStrategies = async ({
5003
5013
  });
5004
5014
  const effectiveAccountId = resolvedAccount?.id ?? requestedAccountId;
5005
5015
  const [strategyCreator, strategyResults] = await Promise.all([
5006
- (0, import_strategies.getStrategyCreator)(strategyName, projectRoot2),
5016
+ (0, import_strategies2.getStrategyCreator)(strategyName, projectRoot2),
5007
5017
  (0, import_redis2.getData)(import_redis2.redisKeys.strategyResults(userName, strategyName), {})
5008
5018
  ]);
5009
5019
  if (!strategyCreator) {
@@ -5097,6 +5107,214 @@ var logStrategySkipStats = (runtimeStrategies, strategyStats) => {
5097
5107
 
5098
5108
  // src/lib/signals/evaluations.ts
5099
5109
  var import_redis3 = require("@tradejs/infra/redis");
5110
+
5111
+ // src/lib/runtimeLineage.ts
5112
+ var import_node_child_process = require("child_process");
5113
+ var import_node_crypto = require("crypto");
5114
+ var import_promises = __toESM(require("fs/promises"));
5115
+ var import_node_path = __toESM(require("path"));
5116
+ var RUNTIME_CONTEXT_ENV_KEYS = [
5117
+ "AI_MODE",
5118
+ "MIN_AI_QUALITY",
5119
+ "INTERVAL",
5120
+ "DERIVATIVES_CONTEXT_ENABLED",
5121
+ "DERIVATIVES_CONTEXT_TARGET_ENABLED",
5122
+ "DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
5123
+ "DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
5124
+ "DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
5125
+ "COINMARKETCAP_CONTEXT_ENABLED",
5126
+ "COINMARKETCAP_CONTEXT_MAX_AGE_MS",
5127
+ "COINMARKETCAP_CONTEXT_EXCHANGE_LIQUIDITY_ENABLED",
5128
+ "COINMARKETCAP_CONTEXT_EXCHANGE_SLUGS",
5129
+ "COINMARKETCAP_CONTEXT_FEAR_GREED_ENABLED",
5130
+ "COINMARKETCAP_CONTEXT_HISTORICAL_ACCESS_MONTHS",
5131
+ "COINMARKETCAP_CONTEXT_BACKFILL_ENABLED",
5132
+ "COINMARKETCAP_CONTEXT_BACKFILL_MAX_DAYS",
5133
+ "COINMARKETCAP_CONTEXT_BACKFILL_WARMUP_DAYS"
5134
+ ];
5135
+ var RUNTIME_CONTEXT_DATA_MODEL = {
5136
+ derivativesSourceIntervals: "15m",
5137
+ derivativesDerivedIntervals: "1h",
5138
+ derivativesHourlyFallback: "stored-1h",
5139
+ derivativesDataModelVersion: 2,
5140
+ coinMarketCapAvailabilityModel: "closed-interval-all-daily",
5141
+ coinMarketCapDataModelVersion: 3,
5142
+ binanceBreadthUniverseModel: "fixed-versioned-top5-top10-top30-top50-top100",
5143
+ binanceBreadthDataModelVersion: 2
5144
+ };
5145
+ var normalizeForStableJson = (value) => {
5146
+ if (Array.isArray(value)) {
5147
+ return value.map(normalizeForStableJson);
5148
+ }
5149
+ if (value && typeof value === "object") {
5150
+ return Object.fromEntries(
5151
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalizeForStableJson(entry)])
5152
+ );
5153
+ }
5154
+ return value;
5155
+ };
5156
+ var fingerprintRuntimeValue = (value) => (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
5157
+ var gitLineageCache = /* @__PURE__ */ new Map();
5158
+ var gateFingerprintCache = /* @__PURE__ */ new Map();
5159
+ var binanceBreadthFingerprintCache = /* @__PURE__ */ new Map();
5160
+ var resolveGitLineage = (projectRoot2) => {
5161
+ const cached = gitLineageCache.get(projectRoot2);
5162
+ if (cached) {
5163
+ return cached;
5164
+ }
5165
+ const envSha = String(process.env.TRADEJS_GIT_SHA ?? "").trim();
5166
+ if (envSha && envSha !== "unknown") {
5167
+ const result = {
5168
+ gitSha: envSha,
5169
+ gitDirty: false
5170
+ };
5171
+ gitLineageCache.set(projectRoot2, result);
5172
+ return result;
5173
+ }
5174
+ try {
5175
+ const gitSha = (0, import_node_child_process.execFileSync)("git", ["rev-parse", "HEAD"], {
5176
+ cwd: projectRoot2,
5177
+ encoding: "utf8",
5178
+ stdio: ["ignore", "pipe", "ignore"]
5179
+ }).trim();
5180
+ const status = (0, import_node_child_process.execFileSync)(
5181
+ "git",
5182
+ ["status", "--porcelain", "--untracked-files=normal"],
5183
+ {
5184
+ cwd: projectRoot2,
5185
+ encoding: "utf8",
5186
+ stdio: ["ignore", "pipe", "ignore"]
5187
+ }
5188
+ ).trim();
5189
+ const result = {
5190
+ gitSha: gitSha || null,
5191
+ gitDirty: status.length > 0
5192
+ };
5193
+ gitLineageCache.set(projectRoot2, result);
5194
+ return result;
5195
+ } catch {
5196
+ const result = {
5197
+ gitSha: null,
5198
+ gitDirty: null
5199
+ };
5200
+ gitLineageCache.set(projectRoot2, result);
5201
+ return result;
5202
+ }
5203
+ };
5204
+ var readOptionalFile = async (filePath) => {
5205
+ try {
5206
+ return await import_promises.default.readFile(filePath);
5207
+ } catch {
5208
+ return null;
5209
+ }
5210
+ };
5211
+ var resolveBinanceBreadthFingerprint = async (projectRoot2) => {
5212
+ if (binanceBreadthFingerprintCache.has(projectRoot2)) {
5213
+ return binanceBreadthFingerprintCache.get(projectRoot2) ?? null;
5214
+ }
5215
+ const content = await readOptionalFile(
5216
+ import_node_path.default.join(
5217
+ projectRoot2,
5218
+ "packages/node/src/config/binanceBreadthUniverses.json"
5219
+ )
5220
+ );
5221
+ let result = null;
5222
+ if (content) {
5223
+ try {
5224
+ const parsed = JSON.parse(content.toString("utf8"));
5225
+ result = typeof parsed.fingerprint === "string" && parsed.fingerprint.trim() ? parsed.fingerprint.trim() : null;
5226
+ } catch {
5227
+ result = null;
5228
+ }
5229
+ }
5230
+ binanceBreadthFingerprintCache.set(projectRoot2, result);
5231
+ return result;
5232
+ };
5233
+ var resolveGateFingerprint = async ({
5234
+ projectRoot: projectRoot2,
5235
+ strategyName,
5236
+ gitSha
5237
+ }) => {
5238
+ const cacheKey = `${projectRoot2}:${strategyName}:${gitSha ?? "unknown"}`;
5239
+ const cached = gateFingerprintCache.get(cacheKey);
5240
+ if (cached) {
5241
+ return cached;
5242
+ }
5243
+ const relativeCandidates = [
5244
+ `packages/strategies/src/${strategyName}/adapters/ai.ts`,
5245
+ `packages/strategies/src/${strategyName}/guardrails.ts`,
5246
+ `packages/strategies/src/${strategyName}/pockets.ts`,
5247
+ `packages/strategies/src/${strategyName}/config.ts`,
5248
+ "packages/node/src/ai.ts"
5249
+ ];
5250
+ const optionalEntries = await Promise.all(
5251
+ relativeCandidates.map(async (relativePath) => {
5252
+ const content = await readOptionalFile(
5253
+ import_node_path.default.join(projectRoot2, relativePath)
5254
+ );
5255
+ return content == null ? null : { relativePath, content };
5256
+ })
5257
+ );
5258
+ const sourceEntries = [];
5259
+ for (const entry of optionalEntries) {
5260
+ if (entry != null) {
5261
+ sourceEntries.push(entry);
5262
+ }
5263
+ }
5264
+ const hash = (0, import_node_crypto.createHash)("sha256");
5265
+ hash.update(strategyName);
5266
+ if (sourceEntries.length) {
5267
+ for (const entry of sourceEntries) {
5268
+ hash.update(entry.relativePath);
5269
+ hash.update(entry.content);
5270
+ }
5271
+ } else {
5272
+ hash.update(gitSha ?? "unknown-git-sha");
5273
+ }
5274
+ const fingerprint = hash.digest("hex").slice(0, 16);
5275
+ gateFingerprintCache.set(cacheKey, fingerprint);
5276
+ return fingerprint;
5277
+ };
5278
+ var buildRuntimeLineage = async ({
5279
+ projectRoot: projectRoot2,
5280
+ strategyName,
5281
+ config,
5282
+ runContext,
5283
+ env = process.env
5284
+ }) => {
5285
+ const git = resolveGitLineage(projectRoot2);
5286
+ const context = {
5287
+ ...Object.fromEntries(
5288
+ RUNTIME_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
5289
+ ),
5290
+ ...RUNTIME_CONTEXT_DATA_MODEL,
5291
+ binanceBreadthUniverseFingerprint: await resolveBinanceBreadthFingerprint(projectRoot2),
5292
+ ...runContext
5293
+ };
5294
+ return {
5295
+ schemaVersion: 1,
5296
+ ...git,
5297
+ gateFingerprint: await resolveGateFingerprint({
5298
+ projectRoot: projectRoot2,
5299
+ strategyName,
5300
+ gitSha: git.gitSha
5301
+ }),
5302
+ configFingerprint: fingerprintRuntimeValue(config),
5303
+ contextFingerprint: fingerprintRuntimeValue(context)
5304
+ };
5305
+ };
5306
+ var runtimeLineageKey = (lineage) => [
5307
+ lineage.schemaVersion,
5308
+ lineage.gitSha ?? "unknown",
5309
+ lineage.gitDirty == null ? "unknown" : lineage.gitDirty ? "dirty" : "clean",
5310
+ lineage.gateFingerprint,
5311
+ lineage.configFingerprint,
5312
+ lineage.contextFingerprint
5313
+ ].join(":");
5314
+
5315
+ // src/lib/signals/evaluations.ts
5316
+ var runtimeLineageScopeCache = /* @__PURE__ */ new Map();
5317
+ var runtimeLineageScopeCacheDayKey = "";
5100
5318
  var buildRuntimeSignalEvaluationId = ({
5101
5319
  strategyName,
5102
5320
  symbol,
@@ -5105,7 +5323,46 @@ var buildRuntimeSignalEvaluationId = ({
5105
5323
  }) => runtimeConfigId && runtimeConfigId !== "config" ? [strategyName, runtimeConfigId, symbol, timestamp].join(":") : [strategyName, symbol, timestamp].join(":");
5106
5324
  var saveRuntimeSignalEvaluation = async (evaluation) => {
5107
5325
  const dayKey = (0, import_time.getRuntimeStorageDayKey)(evaluation.timestamp);
5326
+ if (runtimeLineageScopeCacheDayKey !== dayKey) {
5327
+ runtimeLineageScopeCache.clear();
5328
+ runtimeLineageScopeCacheDayKey = dayKey;
5329
+ }
5108
5330
  const runtimeSignalRetentionTtl = getRuntimeSignalRetentionTtlSeconds();
5331
+ if (evaluation.runtimeLineage) {
5332
+ const lineageField = [
5333
+ evaluation.strategy,
5334
+ evaluation.runtimeConfigId ?? "config",
5335
+ evaluation.symbol,
5336
+ runtimeLineageKey(evaluation.runtimeLineage)
5337
+ ].join(":");
5338
+ const lineageBucket = import_redis3.redisKeys.runtimeLineageScopeBucket(
5339
+ evaluation.userName,
5340
+ dayKey
5341
+ );
5342
+ const cacheKey = `${lineageBucket}:${lineageField}`;
5343
+ const existing = runtimeLineageScopeCache.get(cacheKey) ?? await (0, import_redis3.getHashJsonField)(
5344
+ lineageBucket,
5345
+ lineageField
5346
+ );
5347
+ const scope = {
5348
+ strategy: evaluation.strategy,
5349
+ symbol: evaluation.symbol,
5350
+ runtimeConfigId: evaluation.runtimeConfigId,
5351
+ lineage: evaluation.runtimeLineage,
5352
+ firstTimestamp: Math.min(
5353
+ existing?.firstTimestamp ?? evaluation.timestamp,
5354
+ evaluation.timestamp
5355
+ ),
5356
+ lastTimestamp: Math.max(
5357
+ existing?.lastTimestamp ?? evaluation.timestamp,
5358
+ evaluation.timestamp
5359
+ )
5360
+ };
5361
+ runtimeLineageScopeCache.set(cacheKey, scope);
5362
+ await (0, import_redis3.setHashJsonField)(lineageBucket, lineageField, scope, {
5363
+ expire: RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS
5364
+ });
5365
+ }
5109
5366
  if (shouldStoreDetailedRuntimeSignalEvaluation(evaluation)) {
5110
5367
  await (0, import_redis3.setHashJsonField)(
5111
5368
  import_redis3.redisKeys.runtimeSignalEvaluationBucket(
@@ -5565,7 +5822,7 @@ var resolveSignalsKlineWsWaitMs = () => resolveNonNegativeInteger(process.env.SI
5565
5822
  var createDefaultSignalsLifecycle = (runtimeInterval = interval) => createSignalsStrategyLifecycle({
5566
5823
  intervalMs: Number(runtimeInterval) * 6e4,
5567
5824
  maxLiveBars: resolveSignalsDaemonMaxLiveBars(runtimeInterval),
5568
- releaseState: import_strategies2.releaseStrategyReplayCache
5825
+ releaseState: import_strategies3.releaseStrategyReplayCache
5569
5826
  });
5570
5827
  var timeOperation2 = (label, operation) => timeOperation(
5571
5828
  label,
@@ -5710,6 +5967,20 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5710
5967
  interval: interval2,
5711
5968
  makeOrders: flags.makeOrders
5712
5969
  });
5970
+ const runtimeLineage = await buildRuntimeLineage({
5971
+ projectRoot,
5972
+ strategyName,
5973
+ config: {
5974
+ configId,
5975
+ strategyConfig,
5976
+ symbolResultConfig: strategyResults?.[symbol]?.config ?? null
5977
+ },
5978
+ runContext: {
5979
+ connectorName: connectorName.toLowerCase(),
5980
+ interval: String(interval2),
5981
+ universe
5982
+ }
5983
+ });
5713
5984
  const lifecycleKey = buildSignalsStrategyLifecycleKey({
5714
5985
  connectorName,
5715
5986
  universe: runtimeScopeUniverse,
@@ -5791,6 +6062,7 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5791
6062
  userName: flags.user,
5792
6063
  strategy: strategyName,
5793
6064
  runtimeConfigId: configId,
6065
+ runtimeLineage,
5794
6066
  symbol,
5795
6067
  interval: interval2,
5796
6068
  universe,
@@ -5809,10 +6081,11 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5809
6081
  stats.signals += 1;
5810
6082
  }
5811
6083
  signal.runtimeConfigId = configId;
6084
+ signal.runtimeLineage = runtimeLineage;
5812
6085
  if (configId && configId !== "config" && !signal.signalId.endsWith(`:${configId}`)) {
5813
6086
  signal.signalId = `${signal.signalId}:${configId}`;
5814
6087
  }
5815
- await (0, import_strategies3.enrichSignalWithBinanceMarketContext)({
6088
+ await (0, import_strategies4.enrichSignalWithBinanceMarketContext)({
5816
6089
  signal,
5817
6090
  env: "CRON"
5818
6091
  });
@@ -5855,6 +6128,7 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5855
6128
  userName: flags.user,
5856
6129
  strategy: strategyName,
5857
6130
  runtimeConfigId: configId,
6131
+ runtimeLineage,
5858
6132
  symbol,
5859
6133
  interval: interval2,
5860
6134
  universe,