@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.
@@ -4990,7 +4990,13 @@ var loadReplayRuntimeEvidenceMetadata = async (params) => {
4990
4990
  userName: artifact.userName,
4991
4991
  startTime: artifact.startTime,
4992
4992
  endTime: artifact.endTime,
4993
- deployment: artifact.deployment
4993
+ deployment: artifact.deployment,
4994
+ decisionTraceTargets: unwrapRows(
4995
+ artifact.runtime.trades,
4996
+ "trade"
4997
+ ).flatMap(
4998
+ ({ strategy, symbol, signalTimestamp }) => typeof signalTimestamp === "number" && Number.isFinite(signalTimestamp) && artifact.startTime != null && artifact.endTime != null && signalTimestamp >= artifact.startTime && signalTimestamp < artifact.endTime ? [{ strategy, symbol, signalTimestamp }] : []
4999
+ )
4994
5000
  };
4995
5001
  };
4996
5002
  var buildRuntimeEvidenceLineageScopes = ({
@@ -5593,6 +5599,73 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
5593
5599
  }
5594
5600
  };
5595
5601
 
5602
+ // src/lib/replay/historicalSignalsReplayDecisionTrace.ts
5603
+ var replayDecisionTraceKey = ({
5604
+ strategy,
5605
+ symbol,
5606
+ signalTimestamp
5607
+ }) => JSON.stringify([strategy, symbol, signalTimestamp]);
5608
+ var describeOutcome = (result) => {
5609
+ if (typeof result === "string") {
5610
+ return {
5611
+ stage: result.startsWith("BACKTEST_ENTRY_DELAY_") ? "entry_delay" : /(?:NO_PATTERN|POSITION_EXISTS)$/.test(result) ? "core" : "evaluation",
5612
+ reason: result
5613
+ };
5614
+ }
5615
+ if (!result) return { stage: "evaluation", reason: "NO_SIGNAL" };
5616
+ const reason = result.orderSkipReason || result.orderFailureReason || result.orderStatus || "SIGNAL";
5617
+ const patternContext = result.additionalIndicators?.tradingPatternsContext;
5618
+ return {
5619
+ stage: result.riskDecision?.status === "rejected" || /^ENTRY_(?:RR_BELOW|RISK_EXCEEDS)/.test(reason) ? "risk" : /^(?:AI_|ML_|HOOK_BEFORE_ENTRY_GATE|RESEARCH_ENTRY_POLICY)/.test(
5620
+ reason
5621
+ ) ? "gate" : result.orderStatus ? "execution" : "evaluation",
5622
+ reason,
5623
+ direction: result.direction,
5624
+ orderStatus: result.orderStatus,
5625
+ ...typeof patternContext?.selectedPattern === "string" ? { pattern: patternContext.selectedPattern } : {},
5626
+ ...typeof patternContext?.sourceCode === "string" ? { patternCode: patternContext.sourceCode } : {}
5627
+ };
5628
+ };
5629
+ var createReplayDecisionTrace = (targets = []) => {
5630
+ const traces = /* @__PURE__ */ new Map();
5631
+ const symbols = new Set(targets.map(({ symbol }) => symbol));
5632
+ for (const target of targets) {
5633
+ traces.set(replayDecisionTraceKey(target), {
5634
+ ...target,
5635
+ decision: {
5636
+ stage: "not_evaluated",
5637
+ reason: "REPLAY_CANDLE_NOT_EVALUATED"
5638
+ }
5639
+ });
5640
+ }
5641
+ const record = (strategy, symbol, candle, result, notEvaluatedReason) => {
5642
+ if (!symbols.has(symbol)) return;
5643
+ const { timestamp, open, high, low, close, volume } = candle;
5644
+ const trace = traces.get(
5645
+ replayDecisionTraceKey({ strategy, symbol, signalTimestamp: timestamp })
5646
+ );
5647
+ if (result && typeof result !== "string" && result.timestamp !== timestamp) {
5648
+ const original = traces.get(
5649
+ replayDecisionTraceKey({
5650
+ strategy,
5651
+ symbol,
5652
+ signalTimestamp: result.timestamp
5653
+ })
5654
+ );
5655
+ if (original)
5656
+ original.followUp = {
5657
+ ...describeOutcome(result),
5658
+ candleTimestamp: timestamp
5659
+ };
5660
+ notEvaluatedReason = "REPLAY_PROCESSED_DELAYED_ENTRY";
5661
+ }
5662
+ if (!trace) return;
5663
+ trace.candle = { timestamp, open, high, low, close, volume };
5664
+ trace.decision = notEvaluatedReason ? { stage: "not_evaluated", reason: notEvaluatedReason } : describeOutcome(result);
5665
+ };
5666
+ return { record, values: () => [...traces.values()] };
5667
+ };
5668
+
5596
5669
  // src/lib/replay/historicalSignalsReplaySkipEvidence.ts
