@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.
- 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
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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(
|
|
942
|
-
|
|
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) =>
|
|
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 =
|
|
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: `
|
|
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.
|
|
1148
|
+
(decision) => decision.status === "approved"
|
|
1090
1149
|
).length,
|
|
1091
1150
|
rejected: allocatorDecisions.filter(
|
|
1092
|
-
(decision) => decision.
|
|
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.
|
|
1163
|
+
(decision) => decision.status === "approved"
|
|
1099
1164
|
).length,
|
|
1100
1165
|
rejected: riskDecisions.filter(
|
|
1101
|
-
(decision) => decision.
|
|
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
|
-
-
|
|
1222
|
-
- Risk
|
|
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
|
-
-
|
|
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
|
},
|
package/dist/scripts/signals.js
CHANGED
|
@@ -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.
|
|
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.
|
|
40
|
-
"@tradejs/core": "^3.1.
|
|
41
|
-
"@tradejs/indicators": "^3.1.
|
|
42
|
-
"@tradejs/infra": "^3.1.
|
|
43
|
-
"@tradejs/node": "^3.1.
|
|
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.
|
|
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",
|