@tradejs/cli 3.1.26 → 3.1.27-beta.247
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.
- package/dist/cli.js +436 -45
- package/dist/lib/runtimeDebugEvidence.js +1 -0
- package/dist/lib/runtimeParityDetails.js +128 -6
- package/dist/lib/runtimeScorecard.js +103 -20
- package/dist/lib/runtimeSignalsLoader.js +1 -0
- package/dist/lib/runtimeSignalsStorage.js +33 -0
- package/dist/scripts/replay.js +266 -19
- package/dist/scripts/replayRunner.js +266 -19
- package/dist/scripts/replayRuntimeEvidence.js +14 -1
- package/dist/scripts/runtimeEvidence.js +1 -0
- package/dist/scripts/runtimeParity.js +1 -0
- package/dist/scripts/runtimeScorecard.js +109 -20
- package/dist/scripts/signals.js +37 -0
- package/dist/scripts/signalsDaemon.js +37 -0
- package/dist/scripts/signalsSummary.js +1 -0
- package/package.json +7 -7
|
@@ -104,6 +104,7 @@ var import_redis2 = require("@tradejs/infra/redis");
|
|
|
104
104
|
|
|
105
105
|
// src/lib/runtimeSignalsStorage.ts
|
|
106
106
|
var import_constants = require("@tradejs/core/constants");
|
|
107
|
+
var import_data = require("@tradejs/core/data");
|
|
107
108
|
var import_time2 = require("@tradejs/core/time");
|
|
108
109
|
var isRuntimeSignalBucketRef = (value) => {
|
|
109
110
|
if (!value || typeof value !== "object") {
|
|
@@ -208,6 +208,90 @@ var findNearestEvaluation = ({
|
|
|
208
208
|
timestampDiffMs: bestDiff
|
|
209
209
|
} : null;
|
|
210
210
|
};
|
|
211
|
+
var findNearestRuntimeEvaluationOutcome = ({
|
|
212
|
+
entry,
|
|
213
|
+
lineageScopes,
|
|
214
|
+
toleranceMs
|
|
215
|
+
}) => {
|
|
216
|
+
const strategy = getEntryStrategy(entry);
|
|
217
|
+
const timestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
218
|
+
if (!strategy) return null;
|
|
219
|
+
let nearest = null;
|
|
220
|
+
for (const scope of lineageScopes) {
|
|
221
|
+
if (scope.strategy !== strategy || scope.symbol !== entry.symbol) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
for (const run of scope.evaluationRuns ?? []) {
|
|
225
|
+
if (run.stepMs <= 0 || timestamp < run.firstTimestamp - toleranceMs || timestamp > run.lastTimestamp + toleranceMs) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const step = Math.round((timestamp - run.firstTimestamp) / run.stepMs);
|
|
229
|
+
const candidateTimestamp = run.firstTimestamp + step * run.stepMs;
|
|
230
|
+
const timestampDiffMs = Math.abs(candidateTimestamp - timestamp);
|
|
231
|
+
if (candidateTimestamp < run.firstTimestamp || candidateTimestamp > run.lastTimestamp || timestampDiffMs > toleranceMs || nearest && timestampDiffMs >= nearest.timestampDiffMs) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
nearest = {
|
|
235
|
+
status: run.status,
|
|
236
|
+
reason: run.reason,
|
|
237
|
+
timestamp: candidateTimestamp,
|
|
238
|
+
timestampDiffMs,
|
|
239
|
+
source: "lineage_scope_compact"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return nearest;
|
|
244
|
+
};
|
|
245
|
+
var findNearestReplayEvaluationOutcome = ({
|
|
246
|
+
entry,
|
|
247
|
+
lineageScopes,
|
|
248
|
+
toleranceMs,
|
|
249
|
+
timestampOffsetMs
|
|
250
|
+
}) => {
|
|
251
|
+
const strategy = getEntryStrategy(entry);
|
|
252
|
+
if (!strategy) return null;
|
|
253
|
+
let nearest = null;
|
|
254
|
+
for (const scope of lineageScopes) {
|
|
255
|
+
if (scope.strategy !== strategy || scope.symbol !== entry.symbol) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
for (const run of scope.evaluationRuns ?? []) {
|
|
259
|
+
if (run.stepMs <= 0) continue;
|
|
260
|
+
const targetTimestamp = entry.timestamp - timestampOffsetMs;
|
|
261
|
+
if (targetTimestamp < run.firstTimestamp - toleranceMs || targetTimestamp > run.lastTimestamp + toleranceMs) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const step = Math.round(
|
|
265
|
+
(targetTimestamp - run.firstTimestamp) / run.stepMs
|
|
266
|
+
);
|
|
267
|
+
const candidateTimestamp = run.firstTimestamp + step * run.stepMs;
|
|
268
|
+
const timestampDiffMs = Math.abs(
|
|
269
|
+
candidateTimestamp + timestampOffsetMs - entry.timestamp
|
|
270
|
+
);
|
|
271
|
+
if (candidateTimestamp < run.firstTimestamp || candidateTimestamp > run.lastTimestamp || timestampDiffMs > toleranceMs || nearest && timestampDiffMs >= nearest.timestampDiffMs) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
nearest = {
|
|
275
|
+
status: run.status,
|
|
276
|
+
reason: run.reason,
|
|
277
|
+
timestamp: candidateTimestamp,
|
|
278
|
+
timestampDiffMs,
|
|
279
|
+
source: "replay_scope_compact"
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return nearest;
|
|
284
|
+
};
|
|
285
|
+
var hasRuntimeLineageCoverage = ({
|
|
286
|
+
entry,
|
|
287
|
+
lineageScopes
|
|
288
|
+
}) => {
|
|
289
|
+
const strategy = getEntryStrategy(entry);
|
|
290
|
+
const timestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
291
|
+
return lineageScopes.some(
|
|
292
|
+
(scope) => scope.strategy === strategy && scope.symbol === entry.symbol && timestamp >= scope.firstTimestamp && timestamp <= scope.lastTimestamp
|
|
293
|
+
);
|
|
294
|
+
};
|
|
211
295
|
var classifySignalDiagnostic = (signal) => {
|
|
212
296
|
if (signal.orderStatus === "failed") {
|
|
213
297
|
return {
|
|
@@ -452,6 +536,8 @@ var buildReplayMismatchDrilldown = ({
|
|
|
452
536
|
runtimeSignalEvaluations = [],
|
|
453
537
|
replaySignals = [],
|
|
454
538
|
replaySignalEvaluations = [],
|
|
539
|
+
replayLineageScopes = [],
|
|
540
|
+
runtimeLineageScopes = [],
|
|
455
541
|
toleranceMs,
|
|
456
542
|
backtestTimestampOffsetMs,
|
|
457
543
|
limit
|
|
@@ -477,7 +563,16 @@ var buildReplayMismatchDrilldown = ({
|
|
|
477
563
|
toleranceMs,
|
|
478
564
|
timestampOffsetMs: backtestTimestampOffsetMs
|
|
479
565
|
});
|
|
480
|
-
const
|
|
566
|
+
const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
|
|
567
|
+
entry,
|
|
568
|
+
lineageScopes: replayLineageScopes,
|
|
569
|
+
toleranceMs,
|
|
570
|
+
timestampOffsetMs: backtestTimestampOffsetMs
|
|
571
|
+
});
|
|
572
|
+
const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
|
|
573
|
+
classification: "core_skipped",
|
|
574
|
+
reason: nearestReplayEvaluationOutcome.reason
|
|
575
|
+
} : nearestCandidate?.nearest ? {
|
|
481
576
|
classification: "timing_or_price_drift",
|
|
482
577
|
reason: nearestCandidate.reason
|
|
483
578
|
} : {
|
|
@@ -501,7 +596,8 @@ var buildReplayMismatchDrilldown = ({
|
|
|
501
596
|
timestampDiffMs: nearestReplayEvaluation.timestampDiffMs
|
|
502
597
|
}),
|
|
503
598
|
...includeArtifacts ? { replayEvaluationArtifact: nearestReplayEvaluation.evaluation } : {}
|
|
504
|
-
} : {}
|
|
599
|
+
} : {},
|
|
600
|
+
...nearestReplayEvaluationOutcome ? { replayEvaluationOutcome: nearestReplayEvaluationOutcome } : {}
|
|
505
601
|
};
|
|
506
602
|
});
|
|
507
603
|
const backtestOnlyDiagnostics = backtestOnly.map((entry, index) => {
|
|
@@ -519,9 +615,26 @@ var buildReplayMismatchDrilldown = ({
|
|
|
519
615
|
toleranceMs,
|
|
520
616
|
timestampOffsetMs: 0
|
|
521
617
|
});
|
|
522
|
-
const
|
|
618
|
+
const nearestRuntimeEvaluationOutcome = findNearestRuntimeEvaluationOutcome(
|
|
619
|
+
{
|
|
620
|
+
entry,
|
|
621
|
+
lineageScopes: runtimeLineageScopes,
|
|
622
|
+
toleranceMs
|
|
623
|
+
}
|
|
624
|
+
);
|
|
625
|
+
const runtimeLineageCoversEntry = hasRuntimeLineageCoverage({
|
|
626
|
+
entry,
|
|
627
|
+
lineageScopes: runtimeLineageScopes
|
|
628
|
+
});
|
|
629
|
+
const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestRuntimeEvaluationOutcome ? {
|
|
630
|
+
classification: "core_skipped",
|
|
631
|
+
reason: nearestRuntimeEvaluationOutcome.reason
|
|
632
|
+
} : nearestCandidate?.nearest ? {
|
|
523
633
|
classification: "timing_or_price_drift",
|
|
524
634
|
reason: nearestCandidate.reason
|
|
635
|
+
} : runtimeLineageCoversEntry ? {
|
|
636
|
+
classification: "runtime_evaluation_detail_unavailable",
|
|
637
|
+
reason: "runtime_scope_covers_timestamp_but_skip_detail_was_not_stored"
|
|
525
638
|
} : {
|
|
526
639
|
classification: "no_runtime_evaluation",
|
|
527
640
|
reason: "no_runtime_signal_or_evaluation"
|
|
@@ -545,7 +658,8 @@ var buildReplayMismatchDrilldown = ({
|
|
|
545
658
|
...includeArtifacts ? {
|
|
546
659
|
runtimeEvaluationArtifact: nearestRuntimeEvaluation.evaluation
|
|
547
660
|
} : {}
|
|
548
|
-
} : {}
|
|
661
|
+
} : {},
|
|
662
|
+
...nearestRuntimeEvaluationOutcome ? { runtimeEvaluationOutcome: nearestRuntimeEvaluationOutcome } : {}
|
|
549
663
|
};
|
|
550
664
|
});
|
|
551
665
|
for (const diagnostic of runtimeOnlyDiagnostics) {
|
|
@@ -592,7 +706,9 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
592
706
|
runtimeSignals,
|
|
593
707
|
runtimeSignalEvaluations,
|
|
594
708
|
replaySignals,
|
|
595
|
-
replaySignalEvaluations
|
|
709
|
+
replaySignalEvaluations,
|
|
710
|
+
replayLineageScopes,
|
|
711
|
+
runtimeLineageScopes
|
|
596
712
|
}) => {
|
|
597
713
|
const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
|
|
598
714
|
const backtestDetails = backtestEntries.map(
|
|
@@ -651,6 +767,8 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
651
767
|
runtimeSignalEvaluations,
|
|
652
768
|
replaySignals,
|
|
653
769
|
replaySignalEvaluations,
|
|
770
|
+
replayLineageScopes,
|
|
771
|
+
runtimeLineageScopes,
|
|
654
772
|
toleranceMs,
|
|
655
773
|
backtestTimestampOffsetMs,
|
|
656
774
|
limit
|
|
@@ -671,7 +789,9 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
671
789
|
runtimeSignals,
|
|
672
790
|
runtimeSignalEvaluations,
|
|
673
791
|
replaySignals,
|
|
674
|
-
replaySignalEvaluations
|
|
792
|
+
replaySignalEvaluations,
|
|
793
|
+
replayLineageScopes,
|
|
794
|
+
runtimeLineageScopes
|
|
675
795
|
}) => {
|
|
676
796
|
const toExchangeDetail = (entry) => toExchangeParityDetail({
|
|
677
797
|
entry,
|
|
@@ -758,6 +878,8 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
758
878
|
runtimeSignalEvaluations,
|
|
759
879
|
replaySignals,
|
|
760
880
|
replaySignalEvaluations,
|
|
881
|
+
replayLineageScopes,
|
|
882
|
+
runtimeLineageScopes,
|
|
761
883
|
toleranceMs,
|
|
762
884
|
backtestTimestampOffsetMs,
|
|
763
885
|
limit
|
|
@@ -426,6 +426,20 @@ var countBy = (items, getter) => {
|
|
|
426
426
|
Object.entries(counts).sort(([left], [right]) => left.localeCompare(right))
|
|
427
427
|
);
|
|
428
428
|
};
|
|
429
|
+
var resolveGateDecision = (evaluation) => {
|
|
430
|
+
const analysis = asRecord(evaluation.aiAnalysis);
|
|
431
|
+
const explicit = finiteString(analysis?.gateDecision);
|
|
432
|
+
if (explicit === "approved" || explicit === "rejected") {
|
|
433
|
+
return { decision: explicit, source: "gate_decision" };
|
|
434
|
+
}
|
|
435
|
+
if (typeof analysis?.approved === "boolean") {
|
|
436
|
+
return {
|
|
437
|
+
decision: analysis.approved ? "approved" : "rejected",
|
|
438
|
+
source: "legacy_approved_fallback"
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
442
|
+
};
|
|
429
443
|
var buildDistributions = ({
|
|
430
444
|
evaluations,
|
|
431
445
|
signals
|
|
@@ -606,18 +620,15 @@ var buildRuntimeScorecard = ({
|
|
|
606
620
|
const signalEvaluations = rows.evaluations.filter(
|
|
607
621
|
(evaluation) => evaluation.status === "signal"
|
|
608
622
|
);
|
|
609
|
-
const gateDecisions = signalEvaluations.map(
|
|
610
|
-
|
|
611
|
-
decision: finiteString(asRecord(evaluation.aiAnalysis)?.gateDecision)
|
|
612
|
-
})).filter(
|
|
613
|
-
({ decision }) => decision === "approved" || decision === "rejected"
|
|
623
|
+
const gateDecisions = signalEvaluations.map(resolveGateDecision).filter(
|
|
624
|
+
(decision) => Boolean(decision)
|
|
614
625
|
);
|
|
615
626
|
const llmEligibleEvaluations = llmComparatorPolicy === "disabled" ? [] : llmComparatorPolicy === "ai_approved_only" ? signalEvaluations.filter(
|
|
616
|
-
(evaluation) =>
|
|
627
|
+
(evaluation) => resolveGateDecision(evaluation)?.decision === "approved"
|
|
617
628
|
) : signalEvaluations;
|
|
618
629
|
const gateComparisons = llmEligibleEvaluations.map((evaluation) => {
|
|
619
630
|
const analysis = asRecord(evaluation.aiAnalysis);
|
|
620
|
-
const gateDecision =
|
|
631
|
+
const gateDecision = resolveGateDecision(evaluation)?.decision ?? null;
|
|
621
632
|
const llmDecision = finiteString(analysis?.llmDecision);
|
|
622
633
|
if (gateDecision !== "approved" && gateDecision !== "rejected" || llmDecision !== "approved" && llmDecision !== "rejected") {
|
|
623
634
|
return null;
|
|
@@ -652,6 +663,27 @@ var buildRuntimeScorecard = ({
|
|
|
652
663
|
};
|
|
653
664
|
const actualSlippageBps = averageSampleMetric("signalToFillAdverseBps");
|
|
654
665
|
const residualVsModelBps = averageSampleMetric("residualVsCurrentModelBps");
|
|
666
|
+
const signalToArrivalAdverseBps = averageSampleMetric(
|
|
667
|
+
"signalToArrivalAdverseBps"
|
|
668
|
+
);
|
|
669
|
+
const arrivalToFillAdverseBps = averageSampleMetric(
|
|
670
|
+
"arrivalToFillAdverseBps"
|
|
671
|
+
);
|
|
672
|
+
const averageSignalCloseToSubmitMs = averageSampleMetric(
|
|
673
|
+
"signalCloseToSubmitMs"
|
|
674
|
+
);
|
|
675
|
+
const averageSubmitToFillMs = averageSampleMetric("submitToFillMs");
|
|
676
|
+
const fullTelemetryTrades = calibrationSamples.filter(
|
|
677
|
+
(sample) => finiteString(sample.telemetryQuality) === "full"
|
|
678
|
+
).length;
|
|
679
|
+
const arrivalQuoteTrades = calibrationSamples.filter(
|
|
680
|
+
(sample) => finiteNumber(sample.arrivalMid) != null
|
|
681
|
+
).length;
|
|
682
|
+
const arrivalQuoteCoverage = calibrationSamples.length ? round(arrivalQuoteTrades / calibrationSamples.length) : null;
|
|
683
|
+
const hasTelemetryQuality = calibrationSamples.some(
|
|
684
|
+
(sample) => finiteString(sample.telemetryQuality) != null
|
|
685
|
+
);
|
|
686
|
+
const maximumSignalCloseToSubmitMs = thresholds.maximumSignalCloseToSubmitMs ?? 3e4;
|
|
655
687
|
const prospective = getProspectiveSummary(prospectiveEvidenceArtifact);
|
|
656
688
|
const historyTrades = dedupeTrades([
|
|
657
689
|
...scopedHistoryArtifacts,
|
|
@@ -708,7 +740,28 @@ var buildRuntimeScorecard = ({
|
|
|
708
740
|
reactions.push({
|
|
709
741
|
code: "SLIPPAGE_DRIFT",
|
|
710
742
|
severity: "warning",
|
|
711
|
-
message: `
|
|
743
|
+
message: `Signal-close-to-fill residual ${round(residualVsModelBps, 2)} bps exceeds ${thresholds.maximumSlippageResidualBps} bps.`
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (averageSignalCloseToSubmitMs != null && averageSignalCloseToSubmitMs > maximumSignalCloseToSubmitMs) {
|
|
747
|
+
reactions.push({
|
|
748
|
+
code: "PRE_SUBMIT_LATENCY",
|
|
749
|
+
severity: "warning",
|
|
750
|
+
message: `Average signal-close-to-submit latency ${round(averageSignalCloseToSubmitMs, 3)} ms exceeds ${maximumSignalCloseToSubmitMs} ms.`
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
if (hasTelemetryQuality && calibrationSamples.length > 0 && fullTelemetryTrades < calibrationSamples.length) {
|
|
754
|
+
reactions.push({
|
|
755
|
+
code: "EXECUTION_TELEMETRY_INCOMPLETE",
|
|
756
|
+
severity: "info",
|
|
757
|
+
message: `Full arrival and fill telemetry is available for ${fullTelemetryTrades}/${calibrationSamples.length} trades.`
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
if (riskDecisions.some((decision) => decision.status === "rejected")) {
|
|
761
|
+
reactions.push({
|
|
762
|
+
code: "RISK_PLAN_EXCEEDS_DECLARED_LIMIT",
|
|
763
|
+
severity: "warning",
|
|
764
|
+
message: "At least one observed order plan exceeds its declared MAX_LOSS_VALUE; telemetry is not yet enforced."
|
|
712
765
|
});
|
|
713
766
|
}
|
|
714
767
|
if (sevenDay.closedTrades >= thresholds.minimumClosedTrades && sevenDay.expectancy != null && sevenDay.expectancy < thresholds.minimumExpectancy) {
|
|
@@ -737,7 +790,10 @@ var buildRuntimeScorecard = ({
|
|
|
737
790
|
deployment: deploymentBinding,
|
|
738
791
|
lineage: runtimeLineage,
|
|
739
792
|
promotionStatus,
|
|
740
|
-
thresholds
|
|
793
|
+
thresholds: {
|
|
794
|
+
...thresholds,
|
|
795
|
+
maximumSignalCloseToSubmitMs
|
|
796
|
+
},
|
|
741
797
|
funnel: {
|
|
742
798
|
evaluations: rows.evaluations.length,
|
|
743
799
|
coreCandidates: signalEvaluations.length,
|
|
@@ -749,25 +805,38 @@ var buildRuntimeScorecard = ({
|
|
|
749
805
|
rejected: gateDecisions.filter(
|
|
750
806
|
({ decision }) => decision === "rejected"
|
|
751
807
|
).length,
|
|
752
|
-
coverage: signalEvaluations.length ? round(gateDecisions.length / signalEvaluations.length) : null
|
|
808
|
+
coverage: signalEvaluations.length ? round(gateDecisions.length / signalEvaluations.length) : null,
|
|
809
|
+
legacyApprovedFallback: gateDecisions.filter(
|
|
810
|
+
({ source }) => source === "legacy_approved_fallback"
|
|
811
|
+
).length
|
|
753
812
|
},
|
|
754
813
|
allocator: {
|
|
755
814
|
available: allocatorDecisions.length > 0,
|
|
756
815
|
approved: allocatorDecisions.filter(
|
|
757
|
-
(decision) => decision.
|
|
816
|
+
(decision) => decision.status === "approved"
|
|
758
817
|
).length,
|
|
759
818
|
rejected: allocatorDecisions.filter(
|
|
760
|
-
(decision) => decision.
|
|
819
|
+
(decision) => decision.status === "rejected"
|
|
820
|
+
).length,
|
|
821
|
+
notApplicable: allocatorDecisions.filter(
|
|
822
|
+
(decision) => decision.status === "not_applicable"
|
|
823
|
+
).length,
|
|
824
|
+
unavailable: allocatorDecisions.filter(
|
|
825
|
+
(decision) => decision.status === "unavailable"
|
|
761
826
|
).length
|
|
762
827
|
},
|
|
763
828
|
risk: {
|
|
764
829
|
available: riskDecisions.length > 0,
|
|
765
830
|
approved: riskDecisions.filter(
|
|
766
|
-
(decision) => decision.
|
|
831
|
+
(decision) => decision.status === "approved"
|
|
767
832
|
).length,
|
|
768
833
|
rejected: riskDecisions.filter(
|
|
769
|
-
(decision) => decision.
|
|
770
|
-
).length
|
|
834
|
+
(decision) => decision.status === "rejected"
|
|
835
|
+
).length,
|
|
836
|
+
unavailable: riskDecisions.filter(
|
|
837
|
+
(decision) => decision.status === "unavailable"
|
|
838
|
+
).length,
|
|
839
|
+
enforced: riskDecisions.some((decision) => decision.enforced === true)
|
|
771
840
|
},
|
|
772
841
|
orderAttempts: orderAttempts.length,
|
|
773
842
|
orderFailures: orderAttempts.filter(
|
|
@@ -829,8 +898,16 @@ var buildRuntimeScorecard = ({
|
|
|
829
898
|
},
|
|
830
899
|
execution: {
|
|
831
900
|
available: actualSlippageBps != null || residualVsModelBps != null,
|
|
901
|
+
metricScope: arrivalToFillAdverseBps == null ? "signal_close_to_fill" : "signal_and_exchange_components",
|
|
902
|
+
samples: calibrationSamples.length,
|
|
903
|
+
fullTelemetryTrades,
|
|
904
|
+
arrivalQuoteCoverage,
|
|
832
905
|
actualSignalToFillSlippageBps: actualSlippageBps,
|
|
833
|
-
residualVsCurrentModelBps: residualVsModelBps
|
|
906
|
+
residualVsCurrentModelBps: residualVsModelBps,
|
|
907
|
+
signalToArrivalAdverseBps,
|
|
908
|
+
arrivalToFillAdverseBps,
|
|
909
|
+
averageSignalCloseToSubmitMs,
|
|
910
|
+
averageSubmitToFillMs
|
|
834
911
|
},
|
|
835
912
|
prospective,
|
|
836
913
|
rolling,
|
|
@@ -884,10 +961,10 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
|
|
|
884
961
|
|
|
885
962
|
- Evaluations: ${scorecard.funnel.evaluations}
|
|
886
963
|
- Core candidates: ${scorecard.funnel.coreCandidates}
|
|
887
|
-
- Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected}
|
|
964
|
+
- Gate approvals/rejects: ${scorecard.funnel.gate.approved}/${scorecard.funnel.gate.rejected} (legacy fallback: ${scorecard.funnel.gate.legacyApprovedFallback})
|
|
888
965
|
- AI / LLM disagreement: ${scorecard.gateComparison.disagreements}/${scorecard.gateComparison.compared}
|
|
889
|
-
-
|
|
890
|
-
- Risk
|
|
966
|
+
- Runtime admission approved/rejected/not-applicable: ${scorecard.funnel.allocator.available ? `${scorecard.funnel.allocator.approved}/${scorecard.funnel.allocator.rejected}/${scorecard.funnel.allocator.notApplicable}` : "unavailable"}
|
|
967
|
+
- Risk assessment within/exceeds/unavailable: ${scorecard.funnel.risk.available ? `${scorecard.funnel.risk.approved}/${scorecard.funnel.risk.rejected}/${scorecard.funnel.risk.unavailable}` : "unavailable"} (enforced: ${scorecard.funnel.risk.enforced})
|
|
891
968
|
- Order attempts/failures: ${scorecard.funnel.orderAttempts}/${scorecard.funnel.orderFailures}
|
|
892
969
|
- Fills: ${scorecard.funnel.fills}
|
|
893
970
|
- Closed trades: ${scorecard.funnel.closedTrades}
|
|
@@ -896,8 +973,14 @@ Window: ${new Date(scorecard.window.startTime).toISOString()} \u2014 ${new Date(
|
|
|
896
973
|
|
|
897
974
|
- Parity: ${scorecard.parity.ratio == null ? "n/a" : `${round(scorecard.parity.ratio * 100, 2)}%`}
|
|
898
975
|
- Runtime/replay mismatches: ${scorecard.parity.backtestOnly + scorecard.parity.runtimeOnly}
|
|
899
|
-
-
|
|
976
|
+
- Execution metric scope: ${scorecard.execution.metricScope}
|
|
977
|
+
- Telemetry samples/full/arrival coverage: ${scorecard.execution.samples}/${scorecard.execution.fullTelemetryTrades}/${scorecard.execution.arrivalQuoteCoverage == null ? "n/a" : `${round(scorecard.execution.arrivalQuoteCoverage * 100, 2)}%`}
|
|
978
|
+
- Signal-close-to-fill adverse move: ${scorecard.execution.actualSignalToFillSlippageBps ?? "n/a"} bps
|
|
979
|
+
- Signal-to-arrival adverse move: ${scorecard.execution.signalToArrivalAdverseBps ?? "n/a"} bps
|
|
980
|
+
- Arrival-to-fill exchange slippage: ${scorecard.execution.arrivalToFillAdverseBps ?? "n/a"} bps
|
|
900
981
|
- Residual vs current model: ${scorecard.execution.residualVsCurrentModelBps ?? "n/a"} bps
|
|
982
|
+
- Average signal-close-to-submit: ${scorecard.execution.averageSignalCloseToSubmitMs ?? "n/a"} ms
|
|
983
|
+
- Average submit-to-fill: ${scorecard.execution.averageSubmitToFillMs ?? "n/a"} ms
|
|
901
984
|
|
|
902
985
|
## Rolling performance
|
|
903
986
|
|
|
@@ -32,6 +32,7 @@ var import_redis = require("@tradejs/infra/redis");
|
|
|
32
32
|
|
|
33
33
|
// src/lib/runtimeSignalsStorage.ts
|
|
34
34
|
var import_constants = require("@tradejs/core/constants");
|
|
35
|
+
var import_data = require("@tradejs/core/data");
|
|
35
36
|
var import_time = require("@tradejs/core/time");
|
|
36
37
|
var isRuntimeSignalBucketRef = (value) => {
|
|
37
38
|
if (!value || typeof value !== "object") {
|
|
@@ -23,6 +23,7 @@ __export(runtimeSignalsStorage_exports, {
|
|
|
23
23
|
DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS: () => DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS,
|
|
24
24
|
RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS: () => RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS,
|
|
25
25
|
RUNTIME_SIGNAL_RETENTION_DAYS_ENV: () => RUNTIME_SIGNAL_RETENTION_DAYS_ENV,
|
|
26
|
+
appendRuntimeSignalEvaluationRun: () => appendRuntimeSignalEvaluationRun,
|
|
26
27
|
buildRuntimeSignalStatsIncrements: () => buildRuntimeSignalStatsIncrements,
|
|
27
28
|
getRuntimeSignalRetentionTtlSeconds: () => getRuntimeSignalRetentionTtlSeconds,
|
|
28
29
|
getRuntimeStorageDayKey: () => import_time.getRuntimeStorageDayKey,
|
|
@@ -37,7 +38,38 @@ __export(runtimeSignalsStorage_exports, {
|
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(runtimeSignalsStorage_exports);
|
|
39
40
|
var import_constants = require("@tradejs/core/constants");
|
|
41
|
+
var import_data = require("@tradejs/core/data");
|
|
40
42
|
var import_time = require("@tradejs/core/time");
|
|
43
|
+
var appendRuntimeSignalEvaluationRun = (runs = [], evaluation) => {
|
|
44
|
+
if (evaluation.status !== "skip") {
|
|
45
|
+
return [...runs];
|
|
46
|
+
}
|
|
47
|
+
const stepMs = (0, import_data.intervalToMs)(evaluation.interval);
|
|
48
|
+
const reason = evaluation.reason?.trim() || "NO_SIGNAL";
|
|
49
|
+
const existing = runs.find(
|
|
50
|
+
(run) => run.status === "skip" && run.reason === reason && run.stepMs === stepMs && evaluation.timestamp >= run.firstTimestamp && evaluation.timestamp <= run.lastTimestamp && (evaluation.timestamp - run.firstTimestamp) % stepMs === 0
|
|
51
|
+
);
|
|
52
|
+
if (existing) {
|
|
53
|
+
return [...runs];
|
|
54
|
+
}
|
|
55
|
+
const last = runs.at(-1);
|
|
56
|
+
if (last?.status === "skip" && last.reason === reason && last.stepMs === stepMs && evaluation.timestamp === last.lastTimestamp + stepMs) {
|
|
57
|
+
return [
|
|
58
|
+
...runs.slice(0, -1),
|
|
59
|
+
{ ...last, lastTimestamp: evaluation.timestamp }
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
...runs,
|
|
64
|
+
{
|
|
65
|
+
status: "skip",
|
|
66
|
+
reason,
|
|
67
|
+
firstTimestamp: evaluation.timestamp,
|
|
68
|
+
lastTimestamp: evaluation.timestamp,
|
|
69
|
+
stepMs
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
};
|
|
41
73
|
var SECONDS_PER_DAY = 86400;
|
|
42
74
|
var RUNTIME_SIGNAL_RETENTION_DAYS_ENV = "RUNTIME_SIGNAL_RETENTION_DAYS";
|
|
43
75
|
var DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS = import_constants.TTL_3D;
|
|
@@ -175,6 +207,7 @@ var parseRuntimeSignalStatsBucket = (value) => {
|
|
|
175
207
|
DEFAULT_RUNTIME_SIGNAL_RETENTION_TTL_SECONDS,
|
|
176
208
|
RUNTIME_LINEAGE_SCOPE_RETENTION_TTL_SECONDS,
|
|
177
209
|
RUNTIME_SIGNAL_RETENTION_DAYS_ENV,
|
|
210
|
+
appendRuntimeSignalEvaluationRun,
|
|
178
211
|
buildRuntimeSignalStatsIncrements,
|
|
179
212
|
getRuntimeSignalRetentionTtlSeconds,
|
|
180
213
|
getRuntimeStorageDayKey,
|