5597
5670
  var import_data2 = require("@tradejs/core/data");
5598
5671
  var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
@@ -5703,6 +5776,7 @@ var executeHistoricalReplay = async (context, adapters) => {
5703
5776
  context.plan.runtimeLineages,
5704
5777
  context.collectSkipEvidence
5705
5778
  );
5779
+ const decisionTrace = createReplayDecisionTrace(context.decisionTraceTargets);
5706
5780
  const reportProgress = (timestamp) => {
5707
5781
  if (adapters.progress.enabled === false) return;
5708
5782
  adapters.progress.tick({
@@ -5731,6 +5805,20 @@ var executeHistoricalReplay = async (context, adapters) => {
5731
5805
  status: "completed",
5732
5806
  durationMs: adapters.clock.now() - cycleStartedAt
5733
5807
  });
5808
+ if (context.decisionTraceTargets?.length) {
5809
+ for (const symbolRuntime of cycleSymbols) {
5810
+ const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
5811
+ for (const strategy of symbolRuntime.strategies) {
5812
+ decisionTrace.record(
5813
+ strategy.strategyName,
5814
+ symbolRuntime.symbol,
5815
+ candle,
5816
+ void 0,
5817
+ "REPLAY_CYCLE_ABORTED"
5818
+ );
5819
+ }
5820
+ }
5821
+ }
5734
5822
  advanceAbortedCycle(cycleSymbols, timestamp);
5735
5823
  reportProgress(timestamp);
5736
5824
  continue;
@@ -5741,6 +5829,17 @@ var executeHistoricalReplay = async (context, adapters) => {
5741
5829
  const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
5742
5830
  const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
5743
5831
  if (!candle || !btcCandle || candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) !== timestamp || btcCandle.timestamp !== candle.timestamp) {
5832
+ if (candle && context.decisionTraceTargets?.length) {
5833
+ for (const strategy of symbolRuntime.strategies) {
5834
+ decisionTrace.record(
5835
+ strategy.strategyName,
5836
+ symbolRuntime.symbol,
5837
+ candle,
5838
+ void 0,
5839
+ "REPLAY_CANDLE_OR_REFERENCE_NOT_ALIGNED"
5840
+ );
5841
+ }
5842
+ }
5744
5843
  continue;
5745
5844
  }
5746
5845
  for (const strategyRuntime of symbolRuntime.strategies) {
@@ -5749,6 +5848,12 @@ var executeHistoricalReplay = async (context, adapters) => {
5749
5848
  btcCandle,
5750
5849
  ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
5751
5850
  );
5851
+ decisionTrace.record(
5852
+ strategyRuntime.strategyName,
5853
+ symbolRuntime.symbol,
5854
+ candle,
5855
+ result
5856
+ );
5752
5857
  skipEvidence.record({
5753
5858
  strategyName: strategyRuntime.strategyName,
5754
5859
  symbol: symbolRuntime.symbol,
@@ -5780,7 +5885,8 @@ var executeHistoricalReplay = async (context, adapters) => {
5780
5885
  return {
5781
5886
  signals,
5782
5887
  abortedCycles,
5783
- replayLineageScopes: skipEvidence.values()
5888
+ replayLineageScopes: skipEvidence.values(),
5889
+ replayDecisionTraces: decisionTrace.values()
5784
5890
  };
5785
5891
  };
5786
5892
 
@@ -5831,7 +5937,8 @@ var collectHistoricalReplayResult = (context) => ({
5831
5937
  cycleCount: context.cycleCount,
5832
5938
  abortedCycles: context.abortedCycles,
5833
5939
  runtimeLineages: context.runtimeLineages,
5834
- replayLineageScopes: context.replayLineageScopes
5940
+ replayLineageScopes: context.replayLineageScopes,
5941
+ ...context.replayDecisionTraces?.length ? { replayDecisionTraces: context.replayDecisionTraces } : {}
5835
5942
  });
5836
5943
  var compactHistoricalReplayResultForPortfolio = (result) => ({
5837
5944
  ...result,
@@ -5877,7 +5984,10 @@ var mergeHistoricalReplayResults = (parts) => {
5877
5984
  cycleCount: parts.reduce((sum, part) => sum + part.cycleCount, 0),
5878
5985
  abortedCycles: parts.reduce((sum, part) => sum + part.abortedCycles, 0),
5879
5986
  runtimeLineages: parts.flatMap((part) => part.runtimeLineages),
5880
- replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes)
5987
+ replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes),
5988
+ replayDecisionTraces: parts.flatMap(
5989
+ (part) => part.replayDecisionTraces ?? []
5990
+ )
5881
5991
  };
5882
5992
  };
5883
5993
 
@@ -6028,7 +6138,8 @@ var runHistoricalSignalsReplay = async ({
6028
6138
  references,
6029
6139
  showProgress = true,
6030
6140
  collectSkipEvidence = true,
6031
- collectSignals = true
6141
+ collectSignals = true,
6142
+ decisionTraceTargets
6032
6143
  }) => {
6033
6144
  const memory = createReplayMemoryProfile();
6034
6145
  memory?.("prepare:start");
@@ -6115,6 +6226,7 @@ var runHistoricalSignalsReplay = async ({
6115
6226
  connector: replayConnector,
6116
6227
  collectSkipEvidence,
6117
6228
  collectSignals,
6229
+ decisionTraceTargets,
6118
6230
  hooks: projectHooks,
6119
6231
  hookContext
6120
6232
  },
@@ -6151,7 +6263,8 @@ var runHistoricalSignalsReplay = async ({
6151
6263
  cycleCount: plan.orderedTimestamps.length,
6152
6264
  abortedCycles: execution.abortedCycles,
6153
6265
  runtimeLineages: plan.runtimeLineages,
6154
- replayLineageScopes: execution.replayLineageScopes
6266
+ replayLineageScopes: execution.replayLineageScopes,
6267
+ replayDecisionTraces: execution.replayDecisionTraces
6155
6268
  });
6156
6269
  memory?.("result:collected");
6157
6270
  return result;
@@ -6379,6 +6492,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
6379
6492
  direction: trade.direction,
6380
6493
  qty: toFiniteNumberOrNull2(trade.qty),
6381
6494
  timestamp: trade.entryTimestamp,
6495
+ ...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
6382
6496
  price: toFiniteNumberOrNull2(trade.entryPrice),
6383
6497
  orderId: trade.orderId,
6384
6498
  signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
@@ -7124,6 +7238,7 @@ var toRuntimeParityDetail = (entry) => ({
7124
7238
  direction: entry.direction,
7125
7239
  qty: entry.qty ?? null,
7126
7240
  timestamp: entry.timestamp,
7241
+ ...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
7127
7242
  price: entry.price,
7128
7243
  exitType: entry.exitType ?? null,
7129
7244
  exitTimestamp: entry.exitTimestamp ?? null,
@@ -7197,7 +7312,8 @@ var findNearestEvaluation = ({
7197
7312
  evaluations,
7198
7313
  toleranceMs,
7199
7314
  timestampOffsetMs,
7200
- entryTimestampOffsetMs = 0
7315
+ entryTimestampOffsetMs = 0,
7316
+ matchDirection = false
7201
7317
  }) => {
7202
7318
  const entryStrategy2 = getEntryStrategy(entry);
7203
7319
  if (!entryStrategy2) {
@@ -7207,7 +7323,7 @@ var findNearestEvaluation = ({
7207
7323
  let bestDiff = Number.POSITIVE_INFINITY;
7208
7324
  const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
7209
7325
  for (const evaluation of evaluations) {
7210
- if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol) {
7326
+ if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol || matchDirection && evaluation.direction != null && evaluation.direction !== entry.direction) {
7211
7327
  continue;
7212
7328
  }
7213
7329
  const diff = Math.abs(
@@ -7553,6 +7669,7 @@ var buildReplayMismatchDrilldown = ({
7553
7669
  replaySignals = [],
7554
7670
  replaySignalEvaluations = [],
7555
7671
  replayLineageScopes = [],
7672
+ replayDecisionTraces = [],
7556
7673
  runtimeLineageScopes = [],
7557
7674
  toleranceMs,
7558
7675
  backtestTimestampOffsetMs,
@@ -7561,48 +7678,66 @@ var buildReplayMismatchDrilldown = ({
7561
7678
  const nearestByEntry = new Map(
7562
7679
  nearestCandidates.map((candidate) => [candidate.entry, candidate])
7563
7680
  );
7681
+ const traceByCandle = new Map(
7682
+ replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
7683
+ );
7564
7684
  const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
7565
7685
  let runtimeOnlyArtifactsIncluded = 0;
7566
7686
  let backtestOnlyArtifactsIncluded = 0;
7567
7687
  const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
7568
- const entryOffsetMs = resolveTradeParityTimestampOffset(
7569
- getEntryStrategy(entry) ?? "",
7570
- backtestTimestampOffsetMs
7571
- );
7688
+ const isRuntimeTrade = entry.source === "runtime";
7689
+ const decisionEntry = isRuntimeTrade ? entry.signalTimestamp == null ? null : { ...entry, timestamp: entry.signalTimestamp } : entry;
7690
+ const decisionClock = {
7691
+ toleranceMs: isRuntimeTrade ? 0 : toleranceMs,
7692
+ timestampOffsetMs: isRuntimeTrade ? 0 : resolveTradeParityTimestampOffset(
7693
+ getEntryStrategy(entry) ?? "",
7694
+ backtestTimestampOffsetMs
7695
+ )
7696
+ };
7572
7697
  const includeArtifacts = index < artifactLimit;
7573
7698
  const nearestCandidate = nearestByEntry.get(entry);
7574
- const nearestReplaySignal = findNearestSignal({
7575
- entry,
7699
+ const nearestReplaySignal = decisionEntry && findNearestSignal({
7700
+ entry: decisionEntry,
7576
7701
  signals: replaySignals,
7577
- toleranceMs,
7578
- timestampOffsetMs: entryOffsetMs
7702
+ ...decisionClock
7579
7703
  });
7580
- const nearestReplayEvaluation = findNearestEvaluation({
7581
- entry,
7704
+ const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
7705
+ entry: decisionEntry,
7582
7706
  evaluations: replaySignalEvaluations,
7583
- toleranceMs,
7584
- timestampOffsetMs: entryOffsetMs
7707
+ matchDirection: isRuntimeTrade,
7708
+ ...decisionClock
7585
7709
  });
7586
- const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
7587
- entry,
7710
+ const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
7711
+ entry: decisionEntry,
7588
7712
  lineageScopes: replayLineageScopes,
7589
- toleranceMs,
7590
- timestampOffsetMs: entryOffsetMs
7713
+ ...decisionClock
7591
7714
  });
7715
+ const strategy = getEntryStrategy(entry);
7716
+ const replayDecisionTrace = isRuntimeTrade && strategy && decisionEntry ? traceByCandle.get(
7717
+ replayDecisionTraceKey({
7718
+ strategy,
7719
+ symbol: entry.symbol,
7720
+ signalTimestamp: decisionEntry.timestamp
7721
+ })
7722
+ ) : void 0;
7723
+ 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";
7592
7724
  const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
7593
- classification: "core_skipped",
7725
+ classification: nearestReplayEvaluationOutcome.reason.startsWith(
7726
+ "BACKTEST_ENTRY_DELAY_"
7727
+ ) ? "entry_delayed" : "core_skipped",
7594
7728
  reason: nearestReplayEvaluationOutcome.reason
7595
- } : nearestCandidate?.nearest ? {
7729
+ } : !isRuntimeTrade && nearestCandidate?.nearest ? {
7596
7730
  classification: "timing_or_price_drift",
7597
7731
  reason: nearestCandidate.reason
7598
7732
  } : {
7599
7733
  classification: "no_replay_candidate",
7600
- reason: "no_replay_signal_or_backtest_candidate"
7734
+ reason: unavailableReason
7601
7735
  };
7602
7736
  return {
7603
7737
  entry,
7604
7738
  ...classification,
7605
7739
  ...nearestCandidate ? { nearestCandidate } : {},
7740
+ ...replayDecisionTrace ? { replayDecisionTrace } : {},
7606
7741
  ...nearestReplaySignal ? {
7607
7742
  replaySignal: toSignalDiagnostic({
7608
7743
  signal: nearestReplaySignal.signal,
@@ -7728,6 +7863,7 @@ var buildReplayRuntimeComparisonDetails = ({
7728
7863
  replaySignals,
7729
7864
  replaySignalEvaluations,
7730
7865
  replayLineageScopes,
7866
+ replayDecisionTraces,
7731
7867
  runtimeLineageScopes
7732
7868
  }) => {
7733
7869
  const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
@@ -7788,6 +7924,7 @@ var buildReplayRuntimeComparisonDetails = ({
7788
7924
  replaySignals,
7789
7925
  replaySignalEvaluations,
7790
7926
  replayLineageScopes,
7927
+ replayDecisionTraces,
7791
7928
  runtimeLineageScopes,
7792
7929
  toleranceMs,
7793
7930
  backtestTimestampOffsetMs,
@@ -8813,6 +8950,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8813
8950
  replaySignals,
8814
8951
  replayLineages,
8815
8952
  replayLineageScopes,
8953
+ replayDecisionTraces,
8816
8954
  runtimeEvidencePath
8817
8955
  }) => {
8818
8956
  const { connector, connectorName, window } = getRuntimeCompareContext();
@@ -8964,7 +9102,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8964
9102
  replaySignals,
8965
9103
  replaySignalEvaluations,
8966
9104
  runtimeLineageScopes: comparable.runtimeLineageScopes,
8967
- replayLineageScopes
9105
+ replayLineageScopes,
9106
+ replayDecisionTraces
8968
9107
  });
8969
9108
  const parityRows = summarizeTradeParityByStrategy({
8970
9109
  runtimeEntries: runtimeDedupe.entries,
@@ -9891,6 +10030,7 @@ var finishReplay = async ({
9891
10030
  replaySignals: replayResult.signals,
9892
10031
  replayLineages: replayResult.runtimeLineages,
9893
10032
  replayLineageScopes: replayResult.replayLineageScopes,
10033
+ replayDecisionTraces: replayResult.replayDecisionTraces,
9894
10034
  runtimeEvidencePath: replayRuntimeEvidencePath
9895
10035
  });
9896
10036
  const finishedAt = /* @__PURE__ */ new Date();
@@ -10240,7 +10380,8 @@ var replayBacktest = async () => {
10240
10380
  references: historicalReplayReferences,
10241
10381
  showProgress: !replayFlags.portfolioReport,
10242
10382
  collectSkipEvidence: !replayFlags.portfolioReport,
10243
- collectSignals: !replayFlags.portfolioReport
10383
+ collectSignals: !replayFlags.portfolioReport,
10384
+ decisionTraceTargets: replayFlags.portfolioReport ? void 0 : evidenceMetadata?.decisionTraceTargets
10244
10385
  });
10245
10386
  return replayFlags.portfolioReport ? compactHistoricalReplayResultForPortfolio(result) : result;
10246
10387
  };
@@ -39,6 +39,9 @@ var import_node_path = __toESM(require("path"));
39
39
  var import_args = __toESM(require("args"));
40
40
  var import_chalk = __toESM(require("chalk"));
41
41
 
42
+ // src/lib/runtimeFeedbackNotification.ts
43
+ var import_userSettings = require("@tradejs/infra/userSettings");
44
+
42
45
  // src/lib/runtimeFeedbackArtifacts.ts
43
46
  var runtimeFeedbackArtifacts_exports = {};
44
47
  __reExport(runtimeFeedbackArtifacts_exports, require("@tradejs/node/diagnostics"));
@@ -223,11 +226,14 @@ var notifyRuntimeFeedbackBundle = async ({
223
226
  }) => {
224
227
  const verified = await (0, runtimeFeedbackArtifacts_exports.verifyRuntimeFeedbackReplayBundle)(bundleDir);
225
228
  const message = buildRuntimeFeedbackTelegramMessage(verified.replayEvidence);
226
- await send(message, { userName: verified.manifest.userName });
229
+ const settings = await (0, import_userSettings.getUserSettings)(verified.manifest.userName);
230
+ const sent = settings.NOTIFY_RUNTIME_DEBUG !== false;
231
+ if (sent) await send(message, { userName: verified.manifest.userName });
227
232
  return {
228
233
  artifactId: verified.manifest.artifactId,
229
234
  userName: verified.manifest.userName,
230
- message
235
+ message,
236
+ sent
231
237
  };
232
238
  };
233
239
 
@@ -249,7 +255,9 @@ var runtimeFeedbackNotify = async () => {
249
255
  }
250
256
  const result = await notifyRuntimeFeedbackBundle({ bundleDir });
251
257
  console.log(
252
- import_chalk.default.green(`Runtime feedback notification sent: ${result.artifactId}`)
258
+ import_chalk.default.green(
259
+ `Runtime feedback notification ${result.sent ? "sent" : "disabled"}: ${result.artifactId}`
260
+ )
253
261
  );
254
262
  };
255
263
  var main = runtimeFeedbackNotify;
@@ -43,6 +43,7 @@ var import_chalk = __toESM(require("chalk"));
43
43
  var import_node_child_process = require("child_process");
44
44
  var import_promises = __toESM(require("fs/promises"));
45
45
  var import_node_path = __toESM(require("path"));
46
+ var import_mcp = require("@tradejs/node/mcp");
46
47
 
47
48
  // src/lib/runtimeEvidenceDeployment.ts
48
49
  var runtimeEvidenceDeployment_exports = {};
@@ -190,44 +191,58 @@ var runLoggedCommand = async ({
190
191
  env,
191
192
  logHandle
192
193
  }) => {
193
- await logHandle.write(`
194
- $ ${[executable, ...command].join(" ")}
195
- `);
196
- await new Promise((resolve, reject) => {
197
- let pendingLogWrite = Promise.resolve();
198
- const appendLog = (chunk) => {
199
- pendingLogWrite = pendingLogWrite.then(async () => {
200
- await logHandle.write(chunk);
201
- });
202
- };
203
- const child = (0, import_node_child_process.spawn)(executable, command, {
204
- cwd,
205
- env,
206
- stdio: ["ignore", "pipe", "pipe"]
207
- });
208
- child.stdout.on("data", (chunk) => {
209
- process.stdout.write(chunk);
210
- appendLog(chunk);
211
- });
212
- child.stderr.on("data", (chunk) => {
213
- process.stderr.write(chunk);
214
- appendLog(chunk);
215
- });
194
+ await logHandle.writeFile(
195
+ "\n" + (0, import_mcp.formatMcpArtifactLogLine)(`$ ${[executable, ...command].join(" ")}`)
196
+ );
197
+ const child = (0, import_node_child_process.spawn)(executable, command, {
198
+ cwd,
199
+ env,
200
+ stdio: ["ignore", "pipe", "pipe"]
201
+ });
202
+ const exit = new Promise((resolve, reject) => {
216
203
  child.once("error", reject);
217
204
  child.once("close", (code, signal) => {
218
- void pendingLogWrite.then(() => {
219
- if (code === 0) {
220
- resolve();
221
- return;
222
- }
205
+ if (code === 0) resolve();
206
+ else {
223
207
  reject(
224
208
  new Error(
225
209
  `Runtime feedback command failed: exit=${String(code)} signal=${String(signal)}`
226
210
  )
227
211
  );
228
- }, reject);
212
+ }
229
213
  });
230
214
  });
215
+ let pendingLogWrite = Promise.resolve();
216
+ const copyLog = async (input, output) => {
217
+ const writeLine = async (line) => {
218
+ const formatted = (0, import_mcp.formatMcpArtifactLogLine)(line);
219
+ pendingLogWrite = pendingLogWrite.then(
220
+ () => logHandle.writeFile(formatted)
221
+ );
222
+ await pendingLogWrite;
223
+ output.write(formatted);
224
+ };
225
+ let pendingLine = "";
226
+ try {
227
+ for await (const chunk of input.setEncoding("utf8")) {
228
+ const lines = (pendingLine + chunk).split("\n");
229
+ pendingLine = lines.pop() ?? "";
230
+ for (const line of lines) await writeLine(line);
231
+ }
232
+ if (pendingLine) await writeLine(pendingLine);
233
+ } catch (error) {
234
+ child.kill();
235
+ throw error;
236
+ }
237
+ };
238
+ const results = await Promise.allSettled([
239
+ copyLog(child.stdout, process.stdout),
240
+ copyLog(child.stderr, process.stderr),
241
+ exit
242
+ ]);
243
+ for (const result of results) {
244
+ if (result.status === "rejected") throw result.reason;
245
+ }
231
246
  };
232
247
  var runRuntimeFeedbackReplay = async ({
233
248
  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,
@@ -35,6 +35,7 @@ __export(signalsSummary_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(signalsSummary_exports);
37
37
  var import_config = require("dotenv/config");
38
+ var import_userSettings = require("@tradejs/infra/userSettings");
38
39
  var import_args = __toESM(require("args"));
39
40
  var import_logger = require("@tradejs/infra/logger");
40
41
  var import_connectors = require("@tradejs/node/connectors");
@@ -1295,6 +1296,7 @@ none`
1295
1296
  };
1296
1297
  };
1297
1298
  var signalsSummary = async () => {
1299
+ const includeDebug = flags.printOnly || (await (0, import_userSettings.getUserSettings)(flags.user)).NOTIFY_RUNTIME_DEBUG !== false;
1298
1300
  const hours = Math.max(
1299
1301
  1,
1300
1302
  Number.parseInt(String(flags.hours ?? 24), 10) || 24
@@ -1415,18 +1417,15 @@ var signalsSummary = async () => {
1415
1417
  content: signalsMessage,
1416
1418
  deploymentId: scope.deploymentId
1417
1419
  });
1418
- const tradesMessageWithAttachmentSummary = appendReportAttachmentSummary({
1420
+ const tradesMessageWithAttachmentSummary = includeDebug ? appendReportAttachmentSummary({
1419
1421
  message: tradesMessage,
1420
1422
  signalsFilename: signalsAttachment.filename,
1421
1423
  filename: debugAttachment?.filename,
1422
1424
  tradesCount: debugAttachment ? scopeTrades.length : void 0,
1423
1425
  signalsCount: debugAttachment ? debugAttachment.summary?.signals ?? debugSignals.length : void 0,
1424
1426
  evaluationsCount: debugAttachment ? debugAttachment.summary?.evaluations ?? 0 : void 0
1425
- });
1426
- const attachments = [
1427
- signalsAttachment,
1428
- ...debugAttachment ? [debugAttachment] : []
1429
- ];
1427
+ }) : tradesMessage;
1428
+ const attachments = includeDebug ? [signalsAttachment, ...debugAttachment ? [debugAttachment] : []] : [];
1430
1429
  if (flags.printOnly) {
1431
1430
  console.log(tradesMessageWithAttachmentSummary);
1432
1431
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "3.1.29-beta.289",
3
+ "version": "3.1.29-beta.290",
4
4
  "description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -37,13 +37,13 @@
37
37
  "tradejs": "dist/cli.js"
38
38
  },
39
39
  "dependencies": {
40
- "@tradejs/connectors": "^3.1.29-beta.289",
41
- "@tradejs/core": "^3.1.29-beta.289",
42
- "@tradejs/indicators": "^3.1.29-beta.289",
43
- "@tradejs/infra": "^3.1.29-beta.289",
44
- "@tradejs/node": "^3.1.29-beta.289",
40
+ "@tradejs/connectors": "^3.1.29-beta.290",
41
+ "@tradejs/core": "^3.1.29-beta.290",
42
+ "@tradejs/indicators": "^3.1.29-beta.290",
43
+ "@tradejs/infra": "^3.1.29-beta.290",
44
+ "@tradejs/node": "^3.1.29-beta.290",
45
45
  "@tradejs/strategy-kit": "3.0.6",
46
- "@tradejs/types": "^3.1.29-beta.289",
46
+ "@tradejs/types": "^3.1.29-beta.290",
47
47
  "args": "^5.0.3",
48
48
  "bcryptjs": "^2.4.3",
49
49
  "chalk": "4.1.2",