@tradejs/cli 3.1.29-beta.288 → 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 = ({
@@ -5386,7 +5392,10 @@ var createPortfolioReplayConnector = (connector) => {
5386
5392
  return {
5387
5393
  __tradejsReplayConnector: true,
5388
5394
  __tradejsTestConnector: true,
5389
- capabilities: connector.capabilities,
5395
+ capabilities: {
5396
+ ...connector.capabilities,
5397
+ orderSubmissionValidation: true
5398
+ },
5390
5399
  universe: connector.universe,
5391
5400
  accountId: connector.accountId,
5392
5401
  deploymentId: connector.deploymentId,
@@ -5418,6 +5427,7 @@ var createPortfolioReplayConnector = (connector) => {
5418
5427
  })
5419
5428
  ),
5420
5429
  placeOrder: async (order) => {
5430
+ const { beforeSubmit, ...submittedOrder } = order;
5421
5431
  if (positionsBySymbol.has(order.symbol)) {
5422
5432
  return false;
5423
5433
  }
@@ -5434,8 +5444,12 @@ var createPortfolioReplayConnector = (connector) => {
5434
5444
  stage: "entry",
5435
5445
  signal: order.signal
5436
5446
  });
5447
+ await beforeSubmit?.({
5448
+ qty: order.qty,
5449
+ executionPrice: entryPrice
5450
+ });
5437
5451
  const nextPosition = {
5438
- ...order,
5452
+ ...submittedOrder,
5439
5453
  timestamp: executionTimestamp ?? order.timestamp,
5440
5454
  price: entryPrice,
5441
5455
  signal: compactReplaySignal(order.signal),
@@ -5460,7 +5474,7 @@ var createPortfolioReplayConnector = (connector) => {
5460
5474
  logOrder({
5461
5475
  positionState,
5462
5476
  data: {
5463
- ...order,
5477
+ ...submittedOrder,
5464
5478
  timestamp: executionTimestamp ?? order.timestamp,
5465
5479
  price: entryPrice,
5466
5480
  profit,
@@ -5580,6 +5594,73 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
5580
5594
  }
5581
5595
  };
5582
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
+
5583
5664
  // src/lib/replay/historicalSignalsReplaySkipEvidence.ts
5584
5665
  var import_data2 = require("@tradejs/core/data");
5585
5666
  var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
@@ -5690,6 +5771,7 @@ var executeHistoricalReplay = async (context, adapters) => {
5690
5771
  context.plan.runtimeLineages,
5691
5772
  context.collectSkipEvidence
5692
5773
  );
5774
+ const decisionTrace = createReplayDecisionTrace(context.decisionTraceTargets);
5693
5775
  const reportProgress = (timestamp) => {
5694
5776
  if (adapters.progress.enabled === false) return;
5695
5777
  adapters.progress.tick({
@@ -5718,6 +5800,20 @@ var executeHistoricalReplay = async (context, adapters) => {
5718
5800
  status: "completed",
5719
5801
  durationMs: adapters.clock.now() - cycleStartedAt
5720
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
+ }
5721
5817
  advanceAbortedCycle(cycleSymbols, timestamp);
5722
5818
  reportProgress(timestamp);
5723
5819
  continue;
@@ -5728,6 +5824,17 @@ var executeHistoricalReplay = async (context, adapters) => {
5728
5824
  const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
5729
5825
  const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
5730
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
+ }
5731
5838
  continue;
5732
5839
  }
5733
5840
  for (const strategyRuntime of symbolRuntime.strategies) {
@@ -5736,6 +5843,12 @@ var executeHistoricalReplay = async (context, adapters) => {
5736
5843
  btcCandle,
5737
5844
  ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
5738
5845
  );
5846
+ decisionTrace.record(
5847
+ strategyRuntime.strategyName,
5848
+ symbolRuntime.symbol,
5849
+ candle,
5850
+ result
5851
+ );
5739
5852
  skipEvidence.record({
5740
5853
  strategyName: strategyRuntime.strategyName,
5741
5854
  symbol: symbolRuntime.symbol,
@@ -5767,7 +5880,8 @@ var executeHistoricalReplay = async (context, adapters) => {
5767
5880
  return {
5768
5881
  signals,
5769
5882
  abortedCycles,
5770
- replayLineageScopes: skipEvidence.values()
5883
+ replayLineageScopes: skipEvidence.values(),
5884
+ replayDecisionTraces: decisionTrace.values()
5771
5885
  };
5772
5886
  };
5773
5887
 
@@ -5818,7 +5932,8 @@ var collectHistoricalReplayResult = (context) => ({
5818
5932
  cycleCount: context.cycleCount,
5819
5933
  abortedCycles: context.abortedCycles,
5820
5934
  runtimeLineages: context.runtimeLineages,
5821
- replayLineageScopes: context.replayLineageScopes
5935
+ replayLineageScopes: context.replayLineageScopes,
5936
+ ...context.replayDecisionTraces?.length ? { replayDecisionTraces: context.replayDecisionTraces } : {}
5822
5937
  });
5823
5938
  var compactHistoricalReplayResultForPortfolio = (result) => ({
5824
5939
  ...result,
@@ -5864,7 +5979,10 @@ var mergeHistoricalReplayResults = (parts) => {
5864
5979
  cycleCount: parts.reduce((sum, part) => sum + part.cycleCount, 0),
5865
5980
  abortedCycles: parts.reduce((sum, part) => sum + part.abortedCycles, 0),
5866
5981
  runtimeLineages: parts.flatMap((part) => part.runtimeLineages),
5867
- replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes)
5982
+ replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes),
5983
+ replayDecisionTraces: parts.flatMap(
5984
+ (part) => part.replayDecisionTraces ?? []
5985
+ )
5868
5986
  };
5869
5987
  };
5870
5988
 
@@ -6015,7 +6133,8 @@ var runHistoricalSignalsReplay = async ({
6015
6133
  references,
6016
6134
  showProgress = true,
6017
6135
  collectSkipEvidence = true,
6018
- collectSignals = true
6136
+ collectSignals = true,
6137
+ decisionTraceTargets
6019
6138
  }) => {
6020
6139
  const memory = createReplayMemoryProfile();
6021
6140
  memory?.("prepare:start");
@@ -6102,6 +6221,7 @@ var runHistoricalSignalsReplay = async ({
6102
6221
  connector: replayConnector,
6103
6222
  collectSkipEvidence,
6104
6223
  collectSignals,
6224
+ decisionTraceTargets,
6105
6225
  hooks: projectHooks,
6106
6226
  hookContext
6107
6227
  },
@@ -6138,7 +6258,8 @@ var runHistoricalSignalsReplay = async ({
6138
6258
  cycleCount: plan.orderedTimestamps.length,
6139
6259
  abortedCycles: execution.abortedCycles,
6140
6260
  runtimeLineages: plan.runtimeLineages,
6141
- replayLineageScopes: execution.replayLineageScopes
6261
+ replayLineageScopes: execution.replayLineageScopes,
6262
+ replayDecisionTraces: execution.replayDecisionTraces
6142
6263
  });
6143
6264
  memory?.("result:collected");
6144
6265
  return result;
@@ -6366,6 +6487,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
6366
6487
  direction: trade.direction,
6367
6488
  qty: toFiniteNumberOrNull2(trade.qty),
6368
6489
  timestamp: trade.entryTimestamp,
6490
+ ...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
6369
6491
  price: toFiniteNumberOrNull2(trade.entryPrice),
6370
6492
  orderId: trade.orderId,
6371
6493
  signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
@@ -7111,6 +7233,7 @@ var toRuntimeParityDetail = (entry) => ({
7111
7233
  direction: entry.direction,
7112
7234
  qty: entry.qty ?? null,
7113
7235
  timestamp: entry.timestamp,
7236
+ ...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
7114
7237
  price: entry.price,
7115
7238
  exitType: entry.exitType ?? null,
7116
7239
  exitTimestamp: entry.exitTimestamp ?? null,
@@ -7184,7 +7307,8 @@ var findNearestEvaluation = ({
7184
7307
  evaluations,
7185
7308
  toleranceMs,
7186
7309
  timestampOffsetMs,
7187
- entryTimestampOffsetMs = 0
7310
+ entryTimestampOffsetMs = 0,
7311
+ matchDirection = false
7188
7312
  }) => {
7189
7313
  const entryStrategy2 = getEntryStrategy(entry);
7190
7314
  if (!entryStrategy2) {
@@ -7194,7 +7318,7 @@ var findNearestEvaluation = ({
7194
7318
  let bestDiff = Number.POSITIVE_INFINITY;
7195
7319
  const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
7196
7320
  for (const evaluation of evaluations) {
7197
- 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) {
7198
7322
  continue;
7199
7323
  }
7200
7324
  const diff = Math.abs(
@@ -7540,6 +7664,7 @@ var buildReplayMismatchDrilldown = ({
7540
7664
  replaySignals = [],
7541
7665
  replaySignalEvaluations = [],
7542
7666
  replayLineageScopes = [],
7667
+ replayDecisionTraces = [],
7543
7668
  runtimeLineageScopes = [],
7544
7669
  toleranceMs,
7545
7670
  backtestTimestampOffsetMs,
@@ -7548,48 +7673,66 @@ var buildReplayMismatchDrilldown = ({
7548
7673
  const nearestByEntry = new Map(
7549
7674
  nearestCandidates.map((candidate) => [candidate.entry, candidate])
7550
7675
  );
7676
+ const traceByCandle = new Map(
7677
+ replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
7678
+ );
7551
7679
  const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
7552
7680
  let runtimeOnlyArtifactsIncluded = 0;
7553
7681
  let backtestOnlyArtifactsIncluded = 0;
7554
7682
  const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
7555
- const entryOffsetMs = resolveTradeParityTimestampOffset(
7556
- getEntryStrategy(entry) ?? "",
7557
- backtestTimestampOffsetMs
7558
- );
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
+ };
7559
7692
  const includeArtifacts = index < artifactLimit;
7560
7693
  const nearestCandidate = nearestByEntry.get(entry);
7561
- const nearestReplaySignal = findNearestSignal({
7562
- entry,
7694
+ const nearestReplaySignal = decisionEntry && findNearestSignal({
7695
+ entry: decisionEntry,
7563
7696
  signals: replaySignals,
7564
- toleranceMs,
7565
- timestampOffsetMs: entryOffsetMs
7697
+ ...decisionClock
7566
7698
  });
7567
- const nearestReplayEvaluation = findNearestEvaluation({
7568
- entry,
7699
+ const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
7700
+ entry: decisionEntry,
7569
7701
  evaluations: replaySignalEvaluations,
7570
- toleranceMs,
7571
- timestampOffsetMs: entryOffsetMs
7702
+ matchDirection: isRuntimeTrade,
7703
+ ...decisionClock
7572
7704
  });
7573
- const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
7574
- entry,
7705
+ const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
7706
+ entry: decisionEntry,
7575
7707
  lineageScopes: replayLineageScopes,
7576
- toleranceMs,
7577
- timestampOffsetMs: entryOffsetMs
7708
+ ...decisionClock
7578
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";
7579
7719
  const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
7580
- classification: "core_skipped",
7720
+ classification: nearestReplayEvaluationOutcome.reason.startsWith(
7721
+ "BACKTEST_ENTRY_DELAY_"
7722
+ ) ? "entry_delayed" : "core_skipped",
7581
7723
  reason: nearestReplayEvaluationOutcome.reason
7582
- } : nearestCandidate?.nearest ? {
7724
+ } : !isRuntimeTrade && nearestCandidate?.nearest ? {
7583
7725
  classification: "timing_or_price_drift",
7584
7726
  reason: nearestCandidate.reason
7585
7727
  } : {
7586
7728
  classification: "no_replay_candidate",
7587
- reason: "no_replay_signal_or_backtest_candidate"
7729
+ reason: unavailableReason
7588
7730
  };
7589
7731
  return {
7590
7732
  entry,
7591
7733
  ...classification,
7592
7734
  ...nearestCandidate ? { nearestCandidate } : {},
7735
+ ...replayDecisionTrace ? { replayDecisionTrace } : {},
7593
7736
  ...nearestReplaySignal ? {
7594
7737
  replaySignal: toSignalDiagnostic({
7595
7738
  signal: nearestReplaySignal.signal,
@@ -7715,6 +7858,7 @@ var buildReplayRuntimeComparisonDetails = ({
7715
7858
  replaySignals,
7716
7859
  replaySignalEvaluations,
7717
7860
  replayLineageScopes,
7861
+ replayDecisionTraces,
7718
7862
  runtimeLineageScopes
7719
7863
  }) => {
7720
7864
  const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
@@ -7775,6 +7919,7 @@ var buildReplayRuntimeComparisonDetails = ({
7775
7919
  replaySignals,
7776
7920
  replaySignalEvaluations,
7777
7921
  replayLineageScopes,
7922
+ replayDecisionTraces,
7778
7923
  runtimeLineageScopes,
7779
7924
  toleranceMs,
7780
7925
  backtestTimestampOffsetMs,
@@ -8800,6 +8945,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8800
8945
  replaySignals,
8801
8946
  replayLineages,
8802
8947
  replayLineageScopes,
8948
+ replayDecisionTraces,
8803
8949
  runtimeEvidencePath
8804
8950
  }) => {
8805
8951
  const { connector, connectorName, window } = getRuntimeCompareContext();
@@ -8951,7 +9097,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8951
9097
  replaySignals,
8952
9098
  replaySignalEvaluations,
8953
9099
  runtimeLineageScopes: comparable.runtimeLineageScopes,
8954
- replayLineageScopes
9100
+ replayLineageScopes,
9101
+ replayDecisionTraces
8955
9102
  });
8956
9103
  const parityRows = summarizeTradeParityByStrategy({
8957
9104
  runtimeEntries: runtimeDedupe.entries,
@@ -9878,6 +10025,7 @@ var finishReplay = async ({
9878
10025
  replaySignals: replayResult.signals,
9879
10026
  replayLineages: replayResult.runtimeLineages,
9880
10027
  replayLineageScopes: replayResult.replayLineageScopes,
10028
+ replayDecisionTraces: replayResult.replayDecisionTraces,
9881
10029
  runtimeEvidencePath: replayRuntimeEvidencePath
9882
10030
  });
9883
10031
  const finishedAt = /* @__PURE__ */ new Date();
@@ -10227,7 +10375,8 @@ var replayBacktest = async () => {
10227
10375
  references: historicalReplayReferences,
10228
10376
  showProgress: !replayFlags.portfolioReport,
10229
10377
  collectSkipEvidence: !replayFlags.portfolioReport,
10230
- collectSignals: !replayFlags.portfolioReport
10378
+ collectSignals: !replayFlags.portfolioReport,
10379
+ decisionTraceTargets: replayFlags.portfolioReport ? void 0 : evidenceMetadata?.decisionTraceTargets
10231
10380
  });
10232
10381
  return replayFlags.portfolioReport ? compactHistoricalReplayResultForPortfolio(result) : result;
10233
10382
  };