@tradejs/cli 2.0.5 → 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.
@@ -34,9 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  ));
35
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
36
 
37
- // ../../node_modules/debug/node_modules/ms/index.js
37
+ // ../../node_modules/ms/index.js
38
38
  var require_ms = __commonJS({
39
- "../../node_modules/debug/node_modules/ms/index.js"(exports2, module2) {
39
+ "../../node_modules/ms/index.js"(exports2, module2) {
40
40
  "use strict";
41
41
  var s = 1e3;
42
42
  var m = s * 60;
@@ -151,9 +151,9 @@ var require_ms = __commonJS({
151
151
  }
152
152
  });
153
153
 
154
- // ../../node_modules/debug/src/common.js
154
+ // ../../node_modules/list-it/node_modules/debug/src/common.js
155
155
  var require_common = __commonJS({
156
- "../../node_modules/debug/src/common.js"(exports2, module2) {
156
+ "../../node_modules/list-it/node_modules/debug/src/common.js"(exports2, module2) {
157
157
  "use strict";
158
158
  function setup(env) {
159
159
  createDebug.debug = createDebug;
@@ -255,50 +255,64 @@ var require_common = __commonJS({
255
255
  createDebug.namespaces = namespaces;
256
256
  createDebug.names = [];
257
257
  createDebug.skips = [];
258
- let i;
259
- const split = (typeof namespaces === "string" ? namespaces : "").split(/[\s,]+/);
260
- const len = split.length;
261
- for (i = 0; i < len; i++) {
262
- if (!split[i]) {
263
- continue;
258
+ const split = (typeof namespaces === "string" ? namespaces : "").trim().replace(/\s+/g, ",").split(",").filter(Boolean);
259
+ for (const ns of split) {
260
+ if (ns[0] === "-") {
261
+ createDebug.skips.push(ns.slice(1));
262
+ } else {
263
+ createDebug.names.push(ns);
264
264
  }
265
- namespaces = split[i].replace(/\*/g, ".*?");
266
- if (namespaces[0] === "-") {
267
- createDebug.skips.push(new RegExp("^" + namespaces.slice(1) + "$"));
265
+ }
266
+ }
267
+ function matchesTemplate(search, template) {
268
+ let searchIndex = 0;
269
+ let templateIndex = 0;
270
+ let starIndex = -1;
271
+ let matchIndex = 0;
272
+ while (searchIndex < search.length) {
273
+ if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
274
+ if (template[templateIndex] === "*") {
275
+ starIndex = templateIndex;
276
+ matchIndex = searchIndex;
277
+ templateIndex++;
278
+ } else {
279
+ searchIndex++;
280
+ templateIndex++;
281
+ }
282
+ } else if (starIndex !== -1) {
283
+ templateIndex = starIndex + 1;
284
+ matchIndex++;
285
+ searchIndex = matchIndex;
268
286
  } else {
269
- createDebug.names.push(new RegExp("^" + namespaces + "$"));
287
+ return false;
270
288
  }
271
289
  }
290
+ while (templateIndex < template.length && template[templateIndex] === "*") {
291
+ templateIndex++;
292
+ }
293
+ return templateIndex === template.length;
272
294
  }
273
295
  function disable() {
274
296
  const namespaces = [
275
- ...createDebug.names.map(toNamespace),
276
- ...createDebug.skips.map(toNamespace).map((namespace) => "-" + namespace)
297
+ ...createDebug.names,
298
+ ...createDebug.skips.map((namespace) => "-" + namespace)
277
299
  ].join(",");
278
300
  createDebug.enable("");
279
301
  return namespaces;
280
302
  }
281
303
  function enabled(name) {
282
- if (name[name.length - 1] === "*") {
283
- return true;
284
- }
285
- let i;
286
- let len;
287
- for (i = 0, len = createDebug.skips.length; i < len; i++) {
288
- if (createDebug.skips[i].test(name)) {
304
+ for (const skip of createDebug.skips) {
305
+ if (matchesTemplate(name, skip)) {
289
306
  return false;
290
307
  }
291
308
  }
292
- for (i = 0, len = createDebug.names.length; i < len; i++) {
293
- if (createDebug.names[i].test(name)) {
309
+ for (const ns of createDebug.names) {
310
+ if (matchesTemplate(name, ns)) {
294
311
  return true;
295
312
  }
296
313
  }
297
314
  return false;
298
315
  }
299
- function toNamespace(regexp) {
300
- return regexp.toString().substring(2, regexp.toString().length - 2).replace(/\.\*\?$/, "*");
301
- }
302
316
  function coerce(val) {
303
317
  if (val instanceof Error) {
304
318
  return val.stack || val.message;
@@ -315,9 +329,9 @@ var require_common = __commonJS({
315
329
  }
316
330
  });
317
331
 
318
- // ../../node_modules/debug/src/browser.js
332
+ // ../../node_modules/list-it/node_modules/debug/src/browser.js
319
333
  var require_browser = __commonJS({
320
- "../../node_modules/debug/src/browser.js"(exports2, module2) {
334
+ "../../node_modules/list-it/node_modules/debug/src/browser.js"(exports2, module2) {
321
335
  "use strict";
322
336
  exports2.formatArgs = formatArgs;
323
337
  exports2.save = save;
@@ -418,10 +432,11 @@ var require_browser = __commonJS({
418
432
  if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
419
433
  return false;
420
434
  }
435
+ let m;
421
436
  return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773
422
437
  typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31?
423
438
  // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
424
- typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
439
+ typeof navigator !== "undefined" && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker
425
440
  typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/);
426
441
  }
427
442
  function formatArgs(args2) {
@@ -459,7 +474,7 @@ var require_browser = __commonJS({
459
474
  function load() {
460
475
  let r;
461
476
  try {
462
- r = exports2.storage.getItem("debug");
477
+ r = exports2.storage.getItem("debug") || exports2.storage.getItem("DEBUG");
463
478
  } catch (error) {
464
479
  }
465
480
  if (!r && typeof process !== "undefined" && "env" in process) {
@@ -600,9 +615,9 @@ var require_supports_color = __commonJS({
600
615
  }
601
616
  });
602
617
 
603
- // ../../node_modules/debug/src/node.js
618
+ // ../../node_modules/list-it/node_modules/debug/src/node.js
604
619
  var require_node = __commonJS({
605
- "../../node_modules/debug/src/node.js"(exports2, module2) {
620
+ "../../node_modules/list-it/node_modules/debug/src/node.js"(exports2, module2) {
606
621
  "use strict";
607
622
  var tty = require("tty");
608
623
  var util = require("util");
@@ -743,7 +758,7 @@ var require_node = __commonJS({
743
758
  return (/* @__PURE__ */ new Date()).toISOString() + " ";
744
759
  }
745
760
  function log(...args2) {
746
- return process.stderr.write(util.format(...args2) + "\n");
761
+ return process.stderr.write(util.formatWithOptions(exports2.inspectOpts, ...args2) + "\n");
747
762
  }
748
763
  function save(namespaces) {
749
764
  if (namespaces) {
@@ -775,9 +790,9 @@ var require_node = __commonJS({
775
790
  }
776
791
  });
777
792
 
778
- // ../../node_modules/debug/src/index.js
793
+ // ../../node_modules/list-it/node_modules/debug/src/index.js
779
794
  var require_src = __commonJS({
780
- "../../node_modules/debug/src/index.js"(exports2, module2) {
795
+ "../../node_modules/list-it/node_modules/debug/src/index.js"(exports2, module2) {
781
796
  "use strict";
782
797
  if (typeof process === "undefined" || process.type === "renderer" || process.browser === true || process.__nwjs) {
783
798
  module2.exports = require_browser();
@@ -1633,9 +1648,9 @@ var import_chalk7 = __toESM(require("chalk"));
1633
1648
  var import_connectors6 = require("@tradejs/node/connectors");
1634
1649
  var import_cli2 = require("@tradejs/node/cli");
1635
1650
  var import_async3 = require("@tradejs/core/async");
1636
- var import_strategies2 = require("@tradejs/core/strategies");
1651
+ var import_strategies3 = require("@tradejs/core/strategies");
1637
1652
  var import_constants3 = require("@tradejs/core/constants");
1638
- var import_strategies3 = require("@tradejs/node/strategies");
1653
+ var import_strategies4 = require("@tradejs/node/strategies");
1639
1654
  var import_time3 = require("@tradejs/core/time");
1640
1655
  var import_logger4 = require("@tradejs/infra/logger");
1641
1656
  var import_redis5 = require("@tradejs/infra/redis");
@@ -1647,6 +1662,7 @@ var import_time = require("@tradejs/core/time");
1647
1662
  var SECONDS_PER_DAY = 86400;
1648
1663
  var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
1649
1664
  var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants.TTL_3D;
1665
+ var RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS = import_constants.TTL_1M;
1650
1666
  var parsePositiveNumber = (value) => {
1651
1667
  if (value == null || value === "") {
1652
1668
  return null;
@@ -1808,6 +1824,7 @@ var import_progress = __toESM(require("progress"));
1808
1824
  var import_chalk = __toESM(require("chalk"));
1809
1825
  var import_connectors = require("@tradejs/connectors");
1810
1826
  var import_connectors2 = require("@tradejs/node/connectors");
1827
+ var import_strategies = require("@tradejs/node/strategies");
1811
1828
  var import_timescale = require("@tradejs/infra/timescale");
1812
1829
 
1813
1830
  // src/lib/binanceMarketData.ts
@@ -1817,14 +1834,6 @@ var MARKET_FEATURE_INTERVAL_MS = {
1817
1834
  "15m": 9e5,
1818
1835
  "1h": 36e5
1819
1836
  };
1820
- var STABLE_QUOTE_SYMBOLS = /* @__PURE__ */ new Set([
1821
- "USDCUSDT",
1822
- "FDUSDUSDT",
1823
- "TUSDUSDT",
1824
- "BUSDUSDT",
1825
- "USDPUSDT",
1826
- "DAIUSDT"
1827
- ]);
1828
1837
  var safeDivide = (num, den) => Number.isFinite(num) && Number.isFinite(den) && den !== 0 ? num / den : null;
1829
1838
  var mean = (values) => values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
1830
1839
  var standardDeviation = (values) => {
@@ -1868,9 +1877,6 @@ var classifyBtcAltRegime = ({
1868
1877
  if (btcVsAltReturn24h < -5e-3) return "alt_lead";
1869
1878
  return "neutral";
1870
1879
  };
1871
- var selectBreadthUniverseFromTickers = (tickers, limit) => tickers.filter(
1872
- (ticker) => ticker.symbol.endsWith("USDT") && ticker.symbol !== "BTCUSDT" && !STABLE_QUOTE_SYMBOLS.has(ticker.symbol) && Number.isFinite(ticker.turnover24h) && ticker.turnover24h > 0
1873
- ).sort((a, b) => b.turnover24h - a.turnover24h).slice(0, Math.max(0, limit)).map((ticker) => ticker.symbol);
1874
1880
  var aggregateAggTradesToRows = ({
1875
1881
  symbol,
1876
1882
  interval: interval2,
@@ -2352,10 +2358,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2352
2358
  process.env.BINANCE_MARKET_CONTEXT_TRADE_FLOW_SYMBOL_LIMIT,
2353
2359
  2
2354
2360
  );
2355
- const breadthLimit = asInt(
2356
- process.env.BINANCE_MARKET_CONTEXT_BREADTH_LIMIT,
2357
- 30
2358
- );
2359
2361
  const breadthChunkDays = asFloat(
2360
2362
  process.env.BINANCE_MARKET_CONTEXT_BREADTH_CHUNK_DAYS,
2361
2363
  30
@@ -2478,88 +2480,113 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2478
2480
  }
2479
2481
  }
2480
2482
  if (includeBreadth) {
2481
- const tickers = await connector.getTickers();
2482
- const breadthSymbols = selectBreadthUniverseFromTickers(
2483
- tickers,
2484
- breadthLimit
2485
- );
2486
- const universe = `binance_top${breadthSymbols.length}_usdt`;
2487
- const coverage = await (0, import_timescale.getMarketBreadthCoverage)({
2488
- universe,
2489
- interval: interval2,
2490
- startMs: breadthStartMs,
2491
- endMs
2492
- });
2493
- const hasBtcAltMetrics = (coverage?.btcAltMetricsRows ?? 0) >= Math.floor(Math.max(1, coverage?.rows ?? 0) * 0.9);
2494
- if (!hasCoverage({
2495
- coverage,
2483
+ const breadthUniverses = (0, import_strategies.getBinanceBreadthUniverses)();
2484
+ const breadthSymbols = breadthUniverses.at(-1)?.symbols ?? [];
2485
+ const connectorInterval = marketIntervalToConnectorInterval(interval2);
2486
+ const chunks = buildBreadthBackfillChunks({
2496
2487
  startMs: breadthStartMs,
2497
2488
  endMs,
2498
- intervalMs: intervalMs2
2499
- }) || !hasBtcAltMetrics) {
2500
- const connectorInterval = marketIntervalToConnectorInterval(interval2);
2501
- const chunks = buildBreadthBackfillChunks({
2502
- startMs: breadthStartMs,
2503
- endMs,
2504
- intervalMs: intervalMs2,
2505
- chunkDays: breadthChunkDays
2506
- });
2507
- const missingChunks = filterMissingBreadthBackfillChunks({
2508
- chunks,
2509
- coverage,
2510
- intervalMs: intervalMs2
2511
- });
2512
- const bar = new import_progress.default(
2513
- "breadth :current/:total [:bar][:percent] :etas(s) candles=:candles skip=:skip chunk=:chunk :symbol",
2514
- {
2515
- total: Math.max(1, missingChunks.length * breadthSymbols.length),
2516
- width: 24
2517
- }
2518
- );
2519
- let candlesRead = 0;
2520
- let skippedBreadthChunks = chunks.length - missingChunks.length;
2521
- if (!missingChunks.length) {
2522
- bar.tick(1, {
2523
- candles: 0,
2524
- skip: skippedBreadthChunks,
2525
- chunk: "cached",
2526
- 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
2527
2499
  });
2528
- }
2529
- for (let chunkIndex = 0; chunkIndex < missingChunks.length; chunkIndex += 1) {
2530
- const chunk = missingChunks[chunkIndex];
2531
- const originalChunkIndex = chunks.findIndex(
2532
- (item) => item.startMs === chunk.startMs && item.endMs === chunk.endMs
2533
- );
2534
- const candlesBySymbol = {};
2535
- for (const symbol of breadthSymbols) {
2536
- const candles = await connector.kline({
2537
- symbol,
2538
- interval: connectorInterval,
2539
- start: chunk.fetchStartMs,
2540
- end: chunk.endMs,
2541
- silent: true
2542
- });
2543
- candlesBySymbol[symbol] = candles;
2544
- candlesRead += candles.length;
2545
- bar.tick(1, {
2546
- candles: candlesRead,
2547
- skip: skippedBreadthChunks,
2548
- chunk: `${originalChunkIndex + 1}/${chunks.length}`,
2549
- symbol
2550
- });
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
+ );
2551
2515
  }
2552
- const btcCandles = await connector.kline({
2553
- 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,
2554
2554
  interval: connectorInterval,
2555
2555
  start: chunk.fetchStartMs,
2556
2556
  end: chunk.endMs,
2557
2557
  silent: true
2558
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
+ );
2559
2586
  const rows = buildMarketBreadthRows({
2560
- universe,
2587
+ universe: plan.definition.universe,
2561
2588
  interval: interval2,
2562
- candlesBySymbol,
2589
+ candlesBySymbol: universeCandles,
2563
2590
  btcCandles
2564
2591
  }).filter((row) => {
2565
2592
  const ts = row.ts.getTime();
@@ -2568,8 +2595,6 @@ var backfillBinanceMarketContext = async (params, enabled) => {
2568
2595
  await (0, import_timescale.upsertMarketBreadthRows)(rows);
2569
2596
  breadthRows += rows.length;
2570
2597
  }
2571
- } else {
2572
- console.log(import_chalk.default.gray(`breadth cached: universe=${universe}`));
2573
2598
  }
2574
2599
  }
2575
2600
  console.log(
@@ -3794,27 +3819,27 @@ var createCoinMarketCapFetchStats = () => ({
3794
3819
  totalCredits: 0,
3795
3820
  byPath: /* @__PURE__ */ new Map()
3796
3821
  });
3797
- var getCoinMarketCapPathLabel = (path) => {
3798
- if (path.includes("global-metrics")) return "global";
3799
- if (path.includes("cryptocurrency/quotes")) return "reference";
3800
- if (path.includes("exchange/quotes")) return "exchangeLiquidity";
3801
- if (path.includes("fear-and-greed")) return "fearGreed";
3802
- if (path.includes("cmc100")) return "cmc100";
3803
- if (path.includes("cmc20")) return "cmc20";
3804
- return path;
3805
- };
3806
- 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) => {
3807
3832
  if (!stats) return;
3808
- const existing = stats.byPath.get(path) ?? { requests: 0, credits: 0 };
3833
+ const existing = stats.byPath.get(path2) ?? { requests: 0, credits: 0 };
3809
3834
  existing.requests += 1;
3810
3835
  existing.credits += creditCount ?? 0;
3811
3836
  stats.requests += 1;
3812
3837
  stats.totalCredits += creditCount ?? 0;
3813
- stats.byPath.set(path, existing);
3838
+ stats.byPath.set(path2, existing);
3814
3839
  };
3815
3840
  var formatCoinMarketCapFetchStats = (stats) => {
3816
3841
  const byPath = [...stats.byPath.entries()].map(
3817
- ([path, item]) => `${getCoinMarketCapPathLabel(path)}=${item.requests}req/${item.credits}cr`
3842
+ ([path2, item]) => `${getCoinMarketCapPathLabel(path2)}=${item.requests}req/${item.credits}cr`
3818
3843
  ).join(" ");
3819
3844
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
3820
3845
  };
@@ -4886,7 +4911,7 @@ var updateBtcReferenceHistory = async ({
4886
4911
  var import_logger = require("@tradejs/infra/logger");
4887
4912
  var import_redis2 = require("@tradejs/infra/redis");
4888
4913
  var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
4889
- var import_strategies = require("@tradejs/node/strategies");
4914
+ var import_strategies2 = require("@tradejs/node/strategies");
4890
4915
 
4891
4916
  // src/lib/runtimeRedis.ts
4892
4917
  var import_redis = require("@tradejs/infra/redis");
@@ -4986,7 +5011,7 @@ var loadRuntimeStrategies = async ({
4986
5011
  });
4987
5012
  const effectiveAccountId = resolvedAccount?.id ?? requestedAccountId;
4988
5013
  const [strategyCreator, strategyResults] = await Promise.all([
4989
- (0, import_strategies.getStrategyCreator)(strategyName, projectRoot2),
5014
+ (0, import_strategies2.getStrategyCreator)(strategyName, projectRoot2),
4990
5015
  (0, import_redis2.getData)(import_redis2.redisKeys.strategyResults(userName, strategyName), {})
4991
5016
  ]);
4992
5017
  if (!strategyCreator) {
@@ -5080,6 +5105,214 @@ var logStrategySkipStats = (runtimeStrategies, strategyStats) => {
5080
5105
 
5081
5106
  // src/lib/signals/evaluations.ts
5082
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 = "";
5083
5316
  var buildRuntimeSignalEvaluationId = ({
5084
5317
  strategyName,
5085
5318
  symbol,
@@ -5088,7 +5321,46 @@ var buildRuntimeSignalEvaluationId = ({
5088
5321
  }) => runtimeConfigId && runtimeConfigId !== "config" ? [strategyName, runtimeConfigId, symbol, timestamp].join(":") : [strategyName, symbol, timestamp].join(":");
5089
5322
  var saveRuntimeSignalEvaluation = async (evaluation) => {
5090
5323
  const dayKey = (0, import_time.getRuntimeStorageDayKey)(evaluation.timestamp);
5324
+ if (runtimeLineageScopeCacheDayKey !== dayKey) {
5325
+ runtimeLineageScopeCache.clear();
5326
+ runtimeLineageScopeCacheDayKey = dayKey;
5327
+ }
5091
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
+ }
5092
5364
  if (shouldStoreDetailedRuntimeSignalEvaluation(evaluation)) {
5093
5365
  await (0, import_redis3.setHashJsonField)(
5094
5366
  import_redis3.redisKeys.runtimeSignalEvaluationBucket(
@@ -5548,7 +5820,7 @@ var resolveSignalsKlineWsWaitMs = () => resolveNonNegativeInteger(process.env.SI
5548
5820
  var createDefaultSignalsLifecycle = (runtimeInterval = interval) => createSignalsStrategyLifecycle({
5549
5821
  intervalMs: Number(runtimeInterval) * 6e4,
5550
5822
  maxLiveBars: resolveSignalsDaemonMaxLiveBars(runtimeInterval),
5551
- releaseState: import_strategies2.releaseStrategyReplayCache
5823
+ releaseState: import_strategies3.releaseStrategyReplayCache
5552
5824
  });
5553
5825
  var timeOperation2 = (label, operation) => timeOperation(
5554
5826
  label,
@@ -5693,6 +5965,20 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5693
5965
  interval: interval2,
5694
5966
  makeOrders: flags.makeOrders
5695
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
+ });
5696
5982
  const lifecycleKey = buildSignalsStrategyLifecycleKey({
5697
5983
  connectorName,
5698
5984
  universe: runtimeScopeUniverse,
@@ -5774,6 +6060,7 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5774
6060
  userName: flags.user,
5775
6061
  strategy: strategyName,
5776
6062
  runtimeConfigId: configId,
6063
+ runtimeLineage,
5777
6064
  symbol,
5778
6065
  interval: interval2,
5779
6066
  universe,
@@ -5792,10 +6079,11 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5792
6079
  stats.signals += 1;
5793
6080
  }
5794
6081
  signal.runtimeConfigId = configId;
6082
+ signal.runtimeLineage = runtimeLineage;
5795
6083
  if (configId && configId !== "config" && !signal.signalId.endsWith(`:${configId}`)) {
5796
6084
  signal.signalId = `${signal.signalId}:${configId}`;
5797
6085
  }
5798
- await (0, import_strategies3.enrichSignalWithBinanceMarketContext)({
6086
+ await (0, import_strategies4.enrichSignalWithBinanceMarketContext)({
5799
6087
  signal,
5800
6088
  env: "CRON"
5801
6089
  });
@@ -5838,6 +6126,7 @@ var findSignals = async (symbol, connectorName, connector, btcBinanceData, btcCo
5838
6126
  userName: flags.user,
5839
6127
  strategy: strategyName,
5840
6128
  runtimeConfigId: configId,
6129
+ runtimeLineage,
5841
6130
  symbol,
5842
6131
  interval: interval2,
5843
6132
  universe,