@tradejs/cli 3.1.29-beta.289 → 3.1.29-beta.290
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +239 -70
- package/dist/lib/runtimeFeedbackNotification.js +6 -2
- package/dist/lib/runtimeFeedbackReplay.js +44 -29
- package/dist/lib/runtimeParity.js +1 -0
- package/dist/lib/runtimeParityDetails.js +51 -21
- package/dist/scripts/replay.js +170 -29
- package/dist/scripts/replayRunner.js +170 -29
- package/dist/scripts/runtimeFeedbackNotify.js +11 -3
- package/dist/scripts/runtimeFeedbackReplay.js +44 -29
- package/dist/scripts/runtimeParity.js +1 -0
- package/dist/scripts/signalsSummary.js +5 -6
- package/package.json +7 -7
|
@@ -39,6 +39,7 @@ module.exports = __toCommonJS(runtimeFeedbackReplay_exports);
|
|
|
39
39
|
var import_node_child_process = require("child_process");
|
|
40
40
|
var import_promises = __toESM(require("fs/promises"));
|
|
41
41
|
var import_node_path = __toESM(require("path"));
|
|
42
|
+
var import_mcp = require("@tradejs/node/mcp");
|
|
42
43
|
|
|
43
44
|
// src/lib/runtimeEvidenceDeployment.ts
|
|
44
45
|
var runtimeEvidenceDeployment_exports = {};
|
|
@@ -186,44 +187,58 @@ var runLoggedCommand = async ({
|
|
|
186
187
|
env,
|
|
187
188
|
logHandle
|
|
188
189
|
}) => {
|
|
189
|
-
await logHandle.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
};
|
|
199
|
-
const child = (0, import_node_child_process.spawn)(executable, command, {
|
|
200
|
-
cwd,
|
|
201
|
-
env,
|
|
202
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
203
|
-
});
|
|
204
|
-
child.stdout.on("data", (chunk) => {
|
|
205
|
-
process.stdout.write(chunk);
|
|
206
|
-
appendLog(chunk);
|
|
207
|
-
});
|
|
208
|
-
child.stderr.on("data", (chunk) => {
|
|
209
|
-
process.stderr.write(chunk);
|
|
210
|
-
appendLog(chunk);
|
|
211
|
-
});
|
|
190
|
+
await logHandle.writeFile(
|
|
191
|
+
"\n" + (0, import_mcp.formatMcpArtifactLogLine)(`$ ${[executable, ...command].join(" ")}`)
|
|
192
|
+
);
|
|
193
|
+
const child = (0, import_node_child_process.spawn)(executable, command, {
|
|
194
|
+
cwd,
|
|
195
|
+
env,
|
|
196
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
197
|
+
});
|
|
198
|
+
const exit = new Promise((resolve, reject) => {
|
|
212
199
|
child.once("error", reject);
|
|
213
200
|
child.once("close", (code, signal) => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
resolve();
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
201
|
+
if (code === 0) resolve();
|
|
202
|
+
else {
|
|
219
203
|
reject(
|
|
220
204
|
new Error(
|
|
221
205
|
`Runtime feedback command failed: exit=${String(code)} signal=${String(signal)}`
|
|
222
206
|
)
|
|
223
207
|
);
|
|
224
|
-
}
|
|
208
|
+
}
|
|
225
209
|
});
|
|
226
210
|
});
|
|
211
|
+
let pendingLogWrite = Promise.resolve();
|
|
212
|
+
const copyLog = async (input, output) => {
|
|
213
|
+
const writeLine = async (line) => {
|
|
214
|
+
const formatted = (0, import_mcp.formatMcpArtifactLogLine)(line);
|
|
215
|
+
pendingLogWrite = pendingLogWrite.then(
|
|
216
|
+
() => logHandle.writeFile(formatted)
|
|
217
|
+
);
|
|
218
|
+
await pendingLogWrite;
|
|
219
|
+
output.write(formatted);
|
|
220
|
+
};
|
|
221
|
+
let pendingLine = "";
|
|
222
|
+
try {
|
|
223
|
+
for await (const chunk of input.setEncoding("utf8")) {
|
|
224
|
+
const lines = (pendingLine + chunk).split("\n");
|
|
225
|
+
pendingLine = lines.pop() ?? "";
|
|
226
|
+
for (const line of lines) await writeLine(line);
|
|
227
|
+
}
|
|
228
|
+
if (pendingLine) await writeLine(pendingLine);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
child.kill();
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const results = await Promise.allSettled([
|
|
235
|
+
copyLog(child.stdout, process.stdout),
|
|
236
|
+
copyLog(child.stderr, process.stderr),
|
|
237
|
+
exit
|
|
238
|
+
]);
|
|
239
|
+
for (const result of results) {
|
|
240
|
+
if (result.status === "rejected") throw result.reason;
|
|
241
|
+
}
|
|
227
242
|
};
|
|
228
243
|
var runRuntimeFeedbackReplay = async ({
|
|
229
244
|
runtimeEvidencePath,
|
|
@@ -159,6 +159,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
|
|
|
159
159
|
direction: trade.direction,
|
|
160
160
|
qty: toFiniteNumberOrNull(trade.qty),
|
|
161
161
|
timestamp: trade.entryTimestamp,
|
|
162
|
+
...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
|
|
162
163
|
price: toFiniteNumberOrNull(trade.entryPrice),
|
|
163
164
|
orderId: trade.orderId,
|
|
164
165
|
signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
|
|
@@ -45,6 +45,13 @@ var getBacktestParityComparisonTimestamp = (entry, timestampOffsetMs = 0) => {
|
|
|
45
45
|
return (entry.signalTimestamp ?? entry.timestamp) + offsetMs;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
// src/lib/replay/historicalSignalsReplayDecisionTrace.ts
|
|
49
|
+
var replayDecisionTraceKey = ({
|
|
50
|
+
strategy,
|
|
51
|
+
symbol,
|
|
52
|
+
signalTimestamp
|
|
53
|
+
}) => JSON.stringify([strategy, symbol, signalTimestamp]);
|
|
54
|
+
|
|
48
55
|
// src/lib/runtimeParityDetails.ts
|
|
49
56
|
var DEFAULT_REPLAY_PARITY_DETAILS_LIMIT = 500;
|
|
50
57
|
var DEFAULT_REPLAY_PARITY_ARTIFACT_LIMIT = 100;
|
|
@@ -120,6 +127,7 @@ var toRuntimeParityDetail = (entry) => ({
|
|
|
120
127
|
direction: entry.direction,
|
|
121
128
|
qty: entry.qty ?? null,
|
|
122
129
|
timestamp: entry.timestamp,
|
|
130
|
+
...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
|
|
123
131
|
price: entry.price,
|
|
124
132
|
exitType: entry.exitType ?? null,
|
|
125
133
|
exitTimestamp: entry.exitTimestamp ?? null,
|
|
@@ -193,7 +201,8 @@ var findNearestEvaluation = ({
|
|
|
193
201
|
evaluations,
|
|
194
202
|
toleranceMs,
|
|
195
203
|
timestampOffsetMs,
|
|
196
|
-
entryTimestampOffsetMs = 0
|
|
204
|
+
entryTimestampOffsetMs = 0,
|
|
205
|
+
matchDirection = false
|
|
197
206
|
}) => {
|
|
198
207
|
const entryStrategy = getEntryStrategy(entry);
|
|
199
208
|
if (!entryStrategy) {
|
|
@@ -203,7 +212,7 @@ var findNearestEvaluation = ({
|
|
|
203
212
|
let bestDiff = Number.POSITIVE_INFINITY;
|
|
204
213
|
const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
|
|
205
214
|
for (const evaluation of evaluations) {
|
|
206
|
-
if (evaluation.strategy !== entryStrategy || evaluation.symbol !== entry.symbol) {
|
|
215
|
+
if (evaluation.strategy !== entryStrategy || evaluation.symbol !== entry.symbol || matchDirection && evaluation.direction != null && evaluation.direction !== entry.direction) {
|
|
207
216
|
continue;
|
|
208
217
|
}
|
|
209
218
|
const diff = Math.abs(
|
|
@@ -549,6 +558,7 @@ var buildReplayMismatchDrilldown = ({
|
|
|
549
558
|
replaySignals = [],
|
|
550
559
|
replaySignalEvaluations = [],
|
|
551
560
|
replayLineageScopes = [],
|
|
561
|
+
replayDecisionTraces = [],
|
|
552
562
|
runtimeLineageScopes = [],
|
|
553
563
|
toleranceMs,
|
|
554
564
|
backtestTimestampOffsetMs,
|
|
@@ -557,48 +567,66 @@ var buildReplayMismatchDrilldown = ({
|
|
|
557
567
|
const nearestByEntry = new Map(
|
|
558
568
|
nearestCandidates.map((candidate) => [candidate.entry, candidate])
|
|
559
569
|
);
|
|
570
|
+
const traceByCandle = new Map(
|
|
571
|
+
replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
|
|
572
|
+
);
|
|
560
573
|
const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
|
|
561
574
|
let runtimeOnlyArtifactsIncluded = 0;
|
|
562
575
|
let backtestOnlyArtifactsIncluded = 0;
|
|
563
576
|
const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
577
|
+
const isRuntimeTrade = entry.source === "runtime";
|
|
578
|
+
const decisionEntry = isRuntimeTrade ? entry.signalTimestamp == null ? null : { ...entry, timestamp: entry.signalTimestamp } : entry;
|
|
579
|
+
const decisionClock = {
|
|
580
|
+
toleranceMs: isRuntimeTrade ? 0 : toleranceMs,
|
|
581
|
+
timestampOffsetMs: isRuntimeTrade ? 0 : resolveTradeParityTimestampOffset(
|
|
582
|
+
getEntryStrategy(entry) ?? "",
|
|
583
|
+
backtestTimestampOffsetMs
|
|
584
|
+
)
|
|
585
|
+
};
|
|
568
586
|
const includeArtifacts = index < artifactLimit;
|
|
569
587
|
const nearestCandidate = nearestByEntry.get(entry);
|
|
570
|
-
const nearestReplaySignal = findNearestSignal({
|
|
571
|
-
entry,
|
|
588
|
+
const nearestReplaySignal = decisionEntry && findNearestSignal({
|
|
589
|
+
entry: decisionEntry,
|
|
572
590
|
signals: replaySignals,
|
|
573
|
-
|
|
574
|
-
timestampOffsetMs: entryOffsetMs
|
|
591
|
+
...decisionClock
|
|
575
592
|
});
|
|
576
|
-
const nearestReplayEvaluation = findNearestEvaluation({
|
|
577
|
-
entry,
|
|
593
|
+
const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
|
|
594
|
+
entry: decisionEntry,
|
|
578
595
|
evaluations: replaySignalEvaluations,
|
|
579
|
-
|
|
580
|
-
|
|
596
|
+
matchDirection: isRuntimeTrade,
|
|
597
|
+
...decisionClock
|
|
581
598
|
});
|
|
582
|
-
const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
|
|
583
|
-
entry,
|
|
599
|
+
const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
|
|
600
|
+
entry: decisionEntry,
|
|
584
601
|
lineageScopes: replayLineageScopes,
|
|
585
|
-
|
|
586
|
-
timestampOffsetMs: entryOffsetMs
|
|
602
|
+
...decisionClock
|
|
587
603
|
});
|
|
604
|
+
const strategy = getEntryStrategy(entry);
|
|
605
|
+
const replayDecisionTrace = isRuntimeTrade && strategy && decisionEntry ? traceByCandle.get(
|
|
606
|
+
replayDecisionTraceKey({
|
|
607
|
+
strategy,
|
|
608
|
+
symbol: entry.symbol,
|
|
609
|
+
signalTimestamp: decisionEntry.timestamp
|
|
610
|
+
})
|
|
611
|
+
) : void 0;
|
|
612
|
+
const unavailableReason = !isRuntimeTrade ? "no_replay_signal_or_backtest_candidate" : !decisionEntry ? "replay_signal_timestamp_unavailable" : replayDecisionTrace?.decision.stage === "not_evaluated" ? replayDecisionTrace.decision.reason : "replay_evaluation_unavailable";
|
|
588
613
|
const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
|
|
589
|
-
classification:
|
|
614
|
+
classification: nearestReplayEvaluationOutcome.reason.startsWith(
|
|
615
|
+
"BACKTEST_ENTRY_DELAY_"
|
|
616
|
+
) ? "entry_delayed" : "core_skipped",
|
|
590
617
|
reason: nearestReplayEvaluationOutcome.reason
|
|
591
|
-
} : nearestCandidate?.nearest ? {
|
|
618
|
+
} : !isRuntimeTrade && nearestCandidate?.nearest ? {
|
|
592
619
|
classification: "timing_or_price_drift",
|
|
593
620
|
reason: nearestCandidate.reason
|
|
594
621
|
} : {
|
|
595
622
|
classification: "no_replay_candidate",
|
|
596
|
-
reason:
|
|
623
|
+
reason: unavailableReason
|
|
597
624
|
};
|
|
598
625
|
return {
|
|
599
626
|
entry,
|
|
600
627
|
...classification,
|
|
601
628
|
...nearestCandidate ? { nearestCandidate } : {},
|
|
629
|
+
...replayDecisionTrace ? { replayDecisionTrace } : {},
|
|
602
630
|
...nearestReplaySignal ? {
|
|
603
631
|
replaySignal: toSignalDiagnostic({
|
|
604
632
|
signal: nearestReplaySignal.signal,
|
|
@@ -724,6 +752,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
724
752
|
replaySignals,
|
|
725
753
|
replaySignalEvaluations,
|
|
726
754
|
replayLineageScopes,
|
|
755
|
+
replayDecisionTraces,
|
|
727
756
|
runtimeLineageScopes
|
|
728
757
|
}) => {
|
|
729
758
|
const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
|
|
@@ -784,6 +813,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
784
813
|
replaySignals,
|
|
785
814
|
replaySignalEvaluations,
|
|
786
815
|
replayLineageScopes,
|
|
816
|
+
replayDecisionTraces,
|
|
787
817
|
runtimeLineageScopes,
|
|
788
818
|
toleranceMs,
|
|
789
819
|
backtestTimestampOffsetMs,
|
package/dist/scripts/replay.js
CHANGED
|
@@ -4985,7 +4985,13 @@ var loadReplayRuntimeEvidenceMetadata = async (params) => {
|
|
|
4985
4985
|
userName: artifact.userName,
|
|
4986
4986
|
startTime: artifact.startTime,
|
|
4987
4987
|
endTime: artifact.endTime,
|
|
4988
|
-
deployment: artifact.deployment
|
|
4988
|
+
deployment: artifact.deployment,
|
|
4989
|
+
decisionTraceTargets: unwrapRows(
|
|
4990
|
+
artifact.runtime.trades,
|
|
4991
|
+
"trade"
|
|
4992
|
+
).flatMap(
|
|
4993
|
+
({ strategy, symbol, signalTimestamp }) => typeof signalTimestamp === "number" && Number.isFinite(signalTimestamp) && artifact.startTime != null && artifact.endTime != null && signalTimestamp >= artifact.startTime && signalTimestamp < artifact.endTime ? [{ strategy, symbol, signalTimestamp }] : []
|
|
4994
|
+
)
|
|
4989
4995
|
};
|
|
4990
4996
|
};
|
|
4991
4997
|
var buildRuntimeEvidenceLineageScopes = ({
|
|
@@ -5588,6 +5594,73 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
|
5588
5594
|
}
|
|
5589
5595
|
};
|
|
5590
5596
|
|
|
5597
|
+
// src/lib/replay/historicalSignalsReplayDecisionTrace.ts
|
|
5598
|
+
var replayDecisionTraceKey = ({
|
|
5599
|
+
strategy,
|
|
5600
|
+
symbol,
|
|
5601
|
+
signalTimestamp
|
|
5602
|
+
}) => JSON.stringify([strategy, symbol, signalTimestamp]);
|
|
5603
|
+
var describeOutcome = (result) => {
|
|
5604
|
+
if (typeof result === "string") {
|
|
5605
|
+
return {
|
|
5606
|
+
stage: result.startsWith("BACKTEST_ENTRY_DELAY_") ? "entry_delay" : /(?:NO_PATTERN|POSITION_EXISTS)$/.test(result) ? "core" : "evaluation",
|
|
5607
|
+
reason: result
|
|
5608
|
+
};
|
|
5609
|
+
}
|
|
5610
|
+
if (!result) return { stage: "evaluation", reason: "NO_SIGNAL" };
|
|
5611
|
+
const reason = result.orderSkipReason || result.orderFailureReason || result.orderStatus || "SIGNAL";
|
|
5612
|
+
const patternContext = result.additionalIndicators?.tradingPatternsContext;
|
|
5613
|
+
return {
|
|
5614
|
+
stage: result.riskDecision?.status === "rejected" || /^ENTRY_(?:RR_BELOW|RISK_EXCEEDS)/.test(reason) ? "risk" : /^(?:AI_|ML_|HOOK_BEFORE_ENTRY_GATE|RESEARCH_ENTRY_POLICY)/.test(
|
|
5615
|
+
reason
|
|
5616
|
+
) ? "gate" : result.orderStatus ? "execution" : "evaluation",
|
|
5617
|
+
reason,
|
|
5618
|
+
direction: result.direction,
|
|
5619
|
+
orderStatus: result.orderStatus,
|
|
5620
|
+
...typeof patternContext?.selectedPattern === "string" ? { pattern: patternContext.selectedPattern } : {},
|
|
5621
|
+
...typeof patternContext?.sourceCode === "string" ? { patternCode: patternContext.sourceCode } : {}
|
|
5622
|
+
};
|
|
5623
|
+
};
|
|
5624
|
+
var createReplayDecisionTrace = (targets = []) => {
|
|
5625
|
+
const traces = /* @__PURE__ */ new Map();
|
|
5626
|
+
const symbols = new Set(targets.map(({ symbol }) => symbol));
|
|
5627
|
+
for (const target of targets) {
|
|
5628
|
+
traces.set(replayDecisionTraceKey(target), {
|
|
5629
|
+
...target,
|
|
5630
|
+
decision: {
|
|
5631
|
+
stage: "not_evaluated",
|
|
5632
|
+
reason: "REPLAY_CANDLE_NOT_EVALUATED"
|
|
5633
|
+
}
|
|
5634
|
+
});
|
|
5635
|
+
}
|
|
5636
|
+
const record = (strategy, symbol, candle, result, notEvaluatedReason) => {
|
|
5637
|
+
if (!symbols.has(symbol)) return;
|
|
5638
|
+
const { timestamp, open, high, low, close, volume } = candle;
|
|
5639
|
+
const trace = traces.get(
|
|
5640
|
+
replayDecisionTraceKey({ strategy, symbol, signalTimestamp: timestamp })
|
|
5641
|
+
);
|
|
5642
|
+
if (result && typeof result !== "string" && result.timestamp !== timestamp) {
|
|
5643
|
+
const original = traces.get(
|
|
5644
|
+
replayDecisionTraceKey({
|
|
5645
|
+
strategy,
|
|
5646
|
+
symbol,
|
|
5647
|
+
signalTimestamp: result.timestamp
|
|
5648
|
+
})
|
|
5649
|
+
);
|
|
5650
|
+
if (original)
|
|
5651
|
+
original.followUp = {
|
|
5652
|
+
...describeOutcome(result),
|
|
5653
|
+
candleTimestamp: timestamp
|
|
5654
|
+
};
|
|
5655
|
+
notEvaluatedReason = "REPLAY_PROCESSED_DELAYED_ENTRY";
|
|
5656
|
+
}
|
|
5657
|
+
if (!trace) return;
|
|
5658
|
+
trace.candle = { timestamp, open, high, low, close, volume };
|
|
5659
|
+
trace.decision = notEvaluatedReason ? { stage: "not_evaluated", reason: notEvaluatedReason } : describeOutcome(result);
|
|
5660
|
+
};
|
|
5661
|
+
return { record, values: () => [...traces.values()] };
|
|
5662
|
+
};
|
|
5663
|
+
|
|
5591
5664
|
// src/lib/replay/historicalSignalsReplaySkipEvidence.ts
|
|
5592
5665
|
var import_data2 = require("@tradejs/core/data");
|
|
5593
5666
|
var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
|
|
@@ -5698,6 +5771,7 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5698
5771
|
context.plan.runtimeLineages,
|
|
5699
5772
|
context.collectSkipEvidence
|
|
5700
5773
|
);
|
|
5774
|
+
const decisionTrace = createReplayDecisionTrace(context.decisionTraceTargets);
|
|
5701
5775
|
const reportProgress = (timestamp) => {
|
|
5702
5776
|
if (adapters.progress.enabled === false) return;
|
|
5703
5777
|
adapters.progress.tick({
|
|
@@ -5726,6 +5800,20 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5726
5800
|
status: "completed",
|
|
5727
5801
|
durationMs: adapters.clock.now() - cycleStartedAt
|
|
5728
5802
|
});
|
|
5803
|
+
if (context.decisionTraceTargets?.length) {
|
|
5804
|
+
for (const symbolRuntime of cycleSymbols) {
|
|
5805
|
+
const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
|
|
5806
|
+
for (const strategy of symbolRuntime.strategies) {
|
|
5807
|
+
decisionTrace.record(
|
|
5808
|
+
strategy.strategyName,
|
|
5809
|
+
symbolRuntime.symbol,
|
|
5810
|
+
candle,
|
|
5811
|
+
void 0,
|
|
5812
|
+
"REPLAY_CYCLE_ABORTED"
|
|
5813
|
+
);
|
|
5814
|
+
}
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5729
5817
|
advanceAbortedCycle(cycleSymbols, timestamp);
|
|
5730
5818
|
reportProgress(timestamp);
|
|
5731
5819
|
continue;
|
|
@@ -5736,6 +5824,17 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5736
5824
|
const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
|
|
5737
5825
|
const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
|
|
5738
5826
|
if (!candle || !btcCandle || candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) !== timestamp || btcCandle.timestamp !== candle.timestamp) {
|
|
5827
|
+
if (candle && context.decisionTraceTargets?.length) {
|
|
5828
|
+
for (const strategy of symbolRuntime.strategies) {
|
|
5829
|
+
decisionTrace.record(
|
|
5830
|
+
strategy.strategyName,
|
|
5831
|
+
symbolRuntime.symbol,
|
|
5832
|
+
candle,
|
|
5833
|
+
void 0,
|
|
5834
|
+
"REPLAY_CANDLE_OR_REFERENCE_NOT_ALIGNED"
|
|
5835
|
+
);
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5739
5838
|
continue;
|
|
5740
5839
|
}
|
|
5741
5840
|
for (const strategyRuntime of symbolRuntime.strategies) {
|
|
@@ -5744,6 +5843,12 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5744
5843
|
btcCandle,
|
|
5745
5844
|
ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
|
|
5746
5845
|
);
|
|
5846
|
+
decisionTrace.record(
|
|
5847
|
+
strategyRuntime.strategyName,
|
|
5848
|
+
symbolRuntime.symbol,
|
|
5849
|
+
candle,
|
|
5850
|
+
result
|
|
5851
|
+
);
|
|
5747
5852
|
skipEvidence.record({
|
|
5748
5853
|
strategyName: strategyRuntime.strategyName,
|
|
5749
5854
|
symbol: symbolRuntime.symbol,
|
|
@@ -5775,7 +5880,8 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5775
5880
|
return {
|
|
5776
5881
|
signals,
|
|
5777
5882
|
abortedCycles,
|
|
5778
|
-
replayLineageScopes: skipEvidence.values()
|
|
5883
|
+
replayLineageScopes: skipEvidence.values(),
|
|
5884
|
+
replayDecisionTraces: decisionTrace.values()
|
|
5779
5885
|
};
|
|
5780
5886
|
};
|
|
5781
5887
|
|
|
@@ -5826,7 +5932,8 @@ var collectHistoricalReplayResult = (context) => ({
|
|
|
5826
5932
|
cycleCount: context.cycleCount,
|
|
5827
5933
|
abortedCycles: context.abortedCycles,
|
|
5828
5934
|
runtimeLineages: context.runtimeLineages,
|
|
5829
|
-
replayLineageScopes: context.replayLineageScopes
|
|
5935
|
+
replayLineageScopes: context.replayLineageScopes,
|
|
5936
|
+
...context.replayDecisionTraces?.length ? { replayDecisionTraces: context.replayDecisionTraces } : {}
|
|
5830
5937
|
});
|
|
5831
5938
|
var compactHistoricalReplayResultForPortfolio = (result) => ({
|
|
5832
5939
|
...result,
|
|
@@ -5872,7 +5979,10 @@ var mergeHistoricalReplayResults = (parts) => {
|
|
|
5872
5979
|
cycleCount: parts.reduce((sum, part) => sum + part.cycleCount, 0),
|
|
5873
5980
|
abortedCycles: parts.reduce((sum, part) => sum + part.abortedCycles, 0),
|
|
5874
5981
|
runtimeLineages: parts.flatMap((part) => part.runtimeLineages),
|
|
5875
|
-
replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes)
|
|
5982
|
+
replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes),
|
|
5983
|
+
replayDecisionTraces: parts.flatMap(
|
|
5984
|
+
(part) => part.replayDecisionTraces ?? []
|
|
5985
|
+
)
|
|
5876
5986
|
};
|
|
5877
5987
|
};
|
|
5878
5988
|
|
|
@@ -6023,7 +6133,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6023
6133
|
references,
|
|
6024
6134
|
showProgress = true,
|
|
6025
6135
|
collectSkipEvidence = true,
|
|
6026
|
-
collectSignals = true
|
|
6136
|
+
collectSignals = true,
|
|
6137
|
+
decisionTraceTargets
|
|
6027
6138
|
}) => {
|
|
6028
6139
|
const memory = createReplayMemoryProfile();
|
|
6029
6140
|
memory?.("prepare:start");
|
|
@@ -6110,6 +6221,7 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6110
6221
|
connector: replayConnector,
|
|
6111
6222
|
collectSkipEvidence,
|
|
6112
6223
|
collectSignals,
|
|
6224
|
+
decisionTraceTargets,
|
|
6113
6225
|
hooks: projectHooks,
|
|
6114
6226
|
hookContext
|
|
6115
6227
|
},
|
|
@@ -6146,7 +6258,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6146
6258
|
cycleCount: plan.orderedTimestamps.length,
|
|
6147
6259
|
abortedCycles: execution.abortedCycles,
|
|
6148
6260
|
runtimeLineages: plan.runtimeLineages,
|
|
6149
|
-
replayLineageScopes: execution.replayLineageScopes
|
|
6261
|
+
replayLineageScopes: execution.replayLineageScopes,
|
|
6262
|
+
replayDecisionTraces: execution.replayDecisionTraces
|
|
6150
6263
|
});
|
|
6151
6264
|
memory?.("result:collected");
|
|
6152
6265
|
return result;
|
|
@@ -6374,6 +6487,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
|
|
|
6374
6487
|
direction: trade.direction,
|
|
6375
6488
|
qty: toFiniteNumberOrNull2(trade.qty),
|
|
6376
6489
|
timestamp: trade.entryTimestamp,
|
|
6490
|
+
...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
|
|
6377
6491
|
price: toFiniteNumberOrNull2(trade.entryPrice),
|
|
6378
6492
|
orderId: trade.orderId,
|
|
6379
6493
|
signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
|
|
@@ -7119,6 +7233,7 @@ var toRuntimeParityDetail = (entry) => ({
|
|
|
7119
7233
|
direction: entry.direction,
|
|
7120
7234
|
qty: entry.qty ?? null,
|
|
7121
7235
|
timestamp: entry.timestamp,
|
|
7236
|
+
...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
|
|
7122
7237
|
price: entry.price,
|
|
7123
7238
|
exitType: entry.exitType ?? null,
|
|
7124
7239
|
exitTimestamp: entry.exitTimestamp ?? null,
|
|
@@ -7192,7 +7307,8 @@ var findNearestEvaluation = ({
|
|
|
7192
7307
|
evaluations,
|
|
7193
7308
|
toleranceMs,
|
|
7194
7309
|
timestampOffsetMs,
|
|
7195
|
-
entryTimestampOffsetMs = 0
|
|
7310
|
+
entryTimestampOffsetMs = 0,
|
|
7311
|
+
matchDirection = false
|
|
7196
7312
|
}) => {
|
|
7197
7313
|
const entryStrategy2 = getEntryStrategy(entry);
|
|
7198
7314
|
if (!entryStrategy2) {
|
|
@@ -7202,7 +7318,7 @@ var findNearestEvaluation = ({
|
|
|
7202
7318
|
let bestDiff = Number.POSITIVE_INFINITY;
|
|
7203
7319
|
const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
|
|
7204
7320
|
for (const evaluation of evaluations) {
|
|
7205
|
-
if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol) {
|
|
7321
|
+
if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol || matchDirection && evaluation.direction != null && evaluation.direction !== entry.direction) {
|
|
7206
7322
|
continue;
|
|
7207
7323
|
}
|
|
7208
7324
|
const diff = Math.abs(
|
|
@@ -7548,6 +7664,7 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7548
7664
|
replaySignals = [],
|
|
7549
7665
|
replaySignalEvaluations = [],
|
|
7550
7666
|
replayLineageScopes = [],
|
|
7667
|
+
replayDecisionTraces = [],
|
|
7551
7668
|
runtimeLineageScopes = [],
|
|
7552
7669
|
toleranceMs,
|
|
7553
7670
|
backtestTimestampOffsetMs,
|
|
@@ -7556,48 +7673,66 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7556
7673
|
const nearestByEntry = new Map(
|
|
7557
7674
|
nearestCandidates.map((candidate) => [candidate.entry, candidate])
|
|
7558
7675
|
);
|
|
7676
|
+
const traceByCandle = new Map(
|
|
7677
|
+
replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
|
|
7678
|
+
);
|
|
7559
7679
|
const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
|
|
7560
7680
|
let runtimeOnlyArtifactsIncluded = 0;
|
|
7561
7681
|
let backtestOnlyArtifactsIncluded = 0;
|
|
7562
7682
|
const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
|
|
7563
|
-
const
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7683
|
+
const isRuntimeTrade = entry.source === "runtime";
|
|
7684
|
+
const decisionEntry = isRuntimeTrade ? entry.signalTimestamp == null ? null : { ...entry, timestamp: entry.signalTimestamp } : entry;
|
|
7685
|
+
const decisionClock = {
|
|
7686
|
+
toleranceMs: isRuntimeTrade ? 0 : toleranceMs,
|
|
7687
|
+
timestampOffsetMs: isRuntimeTrade ? 0 : resolveTradeParityTimestampOffset(
|
|
7688
|
+
getEntryStrategy(entry) ?? "",
|
|
7689
|
+
backtestTimestampOffsetMs
|
|
7690
|
+
)
|
|
7691
|
+
};
|
|
7567
7692
|
const includeArtifacts = index < artifactLimit;
|
|
7568
7693
|
const nearestCandidate = nearestByEntry.get(entry);
|
|
7569
|
-
const nearestReplaySignal = findNearestSignal({
|
|
7570
|
-
entry,
|
|
7694
|
+
const nearestReplaySignal = decisionEntry && findNearestSignal({
|
|
7695
|
+
entry: decisionEntry,
|
|
7571
7696
|
signals: replaySignals,
|
|
7572
|
-
|
|
7573
|
-
timestampOffsetMs: entryOffsetMs
|
|
7697
|
+
...decisionClock
|
|
7574
7698
|
});
|
|
7575
|
-
const nearestReplayEvaluation = findNearestEvaluation({
|
|
7576
|
-
entry,
|
|
7699
|
+
const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
|
|
7700
|
+
entry: decisionEntry,
|
|
7577
7701
|
evaluations: replaySignalEvaluations,
|
|
7578
|
-
|
|
7579
|
-
|
|
7702
|
+
matchDirection: isRuntimeTrade,
|
|
7703
|
+
...decisionClock
|
|
7580
7704
|
});
|
|
7581
|
-
const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
|
|
7582
|
-
entry,
|
|
7705
|
+
const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
|
|
7706
|
+
entry: decisionEntry,
|
|
7583
7707
|
lineageScopes: replayLineageScopes,
|
|
7584
|
-
|
|
7585
|
-
timestampOffsetMs: entryOffsetMs
|
|
7708
|
+
...decisionClock
|
|
7586
7709
|
});
|
|
7710
|
+
const strategy = getEntryStrategy(entry);
|
|
7711
|
+
const replayDecisionTrace = isRuntimeTrade && strategy && decisionEntry ? traceByCandle.get(
|
|
7712
|
+
replayDecisionTraceKey({
|
|
7713
|
+
strategy,
|
|
7714
|
+
symbol: entry.symbol,
|
|
7715
|
+
signalTimestamp: decisionEntry.timestamp
|
|
7716
|
+
})
|
|
7717
|
+
) : void 0;
|
|
7718
|
+
const unavailableReason = !isRuntimeTrade ? "no_replay_signal_or_backtest_candidate" : !decisionEntry ? "replay_signal_timestamp_unavailable" : replayDecisionTrace?.decision.stage === "not_evaluated" ? replayDecisionTrace.decision.reason : "replay_evaluation_unavailable";
|
|
7587
7719
|
const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
|
|
7588
|
-
classification:
|
|
7720
|
+
classification: nearestReplayEvaluationOutcome.reason.startsWith(
|
|
7721
|
+
"BACKTEST_ENTRY_DELAY_"
|
|
7722
|
+
) ? "entry_delayed" : "core_skipped",
|
|
7589
7723
|
reason: nearestReplayEvaluationOutcome.reason
|
|
7590
|
-
} : nearestCandidate?.nearest ? {
|
|
7724
|
+
} : !isRuntimeTrade && nearestCandidate?.nearest ? {
|
|
7591
7725
|
classification: "timing_or_price_drift",
|
|
7592
7726
|
reason: nearestCandidate.reason
|
|
7593
7727
|
} : {
|
|
7594
7728
|
classification: "no_replay_candidate",
|
|
7595
|
-
reason:
|
|
7729
|
+
reason: unavailableReason
|
|
7596
7730
|
};
|
|
7597
7731
|
return {
|
|
7598
7732
|
entry,
|
|
7599
7733
|
...classification,
|
|
7600
7734
|
...nearestCandidate ? { nearestCandidate } : {},
|
|
7735
|
+
...replayDecisionTrace ? { replayDecisionTrace } : {},
|
|
7601
7736
|
...nearestReplaySignal ? {
|
|
7602
7737
|
replaySignal: toSignalDiagnostic({
|
|
7603
7738
|
signal: nearestReplaySignal.signal,
|
|
@@ -7723,6 +7858,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7723
7858
|
replaySignals,
|
|
7724
7859
|
replaySignalEvaluations,
|
|
7725
7860
|
replayLineageScopes,
|
|
7861
|
+
replayDecisionTraces,
|
|
7726
7862
|
runtimeLineageScopes
|
|
7727
7863
|
}) => {
|
|
7728
7864
|
const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
|
|
@@ -7783,6 +7919,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7783
7919
|
replaySignals,
|
|
7784
7920
|
replaySignalEvaluations,
|
|
7785
7921
|
replayLineageScopes,
|
|
7922
|
+
replayDecisionTraces,
|
|
7786
7923
|
runtimeLineageScopes,
|
|
7787
7924
|
toleranceMs,
|
|
7788
7925
|
backtestTimestampOffsetMs,
|
|
@@ -8808,6 +8945,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8808
8945
|
replaySignals,
|
|
8809
8946
|
replayLineages,
|
|
8810
8947
|
replayLineageScopes,
|
|
8948
|
+
replayDecisionTraces,
|
|
8811
8949
|
runtimeEvidencePath
|
|
8812
8950
|
}) => {
|
|
8813
8951
|
const { connector, connectorName, window } = getRuntimeCompareContext();
|
|
@@ -8959,7 +9097,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8959
9097
|
replaySignals,
|
|
8960
9098
|
replaySignalEvaluations,
|
|
8961
9099
|
runtimeLineageScopes: comparable.runtimeLineageScopes,
|
|
8962
|
-
replayLineageScopes
|
|
9100
|
+
replayLineageScopes,
|
|
9101
|
+
replayDecisionTraces
|
|
8963
9102
|
});
|
|
8964
9103
|
const parityRows = summarizeTradeParityByStrategy({
|
|
8965
9104
|
runtimeEntries: runtimeDedupe.entries,
|
|
@@ -9886,6 +10025,7 @@ var finishReplay = async ({
|
|
|
9886
10025
|
replaySignals: replayResult.signals,
|
|
9887
10026
|
replayLineages: replayResult.runtimeLineages,
|
|
9888
10027
|
replayLineageScopes: replayResult.replayLineageScopes,
|
|
10028
|
+
replayDecisionTraces: replayResult.replayDecisionTraces,
|
|
9889
10029
|
runtimeEvidencePath: replayRuntimeEvidencePath
|
|
9890
10030
|
});
|
|
9891
10031
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
@@ -10235,7 +10375,8 @@ var replayBacktest = async () => {
|
|
|
10235
10375
|
references: historicalReplayReferences,
|
|
10236
10376
|
showProgress: !replayFlags.portfolioReport,
|
|
10237
10377
|
collectSkipEvidence: !replayFlags.portfolioReport,
|
|
10238
|
-
collectSignals: !replayFlags.portfolioReport
|
|
10378
|
+
collectSignals: !replayFlags.portfolioReport,
|
|
10379
|
+
decisionTraceTargets: replayFlags.portfolioReport ? void 0 : evidenceMetadata?.decisionTraceTargets
|
|
10239
10380
|
});
|
|
10240
10381
|
return replayFlags.portfolioReport ? compactHistoricalReplayResultForPortfolio(result) : result;
|
|
10241
10382
|
};
|