@tradejs/cli 3.1.29-beta.288 → 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 +250 -73
- 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 +181 -32
- package/dist/scripts/replayRunner.js +181 -32
- 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 = ({
|
|
@@ -5386,7 +5392,10 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5386
5392
|
return {
|
|
5387
5393
|
__tradejsReplayConnector: true,
|
|
5388
5394
|
__tradejsTestConnector: true,
|
|
5389
|
-
capabilities:
|
|
5395
|
+
capabilities: {
|
|
5396
|
+
...connector.capabilities,
|
|
5397
|
+
orderSubmissionValidation: true
|
|
5398
|
+
},
|
|
5390
5399
|
universe: connector.universe,
|
|
5391
5400
|
accountId: connector.accountId,
|
|
5392
5401
|
deploymentId: connector.deploymentId,
|
|
@@ -5418,6 +5427,7 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5418
5427
|
})
|
|
5419
5428
|
),
|
|
5420
5429
|
placeOrder: async (order) => {
|
|
5430
|
+
const { beforeSubmit, ...submittedOrder } = order;
|
|
5421
5431
|
if (positionsBySymbol.has(order.symbol)) {
|
|
5422
5432
|
return false;
|
|
5423
5433
|
}
|
|
@@ -5434,8 +5444,12 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5434
5444
|
stage: "entry",
|
|
5435
5445
|
signal: order.signal
|
|
5436
5446
|
});
|
|
5447
|
+
await beforeSubmit?.({
|
|
5448
|
+
qty: order.qty,
|
|
5449
|
+
executionPrice: entryPrice
|
|
5450
|
+
});
|
|
5437
5451
|
const nextPosition = {
|
|
5438
|
-
...
|
|
5452
|
+
...submittedOrder,
|
|
5439
5453
|
timestamp: executionTimestamp ?? order.timestamp,
|
|
5440
5454
|
price: entryPrice,
|
|
5441
5455
|
signal: compactReplaySignal(order.signal),
|
|
@@ -5460,7 +5474,7 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5460
5474
|
logOrder({
|
|
5461
5475
|
positionState,
|
|
5462
5476
|
data: {
|
|
5463
|
-
...
|
|
5477
|
+
...submittedOrder,
|
|
5464
5478
|
timestamp: executionTimestamp ?? order.timestamp,
|
|
5465
5479
|
price: entryPrice,
|
|
5466
5480
|
profit,
|
|
@@ -5580,6 +5594,73 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
|
5580
5594
|
}
|
|
5581
5595
|
};
|
|
5582
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
|
+
|
|
5583
5664
|
// src/lib/replay/historicalSignalsReplaySkipEvidence.ts
|
|
5584
5665
|
var import_data2 = require("@tradejs/core/data");
|
|
5585
5666
|
var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
|
|
@@ -5690,6 +5771,7 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5690
5771
|
context.plan.runtimeLineages,
|
|
5691
5772
|
context.collectSkipEvidence
|
|
5692
5773
|
);
|
|
5774
|
+
const decisionTrace = createReplayDecisionTrace(context.decisionTraceTargets);
|
|
5693
5775
|
const reportProgress = (timestamp) => {
|
|
5694
5776
|
if (adapters.progress.enabled === false) return;
|
|
5695
5777
|
adapters.progress.tick({
|
|
@@ -5718,6 +5800,20 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5718
5800
|
status: "completed",
|
|
5719
5801
|
durationMs: adapters.clock.now() - cycleStartedAt
|
|
5720
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
|
+
}
|
|
5721
5817
|
advanceAbortedCycle(cycleSymbols, timestamp);
|
|
5722
5818
|
reportProgress(timestamp);
|
|
5723
5819
|
continue;
|
|
@@ -5728,6 +5824,17 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5728
5824
|
const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
|
|
5729
5825
|
const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
|
|
5730
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
|
+
}
|
|
5731
5838
|
continue;
|
|
5732
5839
|
}
|
|
5733
5840
|
for (const strategyRuntime of symbolRuntime.strategies) {
|
|
@@ -5736,6 +5843,12 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5736
5843
|
btcCandle,
|
|
5737
5844
|
ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
|
|
5738
5845
|
);
|
|
5846
|
+
decisionTrace.record(
|
|
5847
|
+
strategyRuntime.strategyName,
|
|
5848
|
+
symbolRuntime.symbol,
|
|
5849
|
+
candle,
|
|
5850
|
+
result
|
|
5851
|
+
);
|
|
5739
5852
|
skipEvidence.record({
|
|
5740
5853
|
strategyName: strategyRuntime.strategyName,
|
|
5741
5854
|
symbol: symbolRuntime.symbol,
|
|
@@ -5767,7 +5880,8 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5767
5880
|
return {
|
|
5768
5881
|
signals,
|
|
5769
5882
|
abortedCycles,
|
|
5770
|
-
replayLineageScopes: skipEvidence.values()
|
|
5883
|
+
replayLineageScopes: skipEvidence.values(),
|
|
5884
|
+
replayDecisionTraces: decisionTrace.values()
|
|
5771
5885
|
};
|
|
5772
5886
|
};
|
|
5773
5887
|
|
|
@@ -5818,7 +5932,8 @@ var collectHistoricalReplayResult = (context) => ({
|
|
|
5818
5932
|
cycleCount: context.cycleCount,
|
|
5819
5933
|
abortedCycles: context.abortedCycles,
|
|
5820
5934
|
runtimeLineages: context.runtimeLineages,
|
|
5821
|
-
replayLineageScopes: context.replayLineageScopes
|
|
5935
|
+
replayLineageScopes: context.replayLineageScopes,
|
|
5936
|
+
...context.replayDecisionTraces?.length ? { replayDecisionTraces: context.replayDecisionTraces } : {}
|
|
5822
5937
|
});
|
|
5823
5938
|
var compactHistoricalReplayResultForPortfolio = (result) => ({
|
|
5824
5939
|
...result,
|
|
@@ -5864,7 +5979,10 @@ var mergeHistoricalReplayResults = (parts) => {
|
|
|
5864
5979
|
cycleCount: parts.reduce((sum, part) => sum + part.cycleCount, 0),
|
|
5865
5980
|
abortedCycles: parts.reduce((sum, part) => sum + part.abortedCycles, 0),
|
|
5866
5981
|
runtimeLineages: parts.flatMap((part) => part.runtimeLineages),
|
|
5867
|
-
replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes)
|
|
5982
|
+
replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes),
|
|
5983
|
+
replayDecisionTraces: parts.flatMap(
|
|
5984
|
+
(part) => part.replayDecisionTraces ?? []
|
|
5985
|
+
)
|
|
5868
5986
|
};
|
|
5869
5987
|
};
|
|
5870
5988
|
|
|
@@ -6015,7 +6133,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6015
6133
|
references,
|
|
6016
6134
|
showProgress = true,
|
|
6017
6135
|
collectSkipEvidence = true,
|
|
6018
|
-
collectSignals = true
|
|
6136
|
+
collectSignals = true,
|
|
6137
|
+
decisionTraceTargets
|
|
6019
6138
|
}) => {
|
|
6020
6139
|
const memory = createReplayMemoryProfile();
|
|
6021
6140
|
memory?.("prepare:start");
|
|
@@ -6102,6 +6221,7 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6102
6221
|
connector: replayConnector,
|
|
6103
6222
|
collectSkipEvidence,
|
|
6104
6223
|
collectSignals,
|
|
6224
|
+
decisionTraceTargets,
|
|
6105
6225
|
hooks: projectHooks,
|
|
6106
6226
|
hookContext
|
|
6107
6227
|
},
|
|
@@ -6138,7 +6258,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6138
6258
|
cycleCount: plan.orderedTimestamps.length,
|
|
6139
6259
|
abortedCycles: execution.abortedCycles,
|
|
6140
6260
|
runtimeLineages: plan.runtimeLineages,
|
|
6141
|
-
replayLineageScopes: execution.replayLineageScopes
|
|
6261
|
+
replayLineageScopes: execution.replayLineageScopes,
|
|
6262
|
+
replayDecisionTraces: execution.replayDecisionTraces
|
|
6142
6263
|
});
|
|
6143
6264
|
memory?.("result:collected");
|
|
6144
6265
|
return result;
|
|
@@ -6366,6 +6487,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
|
|
|
6366
6487
|
direction: trade.direction,
|
|
6367
6488
|
qty: toFiniteNumberOrNull2(trade.qty),
|
|
6368
6489
|
timestamp: trade.entryTimestamp,
|
|
6490
|
+
...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
|
|
6369
6491
|
price: toFiniteNumberOrNull2(trade.entryPrice),
|
|
6370
6492
|
orderId: trade.orderId,
|
|
6371
6493
|
signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
|
|
@@ -7111,6 +7233,7 @@ var toRuntimeParityDetail = (entry) => ({
|
|
|
7111
7233
|
direction: entry.direction,
|
|
7112
7234
|
qty: entry.qty ?? null,
|
|
7113
7235
|
timestamp: entry.timestamp,
|
|
7236
|
+
...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
|
|
7114
7237
|
price: entry.price,
|
|
7115
7238
|
exitType: entry.exitType ?? null,
|
|
7116
7239
|
exitTimestamp: entry.exitTimestamp ?? null,
|
|
@@ -7184,7 +7307,8 @@ var findNearestEvaluation = ({
|
|
|
7184
7307
|
evaluations,
|
|
7185
7308
|
toleranceMs,
|
|
7186
7309
|
timestampOffsetMs,
|
|
7187
|
-
entryTimestampOffsetMs = 0
|
|
7310
|
+
entryTimestampOffsetMs = 0,
|
|
7311
|
+
matchDirection = false
|
|
7188
7312
|
}) => {
|
|
7189
7313
|
const entryStrategy2 = getEntryStrategy(entry);
|
|
7190
7314
|
if (!entryStrategy2) {
|
|
@@ -7194,7 +7318,7 @@ var findNearestEvaluation = ({
|
|
|
7194
7318
|
let bestDiff = Number.POSITIVE_INFINITY;
|
|
7195
7319
|
const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
|
|
7196
7320
|
for (const evaluation of evaluations) {
|
|
7197
|
-
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) {
|
|
7198
7322
|
continue;
|
|
7199
7323
|
}
|
|
7200
7324
|
const diff = Math.abs(
|
|
@@ -7540,6 +7664,7 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7540
7664
|
replaySignals = [],
|
|
7541
7665
|
replaySignalEvaluations = [],
|
|
7542
7666
|
replayLineageScopes = [],
|
|
7667
|
+
replayDecisionTraces = [],
|
|
7543
7668
|
runtimeLineageScopes = [],
|
|
7544
7669
|
toleranceMs,
|
|
7545
7670
|
backtestTimestampOffsetMs,
|
|
@@ -7548,48 +7673,66 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7548
7673
|
const nearestByEntry = new Map(
|
|
7549
7674
|
nearestCandidates.map((candidate) => [candidate.entry, candidate])
|
|
7550
7675
|
);
|
|
7676
|
+
const traceByCandle = new Map(
|
|
7677
|
+
replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
|
|
7678
|
+
);
|
|
7551
7679
|
const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
|
|
7552
7680
|
let runtimeOnlyArtifactsIncluded = 0;
|
|
7553
7681
|
let backtestOnlyArtifactsIncluded = 0;
|
|
7554
7682
|
const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
|
|
7555
|
-
const
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
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
|
+
};
|
|
7559
7692
|
const includeArtifacts = index < artifactLimit;
|
|
7560
7693
|
const nearestCandidate = nearestByEntry.get(entry);
|
|
7561
|
-
const nearestReplaySignal = findNearestSignal({
|
|
7562
|
-
entry,
|
|
7694
|
+
const nearestReplaySignal = decisionEntry && findNearestSignal({
|
|
7695
|
+
entry: decisionEntry,
|
|
7563
7696
|
signals: replaySignals,
|
|
7564
|
-
|
|
7565
|
-
timestampOffsetMs: entryOffsetMs
|
|
7697
|
+
...decisionClock
|
|
7566
7698
|
});
|
|
7567
|
-
const nearestReplayEvaluation = findNearestEvaluation({
|
|
7568
|
-
entry,
|
|
7699
|
+
const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
|
|
7700
|
+
entry: decisionEntry,
|
|
7569
7701
|
evaluations: replaySignalEvaluations,
|
|
7570
|
-
|
|
7571
|
-
|
|
7702
|
+
matchDirection: isRuntimeTrade,
|
|
7703
|
+
...decisionClock
|
|
7572
7704
|
});
|
|
7573
|
-
const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
|
|
7574
|
-
entry,
|
|
7705
|
+
const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
|
|
7706
|
+
entry: decisionEntry,
|
|
7575
7707
|
lineageScopes: replayLineageScopes,
|
|
7576
|
-
|
|
7577
|
-
timestampOffsetMs: entryOffsetMs
|
|
7708
|
+
...decisionClock
|
|
7578
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";
|
|
7579
7719
|
const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
|
|
7580
|
-
classification:
|
|
7720
|
+
classification: nearestReplayEvaluationOutcome.reason.startsWith(
|
|
7721
|
+
"BACKTEST_ENTRY_DELAY_"
|
|
7722
|
+
) ? "entry_delayed" : "core_skipped",
|
|
7581
7723
|
reason: nearestReplayEvaluationOutcome.reason
|
|
7582
|
-
} : nearestCandidate?.nearest ? {
|
|
7724
|
+
} : !isRuntimeTrade && nearestCandidate?.nearest ? {
|
|
7583
7725
|
classification: "timing_or_price_drift",
|
|
7584
7726
|
reason: nearestCandidate.reason
|
|
7585
7727
|
} : {
|
|
7586
7728
|
classification: "no_replay_candidate",
|
|
7587
|
-
reason:
|
|
7729
|
+
reason: unavailableReason
|
|
7588
7730
|
};
|
|
7589
7731
|
return {
|
|
7590
7732
|
entry,
|
|
7591
7733
|
...classification,
|
|
7592
7734
|
...nearestCandidate ? { nearestCandidate } : {},
|
|
7735
|
+
...replayDecisionTrace ? { replayDecisionTrace } : {},
|
|
7593
7736
|
...nearestReplaySignal ? {
|
|
7594
7737
|
replaySignal: toSignalDiagnostic({
|
|
7595
7738
|
signal: nearestReplaySignal.signal,
|
|
@@ -7715,6 +7858,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7715
7858
|
replaySignals,
|
|
7716
7859
|
replaySignalEvaluations,
|
|
7717
7860
|
replayLineageScopes,
|
|
7861
|
+
replayDecisionTraces,
|
|
7718
7862
|
runtimeLineageScopes
|
|
7719
7863
|
}) => {
|
|
7720
7864
|
const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
|
|
@@ -7775,6 +7919,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7775
7919
|
replaySignals,
|
|
7776
7920
|
replaySignalEvaluations,
|
|
7777
7921
|
replayLineageScopes,
|
|
7922
|
+
replayDecisionTraces,
|
|
7778
7923
|
runtimeLineageScopes,
|
|
7779
7924
|
toleranceMs,
|
|
7780
7925
|
backtestTimestampOffsetMs,
|
|
@@ -8800,6 +8945,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8800
8945
|
replaySignals,
|
|
8801
8946
|
replayLineages,
|
|
8802
8947
|
replayLineageScopes,
|
|
8948
|
+
replayDecisionTraces,
|
|
8803
8949
|
runtimeEvidencePath
|
|
8804
8950
|
}) => {
|
|
8805
8951
|
const { connector, connectorName, window } = getRuntimeCompareContext();
|
|
@@ -8951,7 +9097,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8951
9097
|
replaySignals,
|
|
8952
9098
|
replaySignalEvaluations,
|
|
8953
9099
|
runtimeLineageScopes: comparable.runtimeLineageScopes,
|
|
8954
|
-
replayLineageScopes
|
|
9100
|
+
replayLineageScopes,
|
|
9101
|
+
replayDecisionTraces
|
|
8955
9102
|
});
|
|
8956
9103
|
const parityRows = summarizeTradeParityByStrategy({
|
|
8957
9104
|
runtimeEntries: runtimeDedupe.entries,
|
|
@@ -9878,6 +10025,7 @@ var finishReplay = async ({
|
|
|
9878
10025
|
replaySignals: replayResult.signals,
|
|
9879
10026
|
replayLineages: replayResult.runtimeLineages,
|
|
9880
10027
|
replayLineageScopes: replayResult.replayLineageScopes,
|
|
10028
|
+
replayDecisionTraces: replayResult.replayDecisionTraces,
|
|
9881
10029
|
runtimeEvidencePath: replayRuntimeEvidencePath
|
|
9882
10030
|
});
|
|
9883
10031
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
@@ -10227,7 +10375,8 @@ var replayBacktest = async () => {
|
|
|
10227
10375
|
references: historicalReplayReferences,
|
|
10228
10376
|
showProgress: !replayFlags.portfolioReport,
|
|
10229
10377
|
collectSkipEvidence: !replayFlags.portfolioReport,
|
|
10230
|
-
collectSignals: !replayFlags.portfolioReport
|
|
10378
|
+
collectSignals: !replayFlags.portfolioReport,
|
|
10379
|
+
decisionTraceTargets: replayFlags.portfolioReport ? void 0 : evidenceMetadata?.decisionTraceTargets
|
|
10231
10380
|
});
|
|
10232
10381
|
return replayFlags.portfolioReport ? compactHistoricalReplayResultForPortfolio(result) : result;
|
|
10233
10382
|
};
|