@tradejs/cli 3.1.26 → 3.1.27-beta.245

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.
@@ -208,6 +208,50 @@ var findNearestEvaluation = ({
208
208
  timestampDiffMs: bestDiff
209
209
  } : null;
210
210
  };
211
+ var findNearestRuntimeEvaluationOutcome = ({
212
+ entry,
213
+ lineageScopes,
214
+ toleranceMs
215
+ }) => {
216
+ const strategy = getEntryStrategy(entry);
217
+ const timestamp = entry.signalTimestamp ?? entry.timestamp;
218
+ if (!strategy) return null;
219
+ let nearest = null;
220
+ for (const scope of lineageScopes) {
221
+ if (scope.strategy !== strategy || scope.symbol !== entry.symbol) {
222
+ continue;
223
+ }
224
+ for (const run of scope.evaluationRuns ?? []) {
225
+ if (run.stepMs <= 0 || timestamp < run.firstTimestamp - toleranceMs || timestamp > run.lastTimestamp + toleranceMs) {
226
+ continue;
227
+ }
228
+ const step = Math.round((timestamp - run.firstTimestamp) / run.stepMs);
229
+ const candidateTimestamp = run.firstTimestamp + step * run.stepMs;
230
+ const timestampDiffMs = Math.abs(candidateTimestamp - timestamp);
231
+ if (candidateTimestamp < run.firstTimestamp || candidateTimestamp > run.lastTimestamp || timestampDiffMs > toleranceMs || nearest && timestampDiffMs >= nearest.timestampDiffMs) {
232
+ continue;
233
+ }
234
+ nearest = {
235
+ status: run.status,
236
+ reason: run.reason,
237
+ timestamp: candidateTimestamp,
238
+ timestampDiffMs,
239
+ source: "lineage_scope_compact"
240
+ };
241
+ }
242
+ }
243
+ return nearest;
244
+ };
245
+ var hasRuntimeLineageCoverage = ({
246
+ entry,
247
+ lineageScopes
248
+ }) => {
249
+ const strategy = getEntryStrategy(entry);
250
+ const timestamp = entry.signalTimestamp ?? entry.timestamp;
251
+ return lineageScopes.some(
252
+ (scope) => scope.strategy === strategy && scope.symbol === entry.symbol && timestamp >= scope.firstTimestamp && timestamp <= scope.lastTimestamp
253
+ );
254
+ };
211
255
  var classifySignalDiagnostic = (signal) => {
212
256
  if (signal.orderStatus === "failed") {
213
257
  return {
@@ -452,6 +496,7 @@ var buildReplayMismatchDrilldown = ({
452
496
  runtimeSignalEvaluations = [],
453
497
  replaySignals = [],
454
498
  replaySignalEvaluations = [],
499
+ runtimeLineageScopes = [],
455
500
  toleranceMs,
456
501
  backtestTimestampOffsetMs,
457
502
  limit
@@ -519,9 +564,26 @@ var buildReplayMismatchDrilldown = ({
519
564
  toleranceMs,
520
565
  timestampOffsetMs: 0
521
566
  });
522
- const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestCandidate?.nearest ? {
567
+ const nearestRuntimeEvaluationOutcome = findNearestRuntimeEvaluationOutcome(
568
+ {
569
+ entry,
570
+ lineageScopes: runtimeLineageScopes,
571
+ toleranceMs
572
+ }
573
+ );
574
+ const runtimeLineageCoversEntry = hasRuntimeLineageCoverage({
575
+ entry,
576
+ lineageScopes: runtimeLineageScopes
577
+ });
578
+ const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestRuntimeEvaluationOutcome ? {
579
+ classification: "core_skipped",
580
+ reason: nearestRuntimeEvaluationOutcome.reason
581
+ } : nearestCandidate?.nearest ? {
523
582
  classification: "timing_or_price_drift",
524
583
  reason: nearestCandidate.reason
584
+ } : runtimeLineageCoversEntry ? {
585
+ classification: "runtime_evaluation_detail_unavailable",
586
+ reason: "runtime_scope_covers_timestamp_but_skip_detail_was_not_stored"
525
587
  } : {
526
588
  classification: "no_runtime_evaluation",
527
589
  reason: "no_runtime_signal_or_evaluation"
@@ -545,7 +607,8 @@ var buildReplayMismatchDrilldown = ({
545
607
  ...includeArtifacts ? {
546
608
  runtimeEvaluationArtifact: nearestRuntimeEvaluation.evaluation
547
609
  } : {}
548
- } : {}
610
+ } : {},
611
+ ...nearestRuntimeEvaluationOutcome ? { runtimeEvaluationOutcome: nearestRuntimeEvaluationOutcome } : {}
549
612
  };
550
613
  });
551
614
  for (const diagnostic of runtimeOnlyDiagnostics) {
@@ -592,7 +655,8 @@ var buildReplayRuntimeComparisonDetails = ({
592
655
  runtimeSignals,
593
656
  runtimeSignalEvaluations,
594
657
  replaySignals,
595
- replaySignalEvaluations
658
+ replaySignalEvaluations,
659
+ runtimeLineageScopes
596
660
  }) => {
597
661
  const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
598
662
  const backtestDetails = backtestEntries.map(
@@ -651,6 +715,7 @@ var buildReplayRuntimeComparisonDetails = ({
651
715
  runtimeSignalEvaluations,
652
716
  replaySignals,
653
717
  replaySignalEvaluations,
718
+ runtimeLineageScopes,
654
719
  toleranceMs,
655
720
  backtestTimestampOffsetMs,
656
721
  limit
@@ -671,7 +736,8 @@ var buildReplayExchangeComparisonDetails = ({
671
736
  runtimeSignals,
672
737
  runtimeSignalEvaluations,
673
738
  replaySignals,
674
- replaySignalEvaluations
739
+ replaySignalEvaluations,
740
+ runtimeLineageScopes
675
741
  }) => {
676
742
  const toExchangeDetail = (entry) => toExchangeParityDetail({
677
743
  entry,
@@ -758,6 +824,7 @@ var buildReplayExchangeComparisonDetails = ({
758
824
  runtimeSignalEvaluations,
759
825
  replaySignals,
760
826
  replaySignalEvaluations,
827
+ runtimeLineageScopes,
761
828
  toleranceMs,
762
829
  backtestTimestampOffsetMs,
763
830
  limit
@@ -426,6 +426,20 @@ var countBy = (items, getter) => {
426
426
  Object.entries(counts).sort(([left], [right]) => left.localeCompare(right))
427
427
  );
428
428
  };
429
+ var resolveGateDecision = (evaluation) => {
430
+ const analysis = asRecord(evaluation.aiAnalysis);
431
+ const explicit = finiteString(analysis?.gateDecision);
432
+ if (explicit === "approved" || explicit === "rejected") {
433
+ return { decision: explicit, source: "gate_decision" };
434
+ }
435
+ if (typeof analysis?.approved === "boolean") {
436
+ return {
437
+ decision: analysis.approved ? "approved" : "rejected",
438
+ source: "legacy_approved_fallback"
439
+ };
440
+ }
441
+ return null;
442
+ };
429
443
  var buildDistributions = ({
430
444
  evaluations,
431
445
  signals
@@ -606,18 +620,15 @@ var buildRuntimeScorecard = ({
606
620
  const signalEvaluations = rows.evaluations.filter(
607
621
  (evaluation) => evaluation.status === "signal"
608
622
  );
609
- const gateDecisions = signalEvaluations.map((evaluation) => ({
610
- evaluation,
611
- decision: finiteString(asRecord(evaluation.aiAnalysis)?.gateDecision)
612
- })).filter(
613
- ({ decision }) => decision === "approved" || decision === "rejected"
623
+ const gateDecisions = signalEvaluations.map(resolveGateDecision).filter(
624
+ (decision) => Boolean(decision)
614
625
  );
615
626
  const llmEligibleEvaluations = llmComparatorPolicy === "disabled" ? [] : llmComparatorPolicy === "ai_approved_only" ? signalEvaluations.filter(
616
- (evaluation) => finiteString(asRecord(evaluation.aiAnalysis)?.gateDecision) === "approved"
627
+ (evaluation) => resolveGateDecision(evaluation)?.decision === "approved"
617
628
  ) : signalEvaluations;
618
629
  const gateComparisons = llmEligibleEvaluations.map((evaluation) => {
619
630
  const analysis = asRecord(evaluation.aiAnalysis);
620
- const gateDecision = finiteString(analysis?.gateDecision);
631
+ const gateDecision = resolveGateDecision(evaluation)?.decision ?? null;
621
632
  const llmDecision = finiteString(analysis?.llmDecision);
622
633
  if (gateDecision !== "approved" && gateDecision !== "rejected" || llmDecision !== "approved" && llmDecision !== "rejected") {
623
634
  return null;
@@ -652,6 +663,27 @@ var buildRuntimeScorecard = ({
652
663
  };
653
664
  const actualSlippageBps = averageSampleMetric("signalToFillAdverseBps");
654
665
  const residualVsModelBps = averageSampleMetric("residualVsCurrentModelBps");
666
+ const signalToArrivalAdverseBps = averageSampleMetric(
667
+ "signalToArrivalAdverseBps"
668
+ );
669
+ const arrivalToFillAdverseBps = averageSampleMetric(
670
+ "arrivalToFillAdverseBps"
671
+ );
672
+ const averageSignalCloseToSubmitMs = averageSampleMetric(
673
+ "signalCloseToSubmitMs"
674
+ );
675
+ const averageSubmitToFillMs = averageSampleMetric("submitToFillMs");
676
+ const fullTelemetryTrades = calibrationSamples.filter(
677
+ (sample) => finiteString(sample.telemetryQuality) === "full"
678
+ ).length;
679
+ const arrivalQuoteTrades = calibrationSamples.filter(
680
+ (sample) => finiteNumber(sample.arrivalMid) != null
681
+ ).length;
682
+ const arrivalQuoteCoverage = calibrationSamples.length ? round(arrivalQuoteTrades / calibrationSamples.length) : null;
683
+ const hasTelemetryQuality = calibrationSamples.some(
684
+ (sample) => finiteString(sample.telemetryQuality) != null
685
+ );
686
+ const maximumSignalCloseToSubmitMs = thresholds.maximumSignalCloseToSubmitMs ?? 3e4;
655
687
  const prospective = getProspectiveSummary(prospectiveEvidenceArtifact);
656
688
  const historyTrades = dedupeTrades([
657
689
  ...scopedHistoryArtifacts,
@@ -708,7 +740,28 @@ var buildRuntimeScorecard = ({
708
740
  reactions.push({
709
741
  code: "SLIPPAGE_DRIFT",
710
742
  severity: "warning",
711
- message: `Execution residual ${round(residualVsModelBps, 2)} bps exceeds ${thresholds.maximumSlippageResidualBps} bps.`
743
+ message: `Signal-close-to-fill residual ${round(residualVsModelBps, 2)} bps exceeds ${thresholds.maximumSlippageResidualBps} bps.`
744
+ });
745
+ }
746
+ if (averageSignalCloseToSubmitMs != null && averageSignalCloseToSubmitMs > maximumSignalCloseToSubmitMs) {
747
+ reactions.push({
748
+ code: "PRE_SUBMIT_LATENCY",
749
+ severity: "warning",
750
+ message: `Average signal-close-to-submit latency ${round(averageSignalCloseToSubmitMs, 3)} ms exceeds ${maximumSignalCloseToSubmitMs} ms.`
751
+ });
752
+ }
753
+ if (hasTelemetryQuality && calibrationSamples.length > 0 && fullTelemetryTrades < calibrationSamples.length) {
754
+ reactions.push({
755
+ code: "EXECUTION_TELEMETRY_INCOMPLETE",
756
+ severity: "info",
757
+ message: `Full arrival and fill telemetry is available for ${fullTelemetryTrades}/${calibrationSamples.length} trades.`
758
+ });
759
+ }
760
+ if (riskDecisions.some((decision) => decision.status === "rejected")) {
761
+ reactions.push({
762
+ code: "RISK_PLAN_EXCEEDS_DECLARED_LIMIT",
763
+ severity: "warning",
764
+ message: "At least one observed order plan exceeds its declared MAX_LOSS_VALUE; telemetry is not yet enforced."
712
765
  });
713
766
  }
714
767
  if (sevenDay.closedTrades >= thresholds.minimumClosedTrades && sevenDay.expectancy != null && sevenDay.expectancy < thresholds.minimumExpectancy) {
@@ -737,7 +790,10 @@ var buildRuntimeScorecard = ({
737
790
  deployment: deploymentBinding,
738
791
  lineage: runtimeLineage,
739
792
  promotionStatus,
740
- thresholds,
793
+ thresholds: {
794
+ ...thresholds,
795
+ maximumSignalCloseToSubmitMs
796
+ },
741
797
  funnel: {
742
798
  evaluations: rows.evaluations.length,
743
799
  coreCandidates: signalEvaluations.length,
@@ -749,25 +805,38 @@ var buildRuntimeScorecard = ({
749
805
  rejected: gateDecisions.filter(
750
806
  ({ decision }) => decision === "rejected"
751
807
  ).length,
752
- coverage: signalEvaluations.length ? round(gateDecisions.length / signalEvaluations.length) : null
808
+ coverage: signalEvaluations.length ? round(gateDecisions.length / signalEvaluations.length) : null,
809
+ legacyApprovedFallback: gateDecisions.filter(
810
+ ({ source }) => source === "legacy_approved_fallback"
811
+ ).length
753
812
  },
754
813
  allocator: {
755
814
  available: allocatorDecisions.length > 0,
756
815
  approved: allocatorDecisions.filter(
757
- (decision) => decision.decision === "approved"
816
+ (decision) => decision.status === "approved"
758
817
  ).length,
759
818
  rejected: allocatorDecisions.filter(
760
- (decision) => decision.decision === "rejected"
819
+ (decision) => decision.status === "rejected"
820
+ ).length,
821
+ notApplicable: allocatorDecisions.filter(
822
+ (decision) => decision.status === "not_applicable"
823
+ ).length,
824
+ unavailable: allocatorDecisions.filter(
825
+ (decision) => decision.status === "unavailable"
761
826
  ).length
762
827
  },
763
828
  risk: {
764
829
  available: riskDecisions.length > 0,
765
830
  approved: riskDecisions.filter(
766
- (decision) => decision.decision === "approved"
831
+ (decision) => decision.status === "approved"
767
832
  ).length,
768
833
  rejected: riskDecisions.filter(
769
- (decision) => decision.decision === "rejected"
770
- ).length
834
+ (decision) => decision.status === "rejected"
835
+ ).length,
836
+ unavailable: riskDecisions.filter(
837
+ (decision) => decision.status === "unavailable"
838
+ ).length,
839
+ enforced: riskDecisions.some((decision) => decision.enforced === true)
771
840
  },
772
841
  orderAttempts: orderAttempts.length,
773
842
  orderFailures: orderAttempts.filter(
@@ -829,8 +898,16 @@ var buildRuntimeScorecard = ({
829
898
  },
830
899
  execution: {
831
900
  available: actualSlippageBps != null || residualVsModelBps != null,
901
+ metricScope: arrivalToFillAdverseBps == null ? "signal_close_to_fill" : "signal_and_exchange_components",
902
+ samples: calibrationSamples.length,
903
+ fullTelemetryTrades,
904
+ arrivalQuoteCoverage,
832
905
  actualSignalToFillSlippageBps: actualSlippageBps,
833
- residualVsCurrentModelBps: residualVsModelBps
906
+ residualVsCurrentModelBps: residualVsModelBps,
907
+ signalToArrivalAdverseBps,
908
+ arrivalToFillAdverseBps,
909
+ averageSignalCloseToSubmitMs,
910
+ averageSubmitToFillMs
834
911
  },
835
912
  prospective,
836
913
  rolling,
@@ -884,10 +961,10 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
884
961
 
885
962
  - Evaluations: ${scorecard.funnel.evaluations}
886
963
  - Core candidates: ${scorecard.funnel.coreCandidates}
887
- - Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected}
964
+ - Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected} (legacy fallback: ${scorecard.funnel.gate.legacyApprovedFallback})
888
965
  - AI / LLM disagreement: ${scorecard.gateComparison.disagreements}/${scorecard.gateComparison.compared}
889
- - Allocator approvals/rejects: ${scorecard.funnel.allocator.available ? `${scorecard.funnel.allocator.approved}/${scorecard.funnel.allocator.rejected}` : "unavailable"}
890
- - Risk approvals/rejects: ${scorecard.funnel.risk.available ? `${scorecard.funnel.risk.approved}/${scorecard.funnel.risk.rejected}` : "unavailable"}
966
+ - Runtime admission approved/rejected/not-applicable: ${scorecard.funnel.allocator.available ? `${scorecard.funnel.allocator.approved}/${scorecard.funnel.allocator.rejected}/${scorecard.funnel.allocator.notApplicable}` : "unavailable"}
967
+ - Risk assessment within/exceeds/unavailable: ${scorecard.funnel.risk.available ? `${scorecard.funnel.risk.approved}/${scorecard.funnel.risk.rejected}/${scorecard.funnel.risk.unavailable}` : "unavailable"} (enforced: ${scorecard.funnel.risk.enforced})
891
968
  - Order attempts/failures: ${scorecard.funnel.orderAttempts}/${scorecard.funnel.orderFailures}
892
969
  - Fills: ${scorecard.funnel.fills}
893
970
  - Closed trades: ${scorecard.funnel.closedTrades}
@@ -896,8 +973,14 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
896
973
 
897
974
  - Parity: ${scorecard.parity.ratio == null ? "n/a" : `${round(scorecard.parity.ratio * 100, 2)}%`}
898
975
  - Runtime/replay mismatches: ${scorecard.parity.backtestOnly + scorecard.parity.runtimeOnly}
899
- - Actual signal-to-fill slippage: ${scorecard.execution.actualSignalToFillSlippageBps ?? "n/a"} bps
976
+ - Execution metric scope: ${scorecard.execution.metricScope}
977
+ - Telemetry samples/full/arrival coverage: ${scorecard.execution.samples}/${scorecard.execution.fullTelemetryTrades}/${scorecard.execution.arrivalQuoteCoverage == null ? "n/a" : `${round(scorecard.execution.arrivalQuoteCoverage * 100, 2)}%`}
978
+ - Signal-close-to-fill adverse move: ${scorecard.execution.actualSignalToFillSlippageBps ?? "n/a"} bps
979
+ - Signal-to-arrival adverse move: ${scorecard.execution.signalToArrivalAdverseBps ?? "n/a"} bps
980
+ - Arrival-to-fill exchange slippage: ${scorecard.execution.arrivalToFillAdverseBps ?? "n/a"} bps
900
981
  - Residual vs current model: ${scorecard.execution.residualVsCurrentModelBps ?? "n/a"} bps
982
+ - Average signal-close-to-submit: ${scorecard.execution.averageSignalCloseToSubmitMs ?? "n/a"} ms
983
+ - Average submit-to-fill: ${scorecard.execution.averageSubmitToFillMs ?? "n/a"} ms
901
984
 
902
985
  ## Rolling performance
903
986
 
@@ -32,6 +32,7 @@ var import_redis = require("@tradejs/infra/redis");
32
32
 
33
33
  // src/lib/runtimeSignalsStorage.ts
34
34
  var import_constants = require("@tradejs/core/constants");
35
+ var import_data = require("@tradejs/core/data");
35
36
  var import_time = require("@tradejs/core/time");
36
37
  var isRuntimeSignalBucketRef = (value) => {
37
38
  if (!value || typeof value !== "object") {
@@ -23,6 +23,7 @@ __export(runtimeSignalsStorage_exports, {
23
23
  DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS: () => DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS,
24
24
  RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS: () => RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS,
25
25
  RUNTIME_SIGNAL_RETENTION_DAYS_ENV: () => RUNTIME_SIGNAL_RETENTION_DAYS_ENV,
26
+ appendRuntimeSignalEvaluationRun: () => appendRuntimeSignalEvaluationRun,
26
27
  buildRuntimeSignalStatsIncrements: () => buildRuntimeSignalStatsIncrements,
27
28
  getRuntimeSignalRetentionTtlSeconds: () => getRuntimeSignalRetentionTtlSeconds,
28
29
  getRuntimeStorageDayKey: () => import_time.getRuntimeStorageDayKey,
@@ -37,7 +38,38 @@ __export(runtimeSignalsStorage_exports, {
37
38
  });
38
39
  module.exports = __toCommonJS(runtimeSignalsStorage_exports);
39
40
  var import_constants = require("@tradejs/core/constants");
41
+ var import_data = require("@tradejs/core/data");
40
42
  var import_time = require("@tradejs/core/time");
43
+ var appendRuntimeSignalEvaluationRun = (runs = [], evaluation) => {
44
+ if (evaluation.status !== "skip") {
45
+ return [...runs];
46
+ }
47
+ const stepMs = (0, import_data.intervalToMs)(evaluation.interval);
48
+ const reason = evaluation.reason?.trim() || "NO_SIGNAL";
49
+ const existing = runs.find(
50
+ (run) => run.status === "skip" && run.reason === reason && run.stepMs === stepMs && evaluation.timestamp >= run.firstTimestamp && evaluation.timestamp <= run.lastTimestamp && (evaluation.timestamp - run.firstTimestamp) % stepMs === 0
51
+ );
52
+ if (existing) {
53
+ return [...runs];
54
+ }
55
+ const last = runs.at(-1);
56
+ if (last?.status === "skip" && last.reason === reason && last.stepMs === stepMs && evaluation.timestamp === last.lastTimestamp + stepMs) {
57
+ return [
58
+ ...runs.slice(0, -1),
59
+ { ...last, lastTimestamp: evaluation.timestamp }
60
+ ];
61
+ }
62
+ return [
63
+ ...runs,
64
+ {
65
+ status: "skip",
66
+ reason,
67
+ firstTimestamp: evaluation.timestamp,
68
+ lastTimestamp: evaluation.timestamp,
69
+ stepMs
70
+ }
71
+ ];
72
+ };
41
73
  var SECONDS_PER_DAY = 86400;
42
74
  var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
43
75
  var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants.TTL_3D;
@@ -175,6 +207,7 @@ var parseRuntimeSignalStatsBucket = (value) => {
175
207
  DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS,
176
208
  RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS,
177
209
  RUNTIME_SIGNAL_RETENTION_DAYS_ENV,
210
+ appendRuntimeSignalEvaluationRun,
178
211
  buildRuntimeSignalStatsIncrements,
179
212
  getRuntimeSignalRetentionTtlSeconds,
180
213
  getRuntimeStorageDayKey,
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(replay_exports);
37
37
  // src/scripts/replayRunner.ts
38
38
  var import_chalk13 = __toESM(require("chalk"));
39
39
  var import_constants6 = require("@tradejs/core/constants");
40
- var import_data2 = require("@tradejs/core/data");
40
+ var import_data3 = require("@tradejs/core/data");
41
41
  var import_time7 = require("@tradejs/core/time");
42
42
  var import_redis6 = require("@tradejs/infra/redis");
43
43
 
@@ -6440,6 +6440,7 @@ var import_redis4 = require("@tradejs/infra/redis");
6440
6440
 
6441
6441
  // src/lib/runtimeSignalsStorage.ts
6442
6442
  var import_constants4 = require("@tradejs/core/constants");
6443
+ var import_data = require("@tradejs/core/data");
6443
6444
  var import_time4 = require("@tradejs/core/time");
6444
6445
  var isRuntimeSignalBucketRef = (value) => {
6445
6446
  if (!value || typeof value !== "object") {
@@ -6988,6 +6989,50 @@ var findNearestEvaluation = ({
6988
6989
  timestampDiffMs: bestDiff
6989
6990
  } : null;
6990
6991
  };
6992
+ var findNearestRuntimeEvaluationOutcome = ({
6993
+ entry,
6994
+ lineageScopes,
6995
+ toleranceMs
6996
+ }) => {
6997
+ const strategy = getEntryStrategy(entry);
6998
+ const timestamp = entry.signalTimestamp ?? entry.timestamp;
6999
+ if (!strategy) return null;
7000
+ let nearest = null;
7001
+ for (const scope of lineageScopes) {
7002
+ if (scope.strategy !== strategy || scope.symbol !== entry.symbol) {
7003
+ continue;
7004
+ }
7005
+ for (const run of scope.evaluationRuns ?? []) {
7006
+ if (run.stepMs <= 0 || timestamp < run.firstTimestamp - toleranceMs || timestamp > run.lastTimestamp + toleranceMs) {
7007
+ continue;
7008
+ }
7009
+ const step = Math.round((timestamp - run.firstTimestamp) / run.stepMs);
7010
+ const candidateTimestamp = run.firstTimestamp + step * run.stepMs;
7011
+ const timestampDiffMs = Math.abs(candidateTimestamp - timestamp);
7012
+ if (candidateTimestamp < run.firstTimestamp || candidateTimestamp > run.lastTimestamp || timestampDiffMs > toleranceMs || nearest && timestampDiffMs >= nearest.timestampDiffMs) {
7013
+ continue;
7014
+ }
7015
+ nearest = {
7016
+ status: run.status,
7017
+ reason: run.reason,
7018
+ timestamp: candidateTimestamp,
7019
+ timestampDiffMs,
7020
+ source: "lineage_scope_compact"
7021
+ };
7022
+ }
7023
+ }
7024
+ return nearest;
7025
+ };
7026
+ var hasRuntimeLineageCoverage = ({
7027
+ entry,
7028
+ lineageScopes
7029
+ }) => {
7030
+ const strategy = getEntryStrategy(entry);
7031
+ const timestamp = entry.signalTimestamp ?? entry.timestamp;
7032
+ return lineageScopes.some(
7033
+ (scope) => scope.strategy === strategy && scope.symbol === entry.symbol && timestamp >= scope.firstTimestamp && timestamp <= scope.lastTimestamp
7034
+ );
7035
+ };
6991
7036
  var classifySignalDiagnostic = (signal) => {
6992
7037
  if (signal.orderStatus === "failed") {
6993
7038
  return {
@@ -7232,6 +7277,7 @@ var buildReplayMismatchDrilldown = ({
7232
7277
  runtimeSignalEvaluations = [],
7233
7278
  replaySignals = [],
7234
7279
  replaySignalEvaluations = [],
7280
+ runtimeLineageScopes = [],
7235
7281
  toleranceMs,
7236
7282
  backtestTimestampOffsetMs,
7237
7283
  limit
@@ -7299,9 +7345,26 @@ var buildReplayMismatchDrilldown = ({
7299
7345
  toleranceMs,
7300
7346
  timestampOffsetMs: 0
7301
7347
  });
7302
- const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestCandidate?.nearest ? {
7348
+ const nearestRuntimeEvaluationOutcome = findNearestRuntimeEvaluationOutcome(
7349
+ {
7350
+ entry,
7351
+ lineageScopes: runtimeLineageScopes,
7352
+ toleranceMs
7353
+ }
7354
+ );
7355
+ const runtimeLineageCoversEntry = hasRuntimeLineageCoverage({
7356
+ entry,
7357
+ lineageScopes: runtimeLineageScopes
7358
+ });
7359
+ const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestRuntimeEvaluationOutcome ? {
7360
+ classification: "core_skipped",
7361
+ reason: nearestRuntimeEvaluationOutcome.reason
7362
+ } : nearestCandidate?.nearest ? {
7303
7363
  classification: "timing_or_price_drift",
7304
7364
  reason: nearestCandidate.reason
7365
+ } : runtimeLineageCoversEntry ? {
7366
+ classification: "runtime_evaluation_detail_unavailable",
7367
+ reason: "runtime_scope_covers_timestamp_but_skip_detail_was_not_stored"
7305
7368
  } : {
7306
7369
  classification: "no_runtime_evaluation",
7307
7370
  reason: "no_runtime_signal_or_evaluation"
@@ -7325,7 +7388,8 @@ var buildReplayMismatchDrilldown = ({
7325
7388
  ...includeArtifacts ? {
7326
7389
  runtimeEvaluationArtifact: nearestRuntimeEvaluation.evaluation
7327
7390
  } : {}
7328
- } : {}
7391
+ } : {},
7392
+ ...nearestRuntimeEvaluationOutcome ? { runtimeEvaluationOutcome: nearestRuntimeEvaluationOutcome } : {}
7329
7393
  };
7330
7394
  });
7331
7395
  for (const diagnostic of runtimeOnlyDiagnostics) {
@@ -7372,7 +7436,8 @@ var buildReplayRuntimeComparisonDetails = ({
7372
7436
  runtimeSignals,
7373
7437
  runtimeSignalEvaluations,
7374
7438
  replaySignals,
7375
- replaySignalEvaluations
7439
+ replaySignalEvaluations,
7440
+ runtimeLineageScopes
7376
7441
  }) => {
7377
7442
  const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
7378
7443
  const backtestDetails = backtestEntries.map(
@@ -7431,6 +7496,7 @@ var buildReplayRuntimeComparisonDetails = ({
7431
7496
  runtimeSignalEvaluations,
7432
7497
  replaySignals,
7433
7498
  replaySignalEvaluations,
7499
+ runtimeLineageScopes,
7434
7500
  toleranceMs,
7435
7501
  backtestTimestampOffsetMs,
7436
7502
  limit
@@ -7451,7 +7517,8 @@ var buildReplayExchangeComparisonDetails = ({
7451
7517
  runtimeSignals,
7452
7518
  runtimeSignalEvaluations,
7453
7519
  replaySignals,
7454
- replaySignalEvaluations
7520
+ replaySignalEvaluations,
7521
+ runtimeLineageScopes
7455
7522
  }) => {
7456
7523
  const toExchangeDetail = (entry) => toExchangeParityDetail({
7457
7524
  entry,
@@ -7538,6 +7605,7 @@ var buildReplayExchangeComparisonDetails = ({
7538
7605
  runtimeSignalEvaluations,
7539
7606
  replaySignals,
7540
7607
  replaySignalEvaluations,
7608
+ runtimeLineageScopes,
7541
7609
  toleranceMs,
7542
7610
  backtestTimestampOffsetMs,
7543
7611
  limit
@@ -7673,6 +7741,7 @@ var filterReplayComparisonByLineage = ({
7673
7741
  runtimeTrades: comparableRuntimeTrades,
7674
7742
  runtimeSignals: comparableRuntimeSignals,
7675
7743
  runtimeSignalEvaluations: comparableRuntimeEvaluations,
7744
+ runtimeLineageScopes: comparableRuntimeLineageScopes,
7676
7745
  backtestEntries: comparableBacktestEntries,
7677
7746
  lineage: {
7678
7747
  enforced: true,
@@ -8269,6 +8338,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8269
8338
  replaySignalEvaluations,
8270
8339
  runtimeSignals,
8271
8340
  runtimeSignalEvaluations,
8341
+ runtimeLineageScopes,
8272
8342
  lineage
8273
8343
  }) => {
8274
8344
  const { connector, connectorName, window } = getRuntimeCompareContext();
@@ -8320,7 +8390,8 @@ var saveAndPrintReplayExchangeComparison = async ({
8320
8390
  runtimeSignals,
8321
8391
  runtimeSignalEvaluations,
8322
8392
  replaySignals,
8323
- replaySignalEvaluations
8393
+ replaySignalEvaluations,
8394
+ runtimeLineageScopes
8324
8395
  });
8325
8396
  const emptyRowsByStrategy = new Map(
8326
8397
  buildExchangeComparisonRows({
@@ -8376,7 +8447,8 @@ var saveAndPrintReplayExchangeComparison = async ({
8376
8447
  runtimeSignals,
8377
8448
  runtimeSignalEvaluations,
8378
8449
  replaySignals,
8379
- replaySignalEvaluations
8450
+ replaySignalEvaluations,
8451
+ runtimeLineageScopes
8380
8452
  });
8381
8453
  const rows = buildExchangeComparisonRows({
8382
8454
  liveStrategySummaries,
@@ -8527,6 +8599,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8527
8599
  replaySignalEvaluations,
8528
8600
  runtimeSignals: comparable.runtimeSignals,
8529
8601
  runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
8602
+ runtimeLineageScopes: comparable.runtimeLineageScopes,
8530
8603
  lineage: comparable.lineage
8531
8604
  });
8532
8605
  }
@@ -8550,7 +8623,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8550
8623
  runtimeSignals: comparable.runtimeSignals,
8551
8624
  runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
8552
8625
  replaySignals,
8553
- replaySignalEvaluations
8626
+ replaySignalEvaluations,
8627
+ runtimeLineageScopes: comparable.runtimeLineageScopes
8554
8628
  });
8555
8629
  const parityRows = summarizeTradeParityByStrategy({
8556
8630
  runtimeEntries: runtimeDedupe.entries,
@@ -8593,7 +8667,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8593
8667
  // src/lib/replay/outputReport.ts
8594
8668
  var import_promises2 = __toESM(require("fs/promises"));
8595
8669
  var import_node_path2 = __toESM(require("path"));
8596
- var import_data = require("@tradejs/core/data");
8670
+ var import_data2 = require("@tradejs/core/data");
8597
8671
  var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
8598
8672
  timeZone: "Europe/Moscow",
8599
8673
  dateStyle: "short",
@@ -9000,7 +9074,7 @@ var writeReplayOutputReport = async ({
9000
9074
  ];
9001
9075
  await Promise.all([
9002
9076
  import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
9003
- import_promises2.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
9077
+ import_promises2.default.writeFile(jsonPath, `${(0, import_data2.toJson)(report, true)}
9004
9078
  `, "utf8")
9005
9079
  ]);
9006
9080
  return {
@@ -9208,7 +9282,7 @@ var replayBacktest = async () => {
9208
9282
  deploymentId: deployment.id,
9209
9283
  assetClasses: deployment.assetClasses,
9210
9284
  deployment,
9211
- closedIntervalMs: (0, import_data2.intervalToMs)(replayInterval)
9285
+ closedIntervalMs: (0, import_data3.intervalToMs)(replayInterval)
9212
9286
  });
9213
9287
  if (!preparedRun || isReplayUpdateOnlyRun) {
9214
9288
  return;