@tradejs/cli 3.1.26 → 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.
- package/dist/cli.js +235 -36
- package/dist/lib/runtimeDebugEvidence.js +1 -0
- package/dist/lib/runtimeParityDetails.js +71 -4
- 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 +85 -11
- package/dist/scripts/replayRunner.js +85 -11
- package/dist/scripts/replayRuntimeEvidence.js +1 -0
- 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
|
@@ -208,6 +208,50 @@ 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 hasRuntimeLineageCoverage = ({
|
|
246
|
+
entry,
|
|
247
|
+
lineageScopes
|
|
248
|
+
}) => {
|
|
249
|
+
const strategy = getEntryStrategy(entry);
|
|
250
|
+
const timestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
251
|
+
return lineageScopes.some(
|
|
252
|
+
(scope) => scope.strategy === strategy && scope.symbol === entry.symbol && timestamp >= scope.firstTimestamp && timestamp <= scope.lastTimestamp
|
|
253
|
+
);
|
|
254
|
+
};
|
|
211
255
|
var classifySignalDiagnostic = (signal) => {
|
|
212
256
|
if (signal.orderStatus === "failed") {
|
|
213
257
|
return {
|
|
@@ -452,6 +496,7 @@ var buildReplayMismatchDrilldown = ({
|
|
|
452
496
|
runtimeSignalEvaluations = [],
|
|
453
497
|
replaySignals = [],
|
|
454
498
|
replaySignalEvaluations = [],
|
|
499
|
+
runtimeLineageScopes = [],
|
|
455
500
|
toleranceMs,
|
|
456
501
|
backtestTimestampOffsetMs,
|
|
457
502
|
limit
|
|
@@ -519,9 +564,26 @@ var buildReplayMismatchDrilldown = ({
|
|
|
519
564
|
toleranceMs,
|
|
520
565
|
timestampOffsetMs: 0
|
|
521
566
|
});
|
|
522
|
-
const
|
|
567
|
+
const nearestRuntimeEvaluationOutcome = findNearestRuntimeEvaluationOutcome(
|
|
568
|
+
{
|
|
569
|
+
entry,
|
|
570
|
+
lineageScopes: runtimeLineageScopes,
|
|
571
|
+
toleranceMs
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
const runtimeLineageCoversEntry = hasRuntimeLineageCoverage({
|
|
575
|
+
entry,
|
|
576
|
+
lineageScopes: runtimeLineageScopes
|
|
577
|
+
});
|
|
578
|
+
const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestRuntimeEvaluationOutcome ? {
|
|
579
|
+
classification: "core_skipped",
|
|
580
|
+
reason: nearestRuntimeEvaluationOutcome.reason
|
|
581
|
+
} : nearestCandidate?.nearest ? {
|
|
523
582
|
classification: "timing_or_price_drift",
|
|
524
583
|
reason: nearestCandidate.reason
|
|
584
|
+
} : runtimeLineageCoversEntry ? {
|
|
585
|
+
classification: "runtime_evaluation_detail_unavailable",
|
|
586
|
+
reason: "runtime_scope_covers_timestamp_but_skip_detail_was_not_stored"
|
|
525
587
|
} : {
|
|
526
588
|
classification: "no_runtime_evaluation",
|
|
527
589
|
reason: "no_runtime_signal_or_evaluation"
|
|
@@ -545,7 +607,8 @@ var buildReplayMismatchDrilldown = ({
|
|
|
545
607
|
...includeArtifacts ? {
|
|
546
608
|
runtimeEvaluationArtifact: nearestRuntimeEvaluation.evaluation
|
|
547
609
|
} : {}
|
|
548
|
-
} : {}
|
|
610
|
+
} : {},
|
|
611
|
+
...nearestRuntimeEvaluationOutcome ? { runtimeEvaluationOutcome: nearestRuntimeEvaluationOutcome } : {}
|
|
549
612
|
};
|
|
550
613
|
});
|
|
551
614
|
for (const diagnostic of runtimeOnlyDiagnostics) {
|
|
@@ -592,7 +655,8 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
592
655
|
runtimeSignals,
|
|
593
656
|
runtimeSignalEvaluations,
|
|
594
657
|
replaySignals,
|
|
595
|
-
replaySignalEvaluations
|
|
658
|
+
replaySignalEvaluations,
|
|
659
|
+
runtimeLineageScopes
|
|
596
660
|
}) => {
|
|
597
661
|
const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
|
|
598
662
|
const backtestDetails = backtestEntries.map(
|
|
@@ -651,6 +715,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
651
715
|
runtimeSignalEvaluations,
|
|
652
716
|
replaySignals,
|
|
653
717
|
replaySignalEvaluations,
|
|
718
|
+
runtimeLineageScopes,
|
|
654
719
|
toleranceMs,
|
|
655
720
|
backtestTimestampOffsetMs,
|
|
656
721
|
limit
|
|
@@ -671,7 +736,8 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
671
736
|
runtimeSignals,
|
|
672
737
|
runtimeSignalEvaluations,
|
|
673
738
|
replaySignals,
|
|
674
|
-
replaySignalEvaluations
|
|
739
|
+
replaySignalEvaluations,
|
|
740
|
+
runtimeLineageScopes
|
|
675
741
|
}) => {
|
|
676
742
|
const toExchangeDetail = (entry) => toExchangeParityDetail({
|
|
677
743
|
entry,
|
|
@@ -758,6 +824,7 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
758
824
|
runtimeSignalEvaluations,
|
|
759
825
|
replaySignals,
|
|
760
826
|
replaySignalEvaluations,
|
|
827
|
+
runtimeLineageScopes,
|
|
761
828
|
toleranceMs,
|
|
762
829
|
backtestTimestampOffsetMs,
|
|
763
830
|
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,
|
package/dist/scripts/replay.js
CHANGED
|
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(replay_exports);
|
|
|
37
37
|
// src/scripts/replayRunner.ts
|
|
38
38
|
var import_chalk13 = __toESM(require("chalk"));
|
|
39
39
|
var import_constants6 = require("@tradejs/core/constants");
|
|
40
|
-
var
|
|
40
|
+
var import_data3 = require("@tradejs/core/data");
|
|
41
41
|
var import_time7 = require("@tradejs/core/time");
|
|
42
42
|
var import_redis6 = require("@tradejs/infra/redis");
|
|
43
43
|
|
|
@@ -6440,6 +6440,7 @@ var import_redis4 = require("@tradejs/infra/redis");
|
|
|
6440
6440
|
|
|
6441
6441
|
// src/lib/runtimeSignalsStorage.ts
|
|
6442
6442
|
var import_constants4 = require("@tradejs/core/constants");
|
|
6443
|
+
var import_data = require("@tradejs/core/data");
|
|
6443
6444
|
var import_time4 = require("@tradejs/core/time");
|
|
6444
6445
|
var isRuntimeSignalBucketRef = (value) => {
|
|
6445
6446
|
if (!value || typeof value !== "object") {
|
|
@@ -6988,6 +6989,50 @@ var findNearestEvaluation = ({
|
|
|
6988
6989
|
timestampDiffMs: bestDiff
|
|
6989
6990
|
} : null;
|
|
6990
6991
|
};
|
|
6992
|
+
var findNearestRuntimeEvaluationOutcome = ({
|
|
6993
|
+
entry,
|
|
6994
|
+
lineageScopes,
|
|
6995
|
+
toleranceMs
|
|
6996
|
+
}) => {
|
|
6997
|
+
const strategy = getEntryStrategy(entry);
|
|
6998
|
+
const timestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
6999
|
+
if (!strategy) return null;
|
|
7000
|
+
let nearest = null;
|
|
7001
|
+
for (const scope of lineageScopes) {
|
|
7002
|
+
if (scope.strategy !== strategy || scope.symbol !== entry.symbol) {
|
|
7003
|
+
continue;
|
|
7004
|
+
}
|
|
7005
|
+
for (const run of scope.evaluationRuns ?? []) {
|
|
7006
|
+
if (run.stepMs <= 0 || timestamp < run.firstTimestamp - toleranceMs || timestamp > run.lastTimestamp + toleranceMs) {
|
|
7007
|
+
continue;
|
|
7008
|
+
}
|
|
7009
|
+
const step = Math.round((timestamp - run.firstTimestamp) / run.stepMs);
|
|
7010
|
+
const candidateTimestamp = run.firstTimestamp + step * run.stepMs;
|
|
7011
|
+
const timestampDiffMs = Math.abs(candidateTimestamp - timestamp);
|
|
7012
|
+
if (candidateTimestamp < run.firstTimestamp || candidateTimestamp > run.lastTimestamp || timestampDiffMs > toleranceMs || nearest && timestampDiffMs >= nearest.timestampDiffMs) {
|
|
7013
|
+
continue;
|
|
7014
|
+
}
|
|
7015
|
+
nearest = {
|
|
7016
|
+
status: run.status,
|
|
7017
|
+
reason: run.reason,
|
|
7018
|
+
timestamp: candidateTimestamp,
|
|
7019
|
+
timestampDiffMs,
|
|
7020
|
+
source: "lineage_scope_compact"
|
|
7021
|
+
};
|
|
7022
|
+
}
|
|
7023
|
+
}
|
|
7024
|
+
return nearest;
|
|
7025
|
+
};
|
|
7026
|
+
var hasRuntimeLineageCoverage = ({
|
|
7027
|
+
entry,
|
|
7028
|
+
lineageScopes
|
|
7029
|
+
}) => {
|
|
7030
|
+
const strategy = getEntryStrategy(entry);
|
|
7031
|
+
const timestamp = entry.signalTimestamp ?? entry.timestamp;
|
|
7032
|
+
return lineageScopes.some(
|
|
7033
|
+
(scope) => scope.strategy === strategy && scope.symbol === entry.symbol && timestamp >= scope.firstTimestamp && timestamp <= scope.lastTimestamp
|
|
7034
|
+
);
|
|
7035
|
+
};
|
|
6991
7036
|
var classifySignalDiagnostic = (signal) => {
|
|
6992
7037
|
if (signal.orderStatus === "failed") {
|
|
6993
7038
|
return {
|
|
@@ -7232,6 +7277,7 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7232
7277
|
runtimeSignalEvaluations = [],
|
|
7233
7278
|
replaySignals = [],
|
|
7234
7279
|
replaySignalEvaluations = [],
|
|
7280
|
+
runtimeLineageScopes = [],
|
|
7235
7281
|
toleranceMs,
|
|
7236
7282
|
backtestTimestampOffsetMs,
|
|
7237
7283
|
limit
|
|
@@ -7299,9 +7345,26 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7299
7345
|
toleranceMs,
|
|
7300
7346
|
timestampOffsetMs: 0
|
|
7301
7347
|
});
|
|
7302
|
-
const
|
|
7348
|
+
const nearestRuntimeEvaluationOutcome = findNearestRuntimeEvaluationOutcome(
|
|
7349
|
+
{
|
|
7350
|
+
entry,
|
|
7351
|
+
lineageScopes: runtimeLineageScopes,
|
|
7352
|
+
toleranceMs
|
|
7353
|
+
}
|
|
7354
|
+
);
|
|
7355
|
+
const runtimeLineageCoversEntry = hasRuntimeLineageCoverage({
|
|
7356
|
+
entry,
|
|
7357
|
+
lineageScopes: runtimeLineageScopes
|
|
7358
|
+
});
|
|
7359
|
+
const classification = nearestRuntimeSignal ? classifySignalDiagnostic(nearestRuntimeSignal.signal) : nearestRuntimeEvaluation ? classifyEvaluationDiagnostic(nearestRuntimeEvaluation.evaluation) : nearestRuntimeEvaluationOutcome ? {
|
|
7360
|
+
classification: "core_skipped",
|
|
7361
|
+
reason: nearestRuntimeEvaluationOutcome.reason
|
|
7362
|
+
} : nearestCandidate?.nearest ? {
|
|
7303
7363
|
classification: "timing_or_price_drift",
|
|
7304
7364
|
reason: nearestCandidate.reason
|
|
7365
|
+
} : runtimeLineageCoversEntry ? {
|
|
7366
|
+
classification: "runtime_evaluation_detail_unavailable",
|
|
7367
|
+
reason: "runtime_scope_covers_timestamp_but_skip_detail_was_not_stored"
|
|
7305
7368
|
} : {
|
|
7306
7369
|
classification: "no_runtime_evaluation",
|
|
7307
7370
|
reason: "no_runtime_signal_or_evaluation"
|
|
@@ -7325,7 +7388,8 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7325
7388
|
...includeArtifacts ? {
|
|
7326
7389
|
runtimeEvaluationArtifact: nearestRuntimeEvaluation.evaluation
|
|
7327
7390
|
} : {}
|
|
7328
|
-
} : {}
|
|
7391
|
+
} : {},
|
|
7392
|
+
...nearestRuntimeEvaluationOutcome ? { runtimeEvaluationOutcome: nearestRuntimeEvaluationOutcome } : {}
|
|
7329
7393
|
};
|
|
7330
7394
|
});
|
|
7331
7395
|
for (const diagnostic of runtimeOnlyDiagnostics) {
|
|
@@ -7372,7 +7436,8 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7372
7436
|
runtimeSignals,
|
|
7373
7437
|
runtimeSignalEvaluations,
|
|
7374
7438
|
replaySignals,
|
|
7375
|
-
replaySignalEvaluations
|
|
7439
|
+
replaySignalEvaluations,
|
|
7440
|
+
runtimeLineageScopes
|
|
7376
7441
|
}) => {
|
|
7377
7442
|
const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
|
|
7378
7443
|
const backtestDetails = backtestEntries.map(
|
|
@@ -7431,6 +7496,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7431
7496
|
runtimeSignalEvaluations,
|
|
7432
7497
|
replaySignals,
|
|
7433
7498
|
replaySignalEvaluations,
|
|
7499
|
+
runtimeLineageScopes,
|
|
7434
7500
|
toleranceMs,
|
|
7435
7501
|
backtestTimestampOffsetMs,
|
|
7436
7502
|
limit
|
|
@@ -7451,7 +7517,8 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7451
7517
|
runtimeSignals,
|
|
7452
7518
|
runtimeSignalEvaluations,
|
|
7453
7519
|
replaySignals,
|
|
7454
|
-
replaySignalEvaluations
|
|
7520
|
+
replaySignalEvaluations,
|
|
7521
|
+
runtimeLineageScopes
|
|
7455
7522
|
}) => {
|
|
7456
7523
|
const toExchangeDetail = (entry) => toExchangeParityDetail({
|
|
7457
7524
|
entry,
|
|
@@ -7538,6 +7605,7 @@ var buildReplayExchangeComparisonDetails = ({
|
|
|
7538
7605
|
runtimeSignalEvaluations,
|
|
7539
7606
|
replaySignals,
|
|
7540
7607
|
replaySignalEvaluations,
|
|
7608
|
+
runtimeLineageScopes,
|
|
7541
7609
|
toleranceMs,
|
|
7542
7610
|
backtestTimestampOffsetMs,
|
|
7543
7611
|
limit
|
|
@@ -7673,6 +7741,7 @@ var filterReplayComparisonByLineage = ({
|
|
|
7673
7741
|
runtimeTrades: comparableRuntimeTrades,
|
|
7674
7742
|
runtimeSignals: comparableRuntimeSignals,
|
|
7675
7743
|
runtimeSignalEvaluations: comparableRuntimeEvaluations,
|
|
7744
|
+
runtimeLineageScopes: comparableRuntimeLineageScopes,
|
|
7676
7745
|
backtestEntries: comparableBacktestEntries,
|
|
7677
7746
|
lineage: {
|
|
7678
7747
|
enforced: true,
|
|
@@ -8269,6 +8338,7 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8269
8338
|
replaySignalEvaluations,
|
|
8270
8339
|
runtimeSignals,
|
|
8271
8340
|
runtimeSignalEvaluations,
|
|
8341
|
+
runtimeLineageScopes,
|
|
8272
8342
|
lineage
|
|
8273
8343
|
}) => {
|
|
8274
8344
|
const { connector, connectorName, window } = getRuntimeCompareContext();
|
|
@@ -8320,7 +8390,8 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8320
8390
|
runtimeSignals,
|
|
8321
8391
|
runtimeSignalEvaluations,
|
|
8322
8392
|
replaySignals,
|
|
8323
|
-
replaySignalEvaluations
|
|
8393
|
+
replaySignalEvaluations,
|
|
8394
|
+
runtimeLineageScopes
|
|
8324
8395
|
});
|
|
8325
8396
|
const emptyRowsByStrategy = new Map(
|
|
8326
8397
|
buildExchangeComparisonRows({
|
|
@@ -8376,7 +8447,8 @@ var saveAndPrintReplayExchangeComparison = async ({
|
|
|
8376
8447
|
runtimeSignals,
|
|
8377
8448
|
runtimeSignalEvaluations,
|
|
8378
8449
|
replaySignals,
|
|
8379
|
-
replaySignalEvaluations
|
|
8450
|
+
replaySignalEvaluations,
|
|
8451
|
+
runtimeLineageScopes
|
|
8380
8452
|
});
|
|
8381
8453
|
const rows = buildExchangeComparisonRows({
|
|
8382
8454
|
liveStrategySummaries,
|
|
@@ -8527,6 +8599,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8527
8599
|
replaySignalEvaluations,
|
|
8528
8600
|
runtimeSignals: comparable.runtimeSignals,
|
|
8529
8601
|
runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
|
|
8602
|
+
runtimeLineageScopes: comparable.runtimeLineageScopes,
|
|
8530
8603
|
lineage: comparable.lineage
|
|
8531
8604
|
});
|
|
8532
8605
|
}
|
|
@@ -8550,7 +8623,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8550
8623
|
runtimeSignals: comparable.runtimeSignals,
|
|
8551
8624
|
runtimeSignalEvaluations: comparable.runtimeSignalEvaluations,
|
|
8552
8625
|
replaySignals,
|
|
8553
|
-
replaySignalEvaluations
|
|
8626
|
+
replaySignalEvaluations,
|
|
8627
|
+
runtimeLineageScopes: comparable.runtimeLineageScopes
|
|
8554
8628
|
});
|
|
8555
8629
|
const parityRows = summarizeTradeParityByStrategy({
|
|
8556
8630
|
runtimeEntries: runtimeDedupe.entries,
|
|
@@ -8593,7 +8667,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8593
8667
|
// src/lib/replay/outputReport.ts
|
|
8594
8668
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
8595
8669
|
var import_node_path2 = __toESM(require("path"));
|
|
8596
|
-
var
|
|
8670
|
+
var import_data2 = require("@tradejs/core/data");
|
|
8597
8671
|
var formatMsk = (timestamp) => typeof timestamp === "number" && Number.isFinite(timestamp) ? new Intl.DateTimeFormat("ru-RU", {
|
|
8598
8672
|
timeZone: "Europe/Moscow",
|
|
8599
8673
|
dateStyle: "short",
|
|
@@ -9000,7 +9074,7 @@ var writeReplayOutputReport = async ({
|
|
|
9000
9074
|
];
|
|
9001
9075
|
await Promise.all([
|
|
9002
9076
|
import_promises2.default.writeFile(markdownPath, lines.join("\n"), "utf8"),
|
|
9003
|
-
import_promises2.default.writeFile(jsonPath, `${(0,
|
|
9077
|
+
import_promises2.default.writeFile(jsonPath, `${(0, import_data2.toJson)(report, true)}
|
|
9004
9078
|
`, "utf8")
|
|
9005
9079
|
]);
|
|
9006
9080
|
return {
|
|
@@ -9208,7 +9282,7 @@ var replayBacktest = async () => {
|
|
|
9208
9282
|
deploymentId: deployment.id,
|
|
9209
9283
|
assetClasses: deployment.assetClasses,
|
|
9210
9284
|
deployment,
|
|
9211
|
-
closedIntervalMs: (0,
|
|
9285
|
+
closedIntervalMs: (0, import_data3.intervalToMs)(replayInterval)
|
|
9212
9286
|
});
|
|
9213
9287
|
if (!preparedRun || isReplayUpdateOnlyRun) {
|
|
9214
9288
|
return;
|