@tradejs/cli 2.0.17 → 2.0.18

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.
package/dist/cli.js CHANGED
@@ -3678,8 +3678,8 @@ var init_aiPocketSearch = __esm({
3678
3678
  "volumeusd",
3679
3679
  "vwap"
3680
3680
  ]);
3681
- classifyAiPocketFeaturePath = (path24) => {
3682
- const segments = (Array.isArray(path24) ? path24 : path24.split(".")).filter(
3681
+ classifyAiPocketFeaturePath = (path25) => {
3682
+ const segments = (Array.isArray(path25) ? path25 : path25.split(".")).filter(
3683
3683
  Boolean
3684
3684
  );
3685
3685
  const normalized = segments.map((segment) => segment.toLowerCase());
@@ -10868,27 +10868,27 @@ var init_coinMarketCapContextBackfill = __esm({
10868
10868
  totalCredits: 0,
10869
10869
  byPath: /* @__PURE__ */ new Map()
10870
10870
  });
10871
- getCoinMarketCapPathLabel = (path24) => {
10872
- if (path24.includes("global-metrics")) return "global";
10873
- if (path24.includes("cryptocurrency/quotes")) return "reference";
10874
- if (path24.includes("exchange/quotes")) return "exchangeLiquidity";
10875
- if (path24.includes("fear-and-greed")) return "fearGreed";
10876
- if (path24.includes("cmc100")) return "cmc100";
10877
- if (path24.includes("cmc20")) return "cmc20";
10878
- return path24;
10879
- };
10880
- recordCoinMarketCapFetchStats = (stats, path24, creditCount) => {
10871
+ getCoinMarketCapPathLabel = (path25) => {
10872
+ if (path25.includes("global-metrics")) return "global";
10873
+ if (path25.includes("cryptocurrency/quotes")) return "reference";
10874
+ if (path25.includes("exchange/quotes")) return "exchangeLiquidity";
10875
+ if (path25.includes("fear-and-greed")) return "fearGreed";
10876
+ if (path25.includes("cmc100")) return "cmc100";
10877
+ if (path25.includes("cmc20")) return "cmc20";
10878
+ return path25;
10879
+ };
10880
+ recordCoinMarketCapFetchStats = (stats, path25, creditCount) => {
10881
10881
  if (!stats) return;
10882
- const existing = stats.byPath.get(path24) ?? { requests: 0, credits: 0 };
10882
+ const existing = stats.byPath.get(path25) ?? { requests: 0, credits: 0 };
10883
10883
  existing.requests += 1;
10884
10884
  existing.credits += creditCount ?? 0;
10885
10885
  stats.requests += 1;
10886
10886
  stats.totalCredits += creditCount ?? 0;
10887
- stats.byPath.set(path24, existing);
10887
+ stats.byPath.set(path25, existing);
10888
10888
  };
10889
10889
  formatCoinMarketCapFetchStats = (stats) => {
10890
10890
  const byPath = [...stats.byPath.entries()].map(
10891
- ([path24, item]) => `${getCoinMarketCapPathLabel(path24)}=${item.requests}req/${item.credits}cr`
10891
+ ([path25, item]) => `${getCoinMarketCapPathLabel(path25)}=${item.requests}req/${item.credits}cr`
10892
10892
  ).join(" ");
10893
10893
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
10894
10894
  };
@@ -16778,15 +16778,15 @@ var require_pg_connection_string = __commonJS({
16778
16778
  if (config.sslnegotiation === "direct" && config.ssl === void 0) {
16779
16779
  config.ssl = true;
16780
16780
  }
16781
- const fs23 = config.sslcert || config.sslkey || config.sslrootcert ? require("fs") : null;
16781
+ const fs24 = config.sslcert || config.sslkey || config.sslrootcert ? require("fs") : null;
16782
16782
  if (config.sslcert) {
16783
- config.ssl.cert = fs23.readFileSync(config.sslcert).toString();
16783
+ config.ssl.cert = fs24.readFileSync(config.sslcert).toString();
16784
16784
  }
16785
16785
  if (config.sslkey) {
16786
- config.ssl.key = fs23.readFileSync(config.sslkey).toString();
16786
+ config.ssl.key = fs24.readFileSync(config.sslkey).toString();
16787
16787
  }
16788
16788
  if (config.sslrootcert) {
16789
- config.ssl.ca = fs23.readFileSync(config.sslrootcert).toString();
16789
+ config.ssl.ca = fs24.readFileSync(config.sslrootcert).toString();
16790
16790
  }
16791
16791
  if (options.useLibpqCompat && config.uselibpqcompat) {
16792
16792
  throw new Error("Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.");
@@ -18606,7 +18606,7 @@ var require_split2 = __commonJS({
18606
18606
  var require_helper = __commonJS({
18607
18607
  "../../node_modules/pgpass/lib/helper.js"(exports2, module2) {
18608
18608
  "use strict";
18609
- var path24 = require("path");
18609
+ var path25 = require("path");
18610
18610
  var Stream = require("stream").Stream;
18611
18611
  var split = require_split2();
18612
18612
  var util = require("util");
@@ -18645,7 +18645,7 @@ var require_helper = __commonJS({
18645
18645
  };
18646
18646
  module2.exports.getFileName = function(rawEnv) {
18647
18647
  var env = rawEnv || process.env;
18648
- var file = env.PGPASSFILE || (isWin ? path24.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path24.join(env.HOME || "./", ".pgpass"));
18648
+ var file = env.PGPASSFILE || (isWin ? path25.join(env.APPDATA || "./", "postgresql", "pgpass.conf") : path25.join(env.HOME || "./", ".pgpass"));
18649
18649
  return file;
18650
18650
  };
18651
18651
  module2.exports.usePgPass = function(stats, fname) {
@@ -18777,16 +18777,16 @@ var require_helper = __commonJS({
18777
18777
  var require_lib = __commonJS({
18778
18778
  "../../node_modules/pgpass/lib/index.js"(exports2, module2) {
18779
18779
  "use strict";
18780
- var path24 = require("path");
18781
- var fs23 = require("fs");
18780
+ var path25 = require("path");
18781
+ var fs24 = require("fs");
18782
18782
  var helper = require_helper();
18783
18783
  module2.exports = function(connInfo, cb) {
18784
18784
  var file = helper.getFileName();
18785
- fs23.stat(file, function(err, stat) {
18785
+ fs24.stat(file, function(err, stat) {
18786
18786
  if (err || !helper.usePgPass(stat, file)) {
18787
18787
  return cb(void 0);
18788
18788
  }
18789
- var st = fs23.createReadStream(file);
18789
+ var st = fs24.createReadStream(file);
18790
18790
  helper.getPassword(connInfo, st, cb);
18791
18791
  });
18792
18792
  };
@@ -27415,7 +27415,7 @@ var init_replayRuntimeEvidence = __esm({
27415
27415
  });
27416
27416
 
27417
27417
  // src/lib/runtimeEvidenceArtifacts.ts
27418
- var import_node_crypto5, import_promises15, import_node_path8, RUNTIME_EVIDENCE_MANIFEST_FILE, RUNTIME_EVIDENCE_PAYLOAD_FILE, RUNTIME_EVIDENCE_COMPLETE_FILE, asRecord4, sha256, isExistingDestinationError, safeSegment, compactTimestamp, dateParts, assertFiniteTimestamp, parseManifest, publishRuntimeEvidenceBundle, verifyRuntimeEvidenceBundle, discoverRuntimeEvidenceBundles;
27418
+ var import_node_crypto5, import_promises15, import_node_path8, RUNTIME_EVIDENCE_MANIFEST_FILE, RUNTIME_EVIDENCE_PAYLOAD_FILE, RUNTIME_EVIDENCE_COMPLETE_FILE, asRecord4, sha256, runtimeEvidenceIdentitySha256, isExistingDestinationError, safeSegment, compactTimestamp, dateParts, assertFiniteTimestamp, parseManifest, publishRuntimeEvidenceBundle, verifyRuntimeEvidenceBundle, discoverRuntimeEvidenceBundles;
27419
27419
  var init_runtimeEvidenceArtifacts = __esm({
27420
27420
  "src/lib/runtimeEvidenceArtifacts.ts"() {
27421
27421
  "use strict";
@@ -27427,6 +27427,17 @@ var init_runtimeEvidenceArtifacts = __esm({
27427
27427
  RUNTIME_EVIDENCE_COMPLETE_FILE = ".complete";
27428
27428
  asRecord4 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
27429
27429
  sha256 = (value) => (0, import_node_crypto5.createHash)("sha256").update(value).digest("hex");
27430
+ runtimeEvidenceIdentitySha256 = (artifact) => {
27431
+ const identityArtifact = { ...artifact };
27432
+ delete identityArtifact.generatedAt;
27433
+ const runtime = asRecord4(identityArtifact.runtime);
27434
+ if (runtime) {
27435
+ const identityRuntime = { ...runtime };
27436
+ delete identityRuntime.generatedAt;
27437
+ identityArtifact.runtime = identityRuntime;
27438
+ }
27439
+ return sha256(JSON.stringify(identityArtifact));
27440
+ };
27430
27441
  isExistingDestinationError = (error) => {
27431
27442
  const code = error.code;
27432
27443
  return code === "EEXIST" || code === "ENOTEMPTY";
@@ -27497,11 +27508,12 @@ var init_runtimeEvidenceArtifacts = __esm({
27497
27508
  const payload = `${JSON.stringify(artifact, null, 2)}
27498
27509
  `;
27499
27510
  const payloadSha256 = sha256(payload);
27511
+ const identitySha256 = runtimeEvidenceIdentitySha256(artifact);
27500
27512
  const safeDeploymentId = safeSegment(deploymentId, "default");
27501
27513
  const artifactId = [
27502
27514
  compactTimestamp(startTime),
27503
27515
  compactTimestamp(endTime),
27504
- payloadSha256.slice(0, 16)
27516
+ identitySha256.slice(0, 16)
27505
27517
  ].join("_");
27506
27518
  const createdAt = Date.now();
27507
27519
  const manifest = {
@@ -28908,21 +28920,21 @@ var init_serverHealth = __esm({
28908
28920
  }
28909
28921
  return `${minutes}m`;
28910
28922
  };
28911
- readDiskUsage = (path24) => {
28923
+ readDiskUsage = (path25) => {
28912
28924
  try {
28913
- const stats = (0, import_node_fs2.statfsSync)(path24);
28925
+ const stats = (0, import_node_fs2.statfsSync)(path25);
28914
28926
  const totalBlocks = Number(stats.blocks ?? 0);
28915
28927
  const availableBlocks = Number(stats.bavail ?? 0);
28916
28928
  if (totalBlocks <= 0) {
28917
- return { path: path24, usedPct: null };
28929
+ return { path: path25, usedPct: null };
28918
28930
  }
28919
28931
  const usedPct = (totalBlocks - Math.max(0, availableBlocks)) / totalBlocks * 100;
28920
28932
  return {
28921
- path: path24,
28933
+ path: path25,
28922
28934
  usedPct: Number.isFinite(usedPct) ? usedPct : null
28923
28935
  };
28924
28936
  } catch {
28925
- return { path: path24, usedPct: null };
28937
+ return { path: path25, usedPct: null };
28926
28938
  }
28927
28939
  };
28928
28940
  parseMeminfoBytes = (value) => {
@@ -33273,7 +33285,7 @@ var init_signalsDaemon = __esm({
33273
33285
  });
33274
33286
 
33275
33287
  // src/lib/replay/cliConfig.ts
33276
- var import_args32, REPLAY_UNSUPPORTED_FLAGS, hasCliFlag2, assertNoUnsupportedReplayFlags, replayNormalizedArgv, replayFlags, replayInterval, replayUserName, replayProjectRoot, isReplayUpdateOnlyRun;
33288
+ var import_args32, REPLAY_UNSUPPORTED_FLAGS, hasCliFlag2, assertNoUnsupportedReplayFlags, replayNormalizedArgv, replayFlags, replayInterval, replayUserName, replayProjectRoot, replayRuntimeEvidencePath, isReplayUpdateOnlyRun;
33277
33289
  var init_cliConfig2 = __esm({
33278
33290
  "src/lib/replay/cliConfig.ts"() {
33279
33291
  "use strict";
@@ -33322,6 +33334,10 @@ var init_cliConfig2 = __esm({
33322
33334
  import_args32.default.option(["d", "days"], "Run replay only for the last N days");
33323
33335
  import_args32.default.option("startTime", "Explicit replay start timestamp (ms or seconds)");
33324
33336
  import_args32.default.option("endTime", "Explicit replay end timestamp (ms or seconds)");
33337
+ import_args32.default.option(
33338
+ "runtimeEvidence",
33339
+ "Immutable runtime evidence JSON used for offline runtime comparison"
33340
+ );
33325
33341
  import_args32.default.option(["u", "updateOnly"], "Only update tickers history", false);
33326
33342
  import_args32.default.option(["C", "cacheOnly"], "Do not update tickers history", false);
33327
33343
  import_args32.default.option(
@@ -33347,6 +33363,7 @@ var init_cliConfig2 = __esm({
33347
33363
  replayInterval = String(replayFlags.timeframe ?? 15);
33348
33364
  replayUserName = String(replayFlags.user ?? "root");
33349
33365
  replayProjectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
33366
+ replayRuntimeEvidencePath = typeof replayFlags.runtimeEvidence === "string" && replayFlags.runtimeEvidence.trim() ? replayFlags.runtimeEvidence.trim() : null;
33350
33367
  isReplayUpdateOnlyRun = Boolean(replayFlags.updateOnly);
33351
33368
  }
33352
33369
  });
@@ -35374,6 +35391,121 @@ var init_runtimeParityDetails = __esm({
35374
35391
  }
35375
35392
  });
35376
35393
 
35394
+ // src/lib/replay/runtimeEvidenceSource.ts
35395
+ var import_promises20, import_node_path14, asRecord6, asFiniteNumber, unwrapRows2, buildRuntimeEvidenceLineageScopes, loadReplayRuntimeEvidenceSource;
35396
+ var init_runtimeEvidenceSource = __esm({
35397
+ "src/lib/replay/runtimeEvidenceSource.ts"() {
35398
+ "use strict";
35399
+ import_promises20 = __toESM(require("fs/promises"));
35400
+ import_node_path14 = __toESM(require("path"));
35401
+ init_runtimeLineage();
35402
+ asRecord6 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
35403
+ asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
35404
+ unwrapRows2 = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
35405
+ const row = asRecord6(item);
35406
+ return asRecord6(row?.[field]) ?? row;
35407
+ }).filter((item) => item != null);
35408
+ buildRuntimeEvidenceLineageScopes = ({
35409
+ signals: signals2,
35410
+ evaluations
35411
+ }) => {
35412
+ const scopes = /* @__PURE__ */ new Map();
35413
+ const add = ({
35414
+ strategy,
35415
+ symbol,
35416
+ runtimeConfigId,
35417
+ timestamp,
35418
+ lineage
35419
+ }) => {
35420
+ if (!lineage || !Number.isFinite(timestamp)) {
35421
+ return;
35422
+ }
35423
+ const key = `${strategy}::${symbol}::${runtimeLineageKey(lineage)}`;
35424
+ const existing = scopes.get(key);
35425
+ scopes.set(key, {
35426
+ strategy,
35427
+ symbol,
35428
+ ...runtimeConfigId ? { runtimeConfigId } : {},
35429
+ lineage,
35430
+ firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
35431
+ lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
35432
+ });
35433
+ };
35434
+ for (const signal of signals2) {
35435
+ add({
35436
+ strategy: signal.strategy,
35437
+ symbol: signal.symbol,
35438
+ runtimeConfigId: signal.runtimeConfigId,
35439
+ timestamp: signal.timestamp,
35440
+ lineage: signal.runtimeLineage
35441
+ });
35442
+ }
35443
+ for (const evaluation of evaluations) {
35444
+ add({
35445
+ strategy: evaluation.strategy,
35446
+ symbol: evaluation.symbol,
35447
+ runtimeConfigId: evaluation.runtimeConfigId,
35448
+ timestamp: evaluation.timestamp,
35449
+ lineage: evaluation.runtimeLineage
35450
+ });
35451
+ }
35452
+ return [...scopes.values()].sort(
35453
+ (left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.firstTimestamp - right.firstTimestamp
35454
+ );
35455
+ };
35456
+ loadReplayRuntimeEvidenceSource = async ({
35457
+ filePath,
35458
+ projectRoot: projectRoot16,
35459
+ expectedUserName,
35460
+ expectedWindow
35461
+ }) => {
35462
+ const resolvedPath = import_node_path14.default.resolve(projectRoot16, filePath);
35463
+ const root = asRecord6(
35464
+ JSON.parse(await import_promises20.default.readFile(resolvedPath, "utf8"))
35465
+ );
35466
+ const runtime = asRecord6(root?.runtime) ?? root;
35467
+ if (!root || !runtime) {
35468
+ throw new Error(`Invalid runtime evidence JSON: ${resolvedPath}`);
35469
+ }
35470
+ const rootWindow = asRecord6(root.window);
35471
+ const runtimeWindow2 = asRecord6(runtime.window);
35472
+ const startTime = asFiniteNumber(rootWindow?.startTime) ?? asFiniteNumber(runtimeWindow2?.startTime);
35473
+ const endTime = asFiniteNumber(rootWindow?.endTime) ?? asFiniteNumber(runtimeWindow2?.endTime);
35474
+ const userName2 = String(root.userName ?? runtime.userName ?? "").trim();
35475
+ if (!userName2 || userName2 !== expectedUserName) {
35476
+ throw new Error(
35477
+ `Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName2 || "missing"}`
35478
+ );
35479
+ }
35480
+ const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
35481
+ if (startTime !== expectedWindow.start || !endTimeMatches) {
35482
+ throw new Error(
35483
+ `Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
35484
+ );
35485
+ }
35486
+ const trades = unwrapRows2(runtime.trades, "trade");
35487
+ const signals2 = unwrapRows2(runtime.signals, "signal");
35488
+ const evaluations = unwrapRows2(
35489
+ runtime.evaluations,
35490
+ "evaluation"
35491
+ );
35492
+ return {
35493
+ path: resolvedPath,
35494
+ userName: userName2,
35495
+ startTime,
35496
+ endTime,
35497
+ trades,
35498
+ signals: signals2,
35499
+ evaluations,
35500
+ lineageScopes: buildRuntimeEvidenceLineageScopes({
35501
+ signals: signals2,
35502
+ evaluations
35503
+ })
35504
+ };
35505
+ };
35506
+ }
35507
+ });
35508
+
35377
35509
  // src/lib/replay/runtimeComparison.ts
35378
35510
  var import_chalk46, import_time11, getReplayEntryTimestampCompareOffsetMs, buildReplaySignalEvaluations, lineageScopeKey, buildExpectedLineageByScope, hasExpectedLineage, filterReplayComparisonByLineage, formatDrilldownSummary, toFiniteNumberOrNull4, sumOptionalNumbers, firstFiniteNumber, firstString, summarizeBacktestPnlByStrategy, getExchangeExecutionMergeKey, mergeExchangeExecutionGroup, mergeExchangeEntryExecutions, loadExchangeEntryRows, buildPriceDeltaPct3, loadExchangeEntriesForComparison, compareExchangeEntriesToBacktest, findExchangeRuntimeOutcome, splitExchangeMatchesByRuntimeOrderStatus, saveAndPrintReplayExchangeComparison, saveAndPrintReplayRuntimeComparison;
35379
35511
  var init_runtimeComparison = __esm({
@@ -35390,6 +35522,7 @@ var init_runtimeComparison = __esm({
35390
35522
  init_runtimeParityDetails();
35391
35523
  init_runState();
35392
35524
  init_cliConfig2();
35525
+ init_runtimeEvidenceSource();
35393
35526
  init_support();
35394
35527
  init_runtimeLineage();
35395
35528
  getReplayEntryTimestampCompareOffsetMs = () => {
@@ -36160,7 +36293,8 @@ var init_runtimeComparison = __esm({
36160
36293
  liveStrategySummaries,
36161
36294
  backtestEntries,
36162
36295
  replaySignals,
36163
- replayLineages
36296
+ replayLineages,
36297
+ runtimeEvidencePath
36164
36298
  }) => {
36165
36299
  const { connector, connectorName, window: window2 } = getRuntimeCompareContext();
36166
36300
  if (!connector || !window2) {
@@ -36170,12 +36304,23 @@ var init_runtimeComparison = __esm({
36170
36304
  liveStrategySummaries.map((summary) => summary.strategyName)
36171
36305
  );
36172
36306
  const replaySignalEvaluations = buildReplaySignalEvaluations(replaySignals);
36307
+ const runtimeEvidence2 = runtimeEvidencePath ? await loadReplayRuntimeEvidenceSource({
36308
+ filePath: runtimeEvidencePath,
36309
+ projectRoot: replayProjectRoot,
36310
+ expectedUserName: replayUserName,
36311
+ expectedWindow: window2
36312
+ }) : null;
36173
36313
  const [
36174
36314
  rawRuntimeTrades,
36175
36315
  runtimeSignals,
36176
36316
  runtimeSignalEvaluations,
36177
36317
  runtimeLineageScopes
36178
- ] = await Promise.all([
36318
+ ] = runtimeEvidence2 ? [
36319
+ runtimeEvidence2.trades,
36320
+ runtimeEvidence2.signals,
36321
+ runtimeEvidence2.evaluations,
36322
+ runtimeEvidence2.lineageScopes
36323
+ ] : await Promise.all([
36179
36324
  loadRuntimeTrades(replayUserName, {
36180
36325
  startTime: window2.start,
36181
36326
  endTime: window2.end
@@ -36193,7 +36338,14 @@ var init_runtimeComparison = __esm({
36193
36338
  endTime: window2.end
36194
36339
  })
36195
36340
  ]);
36196
- const syncedRuntimeTrades = await syncRuntimeTrades({
36341
+ if (runtimeEvidence2) {
36342
+ console.log(
36343
+ import_chalk46.default.gray(
36344
+ `runtime compare: using immutable evidence ${runtimeEvidence2.path}`
36345
+ )
36346
+ );
36347
+ }
36348
+ const syncedRuntimeTrades = runtimeEvidence2 ? rawRuntimeTrades : await syncRuntimeTrades({
36197
36349
  userName: replayUserName,
36198
36350
  connector,
36199
36351
  trades: rawRuntimeTrades,
@@ -36244,7 +36396,7 @@ var init_runtimeComparison = __esm({
36244
36396
  const windowRuntimeTrades = comparable.runtimeTrades.filter(
36245
36397
  (trade) => trade.entryTimestamp >= window2.start && trade.entryTimestamp < window2.end && relevantStrategies.has(trade.strategy)
36246
36398
  );
36247
- if (!windowRuntimeTrades.length) {
36399
+ if (!windowRuntimeTrades.length && !runtimeEvidence2) {
36248
36400
  console.log("");
36249
36401
  console.log(
36250
36402
  import_chalk46.default.yellow(
@@ -36378,12 +36530,12 @@ var init_runtimeComparison = __esm({
36378
36530
  });
36379
36531
 
36380
36532
  // src/lib/replay/outputReport.ts
36381
- var import_promises20, import_node_path14, import_data4, formatMsk, formatNumber5, formatMoney2, formatPct, tableCell, createMarkdownTable2, entryStrategy, entryKey, buildDrilldownMap, buildPerTradeAnalysis, renderMatchedRows, renderRuntimeOnlyRows, renderOrderFailedRows, renderBacktestOnlyRows, renderStrategyRows, renderLineageRows, writeReplayOutputReport;
36533
+ var import_promises21, import_node_path15, import_data4, formatMsk, formatNumber5, formatMoney2, formatPct, tableCell, createMarkdownTable2, entryStrategy, entryKey, buildDrilldownMap, buildPerTradeAnalysis, renderMatchedRows, renderRuntimeOnlyRows, renderOrderFailedRows, renderBacktestOnlyRows, renderStrategyRows, renderLineageRows, writeReplayOutputReport;
36382
36534
  var init_outputReport = __esm({
36383
36535
  "src/lib/replay/outputReport.ts"() {
36384
36536
  "use strict";
36385
- import_promises20 = __toESM(require("fs/promises"));
36386
- import_node_path14 = __toESM(require("path"));
36537
+ import_promises21 = __toESM(require("fs/promises"));
36538
+ import_node_path15 = __toESM(require("path"));
36387
36539
  import_data4 = require("@tradejs/core/data");
36388
36540
  init_support();
36389
36541
  formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
@@ -36688,8 +36840,8 @@ var init_outputReport = __esm({
36688
36840
  strategySnapshot,
36689
36841
  runtimeComparison
36690
36842
  }) => {
36691
- const outputDir = import_node_path14.default.resolve(projectRoot16, "data/replay/output");
36692
- await import_promises20.default.mkdir(outputDir, { recursive: true });
36843
+ const outputDir = import_node_path15.default.resolve(projectRoot16, "data/replay/output");
36844
+ await import_promises21.default.mkdir(outputDir, { recursive: true });
36693
36845
  const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
36694
36846
  const report = {
36695
36847
  reportType: "replay-output",
@@ -36724,8 +36876,8 @@ var init_outputReport = __esm({
36724
36876
  runtimeComparison,
36725
36877
  perTradeAnalysis
36726
36878
  };
36727
- const markdownPath = import_node_path14.default.join(outputDir, `${timestamp}-replay-report.md`);
36728
- const jsonPath = import_node_path14.default.join(outputDir, `${timestamp}-replay-report.json`);
36879
+ const markdownPath = import_node_path15.default.join(outputDir, `${timestamp}-replay-report.md`);
36880
+ const jsonPath = import_node_path15.default.join(outputDir, `${timestamp}-replay-report.json`);
36729
36881
  const lines = [
36730
36882
  "# Replay Report",
36731
36883
  "",
@@ -36791,8 +36943,8 @@ var init_outputReport = __esm({
36791
36943
  ""
36792
36944
  ];
36793
36945
  await Promise.all([
36794
- import_promises20.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
36795
- import_promises20.default.writeFile(jsonPath, `${(0, import_data4.toJson)(report, true)}
36946
+ import_promises21.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
36947
+ import_promises21.default.writeFile(jsonPath, `${(0, import_data4.toJson)(report, true)}
36796
36948
  `, "utf8")
36797
36949
  ]);
36798
36950
  return {
@@ -36857,7 +37009,8 @@ var init_replayRunner = __esm({
36857
37009
  liveStrategySummaries: replayStrategySnapshot.summaries,
36858
37010
  backtestEntries: replayStrategySnapshot.backtestEntries,
36859
37011
  replaySignals: replayResult.signals,
36860
- replayLineages: replayResult.runtimeLineages
37012
+ replayLineages: replayResult.runtimeLineages,
37013
+ runtimeEvidencePath: replayRuntimeEvidencePath
36861
37014
  });
36862
37015
  const finishedAt = /* @__PURE__ */ new Date();
36863
37016
  const durationSeconds = Number(
@@ -46,6 +46,17 @@ var RUNTIME_EVIDENCE_PAYLOAD_FILE = "runtime-evidence.json";
46
46
  var RUNTIME_EVIDENCE_COMPLETE_FILE = ".complete";
47
47
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
48
48
  var sha256 = (value) => (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
49
+ var runtimeEvidenceIdentitySha256 = (artifact) => {
50
+ const identityArtifact = { ...artifact };
51
+ delete identityArtifact.generatedAt;
52
+ const runtime = asRecord(identityArtifact.runtime);
53
+ if (runtime) {
54
+ const identityRuntime = { ...runtime };
55
+ delete identityRuntime.generatedAt;
56
+ identityArtifact.runtime = identityRuntime;
57
+ }
58
+ return sha256(JSON.stringify(identityArtifact));
59
+ };
49
60
  var isExistingDestinationError = (error) => {
50
61
  const code = error.code;
51
62
  return code === "EEXIST" || code === "ENOTEMPTY";
@@ -116,11 +127,12 @@ var publishRuntimeEvidenceBundle = async ({
116
127
  const payload = `${JSON.stringify(artifact, null, 2)}
117
128
  `;
118
129
  const payloadSha256 = sha256(payload);
130
+ const identitySha256 = runtimeEvidenceIdentitySha256(artifact);
119
131
  const safeDeploymentId = safeSegment(deploymentId, "default");
120
132
  const artifactId = [
121
133
  compactTimestamp(startTime),
122
134
  compactTimestamp(endTime),
123
- payloadSha256.slice(0, 16)
135
+ identitySha256.slice(0, 16)
124
136
  ].join("_");
125
137
  const createdAt = Date.now();
126
138
  const manifest = {
@@ -4349,27 +4349,27 @@ var createCoinMarketCapFetchStats = () => ({
4349
4349
  totalCredits: 0,
4350
4350
  byPath: /* @__PURE__ */ new Map()
4351
4351
  });
4352
- var getCoinMarketCapPathLabel = (path3) => {
4353
- if (path3.includes("global-metrics")) return "global";
4354
- if (path3.includes("cryptocurrency/quotes")) return "reference";
4355
- if (path3.includes("exchange/quotes")) return "exchangeLiquidity";
4356
- if (path3.includes("fear-and-greed")) return "fearGreed";
4357
- if (path3.includes("cmc100")) return "cmc100";
4358
- if (path3.includes("cmc20")) return "cmc20";
4359
- return path3;
4360
- };
4361
- var recordCoinMarketCapFetchStats = (stats, path3, creditCount) => {
4352
+ var getCoinMarketCapPathLabel = (path4) => {
4353
+ if (path4.includes("global-metrics")) return "global";
4354
+ if (path4.includes("cryptocurrency/quotes")) return "reference";
4355
+ if (path4.includes("exchange/quotes")) return "exchangeLiquidity";
4356
+ if (path4.includes("fear-and-greed")) return "fearGreed";
4357
+ if (path4.includes("cmc100")) return "cmc100";
4358
+ if (path4.includes("cmc20")) return "cmc20";
4359
+ return path4;
4360
+ };
4361
+ var recordCoinMarketCapFetchStats = (stats, path4, creditCount) => {
4362
4362
  if (!stats) return;
4363
- const existing = stats.byPath.get(path3) ?? { requests: 0, credits: 0 };
4363
+ const existing = stats.byPath.get(path4) ?? { requests: 0, credits: 0 };
4364
4364
  existing.requests += 1;
4365
4365
  existing.credits += creditCount ?? 0;
4366
4366
  stats.requests += 1;
4367
4367
  stats.totalCredits += creditCount ?? 0;
4368
- stats.byPath.set(path3, existing);
4368
+ stats.byPath.set(path4, existing);
4369
4369
  };
4370
4370
  var formatCoinMarketCapFetchStats = (stats) => {
4371
4371
  const byPath = [...stats.byPath.entries()].map(
4372
- ([path3, item]) => `${getCoinMarketCapPathLabel(path3)}=${item.requests}req/${item.credits}cr`
4372
+ ([path4, item]) => `${getCoinMarketCapPathLabel(path4)}=${item.requests}req/${item.credits}cr`
4373
4373
  ).join(" ");
4374
4374
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
4375
4375
  };
@@ -5949,6 +5949,10 @@ import_args.default.option(["f", "timeframe"], "Timeframe", 15);
5949
5949
  import_args.default.option(["d", "days"], "Run replay only for the last N days");
5950
5950
  import_args.default.option("startTime", "Explicit replay start timestamp (ms or seconds)");
5951
5951
  import_args.default.option("endTime", "Explicit replay end timestamp (ms or seconds)");
5952
+ import_args.default.option(
5953
+ "runtimeEvidence",
5954
+ "Immutable runtime evidence JSON used for offline runtime comparison"
5955
+ );
5952
5956
  import_args.default.option(["u", "updateOnly"], "Only update tickers history", false);
5953
5957
  import_args.default.option(["C", "cacheOnly"], "Do not update tickers history", false);
5954
5958
  import_args.default.option(
@@ -5974,6 +5978,7 @@ var replayFlags = import_args.default.parse(replayNormalizedArgv);
5974
5978
  var replayInterval = String(replayFlags.timeframe ?? 15);
5975
5979
  var replayUserName = String(replayFlags.user ?? "root");
5976
5980
  var replayProjectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
5981
+ var replayRuntimeEvidencePath = typeof replayFlags.runtimeEvidence === "string" && replayFlags.runtimeEvidence.trim() ? replayFlags.runtimeEvidence.trim() : null;
5977
5982
  var isReplayUpdateOnlyRun = Boolean(replayFlags.updateOnly);
5978
5983
 
5979
5984
  // src/lib/replay/support.ts
@@ -8806,6 +8811,114 @@ var buildReplayExchangeComparisonDetails = ({
8806
8811
  };
8807
8812
  };
8808
8813
 
8814
+ // src/lib/replay/runtimeEvidenceSource.ts
8815
+ var import_promises2 = __toESM(require("fs/promises"));
8816
+ var import_node_path2 = __toESM(require("path"));
8817
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
8818
+ var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
8819
+ var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
8820
+ const row = asRecord(item);
8821
+ return asRecord(row?.[field]) ?? row;
8822
+ }).filter((item) => item != null);
8823
+ var buildRuntimeEvidenceLineageScopes = ({
8824
+ signals,
8825
+ evaluations
8826
+ }) => {
8827
+ const scopes = /* @__PURE__ */ new Map();
8828
+ const add = ({
8829
+ strategy,
8830
+ symbol,
8831
+ runtimeConfigId,
8832
+ timestamp,
8833
+ lineage
8834
+ }) => {
8835
+ if (!lineage || !Number.isFinite(timestamp)) {
8836
+ return;
8837
+ }
8838
+ const key = `${strategy}::${symbol}::${runtimeLineageKey(lineage)}`;
8839
+ const existing = scopes.get(key);
8840
+ scopes.set(key, {
8841
+ strategy,
8842
+ symbol,
8843
+ ...runtimeConfigId ? { runtimeConfigId } : {},
8844
+ lineage,
8845
+ firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
8846
+ lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
8847
+ });
8848
+ };
8849
+ for (const signal of signals) {
8850
+ add({
8851
+ strategy: signal.strategy,
8852
+ symbol: signal.symbol,
8853
+ runtimeConfigId: signal.runtimeConfigId,
8854
+ timestamp: signal.timestamp,
8855
+ lineage: signal.runtimeLineage
8856
+ });
8857
+ }
8858
+ for (const evaluation of evaluations) {
8859
+ add({
8860
+ strategy: evaluation.strategy,
8861
+ symbol: evaluation.symbol,
8862
+ runtimeConfigId: evaluation.runtimeConfigId,
8863
+ timestamp: evaluation.timestamp,
8864
+ lineage: evaluation.runtimeLineage
8865
+ });
8866
+ }
8867
+ return [...scopes.values()].sort(
8868
+ (left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.firstTimestamp - right.firstTimestamp
8869
+ );
8870
+ };
8871
+ var loadReplayRuntimeEvidenceSource = async ({
8872
+ filePath,
8873
+ projectRoot,
8874
+ expectedUserName,
8875
+ expectedWindow
8876
+ }) => {
8877
+ const resolvedPath = import_node_path2.default.resolve(projectRoot, filePath);
8878
+ const root = asRecord(
8879
+ JSON.parse(await import_promises2.default.readFile(resolvedPath, "utf8"))
8880
+ );
8881
+ const runtime = asRecord(root?.runtime) ?? root;
8882
+ if (!root || !runtime) {
8883
+ throw new Error(`Invalid runtime evidence JSON: ${resolvedPath}`);
8884
+ }
8885
+ const rootWindow = asRecord(root.window);
8886
+ const runtimeWindow = asRecord(runtime.window);
8887
+ const startTime = asFiniteNumber(rootWindow?.startTime) ?? asFiniteNumber(runtimeWindow?.startTime);
8888
+ const endTime = asFiniteNumber(rootWindow?.endTime) ?? asFiniteNumber(runtimeWindow?.endTime);
8889
+ const userName = String(root.userName ?? runtime.userName ?? "").trim();
8890
+ if (!userName || userName !== expectedUserName) {
8891
+ throw new Error(
8892
+ `Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
8893
+ );
8894
+ }
8895
+ const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
8896
+ if (startTime !== expectedWindow.start || !endTimeMatches) {
8897
+ throw new Error(
8898
+ `Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
8899
+ );
8900
+ }
8901
+ const trades = unwrapRows(runtime.trades, "trade");
8902
+ const signals = unwrapRows(runtime.signals, "signal");
8903
+ const evaluations = unwrapRows(
8904
+ runtime.evaluations,
8905
+ "evaluation"
8906
+ );
8907
+ return {
8908
+ path: resolvedPath,
8909
+ userName,
8910
+ startTime,
8911
+ endTime,
8912
+ trades,
8913
+ signals,
8914
+ evaluations,
8915
+ lineageScopes: buildRuntimeEvidenceLineageScopes({
8916
+ signals,
8917
+ evaluations
8918
+ })
8919
+ };
8920
+ };
8921
+
8809
8922
  // src/lib/replay/runtimeComparison.ts
8810
8923
  var getReplayEntryTimestampCompareOffsetMs = () => {
8811
8924
  const intervalMinutes = Number(replayInterval);
@@ -9575,7 +9688,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
9575
9688
  liveStrategySummaries,
9576
9689
  backtestEntries,
9577
9690
  replaySignals,
9578
- replayLineages
9691
+ replayLineages,
9692
+ runtimeEvidencePath
9579
9693
  }) => {
9580
9694
  const { connector, connectorName, window: window2 } = getRuntimeCompareContext();
9581
9695
  if (!connector || !window2) {
@@ -9585,12 +9699,23 @@ var saveAndPrintReplayRuntimeComparison = async ({
9585
9699
  liveStrategySummaries.map((summary) => summary.strategyName)
9586
9700
  );
9587
9701
  const replaySignalEvaluations = buildReplaySignalEvaluations(replaySignals);
9702
+ const runtimeEvidence = runtimeEvidencePath ? await loadReplayRuntimeEvidenceSource({
9703
+ filePath: runtimeEvidencePath,
9704
+ projectRoot: replayProjectRoot,
9705
+ expectedUserName: replayUserName,
9706
+ expectedWindow: window2
9707
+ }) : null;
9588
9708
  const [
9589
9709
  rawRuntimeTrades,
9590
9710
  runtimeSignals,
9591
9711
  runtimeSignalEvaluations,
9592
9712
  runtimeLineageScopes
9593
- ] = await Promise.all([
9713
+ ] = runtimeEvidence ? [
9714
+ runtimeEvidence.trades,
9715
+ runtimeEvidence.signals,
9716
+ runtimeEvidence.evaluations,
9717
+ runtimeEvidence.lineageScopes
9718
+ ] : await Promise.all([
9594
9719
  loadRuntimeTrades(replayUserName, {
9595
9720
  startTime: window2.start,
9596
9721
  endTime: window2.end
@@ -9608,7 +9733,14 @@ var saveAndPrintReplayRuntimeComparison = async ({
9608
9733
  endTime: window2.end
9609
9734
  })
9610
9735
  ]);
9611
- const syncedRuntimeTrades = await syncRuntimeTrades({
9736
+ if (runtimeEvidence) {
9737
+ console.log(
9738
+ import_chalk11.default.gray(
9739
+ `runtime compare: using immutable evidence ${runtimeEvidence.path}`
9740
+ )
9741
+ );
9742
+ }
9743
+ const syncedRuntimeTrades = runtimeEvidence ? rawRuntimeTrades : await syncRuntimeTrades({
9612
9744
  userName: replayUserName,
9613
9745
  connector,
9614
9746
  trades: rawRuntimeTrades,
@@ -9659,7 +9791,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
9659
9791
  const windowRuntimeTrades = comparable.runtimeTrades.filter(
9660
9792
  (trade) => trade.entryTimestamp >= window2.start && trade.entryTimestamp < window2.end && relevantStrategies.has(trade.strategy)
9661
9793
  );
9662
- if (!windowRuntimeTrades.length) {
9794
+ if (!windowRuntimeTrades.length && !runtimeEvidence) {
9663
9795
  console.log("");
9664
9796
  console.log(
9665
9797
  import_chalk11.default.yellow(
@@ -9791,8 +9923,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
9791
9923
  };
9792
9924
 
9793
9925
  // src/lib/replay/outputReport.ts
9794
- var import_promises2 = __toESM(require("fs/promises"));
9795
- var import_node_path2 = __toESM(require("path"));
9926
+ var import_promises3 = __toESM(require("fs/promises"));
9927
+ var import_node_path3 = __toESM(require("path"));
9796
9928
  var import_data = require("@tradejs/core/data");
9797
9929
  var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
9798
9930
  timeZone: "Europe/Moscow",
@@ -10096,8 +10228,8 @@ var writeReplayOutputReport = async ({
10096
10228
  strategySnapshot,
10097
10229
  runtimeComparison
10098
10230
  }) => {
10099
- const outputDir = import_node_path2.default.resolve(projectRoot, "data/replay/output");
10100
- await import_promises2.default.mkdir(outputDir, { recursive: true });
10231
+ const outputDir = import_node_path3.default.resolve(projectRoot, "data/replay/output");
10232
+ await import_promises3.default.mkdir(outputDir, { recursive: true });
10101
10233
  const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
10102
10234
  const report = {
10103
10235
  reportType: "replay-output",
@@ -10132,8 +10264,8 @@ var writeReplayOutputReport = async ({
10132
10264
  runtimeComparison,
10133
10265
  perTradeAnalysis
10134
10266
  };
10135
- const markdownPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.md`);
10136
- const jsonPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.json`);
10267
+ const markdownPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.md`);
10268
+ const jsonPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.json`);
10137
10269
  const lines = [
10138
10270
  "# Replay Report",
10139
10271
  "",
@@ -10199,8 +10331,8 @@ var writeReplayOutputReport = async ({
10199
10331
  ""
10200
10332
  ];
10201
10333
  await Promise.all([
10202
- import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
10203
- import_promises2.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
10334
+ import_promises3.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
10335
+ import_promises3.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
10204
10336
  `, "utf8")
10205
10337
  ]);
10206
10338
  return {
@@ -10241,7 +10373,8 @@ var finishReplay = async ({
10241
10373
  liveStrategySummaries: replayStrategySnapshot.summaries,
10242
10374
  backtestEntries: replayStrategySnapshot.backtestEntries,
10243
10375
  replaySignals: replayResult.signals,
10244
- replayLineages: replayResult.runtimeLineages
10376
+ replayLineages: replayResult.runtimeLineages,
10377
+ runtimeEvidencePath: replayRuntimeEvidencePath
10245
10378
  });
10246
10379
  const finishedAt = /* @__PURE__ */ new Date();
10247
10380
  const durationSeconds = Number(
@@ -4352,27 +4352,27 @@ var createCoinMarketCapFetchStats = () => ({
4352
4352
  totalCredits: 0,
4353
4353
  byPath: /* @__PURE__ */ new Map()
4354
4354
  });
4355
- var getCoinMarketCapPathLabel = (path3) => {
4356
- if (path3.includes("global-metrics")) return "global";
4357
- if (path3.includes("cryptocurrency/quotes")) return "reference";
4358
- if (path3.includes("exchange/quotes")) return "exchangeLiquidity";
4359
- if (path3.includes("fear-and-greed")) return "fearGreed";
4360
- if (path3.includes("cmc100")) return "cmc100";
4361
- if (path3.includes("cmc20")) return "cmc20";
4362
- return path3;
4363
- };
4364
- var recordCoinMarketCapFetchStats = (stats, path3, creditCount) => {
4355
+ var getCoinMarketCapPathLabel = (path4) => {
4356
+ if (path4.includes("global-metrics")) return "global";
4357
+ if (path4.includes("cryptocurrency/quotes")) return "reference";
4358
+ if (path4.includes("exchange/quotes")) return "exchangeLiquidity";
4359
+ if (path4.includes("fear-and-greed")) return "fearGreed";
4360
+ if (path4.includes("cmc100")) return "cmc100";
4361
+ if (path4.includes("cmc20")) return "cmc20";
4362
+ return path4;
4363
+ };
4364
+ var recordCoinMarketCapFetchStats = (stats, path4, creditCount) => {
4365
4365
  if (!stats) return;
4366
- const existing = stats.byPath.get(path3) ?? { requests: 0, credits: 0 };
4366
+ const existing = stats.byPath.get(path4) ?? { requests: 0, credits: 0 };
4367
4367
  existing.requests += 1;
4368
4368
  existing.credits += creditCount ?? 0;
4369
4369
  stats.requests += 1;
4370
4370
  stats.totalCredits += creditCount ?? 0;
4371
- stats.byPath.set(path3, existing);
4371
+ stats.byPath.set(path4, existing);
4372
4372
  };
4373
4373
  var formatCoinMarketCapFetchStats = (stats) => {
4374
4374
  const byPath = [...stats.byPath.entries()].map(
4375
- ([path3, item]) => `${getCoinMarketCapPathLabel(path3)}=${item.requests}req/${item.credits}cr`
4375
+ ([path4, item]) => `${getCoinMarketCapPathLabel(path4)}=${item.requests}req/${item.credits}cr`
4376
4376
  ).join(" ");
4377
4377
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
4378
4378
  };
@@ -5952,6 +5952,10 @@ import_args.default.option(["f", "timeframe"], "Timeframe", 15);
5952
5952
  import_args.default.option(["d", "days"], "Run replay only for the last N days");
5953
5953
  import_args.default.option("startTime", "Explicit replay start timestamp (ms or seconds)");
5954
5954
  import_args.default.option("endTime", "Explicit replay end timestamp (ms or seconds)");
5955
+ import_args.default.option(
5956
+ "runtimeEvidence",
5957
+ "Immutable runtime evidence JSON used for offline runtime comparison"
5958
+ );
5955
5959
  import_args.default.option(["u", "updateOnly"], "Only update tickers history", false);
5956
5960
  import_args.default.option(["C", "cacheOnly"], "Do not update tickers history", false);
5957
5961
  import_args.default.option(
@@ -5977,6 +5981,7 @@ var replayFlags = import_args.default.parse(replayNormalizedArgv);
5977
5981
  var replayInterval = String(replayFlags.timeframe ?? 15);
5978
5982
  var replayUserName = String(replayFlags.user ?? "root");
5979
5983
  var replayProjectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
5984
+ var replayRuntimeEvidencePath = typeof replayFlags.runtimeEvidence === "string" && replayFlags.runtimeEvidence.trim() ? replayFlags.runtimeEvidence.trim() : null;
5980
5985
  var isReplayUpdateOnlyRun = Boolean(replayFlags.updateOnly);
5981
5986
 
5982
5987
  // src/lib/replay/support.ts
@@ -8809,6 +8814,114 @@ var buildReplayExchangeComparisonDetails = ({
8809
8814
  };
8810
8815
  };
8811
8816
 
8817
+ // src/lib/replay/runtimeEvidenceSource.ts
8818
+ var import_promises2 = __toESM(require("fs/promises"));
8819
+ var import_node_path2 = __toESM(require("path"));
8820
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
8821
+ var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
8822
+ var unwrapRows = (value, field) => (Array.isArray(value) ? value : []).map((item) => {
8823
+ const row = asRecord(item);
8824
+ return asRecord(row?.[field]) ?? row;
8825
+ }).filter((item) => item != null);
8826
+ var buildRuntimeEvidenceLineageScopes = ({
8827
+ signals,
8828
+ evaluations
8829
+ }) => {
8830
+ const scopes = /* @__PURE__ */ new Map();
8831
+ const add = ({
8832
+ strategy,
8833
+ symbol,
8834
+ runtimeConfigId,
8835
+ timestamp,
8836
+ lineage
8837
+ }) => {
8838
+ if (!lineage || !Number.isFinite(timestamp)) {
8839
+ return;
8840
+ }
8841
+ const key = `${strategy}::${symbol}::${runtimeLineageKey(lineage)}`;
8842
+ const existing = scopes.get(key);
8843
+ scopes.set(key, {
8844
+ strategy,
8845
+ symbol,
8846
+ ...runtimeConfigId ? { runtimeConfigId } : {},
8847
+ lineage,
8848
+ firstTimestamp: existing ? Math.min(existing.firstTimestamp, timestamp) : timestamp,
8849
+ lastTimestamp: existing ? Math.max(existing.lastTimestamp, timestamp) : timestamp
8850
+ });
8851
+ };
8852
+ for (const signal of signals) {
8853
+ add({
8854
+ strategy: signal.strategy,
8855
+ symbol: signal.symbol,
8856
+ runtimeConfigId: signal.runtimeConfigId,
8857
+ timestamp: signal.timestamp,
8858
+ lineage: signal.runtimeLineage
8859
+ });
8860
+ }
8861
+ for (const evaluation of evaluations) {
8862
+ add({
8863
+ strategy: evaluation.strategy,
8864
+ symbol: evaluation.symbol,
8865
+ runtimeConfigId: evaluation.runtimeConfigId,
8866
+ timestamp: evaluation.timestamp,
8867
+ lineage: evaluation.runtimeLineage
8868
+ });
8869
+ }
8870
+ return [...scopes.values()].sort(
8871
+ (left, right) => left.strategy.localeCompare(right.strategy) || left.symbol.localeCompare(right.symbol) || left.firstTimestamp - right.firstTimestamp
8872
+ );
8873
+ };
8874
+ var loadReplayRuntimeEvidenceSource = async ({
8875
+ filePath,
8876
+ projectRoot,
8877
+ expectedUserName,
8878
+ expectedWindow
8879
+ }) => {
8880
+ const resolvedPath = import_node_path2.default.resolve(projectRoot, filePath);
8881
+ const root = asRecord(
8882
+ JSON.parse(await import_promises2.default.readFile(resolvedPath, "utf8"))
8883
+ );
8884
+ const runtime = asRecord(root?.runtime) ?? root;
8885
+ if (!root || !runtime) {
8886
+ throw new Error(`Invalid runtime evidence JSON: ${resolvedPath}`);
8887
+ }
8888
+ const rootWindow = asRecord(root.window);
8889
+ const runtimeWindow = asRecord(runtime.window);
8890
+ const startTime = asFiniteNumber(rootWindow?.startTime) ?? asFiniteNumber(runtimeWindow?.startTime);
8891
+ const endTime = asFiniteNumber(rootWindow?.endTime) ?? asFiniteNumber(runtimeWindow?.endTime);
8892
+ const userName = String(root.userName ?? runtime.userName ?? "").trim();
8893
+ if (!userName || userName !== expectedUserName) {
8894
+ throw new Error(
8895
+ `Runtime evidence user mismatch: expected=${expectedUserName}, actual=${userName || "missing"}`
8896
+ );
8897
+ }
8898
+ const endTimeMatches = endTime === expectedWindow.end || endTime === expectedWindow.end + 1;
8899
+ if (startTime !== expectedWindow.start || !endTimeMatches) {
8900
+ throw new Error(
8901
+ `Runtime evidence window mismatch: expected=${expectedWindow.start}..${expectedWindow.end}, actual=${startTime ?? "missing"}..${endTime ?? "missing"}`
8902
+ );
8903
+ }
8904
+ const trades = unwrapRows(runtime.trades, "trade");
8905
+ const signals = unwrapRows(runtime.signals, "signal");
8906
+ const evaluations = unwrapRows(
8907
+ runtime.evaluations,
8908
+ "evaluation"
8909
+ );
8910
+ return {
8911
+ path: resolvedPath,
8912
+ userName,
8913
+ startTime,
8914
+ endTime,
8915
+ trades,
8916
+ signals,
8917
+ evaluations,
8918
+ lineageScopes: buildRuntimeEvidenceLineageScopes({
8919
+ signals,
8920
+ evaluations
8921
+ })
8922
+ };
8923
+ };
8924
+
8812
8925
  // src/lib/replay/runtimeComparison.ts
8813
8926
  var getReplayEntryTimestampCompareOffsetMs = () => {
8814
8927
  const intervalMinutes = Number(replayInterval);
@@ -9578,7 +9691,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
9578
9691
  liveStrategySummaries,
9579
9692
  backtestEntries,
9580
9693
  replaySignals,
9581
- replayLineages
9694
+ replayLineages,
9695
+ runtimeEvidencePath
9582
9696
  }) => {
9583
9697
  const { connector, connectorName, window: window2 } = getRuntimeCompareContext();
9584
9698
  if (!connector || !window2) {
@@ -9588,12 +9702,23 @@ var saveAndPrintReplayRuntimeComparison = async ({
9588
9702
  liveStrategySummaries.map((summary) => summary.strategyName)
9589
9703
  );
9590
9704
  const replaySignalEvaluations = buildReplaySignalEvaluations(replaySignals);
9705
+ const runtimeEvidence = runtimeEvidencePath ? await loadReplayRuntimeEvidenceSource({
9706
+ filePath: runtimeEvidencePath,
9707
+ projectRoot: replayProjectRoot,
9708
+ expectedUserName: replayUserName,
9709
+ expectedWindow: window2
9710
+ }) : null;
9591
9711
  const [
9592
9712
  rawRuntimeTrades,
9593
9713
  runtimeSignals,
9594
9714
  runtimeSignalEvaluations,
9595
9715
  runtimeLineageScopes
9596
- ] = await Promise.all([
9716
+ ] = runtimeEvidence ? [
9717
+ runtimeEvidence.trades,
9718
+ runtimeEvidence.signals,
9719
+ runtimeEvidence.evaluations,
9720
+ runtimeEvidence.lineageScopes
9721
+ ] : await Promise.all([
9597
9722
  loadRuntimeTrades(replayUserName, {
9598
9723
  startTime: window2.start,
9599
9724
  endTime: window2.end
@@ -9611,7 +9736,14 @@ var saveAndPrintReplayRuntimeComparison = async ({
9611
9736
  endTime: window2.end
9612
9737
  })
9613
9738
  ]);
9614
- const syncedRuntimeTrades = await syncRuntimeTrades({
9739
+ if (runtimeEvidence) {
9740
+ console.log(
9741
+ import_chalk11.default.gray(
9742
+ `runtime compare: using immutable evidence ${runtimeEvidence.path}`
9743
+ )
9744
+ );
9745
+ }
9746
+ const syncedRuntimeTrades = runtimeEvidence ? rawRuntimeTrades : await syncRuntimeTrades({
9615
9747
  userName: replayUserName,
9616
9748
  connector,
9617
9749
  trades: rawRuntimeTrades,
@@ -9662,7 +9794,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
9662
9794
  const windowRuntimeTrades = comparable.runtimeTrades.filter(
9663
9795
  (trade) => trade.entryTimestamp >= window2.start && trade.entryTimestamp < window2.end && relevantStrategies.has(trade.strategy)
9664
9796
  );
9665
- if (!windowRuntimeTrades.length) {
9797
+ if (!windowRuntimeTrades.length && !runtimeEvidence) {
9666
9798
  console.log("");
9667
9799
  console.log(
9668
9800
  import_chalk11.default.yellow(
@@ -9794,8 +9926,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
9794
9926
  };
9795
9927
 
9796
9928
  // src/lib/replay/outputReport.ts
9797
- var import_promises2 = __toESM(require("fs/promises"));
9798
- var import_node_path2 = __toESM(require("path"));
9929
+ var import_promises3 = __toESM(require("fs/promises"));
9930
+ var import_node_path3 = __toESM(require("path"));
9799
9931
  var import_data = require("@tradejs/core/data");
9800
9932
  var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
9801
9933
  timeZone: "Europe/Moscow",
@@ -10099,8 +10231,8 @@ var writeReplayOutputReport = async ({
10099
10231
  strategySnapshot,
10100
10232
  runtimeComparison
10101
10233
  }) => {
10102
- const outputDir = import_node_path2.default.resolve(projectRoot, "data/replay/output");
10103
- await import_promises2.default.mkdir(outputDir, { recursive: true });
10234
+ const outputDir = import_node_path3.default.resolve(projectRoot, "data/replay/output");
10235
+ await import_promises3.default.mkdir(outputDir, { recursive: true });
10104
10236
  const perTradeAnalysis = buildPerTradeAnalysis(runtimeComparison);
10105
10237
  const report = {
10106
10238
  reportType: "replay-output",
@@ -10135,8 +10267,8 @@ var writeReplayOutputReport = async ({
10135
10267
  runtimeComparison,
10136
10268
  perTradeAnalysis
10137
10269
  };
10138
- const markdownPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.md`);
10139
- const jsonPath = import_node_path2.default.join(outputDir, `${timestamp}-replay-report.json`);
10270
+ const markdownPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.md`);
10271
+ const jsonPath = import_node_path3.default.join(outputDir, `${timestamp}-replay-report.json`);
10140
10272
  const lines = [
10141
10273
  "# Replay Report",
10142
10274
  "",
@@ -10202,8 +10334,8 @@ var writeReplayOutputReport = async ({
10202
10334
  ""
10203
10335
  ];
10204
10336
  await Promise.all([
10205
- import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
10206
- import_promises2.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
10337
+ import_promises3.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
10338
+ import_promises3.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
10207
10339
  `, "utf8")
10208
10340
  ]);
10209
10341
  return {
@@ -10244,7 +10376,8 @@ var finishReplay = async ({
10244
10376
  liveStrategySummaries: replayStrategySnapshot.summaries,
10245
10377
  backtestEntries: replayStrategySnapshot.backtestEntries,
10246
10378
  replaySignals: replayResult.signals,
10247
- replayLineages: replayResult.runtimeLineages
10379
+ replayLineages: replayResult.runtimeLineages,
10380
+ runtimeEvidencePath: replayRuntimeEvidencePath
10248
10381
  });
10249
10382
  const finishedAt = /* @__PURE__ */ new Date();
10250
10383
  const durationSeconds = Number(
@@ -417,6 +417,17 @@ var RUNTIME_EVIDENCE_PAYLOAD_FILE = "runtime-evidence.json";
417
417
  var RUNTIME_EVIDENCE_COMPLETE_FILE = ".complete";
418
418
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
419
419
  var sha256 = (value) => (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
420
+ var runtimeEvidenceIdentitySha256 = (artifact) => {
421
+ const identityArtifact = { ...artifact };
422
+ delete identityArtifact.generatedAt;
423
+ const runtime = asRecord(identityArtifact.runtime);
424
+ if (runtime) {
425
+ const identityRuntime = { ...runtime };
426
+ delete identityRuntime.generatedAt;
427
+ identityArtifact.runtime = identityRuntime;
428
+ }
429
+ return sha256(JSON.stringify(identityArtifact));
430
+ };
420
431
  var isExistingDestinationError = (error) => {
421
432
  const code = error.code;
422
433
  return code === "EEXIST" || code === "ENOTEMPTY";
@@ -487,11 +498,12 @@ var publishRuntimeEvidenceBundle = async ({
487
498
  const payload = `${JSON.stringify(artifact, null, 2)}
488
499
  `;
489
500
  const payloadSha256 = sha256(payload);
501
+ const identitySha256 = runtimeEvidenceIdentitySha256(artifact);
490
502
  const safeDeploymentId = safeSegment(deploymentId, "default");
491
503
  const artifactId = [
492
504
  compactTimestamp(startTime),
493
505
  compactTimestamp(endTime),
494
- payloadSha256.slice(0, 16)
506
+ identitySha256.slice(0, 16)
495
507
  ].join("_");
496
508
  const createdAt = Date.now();
497
509
  const manifest = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "2.0.17",
3
+ "version": "2.0.18",
4
4
  "description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -29,14 +29,14 @@
29
29
  "tradejs": "dist/cli.js"
30
30
  },
31
31
  "dependencies": {
32
- "@tradejs/base": "^2.0.17",
33
- "@tradejs/connectors": "^2.0.17",
34
- "@tradejs/core": "^2.0.17",
35
- "@tradejs/indicators": "^2.0.17",
36
- "@tradejs/infra": "^2.0.17",
37
- "@tradejs/node": "^2.0.17",
38
- "@tradejs/strategies": "^2.0.17",
39
- "@tradejs/types": "^2.0.17",
32
+ "@tradejs/base": "^2.0.18",
33
+ "@tradejs/connectors": "^2.0.18",
34
+ "@tradejs/core": "^2.0.18",
35
+ "@tradejs/indicators": "^2.0.18",
36
+ "@tradejs/infra": "^2.0.18",
37
+ "@tradejs/node": "^2.0.18",
38
+ "@tradejs/strategies": "^2.0.18",
39
+ "@tradejs/types": "^2.0.18",
40
40
  "args": "^5.0.3",
41
41
  "bcryptjs": "^2.4.3",
42
42
  "chalk": "4.1.2",