@tradejs/cli 3.1.29-beta.289 → 3.1.29-beta.290

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.
@@ -39,6 +39,7 @@ module.exports = __toCommonJS(runtimeFeedbackReplay_exports);
39
39
  var import_node_child_process = require("child_process");
40
40
  var import_promises = __toESM(require("fs/promises"));
41
41
  var import_node_path = __toESM(require("path"));
42
+ var import_mcp = require("@tradejs/node/mcp");
42
43
 
43
44
  // src/lib/runtimeEvidenceDeployment.ts
44
45
  var runtimeEvidenceDeployment_exports = {};
@@ -186,44 +187,58 @@ var runLoggedCommand = async ({
186
187
  env,
187
188
  logHandle
188
189
  }) => {
189
- await logHandle.write(`
190
- $ ${[executable, ...command].join(" ")}
191
- `);
192
- await new Promise((resolve, reject) => {
193
- let pendingLogWrite = Promise.resolve();
194
- const appendLog = (chunk) => {
195
- pendingLogWrite = pendingLogWrite.then(async () => {
196
- await logHandle.write(chunk);
197
- });
198
- };
199
- const child = (0, import_node_child_process.spawn)(executable, command, {
200
- cwd,
201
- env,
202
- stdio: ["ignore", "pipe", "pipe"]
203
- });
204
- child.stdout.on("data", (chunk) => {
205
- process.stdout.write(chunk);
206
- appendLog(chunk);
207
- });
208
- child.stderr.on("data", (chunk) => {
209
- process.stderr.write(chunk);
210
- appendLog(chunk);
211
- });
190
+ await logHandle.writeFile(
191
+ "\n" + (0, import_mcp.formatMcpArtifactLogLine)(`$ ${[executable, ...command].join(" ")}`)
192
+ );
193
+ const child = (0, import_node_child_process.spawn)(executable, command, {
194
+ cwd,
195
+ env,
196
+ stdio: ["ignore", "pipe", "pipe"]
197
+ });
198
+ const exit = new Promise((resolve, reject) => {
212
199
  child.once("error", reject);
213
200
  child.once("close", (code, signal) => {
214
- void pendingLogWrite.then(() => {
215
- if (code === 0) {
216
- resolve();
217
- return;
218
- }
201
+ if (code === 0) resolve();
202
+ else {
219
203
  reject(
220
204
  new Error(
221
205
  `Runtime feedback command failed: exit=${String(code)} signal=${String(signal)}`
222
206
  )
223
207
  );
224
- }, reject);
208
+ }
225
209
  });
226
210
  });
211
+ let pendingLogWrite = Promise.resolve();
212
+ const copyLog = async (input, output) => {
213
+ const writeLine = async (line) => {
214
+ const formatted = (0, import_mcp.formatMcpArtifactLogLine)(line);
215
+ pendingLogWrite = pendingLogWrite.then(
216
+ () => logHandle.writeFile(formatted)
217
+ );
218
+ await pendingLogWrite;
219
+ output.write(formatted);
220
+ };
221
+ let pendingLine = "";
222
+ try {
223
+ for await (const chunk of input.setEncoding("utf8")) {
224
+ const lines = (pendingLine + chunk).split("\n");
225
+ pendingLine = lines.pop() ?? "";
226
+ for (const line of lines) await writeLine(line);
227
+ }
228
+ if (pendingLine) await writeLine(pendingLine);
229
+ } catch (error) {
230
+ child.kill();
231
+ throw error;
232
+ }
233
+ };
234
+ const results = await Promise.allSettled([
235
+ copyLog(child.stdout, process.stdout),
236
+ copyLog(child.stderr, process.stderr),
237
+ exit
238
+ ]);
239
+ for (const result of results) {
240
+ if (result.status === "rejected") throw result.reason;
241
+ }
227
242
  };
228
243
  var runRuntimeFeedbackReplay = async ({
229
244
  runtimeEvidencePath,
@@ -159,6 +159,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
159
159
  direction: trade.direction,
160
160
  qty: toFiniteNumberOrNull(trade.qty),
161
161
  timestamp: trade.entryTimestamp,
162
+ ...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
162
163
  price: toFiniteNumberOrNull(trade.entryPrice),
163
164
  orderId: trade.orderId,
164
165
  signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
@@ -45,6 +45,13 @@ var getBacktestParityComparisonTimestamp = (entry, timestampOffsetMs = 0) => {
45
45
  return (entry.signalTimestamp ?? entry.timestamp) + offsetMs;
46
46
  };
47
47
 
48
+ // src/lib/replay/historicalSignalsReplayDecisionTrace.ts
49
+ var replayDecisionTraceKey = ({
50
+ strategy,
51
+ symbol,
52
+ signalTimestamp
53
+ }) => JSON.stringify([strategy, symbol, signalTimestamp]);
54
+
48
55
  // src/lib/runtimeParityDetails.ts
49
56
  var DEFAULT_REPLAY_PARITY_DETAILS_LIMIT = 500;
50
57
  var DEFAULT_REPLAY_PARITY_ARTIFACT_LIMIT = 100;
@@ -120,6 +127,7 @@ var toRuntimeParityDetail = (entry) => ({
120
127
  direction: entry.direction,
121
128
  qty: entry.qty ?? null,
122
129
  timestamp: entry.timestamp,
130
+ ...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
123
131
  price: entry.price,
124
132
  exitType: entry.exitType ?? null,
125
133
  exitTimestamp: entry.exitTimestamp ?? null,
@@ -193,7 +201,8 @@ var findNearestEvaluation = ({
193
201
  evaluations,
194
202
  toleranceMs,
195
203
  timestampOffsetMs,
196
- entryTimestampOffsetMs = 0
204
+ entryTimestampOffsetMs = 0,
205
+ matchDirection = false
197
206
  }) => {
198
207
  const entryStrategy = getEntryStrategy(entry);
199
208
  if (!entryStrategy) {
@@ -203,7 +212,7 @@ var findNearestEvaluation = ({
203
212
  let bestDiff = Number.POSITIVE_INFINITY;
204
213
  const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
205
214
  for (const evaluation of evaluations) {
206
- if (evaluation.strategy !== entryStrategy || evaluation.symbol !== entry.symbol) {
215
+ if (evaluation.strategy !== entryStrategy || evaluation.symbol !== entry.symbol || matchDirection && evaluation.direction != null && evaluation.direction !== entry.direction) {
207
216
  continue;
208
217
  }
209
218
  const diff = Math.abs(
@@ -549,6 +558,7 @@ var buildReplayMismatchDrilldown = ({
549
558
  replaySignals = [],
550
559
  replaySignalEvaluations = [],
551
560
  replayLineageScopes = [],
561
+ replayDecisionTraces = [],
552
562
  runtimeLineageScopes = [],
553
563
  toleranceMs,
554
564
  backtestTimestampOffsetMs,
@@ -557,48 +567,66 @@ var buildReplayMismatchDrilldown = ({
557
567
  const nearestByEntry = new Map(
558
568
  nearestCandidates.map((candidate) => [candidate.entry, candidate])
559
569
  );
570
+ const traceByCandle = new Map(
571
+ replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
572
+ );
560
573
  const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
561
574
  let runtimeOnlyArtifactsIncluded = 0;
562
575
  let backtestOnlyArtifactsIncluded = 0;
563
576
  const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
564
- const entryOffsetMs = resolveTradeParityTimestampOffset(
565
- getEntryStrategy(entry) ?? "",
566
- backtestTimestampOffsetMs
567
- );
577
+ const isRuntimeTrade = entry.source === "runtime";
578
+ const decisionEntry = isRuntimeTrade ? entry.signalTimestamp == null ? null : { ...entry, timestamp: entry.signalTimestamp } : entry;
579
+ const decisionClock = {
580
+ toleranceMs: isRuntimeTrade ? 0 : toleranceMs,
581
+ timestampOffsetMs: isRuntimeTrade ? 0 : resolveTradeParityTimestampOffset(
582
+ getEntryStrategy(entry) ?? "",
583
+ backtestTimestampOffsetMs
584
+ )
585
+ };
568
586
  const includeArtifacts = index < artifactLimit;
569
587
  const nearestCandidate = nearestByEntry.get(entry);
570
- const nearestReplaySignal = findNearestSignal({
571
- entry,
588
+ const nearestReplaySignal = decisionEntry && findNearestSignal({
589
+ entry: decisionEntry,
572
590
  signals: replaySignals,
573
- toleranceMs,
574
- timestampOffsetMs: entryOffsetMs
591
+ ...decisionClock
575
592
  });
576
- const nearestReplayEvaluation = findNearestEvaluation({
577
- entry,
593
+ const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
594
+ entry: decisionEntry,
578
595
  evaluations: replaySignalEvaluations,
579
- toleranceMs,
580
- timestampOffsetMs: entryOffsetMs
596
+ matchDirection: isRuntimeTrade,
597
+ ...decisionClock
581
598
  });
582
- const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
583
- entry,
599
+ const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
600
+ entry: decisionEntry,
584
601
  lineageScopes: replayLineageScopes,
585
- toleranceMs,
586
- timestampOffsetMs: entryOffsetMs
602
+ ...decisionClock
587
603
  });
604
+ const strategy = getEntryStrategy(entry);
605
+ const replayDecisionTrace = isRuntimeTrade && strategy && decisionEntry ? traceByCandle.get(
606
+ replayDecisionTraceKey({
607
+ strategy,
608
+ symbol: entry.symbol,
609
+ signalTimestamp: decisionEntry.timestamp
610
+ })
611
+ ) : void 0;
612
+ const unavailableReason = !isRuntimeTrade ? "no_replay_signal_or_backtest_candidate" : !decisionEntry ? "replay_signal_timestamp_unavailable" : replayDecisionTrace?.decision.stage === "not_evaluated" ? replayDecisionTrace.decision.reason : "replay_evaluation_unavailable";
588
613
  const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
589
- classification: "core_skipped",
614
+ classification: nearestReplayEvaluationOutcome.reason.startsWith(
615
+ "BACKTEST_ENTRY_DELAY_"
616
+ ) ? "entry_delayed" : "core_skipped",
590
617
  reason: nearestReplayEvaluationOutcome.reason
591
- } : nearestCandidate?.nearest ? {
618
+ } : !isRuntimeTrade && nearestCandidate?.nearest ? {
592
619
  classification: "timing_or_price_drift",
593
620
  reason: nearestCandidate.reason
594
621
  } : {
595
622
  classification: "no_replay_candidate",
596
- reason: "no_replay_signal_or_backtest_candidate"
623
+ reason: unavailableReason
597
624
  };
598
625
  return {
599
626
  entry,
600
627
  ...classification,
601
628
  ...nearestCandidate ? { nearestCandidate } : {},
629
+ ...replayDecisionTrace ? { replayDecisionTrace } : {},
602
630
  ...nearestReplaySignal ? {
603
631
  replaySignal: toSignalDiagnostic({
604
632
  signal: nearestReplaySignal.signal,
@@ -724,6 +752,7 @@ var buildReplayRuntimeComparisonDetails = ({
724
752
  replaySignals,
725
753
  replaySignalEvaluations,
726
754
  replayLineageScopes,
755
+ replayDecisionTraces,
727
756
  runtimeLineageScopes
728
757
  }) => {
729
758
  const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
@@ -784,6 +813,7 @@ var buildReplayRuntimeComparisonDetails = ({
784
813
  replaySignals,
785
814
  replaySignalEvaluations,
786
815
  replayLineageScopes,
816
+ replayDecisionTraces,
787
817
  runtimeLineageScopes,
788
818
  toleranceMs,
789
819
  backtestTimestampOffsetMs,
@@ -4985,7 +4985,13 @@ var loadReplayRuntimeEvidenceMetadata = async (params) => {
4985
4985
  userName: artifact.userName,
4986
4986
  startTime: artifact.startTime,
4987
4987
  endTime: artifact.endTime,
4988
- deployment: artifact.deployment
4988
+ deployment: artifact.deployment,
4989
+ decisionTraceTargets: unwrapRows(
4990
+ artifact.runtime.trades,
4991
+ "trade"
4992
+ ).flatMap(
4993
+ ({ strategy, symbol, signalTimestamp }) => typeof signalTimestamp === "number" && Number.isFinite(signalTimestamp) && artifact.startTime != null && artifact.endTime != null && signalTimestamp >= artifact.startTime && signalTimestamp < artifact.endTime ? [{ strategy, symbol, signalTimestamp }] : []
4994
+ )
4989
4995
  };
4990
4996
  };
4991
4997
  var buildRuntimeEvidenceLineageScopes = ({
@@ -5588,6 +5594,73 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
5588
5594
  }
5589
5595
  };
5590
5596
 
5597
+ // src/lib/replay/historicalSignalsReplayDecisionTrace.ts
5598
+ var replayDecisionTraceKey = ({
5599
+ strategy,
5600
+ symbol,
5601
+ signalTimestamp
5602
+ }) => JSON.stringify([strategy, symbol, signalTimestamp]);
5603
+ var describeOutcome = (result) => {
5604
+ if (typeof result === "string") {
5605
+ return {
5606
+ stage: result.startsWith("BACKTEST_ENTRY_DELAY_") ? "entry_delay" : /(?:NO_PATTERN|POSITION_EXISTS)$/.test(result) ? "core" : "evaluation",
5607
+ reason: result
5608
+ };
5609
+ }
5610
+ if (!result) return { stage: "evaluation", reason: "NO_SIGNAL" };
5611
+ const reason = result.orderSkipReason || result.orderFailureReason || result.orderStatus || "SIGNAL";
5612
+ const patternContext = result.additionalIndicators?.tradingPatternsContext;
5613
+ return {
5614
+ stage: result.riskDecision?.status === "rejected" || /^ENTRY_(?:RR_BELOW|RISK_EXCEEDS)/.test(reason) ? "risk" : /^(?:AI_|ML_|HOOK_BEFORE_ENTRY_GATE|RESEARCH_ENTRY_POLICY)/.test(
5615
+ reason
5616
+ ) ? "gate" : result.orderStatus ? "execution" : "evaluation",
5617
+ reason,
5618
+ direction: result.direction,
5619
+ orderStatus: result.orderStatus,
5620
+ ...typeof patternContext?.selectedPattern === "string" ? { pattern: patternContext.selectedPattern } : {},
5621
+ ...typeof patternContext?.sourceCode === "string" ? { patternCode: patternContext.sourceCode } : {}
5622
+ };
5623
+ };
5624
+ var createReplayDecisionTrace = (targets = []) => {
5625
+ const traces = /* @__PURE__ */ new Map();
5626
+ const symbols = new Set(targets.map(({ symbol }) => symbol));
5627
+ for (const target of targets) {
5628
+ traces.set(replayDecisionTraceKey(target), {
5629
+ ...target,
5630
+ decision: {
5631
+ stage: "not_evaluated",
5632
+ reason: "REPLAY_CANDLE_NOT_EVALUATED"
5633
+ }
5634
+ });
5635
+ }
5636
+ const record = (strategy, symbol, candle, result, notEvaluatedReason) => {
5637
+ if (!symbols.has(symbol)) return;
5638
+ const { timestamp, open, high, low, close, volume } = candle;
5639
+ const trace = traces.get(
5640
+ replayDecisionTraceKey({ strategy, symbol, signalTimestamp: timestamp })
5641
+ );
5642
+ if (result && typeof result !== "string" && result.timestamp !== timestamp) {
5643
+ const original = traces.get(
5644
+ replayDecisionTraceKey({
5645
+ strategy,
5646
+ symbol,
5647
+ signalTimestamp: result.timestamp
5648
+ })
5649
+ );
5650
+ if (original)
5651
+ original.followUp = {
5652
+ ...describeOutcome(result),
5653
+ candleTimestamp: timestamp
5654
+ };
5655
+ notEvaluatedReason = "REPLAY_PROCESSED_DELAYED_ENTRY";
5656
+ }
5657
+ if (!trace) return;
5658
+ trace.candle = { timestamp, open, high, low, close, volume };
5659
+ trace.decision = notEvaluatedReason ? { stage: "not_evaluated", reason: notEvaluatedReason } : describeOutcome(result);
5660
+ };
5661
+ return { record, values: () => [...traces.values()] };
5662
+ };
5663
+
5591
5664
  // src/lib/replay/historicalSignalsReplaySkipEvidence.ts
5592
5665
  var import_data2 = require("@tradejs/core/data");
5593
5666
  var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
@@ -5698,6 +5771,7 @@ var executeHistoricalReplay = async (context, adapters) => {
5698
5771
  context.plan.runtimeLineages,
5699
5772
  context.collectSkipEvidence
5700
5773
  );
5774
+ const decisionTrace = createReplayDecisionTrace(context.decisionTraceTargets);
5701
5775
  const reportProgress = (timestamp) => {
5702
5776
  if (adapters.progress.enabled === false) return;
5703
5777
  adapters.progress.tick({
@@ -5726,6 +5800,20 @@ var executeHistoricalReplay = async (context, adapters) => {
5726
5800
  status: "completed",
5727
5801
  durationMs: adapters.clock.now() - cycleStartedAt
5728
5802
  });
5803
+ if (context.decisionTraceTargets?.length) {
5804
+ for (const symbolRuntime of cycleSymbols) {
5805
+ const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5806
+ for (const strategy of symbolRuntime.strategies) {
5807
+ decisionTrace.record(
5808
+ strategy.strategyName,
5809
+ symbolRuntime.symbol,
5810
+ candle,
5811
+ void 0,
5812
+ "REPLAY_CYCLE_ABORTED"
5813
+ );
5814
+ }
5815
+ }
5816
+ }
5729
5817
  advanceAbortedCycle(cycleSymbols, timestamp);
5730
5818
  reportProgress(timestamp);
5731
5819
  continue;
@@ -5736,6 +5824,17 @@ var executeHistoricalReplay = async (context, adapters) => {
5736
5824
  const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
5737
5825
  const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
5738
5826
  if (!candle || !btcCandle || candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) !== timestamp || btcCandle.timestamp !== candle.timestamp) {
5827
+ if (candle && context.decisionTraceTargets?.length) {
5828
+ for (const strategy of symbolRuntime.strategies) {
5829
+ decisionTrace.record(
5830
+ strategy.strategyName,
5831
+ symbolRuntime.symbol,
5832
+ candle,
5833
+ void 0,
5834
+ "REPLAY_CANDLE_OR_REFERENCE_NOT_ALIGNED"
5835
+ );
5836
+ }
5837
+ }
5739
5838
  continue;
5740
5839
  }
5741
5840
  for (const strategyRuntime of symbolRuntime.strategies) {
@@ -5744,6 +5843,12 @@ var executeHistoricalReplay = async (context, adapters) => {
5744
5843
  btcCandle,
5745
5844
  ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
5746
5845
  );
5846
+ decisionTrace.record(
5847
+ strategyRuntime.strategyName,
5848
+ symbolRuntime.symbol,
5849
+ candle,
5850
+ result
5851
+ );
5747
5852
  skipEvidence.record({
5748
5853
  strategyName: strategyRuntime.strategyName,
5749
5854
  symbol: symbolRuntime.symbol,
@@ -5775,7 +5880,8 @@ var executeHistoricalReplay = async (context, adapters) => {
5775
5880
  return {
5776
5881
  signals,
5777
5882
  abortedCycles,
5778
- replayLineageScopes: skipEvidence.values()
5883
+ replayLineageScopes: skipEvidence.values(),
5884
+ replayDecisionTraces: decisionTrace.values()
5779
5885
  };
5780
5886
  };
5781
5887
 
@@ -5826,7 +5932,8 @@ var collectHistoricalReplayResult = (context) => ({
5826
5932
  cycleCount: context.cycleCount,
5827
5933
  abortedCycles: context.abortedCycles,
5828
5934
  runtimeLineages: context.runtimeLineages,
5829
- replayLineageScopes: context.replayLineageScopes
5935
+ replayLineageScopes: context.replayLineageScopes,
5936
+ ...context.replayDecisionTraces?.length ? { replayDecisionTraces: context.replayDecisionTraces } : {}
5830
5937
  });
5831
5938
  var compactHistoricalReplayResultForPortfolio = (result) => ({
5832
5939
  ...result,
@@ -5872,7 +5979,10 @@ var mergeHistoricalReplayResults = (parts) => {
5872
5979
  cycleCount: parts.reduce((sum, part) => sum + part.cycleCount, 0),
5873
5980
  abortedCycles: parts.reduce((sum, part) => sum + part.abortedCycles, 0),
5874
5981
  runtimeLineages: parts.flatMap((part) => part.runtimeLineages),
5875
- replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes)
5982
+ replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes),
5983
+ replayDecisionTraces: parts.flatMap(
5984
+ (part) => part.replayDecisionTraces ?? []
5985
+ )
5876
5986
  };
5877
5987
  };
5878
5988
 
@@ -6023,7 +6133,8 @@ var runHistoricalSignalsReplay = async ({
6023
6133
  references,
6024
6134
  showProgress = true,
6025
6135
  collectSkipEvidence = true,
6026
- collectSignals = true
6136
+ collectSignals = true,
6137
+ decisionTraceTargets
6027
6138
  }) => {
6028
6139
  const memory = createReplayMemoryProfile();
6029
6140
  memory?.("prepare:start");
@@ -6110,6 +6221,7 @@ var runHistoricalSignalsReplay = async ({
6110
6221
  connector: replayConnector,
6111
6222
  collectSkipEvidence,
6112
6223
  collectSignals,
6224
+ decisionTraceTargets,
6113
6225
  hooks: projectHooks,
6114
6226
  hookContext
6115
6227
  },
@@ -6146,7 +6258,8 @@ var runHistoricalSignalsReplay = async ({
6146
6258
  cycleCount: plan.orderedTimestamps.length,
6147
6259
  abortedCycles: execution.abortedCycles,
6148
6260
  runtimeLineages: plan.runtimeLineages,
6149
- replayLineageScopes: execution.replayLineageScopes
6261
+ replayLineageScopes: execution.replayLineageScopes,
6262
+ replayDecisionTraces: execution.replayDecisionTraces
6150
6263
  });
6151
6264
  memory?.("result:collected");
6152
6265
  return result;
@@ -6374,6 +6487,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
6374
6487
  direction: trade.direction,
6375
6488
  qty: toFiniteNumberOrNull2(trade.qty),
6376
6489
  timestamp: trade.entryTimestamp,
6490
+ ...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
6377
6491
  price: toFiniteNumberOrNull2(trade.entryPrice),
6378
6492
  orderId: trade.orderId,
6379
6493
  signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
@@ -7119,6 +7233,7 @@ var toRuntimeParityDetail = (entry) => ({
7119
7233
  direction: entry.direction,
7120
7234
  qty: entry.qty ?? null,
7121
7235
  timestamp: entry.timestamp,
7236
+ ...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
7122
7237
  price: entry.price,
7123
7238
  exitType: entry.exitType ?? null,
7124
7239
  exitTimestamp: entry.exitTimestamp ?? null,
@@ -7192,7 +7307,8 @@ var findNearestEvaluation = ({
7192
7307
  evaluations,
7193
7308
  toleranceMs,
7194
7309
  timestampOffsetMs,
7195
- entryTimestampOffsetMs = 0
7310
+ entryTimestampOffsetMs = 0,
7311
+ matchDirection = false
7196
7312
  }) => {
7197
7313
  const entryStrategy2 = getEntryStrategy(entry);
7198
7314
  if (!entryStrategy2) {
@@ -7202,7 +7318,7 @@ var findNearestEvaluation = ({
7202
7318
  let bestDiff = Number.POSITIVE_INFINITY;
7203
7319
  const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
7204
7320
  for (const evaluation of evaluations) {
7205
- if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol) {
7321
+ if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol || matchDirection && evaluation.direction != null && evaluation.direction !== entry.direction) {
7206
7322
  continue;
7207
7323
  }
7208
7324
  const diff = Math.abs(
@@ -7548,6 +7664,7 @@ var buildReplayMismatchDrilldown = ({
7548
7664
  replaySignals = [],
7549
7665
  replaySignalEvaluations = [],
7550
7666
  replayLineageScopes = [],
7667
+ replayDecisionTraces = [],
7551
7668
  runtimeLineageScopes = [],
7552
7669
  toleranceMs,
7553
7670
  backtestTimestampOffsetMs,
@@ -7556,48 +7673,66 @@ var buildReplayMismatchDrilldown = ({
7556
7673
  const nearestByEntry = new Map(
7557
7674
  nearestCandidates.map((candidate) => [candidate.entry, candidate])
7558
7675
  );
7676
+ const traceByCandle = new Map(
7677
+ replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
7678
+ );
7559
7679
  const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
7560
7680
  let runtimeOnlyArtifactsIncluded = 0;
7561
7681
  let backtestOnlyArtifactsIncluded = 0;
7562
7682
  const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
7563
- const entryOffsetMs = resolveTradeParityTimestampOffset(
7564
- getEntryStrategy(entry) ?? "",
7565
- backtestTimestampOffsetMs
7566
- );
7683
+ const isRuntimeTrade = entry.source === "runtime";
7684
+ const decisionEntry = isRuntimeTrade ? entry.signalTimestamp == null ? null : { ...entry, timestamp: entry.signalTimestamp } : entry;
7685
+ const decisionClock = {
7686
+ toleranceMs: isRuntimeTrade ? 0 : toleranceMs,
7687
+ timestampOffsetMs: isRuntimeTrade ? 0 : resolveTradeParityTimestampOffset(
7688
+ getEntryStrategy(entry) ?? "",
7689
+ backtestTimestampOffsetMs
7690
+ )
7691
+ };
7567
7692
  const includeArtifacts = index < artifactLimit;
7568
7693
  const nearestCandidate = nearestByEntry.get(entry);
7569
- const nearestReplaySignal = findNearestSignal({
7570
- entry,
7694
+ const nearestReplaySignal = decisionEntry && findNearestSignal({
7695
+ entry: decisionEntry,
7571
7696
  signals: replaySignals,
7572
- toleranceMs,
7573
- timestampOffsetMs: entryOffsetMs
7697
+ ...decisionClock
7574
7698
  });
7575
- const nearestReplayEvaluation = findNearestEvaluation({
7576
- entry,
7699
+ const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
7700
+ entry: decisionEntry,
7577
7701
  evaluations: replaySignalEvaluations,
7578
- toleranceMs,
7579
- timestampOffsetMs: entryOffsetMs
7702
+ matchDirection: isRuntimeTrade,
7703
+ ...decisionClock
7580
7704
  });
7581
- const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
7582
- entry,
7705
+ const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
7706
+ entry: decisionEntry,
7583
7707
  lineageScopes: replayLineageScopes,
7584
- toleranceMs,
7585
- timestampOffsetMs: entryOffsetMs
7708
+ ...decisionClock
7586
7709
  });
7710
+ const strategy = getEntryStrategy(entry);
7711
+ const replayDecisionTrace = isRuntimeTrade && strategy && decisionEntry ? traceByCandle.get(
7712
+ replayDecisionTraceKey({
7713
+ strategy,
7714
+ symbol: entry.symbol,
7715
+ signalTimestamp: decisionEntry.timestamp
7716
+ })
7717
+ ) : void 0;
7718
+ const unavailableReason = !isRuntimeTrade ? "no_replay_signal_or_backtest_candidate" : !decisionEntry ? "replay_signal_timestamp_unavailable" : replayDecisionTrace?.decision.stage === "not_evaluated" ? replayDecisionTrace.decision.reason : "replay_evaluation_unavailable";
7587
7719
  const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
7588
- classification: "core_skipped",
7720
+ classification: nearestReplayEvaluationOutcome.reason.startsWith(
7721
+ "BACKTEST_ENTRY_DELAY_"
7722
+ ) ? "entry_delayed" : "core_skipped",
7589
7723
  reason: nearestReplayEvaluationOutcome.reason
7590
- } : nearestCandidate?.nearest ? {
7724
+ } : !isRuntimeTrade && nearestCandidate?.nearest ? {
7591
7725
  classification: "timing_or_price_drift",
7592
7726
  reason: nearestCandidate.reason
7593
7727
  } : {
7594
7728
  classification: "no_replay_candidate",
7595
- reason: "no_replay_signal_or_backtest_candidate"
7729
+ reason: unavailableReason
7596
7730
  };
7597
7731
  return {
7598
7732
  entry,
7599
7733
  ...classification,
7600
7734
  ...nearestCandidate ? { nearestCandidate } : {},
7735
+ ...replayDecisionTrace ? { replayDecisionTrace } : {},
7601
7736
  ...nearestReplaySignal ? {
7602
7737
  replaySignal: toSignalDiagnostic({
7603
7738
  signal: nearestReplaySignal.signal,
@@ -7723,6 +7858,7 @@ var buildReplayRuntimeComparisonDetails = ({
7723
7858
  replaySignals,
7724
7859
  replaySignalEvaluations,
7725
7860
  replayLineageScopes,
7861
+ replayDecisionTraces,
7726
7862
  runtimeLineageScopes
7727
7863
  }) => {
7728
7864
  const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
@@ -7783,6 +7919,7 @@ var buildReplayRuntimeComparisonDetails = ({
7783
7919
  replaySignals,
7784
7920
  replaySignalEvaluations,
7785
7921
  replayLineageScopes,
7922
+ replayDecisionTraces,
7786
7923
  runtimeLineageScopes,
7787
7924
  toleranceMs,
7788
7925
  backtestTimestampOffsetMs,
@@ -8808,6 +8945,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8808
8945
  replaySignals,
8809
8946
  replayLineages,
8810
8947
  replayLineageScopes,
8948
+ replayDecisionTraces,
8811
8949
  runtimeEvidencePath
8812
8950
  }) => {
8813
8951
  const { connector, connectorName, window } = getRuntimeCompareContext();
@@ -8959,7 +9097,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8959
9097
  replaySignals,
8960
9098
  replaySignalEvaluations,
8961
9099
  runtimeLineageScopes: comparable.runtimeLineageScopes,
8962
- replayLineageScopes
9100
+ replayLineageScopes,
9101
+ replayDecisionTraces
8963
9102
  });
8964
9103
  const parityRows = summarizeTradeParityByStrategy({
8965
9104
  runtimeEntries: runtimeDedupe.entries,
@@ -9886,6 +10025,7 @@ var finishReplay = async ({
9886
10025
  replaySignals: replayResult.signals,
9887
10026
  replayLineages: replayResult.runtimeLineages,
9888
10027
  replayLineageScopes: replayResult.replayLineageScopes,
10028
+ replayDecisionTraces: replayResult.replayDecisionTraces,
9889
10029
  runtimeEvidencePath: replayRuntimeEvidencePath
9890
10030
  });
9891
10031
  const finishedAt = /* @__PURE__ */ new Date();
@@ -10235,7 +10375,8 @@ var replayBacktest = async () => {
10235
10375
  references: historicalReplayReferences,
10236
10376
  showProgress: !replayFlags.portfolioReport,
10237
10377
  collectSkipEvidence: !replayFlags.portfolioReport,
10238
- collectSignals: !replayFlags.portfolioReport
10378
+ collectSignals: !replayFlags.portfolioReport,
10379
+ decisionTraceTargets: replayFlags.portfolioReport ? void 0 : evidenceMetadata?.decisionTraceTargets
10239
10380
  });
10240
10381
  return replayFlags.portfolioReport ? compactHistoricalReplayResultForPortfolio(result) : result;
10241
10382
  };