@tradejs/cli 3.1.26-beta.244 → 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.
@@ -40,7 +40,7 @@ __export(replayRunner_exports, {
40
40
  module.exports = __toCommonJS(replayRunner_exports);
41
41
  var import_chalk13 = __toESM(require("chalk"));
42
42
  var import_constants6 = require("@tradejs/core/constants");
43
- var import_data2 = require("@tradejs/core/data");
43
+ var import_data3 = require("@tradejs/core/data");
44
44
  var import_time7 = require("@tradejs/core/time");
45
45
  var import_redis6 = require("@tradejs/infra/redis");
46
46
 
@@ -6443,6 +6443,7 @@ var import_redis4 = require("@tradejs/infra/redis");
6443
6443
 
6444
6444
  // src/lib/runtimeSignalsStorage.ts
6445
6445
  var import_constants4 = require("@tradejs/core/constants");
6446
+ var import_data = require("@tradejs/core/data");
6446
6447
  var import_time4 = require("@tradejs/core/time");
6447
6448
  var isRuntimeSignalBucketRef = (value) => {
6448
6449
  if (!value || typeof value !== "object") {
@@ -6991,6 +6992,50 @@ var findNearestEvaluation = ({
6991
6992
  timestampDiffMs: bestDiff
6992
6993
  } : null;
6993
6994
  };
6995
+ var findNearestRuntimeEvaluationOutcome = ({
6996
+ entry,
6997
+ lineageScopes,
6998
+ toleranceMs
6999
+ }) => {
7000
+ const strategy = getEntryStrategy(entry);
7001
+ const timestamp = entry.signalTimestamp ?? entry.timestamp;
7002
+ if (!strategy) return null;
7003
+ let nearest = null;
7004
+ for (const scope of lineageScopes) {
7005
+ if (scope.strategy !== strategy || scope.symbol !== entry.symbol) {
7006
+ continue;
7007
+ }
7008
+ for (const run of scope.evaluationRuns ?? []) {
7009
+ if (run.stepMs <= 0 || timestamp < run.firstTimestamp - toleranceMs || timestamp > run.lastTimestamp + toleranceMs) {
7010
+ continue;
7011
+ }
7012
+ const step = Math.round((timestamp - run.firstTimestamp) / run.stepMs);
7013
+ const candidateTimestamp = run.firstTimestamp + step * run.stepMs;
7014
+ const timestampDiffMs = Math.abs(candidateTimestamp - timestamp);
7015
+ if (candidateTimestamp < run.firstTimestamp || candidateTimestamp > run.lastTimestamp || timestampDiffMs > toleranceMs || nearest && timestampDiffMs >= nearest.timestampDiffMs) {
7016
+ continue;
7017
+ }
7018
+ nearest = {
7019
+ status: run.status,
7020
+ reason: run.reason,
7021
+ timestamp: candidateTimestamp,
7022
+ timestampDiffMs,
7023
+ source: "lineage_scope_compact"
7024
+ };
7025
+ }
7026
+ }
7027
+ return nearest;
7028
+ };
7029
+ var hasRuntimeLineageCoverage = ({
7030
+ entry,
7031
+ lineageScopes
7032
+ }) => {
7033
+ const strategy = getEntryStrategy(entry);
7034
+ const timestamp = entry.signalTimestamp ?? entry.timestamp;
7035
+ return lineageScopes.some(
7036
+ (scope) => scope.strategy === strategy && scope.symbol === entry.symbol && timestamp >= scope.firstTimestamp && timestamp <= scope.lastTimestamp
7037
+ );
7038
+ };
6994
7039
  var classifySignalDiagnostic = (signal) => {
6995
7040
  if (signal.orderStatus === "failed") {
6996
7041
  return {
@@ -7235,6 +7280,7 @@ var buildReplayMismatchDrilldown = ({
7235
7280
  runtimeSignalEvaluations = [],
7236
7281
  replaySignals = [],
7237
7282
  replaySignalEvaluations = [],
7283
+ runtimeLineageScopes = [],
7238
7284
  toleranceMs,
7239
7285
  backtestTimestampOffsetMs,
7240
7286
  limit
@@ -7302,9 +7348,26 @@ var buildReplayMismatchDrilldown = ({
7302
7348
  toleranceMs,
7303
7349
  timestampOffsetMs: 0
7304
7350
  });
7305
- const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestCandidate?.nearest ? {
7351
+ const nearestRuntimeEvaluationOutcome = findNearestRuntimeEvaluationOutcome(
7352
+ {
7353
+ entry,
7354
+ lineageScopes: runtimeLineageScopes,
7355
+ toleranceMs
7356
+ }
7357
+ );
7358
+ const runtimeLineageCoversEntry = hasRuntimeLineageCoverage({
7359
+ entry,
7360
+ lineageScopes: runtimeLineageScopes
7361
+ });
7362
+ const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestRuntimeEvaluationOutcome ? {
7363
+ classification: "core_skipped",
7364
+ reason: nearestRuntimeEvaluationOutcome.reason
7365
+ } : nearestCandidate?.nearest ? {
7306
7366
  classification: "timing_or_price_drift",
7307
7367
  reason: nearestCandidate.reason
7368
+ } : runtimeLineageCoversEntry ? {
7369
+ classification: "runtime_evaluation_detail_unavailable",
7370
+ reason: "runtime_scope_covers_timestamp_but_skip_detail_was_not_stored"
7308
7371
  } : {
7309
7372
  classification: "no_runtime_evaluation",
7310
7373
  reason: "no_runtime_signal_or_evaluation"
@@ -7328,7 +7391,8 @@ var buildReplayMismatchDrilldown = ({
7328
7391
  ...includeArtifacts ? {
7329
7392
  runtimeEvaluationArtifact: nearestRuntimeEvaluation.evaluation
7330
7393
  } : {}
7331
- } : {}
7394
+ } : {},
7395
+ ...nearestRuntimeEvaluationOutcome ? { runtimeEvaluationOutcome: nearestRuntimeEvaluationOutcome } : {}
7332
7396
  };
7333
7397
  });
7334
7398
  for (const diagnostic of runtimeOnlyDiagnostics) {
@@ -7375,7 +7439,8 @@ var buildReplayRuntimeComparisonDetails = ({
7375
7439
  runtimeSignals,
7376
7440
  runtimeSignalEvaluations,
7377
7441
  replaySignals,
7378
- replaySignalEvaluations
7442
+ replaySignalEvaluations,
7443
+ runtimeLineageScopes
7379
7444
  }) => {
7380
7445
  const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
7381
7446
  const backtestDetails = backtestEntries.map(
@@ -7434,6 +7499,7 @@ var buildReplayRuntimeComparisonDetails = ({
7434
7499
  runtimeSignalEvaluations,
7435
7500
  replaySignals,
7436
7501
  replaySignalEvaluations,
7502
+ runtimeLineageScopes,
7437
7503
  toleranceMs,
7438
7504
  backtestTimestampOffsetMs,
7439
7505
  limit
@@ -7454,7 +7520,8 @@ var buildReplayExchangeComparisonDetails = ({
7454
7520
  runtimeSignals,
7455
7521
  runtimeSignalEvaluations,
7456
7522
  replaySignals,
7457
- replaySignalEvaluations
7523
+ replaySignalEvaluations,
7524
+ runtimeLineageScopes
7458
7525
  }) => {
7459
7526
  const toExchangeDetail = (entry) => toExchangeParityDetail({
7460
7527
  entry,
@@ -7541,6 +7608,7 @@ var buildReplayExchangeComparisonDetails = ({
7541
7608
  runtimeSignalEvaluations,
7542
7609
  replaySignals,
7543
7610
  replaySignalEvaluations,
7611
+ runtimeLineageScopes,
7544
7612
  toleranceMs,
7545
7613
  backtestTimestampOffsetMs,
7546
7614
  limit
@@ -7676,6 +7744,7 @@ var filterReplayComparisonByLineage = ({
7676
7744
  runtimeTrades: comparableRuntimeTrades,
7677
7745
  runtimeSignals: comparableRuntimeSignals,
7678
7746
  runtimeSignalEvaluations: comparableRuntimeEvaluations,
7747
+ runtimeLineageScopes: comparableRuntimeLineageScopes,
7679
7748
  backtestEntries: comparableBacktestEntries,
7680
7749
  lineage: {
7681
7750
  enforced: true,
@@ -8272,6 +8341,7 @@ var saveAndPrintReplayExchangeComparison = async ({
8272
8341
  replaySignalEvaluations,
8273
8342
  runtimeSignals,
8274
8343
  runtimeSignalEvaluations,
8344
+ runtimeLineageScopes,
8275
8345
  lineage
8276
8346
  }) => {
8277
8347
  const { connector, connectorName, window } = getRuntimeCompareContext();
@@ -8323,7 +8393,8 @@ var saveAndPrintReplayExchangeComparison = async ({
8323
8393
  runtimeSignals,
8324
8394
  runtimeSignalEvaluations,
8325
8395
  replaySignals,
8326
- replaySignalEvaluations
8396
+ replaySignalEvaluations,
8397
+ runtimeLineageScopes
8327
8398
  });
8328
8399
  const emptyRowsByStrategy = new Map(
8329
8400
  buildExchangeComparisonRows({
@@ -8379,7 +8450,8 @@ var saveAndPrintReplayExchangeComparison = async ({
8379
8450
  runtimeSignals,
8380
8451
  runtimeSignalEvaluations,
8381
8452
  replaySignals,
8382
- replaySignalEvaluations
8453
+ replaySignalEvaluations,
8454
+ runtimeLineageScopes
8383
8455
  });
8384
8456
  const rows = buildExchangeComparisonRows({
8385
8457
  liveStrategySummaries,
@@ -8530,6 +8602,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8530
8602
  replaySignalEvaluations,
8531
8603
  runtimeSignals: comparable.runtimeSignals,
8532
8604
  runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
8605
+ runtimeLineageScopes: comparable.runtimeLineageScopes,
8533
8606
  lineage: comparable.lineage
8534
8607
  });
8535
8608
  }
@@ -8553,7 +8626,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
8553
8626
  runtimeSignals: comparable.runtimeSignals,
8554
8627
  runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
8555
8628
  replaySignals,
8556
- replaySignalEvaluations
8629
+ replaySignalEvaluations,
8630
+ runtimeLineageScopes: comparable.runtimeLineageScopes
8557
8631
  });
8558
8632
  const parityRows = summarizeTradeParityByStrategy({
8559
8633
  runtimeEntries: runtimeDedupe.entries,
@@ -8596,7 +8670,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
8596
8670
  // src/lib/replay/outputReport.ts
8597
8671
  var import_promises2 = __toESM(require("fs/promises"));
8598
8672
  var import_node_path2 = __toESM(require("path"));
8599
- var import_data = require("@tradejs/core/data");
8673
+ var import_data2 = require("@tradejs/core/data");
8600
8674
  var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
8601
8675
  timeZone: "Europe/Moscow",
8602
8676
  dateStyle: "short",
@@ -9003,7 +9077,7 @@ var writeReplayOutputReport = async ({
9003
9077
  ];
9004
9078
  await Promise.all([
9005
9079
  import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
9006
- import_promises2.default.writeFile(jsonPath, `${(0, import_data.toJson)(report, true)}
9080
+ import_promises2.default.writeFile(jsonPath, `${(0, import_data2.toJson)(report, true)}
9007
9081
  `, "utf8")
9008
9082
  ]);
9009
9083
  return {
@@ -9211,7 +9285,7 @@ var replayBacktest = async () => {
9211
9285
  deploymentId: deployment.id,
9212
9286
  assetClasses: deployment.assetClasses,
9213
9287
  deployment,
9214
- closedIntervalMs: (0, import_data2.intervalToMs)(replayInterval)
9288
+ closedIntervalMs: (0, import_data3.intervalToMs)(replayInterval)
9215
9289
  });
9216
9290
  if (!preparedRun || isReplayUpdateOnlyRun) {
9217
9291
  return;
@@ -117,6 +117,7 @@ var import_redis2 = require("@tradejs/infra/redis");
117
117
 
118
118
  // src/lib/runtimeSignalsStorage.ts
119
119
  var import_constants = require("@tradejs/core/constants");
120
+ var import_data = require("@tradejs/core/data");
120
121
  var import_time2 = require("@tradejs/core/time");
121
122
  var isRuntimeSignalBucketRef = (value) => {
122
123
  if (!value || typeof value !== "object") {
@@ -115,6 +115,7 @@ var import_redis2 = require("@tradejs/infra/redis");
115
115
 
116
116
  // src/lib/runtimeSignalsStorage.ts
117
117
  var import_constants = require("@tradejs/core/constants");
118
+ var import_data = require("@tradejs/core/data");
118
119
  var import_time2 = require("@tradejs/core/time");
119
120
  var isRuntimeSignalBucketRef = (value) => {
120
121
  if (!value || typeof value !== "object") {
@@ -394,6 +394,7 @@ var import_redis2 = require("@tradejs/infra/redis");
394
394
 
395
395
  // src/lib/runtimeSignalsStorage.ts
396
396
  var import_constants = require("@tradejs/core/constants");
397
+ var import_data = require("@tradejs/core/data");
397
398
  var import_time2 = require("@tradejs/core/time");
398
399
  var isRuntimeSignalBucketRef = (value) => {
399
400
  if (!value || typeof value !== "object") {
@@ -758,6 +758,20 @@ var countBy = (items, getter) => {
758
758
  Object.entries(counts).sort(([left], [right]) => left.localeCompare(right))
759
759
  );
760
760
  };
761
+ var resolveGateDecision = (evaluation) => {
762
+ const analysis = asRecord2(evaluation.aiAnalysis);
763
+ const explicit = finiteString(analysis?.gateDecision);
764
+ if (explicit === "approved" || explicit === "rejected") {
765
+ return { decision: explicit, source: "gate_decision" };
766
+ }
767
+ if (typeof analysis?.approved === "boolean") {
768
+ return {
769
+ decision: analysis.approved ? "approved" : "rejected",
770
+ source: "legacy_approved_fallback"
771
+ };
772
+ }
773
+ return null;
774
+ };
761
775
  var buildDistributions = ({
762
776
  evaluations,
763
777
  signals
@@ -938,18 +952,15 @@ var buildRuntimeScorecard = ({
938
952
  const signalEvaluations = rows.evaluations.filter(
939
953
  (evaluation) => evaluation.status === "signal"
940
954
  );
941
- const gateDecisions = signalEvaluations.map((evaluation) => ({
942
- evaluation,
943
- decision: finiteString(asRecord2(evaluation.aiAnalysis)?.gateDecision)
944
- })).filter(
945
- ({ decision }) => decision === "approved" || decision === "rejected"
955
+ const gateDecisions = signalEvaluations.map(resolveGateDecision).filter(
956
+ (decision) => Boolean(decision)
946
957
  );
947
958
  const llmEligibleEvaluations = llmComparatorPolicy === "disabled" ? [] : llmComparatorPolicy === "ai_approved_only" ? signalEvaluations.filter(
948
- (evaluation) => finiteString(asRecord2(evaluation.aiAnalysis)?.gateDecision) === "approved"
959
+ (evaluation) => resolveGateDecision(evaluation)?.decision === "approved"
949
960
  ) : signalEvaluations;
950
961
  const gateComparisons = llmEligibleEvaluations.map((evaluation) => {
951
962
  const analysis = asRecord2(evaluation.aiAnalysis);
952
- const gateDecision = finiteString(analysis?.gateDecision);
963
+ const gateDecision = resolveGateDecision(evaluation)?.decision ?? null;
953
964
  const llmDecision = finiteString(analysis?.llmDecision);
954
965
  if (gateDecision !== "approved" && gateDecision !== "rejected" || llmDecision !== "approved" && llmDecision !== "rejected") {
955
966
  return null;
@@ -984,6 +995,27 @@ var buildRuntimeScorecard = ({
984
995
  };
985
996
  const actualSlippageBps = averageSampleMetric("signalToFillAdverseBps");
986
997
  const residualVsModelBps = averageSampleMetric("residualVsCurrentModelBps");
998
+ const signalToArrivalAdverseBps = averageSampleMetric(
999
+ "signalToArrivalAdverseBps"
1000
+ );
1001
+ const arrivalToFillAdverseBps = averageSampleMetric(
1002
+ "arrivalToFillAdverseBps"
1003
+ );
1004
+ const averageSignalCloseToSubmitMs = averageSampleMetric(
1005
+ "signalCloseToSubmitMs"
1006
+ );
1007
+ const averageSubmitToFillMs = averageSampleMetric("submitToFillMs");
1008
+ const fullTelemetryTrades = calibrationSamples.filter(
1009
+ (sample) => finiteString(sample.telemetryQuality) === "full"
1010
+ ).length;
1011
+ const arrivalQuoteTrades = calibrationSamples.filter(
1012
+ (sample) => finiteNumber(sample.arrivalMid) != null
1013
+ ).length;
1014
+ const arrivalQuoteCoverage = calibrationSamples.length ? round(arrivalQuoteTrades / calibrationSamples.length) : null;
1015
+ const hasTelemetryQuality = calibrationSamples.some(
1016
+ (sample) => finiteString(sample.telemetryQuality) != null
1017
+ );
1018
+ const maximumSignalCloseToSubmitMs = thresholds.maximumSignalCloseToSubmitMs ?? 3e4;
987
1019
  const prospective = getProspectiveSummary(prospectiveEvidenceArtifact);
988
1020
  const historyTrades = dedupeTrades([
989
1021
  ...scopedHistoryArtifacts,
@@ -1040,7 +1072,28 @@ var buildRuntimeScorecard = ({
1040
1072
  reactions.push({
1041
1073
  code: "SLIPPAGE_DRIFT",
1042
1074
  severity: "warning",
1043
- message: `Execution residual ${round(residualVsModelBps, 2)} bps exceeds ${thresholds.maximumSlippageResidualBps} bps.`
1075
+ message: `Signal-close-to-fill residual ${round(residualVsModelBps, 2)} bps exceeds ${thresholds.maximumSlippageResidualBps} bps.`
1076
+ });
1077
+ }
1078
+ if (averageSignalCloseToSubmitMs != null && averageSignalCloseToSubmitMs > maximumSignalCloseToSubmitMs) {
1079
+ reactions.push({
1080
+ code: "PRE_SUBMIT_LATENCY",
1081
+ severity: "warning",
1082
+ message: `Average signal-close-to-submit latency ${round(averageSignalCloseToSubmitMs, 3)} ms exceeds ${maximumSignalCloseToSubmitMs} ms.`
1083
+ });
1084
+ }
1085
+ if (hasTelemetryQuality && calibrationSamples.length > 0 && fullTelemetryTrades < calibrationSamples.length) {
1086
+ reactions.push({
1087
+ code: "EXECUTION_TELEMETRY_INCOMPLETE",
1088
+ severity: "info",
1089
+ message: `Full arrival and fill telemetry is available for ${fullTelemetryTrades}/${calibrationSamples.length} trades.`
1090
+ });
1091
+ }
1092
+ if (riskDecisions.some((decision) => decision.status === "rejected")) {
1093
+ reactions.push({
1094
+ code: "RISK_PLAN_EXCEEDS_DECLARED_LIMIT",
1095
+ severity: "warning",
1096
+ message: "At least one observed order plan exceeds its declared MAX_LOSS_VALUE; telemetry is not yet enforced."
1044
1097
  });
1045
1098
  }
1046
1099
  if (sevenDay.closedTrades >= thresholds.minimumClosedTrades && sevenDay.expectancy != null && sevenDay.expectancy < thresholds.minimumExpectancy) {
@@ -1069,7 +1122,10 @@ var buildRuntimeScorecard = ({
1069
1122
  deployment: deploymentBinding,
1070
1123
  lineage: runtimeLineage,
1071
1124
  promotionStatus,
1072
- thresholds,
1125
+ thresholds: {
1126
+ ...thresholds,
1127
+ maximumSignalCloseToSubmitMs
1128
+ },
1073
1129
  funnel: {
1074
1130
  evaluations: rows.evaluations.length,
1075
1131
  coreCandidates: signalEvaluations.length,
@@ -1081,25 +1137,38 @@ var buildRuntimeScorecard = ({
1081
1137
  rejected: gateDecisions.filter(
1082
1138
  ({ decision }) => decision === "rejected"
1083
1139
  ).length,
1084
- coverage: signalEvaluations.length ? round(gateDecisions.length / signalEvaluations.length) : null
1140
+ coverage: signalEvaluations.length ? round(gateDecisions.length / signalEvaluations.length) : null,
1141
+ legacyApprovedFallback: gateDecisions.filter(
1142
+ ({ source }) => source === "legacy_approved_fallback"
1143
+ ).length
1085
1144
  },
1086
1145
  allocator: {
1087
1146
  available: allocatorDecisions.length > 0,
1088
1147
  approved: allocatorDecisions.filter(
1089
- (decision) => decision.decision === "approved"
1148
+ (decision) => decision.status === "approved"
1090
1149
  ).length,
1091
1150
  rejected: allocatorDecisions.filter(
1092
- (decision) => decision.decision === "rejected"
1151
+ (decision) => decision.status === "rejected"
1152
+ ).length,
1153
+ notApplicable: allocatorDecisions.filter(
1154
+ (decision) => decision.status === "not_applicable"
1155
+ ).length,
1156
+ unavailable: allocatorDecisions.filter(
1157
+ (decision) => decision.status === "unavailable"
1093
1158
  ).length
1094
1159
  },
1095
1160
  risk: {
1096
1161
  available: riskDecisions.length > 0,
1097
1162
  approved: riskDecisions.filter(
1098
- (decision) => decision.decision === "approved"
1163
+ (decision) => decision.status === "approved"
1099
1164
  ).length,
1100
1165
  rejected: riskDecisions.filter(
1101
- (decision) => decision.decision === "rejected"
1102
- ).length
1166
+ (decision) => decision.status === "rejected"
1167
+ ).length,
1168
+ unavailable: riskDecisions.filter(
1169
+ (decision) => decision.status === "unavailable"
1170
+ ).length,
1171
+ enforced: riskDecisions.some((decision) => decision.enforced === true)
1103
1172
  },
1104
1173
  orderAttempts: orderAttempts.length,
1105
1174
  orderFailures: orderAttempts.filter(
@@ -1161,8 +1230,16 @@ var buildRuntimeScorecard = ({
1161
1230
  },
1162
1231
  execution: {
1163
1232
  available: actualSlippageBps != null || residualVsModelBps != null,
1233
+ metricScope: arrivalToFillAdverseBps == null ? "signal_close_to_fill" : "signal_and_exchange_components",
1234
+ samples: calibrationSamples.length,
1235
+ fullTelemetryTrades,
1236
+ arrivalQuoteCoverage,
1164
1237
  actualSignalToFillSlippageBps: actualSlippageBps,
1165
- residualVsCurrentModelBps: residualVsModelBps
1238
+ residualVsCurrentModelBps: residualVsModelBps,
1239
+ signalToArrivalAdverseBps,
1240
+ arrivalToFillAdverseBps,
1241
+ averageSignalCloseToSubmitMs,
1242
+ averageSubmitToFillMs
1166
1243
  },
1167
1244
  prospective,
1168
1245
  rolling,
@@ -1216,10 +1293,10 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
1216
1293
 
1217
1294
  - Evaluations: ${scorecard.funnel.evaluations}
1218
1295
  - Core candidates: ${scorecard.funnel.coreCandidates}
1219
- - Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected}
1296
+ - Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected} (legacy fallback: ${scorecard.funnel.gate.legacyApprovedFallback})
1220
1297
  - AI / LLM disagreement: ${scorecard.gateComparison.disagreements}/${scorecard.gateComparison.compared}
1221
- - Allocator approvals/rejects: ${scorecard.funnel.allocator.available ? `${scorecard.funnel.allocator.approved}/${scorecard.funnel.allocator.rejected}` : "unavailable"}
1222
- - Risk approvals/rejects: ${scorecard.funnel.risk.available ? `${scorecard.funnel.risk.approved}/${scorecard.funnel.risk.rejected}` : "unavailable"}
1298
+ - Runtime admission approved/rejected/not-applicable: ${scorecard.funnel.allocator.available ? `${scorecard.funnel.allocator.approved}/${scorecard.funnel.allocator.rejected}/${scorecard.funnel.allocator.notApplicable}` : "unavailable"}
1299
+ - 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})
1223
1300
  - Order attempts/failures: ${scorecard.funnel.orderAttempts}/${scorecard.funnel.orderFailures}
1224
1301
  - Fills: ${scorecard.funnel.fills}
1225
1302
  - Closed trades: ${scorecard.funnel.closedTrades}
@@ -1228,8 +1305,14 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
1228
1305
 
1229
1306
  - Parity: ${scorecard.parity.ratio == null ? "n/a" : `${round(scorecard.parity.ratio * 100, 2)}%`}
1230
1307
  - Runtime/replay mismatches: ${scorecard.parity.backtestOnly + scorecard.parity.runtimeOnly}
1231
- - Actual signal-to-fill slippage: ${scorecard.execution.actualSignalToFillSlippageBps ?? "n/a"} bps
1308
+ - Execution metric scope: ${scorecard.execution.metricScope}
1309
+ - Telemetry samples/full/arrival coverage: ${scorecard.execution.samples}/${scorecard.execution.fullTelemetryTrades}/${scorecard.execution.arrivalQuoteCoverage == null ? "n/a" : `${round(scorecard.execution.arrivalQuoteCoverage * 100, 2)}%`}
1310
+ - Signal-close-to-fill adverse move: ${scorecard.execution.actualSignalToFillSlippageBps ?? "n/a"} bps
1311
+ - Signal-to-arrival adverse move: ${scorecard.execution.signalToArrivalAdverseBps ?? "n/a"} bps
1312
+ - Arrival-to-fill exchange slippage: ${scorecard.execution.arrivalToFillAdverseBps ?? "n/a"} bps
1232
1313
  - Residual vs current model: ${scorecard.execution.residualVsCurrentModelBps ?? "n/a"} bps
1314
+ - Average signal-close-to-submit: ${scorecard.execution.averageSignalCloseToSubmitMs ?? "n/a"} ms
1315
+ - Average submit-to-fill: ${scorecard.execution.averageSubmitToFillMs ?? "n/a"} ms
1233
1316
 
1234
1317
  ## Rolling performance
1235
1318
 
@@ -1938,6 +2021,11 @@ import_args.default.option(
1938
2021
  "Maximum acceptable execution residual in bps",
1939
2022
  3
1940
2023
  );
2024
+ import_args.default.option(
2025
+ ["L", "maximumSignalCloseToSubmitMs"],
2026
+ "Maximum acceptable average signal-close-to-submit latency in milliseconds",
2027
+ 3e4
2028
+ );
1941
2029
  import_args.default.option(
1942
2030
  ["T", "minimumClosedTrades"],
1943
2031
  "Minimum 7d closed trades for expectancy reactions",
@@ -2030,6 +2118,7 @@ var runtimeScorecard = async () => {
2030
2118
  thresholds: {
2031
2119
  minimumParityRatio: Number(flags.minimumParityRatio),
2032
2120
  maximumSlippageResidualBps: Number(flags.maximumSlippageResidualBps),
2121
+ maximumSignalCloseToSubmitMs: Number(flags.maximumSignalCloseToSubmitMs),
2033
2122
  minimumClosedTrades: Number(flags.minimumClosedTrades),
2034
2123
  minimumExpectancy: Number(flags.minimumExpectancy)
2035
2124
  },
@@ -3778,7 +3778,38 @@ var import_logger = require("@tradejs/infra/logger");
3778
3778
 
3779
3779
  // src/lib/runtimeSignalsStorage.ts
3780
3780
  var import_constants2 = require("@tradejs/core/constants");
3781
+ var import_data = require("@tradejs/core/data");
3781
3782
  var import_time2 = require("@tradejs/core/time");
3783
+ var appendRuntimeSignalEvaluationRun = (runs = [], evaluation) => {
3784
+ if (evaluation.status !== "skip") {
3785
+ return [...runs];
3786
+ }
3787
+ const stepMs = (0, import_data.intervalToMs)(evaluation.interval);
3788
+ const reason = evaluation.reason?.trim() || "NO_SIGNAL";
3789
+ const existing = runs.find(
3790
+ (run) => run.status === "skip" && run.reason === reason && run.stepMs === stepMs && evaluation.timestamp >= run.firstTimestamp && evaluation.timestamp <= run.lastTimestamp && (evaluation.timestamp - run.firstTimestamp) % stepMs === 0
3791
+ );
3792
+ if (existing) {
3793
+ return [...runs];
3794
+ }
3795
+ const last = runs.at(-1);
3796
+ if (last?.status === "skip" && last.reason === reason && last.stepMs === stepMs && evaluation.timestamp === last.lastTimestamp + stepMs) {
3797
+ return [
3798
+ ...runs.slice(0, -1),
3799
+ { ...last, lastTimestamp: evaluation.timestamp }
3800
+ ];
3801
+ }
3802
+ return [
3803
+ ...runs,
3804
+ {
3805
+ status: "skip",
3806
+ reason,
3807
+ firstTimestamp: evaluation.timestamp,
3808
+ lastTimestamp: evaluation.timestamp,
3809
+ stepMs
3810
+ }
3811
+ ];
3812
+ };
3782
3813
  var SECONDS_PER_DAY = 86400;
3783
3814
  var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
3784
3815
  var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants2.TTL_3D;
@@ -4091,6 +4122,10 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
4091
4122
  lastTimestamp: Math.max(
4092
4123
  existing?.lastTimestamp ?? evaluation.timestamp,
4093
4124
  evaluation.timestamp
4125
+ ),
4126
+ evaluationRuns: appendRuntimeSignalEvaluationRun(
4127
+ existing?.evaluationRuns,
4128
+ evaluation
4094
4129
  )
4095
4130
  };
4096
4131
  runtimeLineageScopeCache.set(cacheKey, scope);
@@ -4812,6 +4847,8 @@ var createSignalsTickerEvaluator = ({
4812
4847
  orderStatus: signal.orderStatus,
4813
4848
  orderSkipReason: signal.orderSkipReason,
4814
4849
  ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
4850
+ ...signal.allocatorDecision ? { allocatorDecision: signal.allocatorDecision } : {},
4851
+ ...signal.riskDecision ? { riskDecision: signal.riskDecision } : {},
4815
4852
  ...signal.ml ? { ml: signal.ml } : {}
4816
4853
  });
4817
4854
  import_logger3.logger.info("Signal found %s by strategy %s", symbol, strategyName);
@@ -3776,7 +3776,38 @@ var import_logger = require("@tradejs/infra/logger");
3776
3776
 
3777
3777
  // src/lib/runtimeSignalsStorage.ts
3778
3778
  var import_constants2 = require("@tradejs/core/constants");
3779
+ var import_data = require("@tradejs/core/data");
3779
3780
  var import_time2 = require("@tradejs/core/time");
3781
+ var appendRuntimeSignalEvaluationRun = (runs = [], evaluation) => {
3782
+ if (evaluation.status !== "skip") {
3783
+ return [...runs];
3784
+ }
3785
+ const stepMs = (0, import_data.intervalToMs)(evaluation.interval);
3786
+ const reason = evaluation.reason?.trim() || "NO_SIGNAL";
3787
+ const existing = runs.find(
3788
+ (run) => run.status === "skip" && run.reason === reason && run.stepMs === stepMs && evaluation.timestamp >= run.firstTimestamp && evaluation.timestamp <= run.lastTimestamp && (evaluation.timestamp - run.firstTimestamp) % stepMs === 0
3789
+ );
3790
+ if (existing) {
3791
+ return [...runs];
3792
+ }
3793
+ const last = runs.at(-1);
3794
+ if (last?.status === "skip" && last.reason === reason && last.stepMs === stepMs && evaluation.timestamp === last.lastTimestamp + stepMs) {
3795
+ return [
3796
+ ...runs.slice(0, -1),
3797
+ { ...last, lastTimestamp: evaluation.timestamp }
3798
+ ];
3799
+ }
3800
+ return [
3801
+ ...runs,
3802
+ {
3803
+ status: "skip",
3804
+ reason,
3805
+ firstTimestamp: evaluation.timestamp,
3806
+ lastTimestamp: evaluation.timestamp,
3807
+ stepMs
3808
+ }
3809
+ ];
3810
+ };
3780
3811
  var SECONDS_PER_DAY = 86400;
3781
3812
  var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
3782
3813
  var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants2.TTL_3D;
@@ -4089,6 +4120,10 @@ var flushRuntimeSignalEvaluations = async (evaluations) => {
4089
4120
  lastTimestamp: Math.max(
4090
4121
  existing?.lastTimestamp ?? evaluation.timestamp,
4091
4122
  evaluation.timestamp
4123
+ ),
4124
+ evaluationRuns: appendRuntimeSignalEvaluationRun(
4125
+ existing?.evaluationRuns,
4126
+ evaluation
4092
4127
  )
4093
4128
  };
4094
4129
  runtimeLineageScopeCache.set(cacheKey, scope);
@@ -4810,6 +4845,8 @@ var createSignalsTickerEvaluator = ({
4810
4845
  orderStatus: signal.orderStatus,
4811
4846
  orderSkipReason: signal.orderSkipReason,
4812
4847
  ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
4848
+ ...signal.allocatorDecision ? { allocatorDecision: signal.allocatorDecision } : {},
4849
+ ...signal.riskDecision ? { riskDecision: signal.riskDecision } : {},
4813
4850
  ...signal.ml ? { ml: signal.ml } : {}
4814
4851
  });
4815
4852
  import_logger3.logger.info("Signal found %s by strategy %s", symbol, strategyName);
@@ -436,6 +436,7 @@ var import_redis3 = require("@tradejs/infra/redis");
436
436
 
437
437
  // src/lib/runtimeSignalsStorage.ts
438
438
  var import_constants2 = require("@tradejs/core/constants");
439
+ var import_data = require("@tradejs/core/data");
439
440
  var import_time3 = require("@tradejs/core/time");
440
441
  var isRuntimeSignalBucketRef = (value) => {
441
442
  if (!value || typeof value !== "object") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "3.1.26-beta.244",
3
+ "version": "3.1.27-beta.245",
4
4
  "description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -36,13 +36,13 @@
36
36
  "tradejs": "dist/cli.js"
37
37
  },
38
38
  "dependencies": {
39
- "@tradejs/connectors": "^3.1.26-beta.244",
40
- "@tradejs/core": "^3.1.26-beta.244",
41
- "@tradejs/indicators": "^3.1.26-beta.244",
42
- "@tradejs/infra": "^3.1.26-beta.244",
43
- "@tradejs/node": "^3.1.26-beta.244",
39
+ "@tradejs/connectors": "^3.1.27-beta.245",
40
+ "@tradejs/core": "^3.1.27-beta.245",
41
+ "@tradejs/indicators": "^3.1.27-beta.245",
42
+ "@tradejs/infra": "^3.1.27-beta.245",
43
+ "@tradejs/node": "^3.1.27-beta.245",
44
44
  "@tradejs/strategy-kit": "3.0.4",
45
- "@tradejs/types": "^3.1.26-beta.244",
45
+ "@tradejs/types": "^3.1.27-beta.245",
46
46
  "args": "^5.0.3",
47
47
  "bcryptjs": "^2.4.3",
48
48
  "chalk": "4.1.2",