@tradejs/cli 2.0.6 → 2.0.8

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