@tradejs/cli 3.1.29-beta.289 → 3.1.29-beta.291
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 +2574 -1236
- package/dist/lib/aiPocketSearch.js +37 -3
- package/dist/lib/aiTrainDataset.js +1 -0
- package/dist/lib/aiTrainEvaluationDump.js +7 -2
- package/dist/lib/aiTrainResearch.js +11 -7
- package/dist/lib/coreResearch.js +12 -4
- package/dist/lib/researchMetricSnapshot.js +74 -0
- package/dist/lib/researchVisuals.js +910 -0
- 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/lib/strategyRelease.js +8 -2
- package/dist/lib/strategyResearch.js +133 -33
- package/dist/lib/tickerProfiles.js +76 -0
- package/dist/scripts/aiPocketSearch.js +38 -3
- package/dist/scripts/aiTrain.js +29 -17
- package/dist/scripts/coreResearch.js +12 -4
- package/dist/scripts/replay.js +223 -33
- package/dist/scripts/replayRunner.js +223 -33
- package/dist/scripts/researchVisuals.js +955 -0
- 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/dist/scripts/strategyRelease.js +59 -7
- package/dist/scripts/strategyResearch.js +136 -36
- package/dist/scripts/tickerProfiles.js +114 -0
- package/package.json +8 -7
package/dist/scripts/aiTrain.js
CHANGED
|
@@ -827,7 +827,9 @@ var BASE_CONTEXT_FEATURE_SECTIONS = [
|
|
|
827
827
|
"relative",
|
|
828
828
|
"derivatives",
|
|
829
829
|
"mtf",
|
|
830
|
-
"gateFeatures"
|
|
830
|
+
"gateFeatures",
|
|
831
|
+
"tickerProfile",
|
|
832
|
+
"portfolioContext"
|
|
831
833
|
];
|
|
832
834
|
var asRecord = (value) => typeof value === "object" && value != null && !Array.isArray(value) ? value : null;
|
|
833
835
|
var collectStrategyGateFeatures = (baseContext) => {
|
|
@@ -844,6 +846,7 @@ var pickBaseContextFeatureSections = (baseContext) => Object.fromEntries(
|
|
|
844
846
|
);
|
|
845
847
|
var buildAiTrainEvaluationFeatureSnapshot = ({
|
|
846
848
|
additionalIndicators,
|
|
849
|
+
featureSnapshot,
|
|
847
850
|
mode
|
|
848
851
|
}) => {
|
|
849
852
|
if (mode === "none") {
|
|
@@ -853,12 +856,14 @@ var buildAiTrainEvaluationFeatureSnapshot = ({
|
|
|
853
856
|
const baseContext = asRecord(additional?.baseContext);
|
|
854
857
|
if (!baseContext) {
|
|
855
858
|
return {
|
|
856
|
-
baseContextAvailable: false
|
|
859
|
+
baseContextAvailable: false,
|
|
860
|
+
...featureSnapshot ? { featureSnapshot } : {}
|
|
857
861
|
};
|
|
858
862
|
}
|
|
859
863
|
const strategyGateFeatures = collectStrategyGateFeatures(baseContext);
|
|
860
864
|
const snapshot = {
|
|
861
865
|
baseContextAvailable: true,
|
|
866
|
+
...featureSnapshot ? { featureSnapshot } : {},
|
|
862
867
|
gateFeatures: baseContext.gateFeatures ?? null,
|
|
863
868
|
...strategyGateFeatures ? { strategyGateFeatures } : {}
|
|
864
869
|
};
|
|
@@ -1059,6 +1064,8 @@ var RESEARCH_CONTEXT_ENV_KEYS = [
|
|
|
1059
1064
|
"INTERVAL",
|
|
1060
1065
|
"DERIVATIVES_CONTEXT_ENABLED",
|
|
1061
1066
|
"DERIVATIVES_CONTEXT_TARGET_ENABLED",
|
|
1067
|
+
"TICKER_PROFILES_PROVIDER",
|
|
1068
|
+
"TICKER_PROFILES_UNIVERSE",
|
|
1062
1069
|
"DERIVATIVES_CONTEXT_LOOKBACK_HOURS",
|
|
1063
1070
|
"DERIVATIVES_CONTEXT_EXTRA_REFERENCE_SYMBOLS",
|
|
1064
1071
|
"DERIVATIVES_CONTEXT_EXCHANGE_PRIORITY",
|
|
@@ -1093,13 +1100,15 @@ var writeAiTrainResearchSnapshot = async ({
|
|
|
1093
1100
|
}) => {
|
|
1094
1101
|
const resolvedPath = import_node_path2.default.resolve(outputPath);
|
|
1095
1102
|
await import_promises2.default.mkdir(import_node_path2.default.dirname(resolvedPath), { recursive: true });
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
"
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
+
const bytes = `${JSON.stringify(result)}
|
|
1104
|
+
`;
|
|
1105
|
+
await import_promises2.default.writeFile(resolvedPath, bytes, "utf8");
|
|
1106
|
+
return {
|
|
1107
|
+
schema: "tradejs-research-report-receipt/v1",
|
|
1108
|
+
path: resolvedPath,
|
|
1109
|
+
bytes: Buffer.byteLength(bytes),
|
|
1110
|
+
sha256: (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex")
|
|
1111
|
+
};
|
|
1103
1112
|
};
|
|
1104
1113
|
var normalizeForStableJson = (value) => {
|
|
1105
1114
|
if (Array.isArray(value)) {
|
|
@@ -1416,6 +1425,7 @@ var extractSignalFromAiDatasetRow = (row) => {
|
|
|
1416
1425
|
const { payload } = row;
|
|
1417
1426
|
return {
|
|
1418
1427
|
...payload.signal,
|
|
1428
|
+
aiFeatureReplay: { snapshot: payload.featureSnapshot ?? null },
|
|
1419
1429
|
strategy: payload.signal.strategy,
|
|
1420
1430
|
figures: payload.figures ?? {},
|
|
1421
1431
|
indicators: payload.indicators ?? {},
|
|
@@ -1474,7 +1484,11 @@ import_args.default.option(
|
|
|
1474
1484
|
"Save compact AI approval chart data for the strategies UI",
|
|
1475
1485
|
false
|
|
1476
1486
|
);
|
|
1477
|
-
import_args.default.option(
|
|
1487
|
+
import_args.default.option(
|
|
1488
|
+
["j", "json"],
|
|
1489
|
+
"Print full JSON without --output; otherwise print its receipt",
|
|
1490
|
+
false
|
|
1491
|
+
);
|
|
1478
1492
|
import_args.default.option(
|
|
1479
1493
|
["O", "output"],
|
|
1480
1494
|
"Write the structured JSON summary to a file and suppress table output",
|
|
@@ -1938,6 +1952,7 @@ var main = async () => {
|
|
|
1938
1952
|
const isCorrect = aiApproved === profitableTrade;
|
|
1939
1953
|
const features = buildAiTrainEvaluationFeatureSnapshot({
|
|
1940
1954
|
additionalIndicators: payload.additionalIndicators,
|
|
1955
|
+
featureSnapshot: payload.featureSnapshot,
|
|
1941
1956
|
mode: dumpFeatureMode
|
|
1942
1957
|
});
|
|
1943
1958
|
evaluations.push({
|
|
@@ -2182,14 +2197,11 @@ var main = async () => {
|
|
|
2182
2197
|
ttlMs: import_constants.TTL_1M
|
|
2183
2198
|
});
|
|
2184
2199
|
}
|
|
2185
|
-
|
|
2186
|
-
await writeAiTrainResearchSnapshot({
|
|
2187
|
-
outputPath: jsonOutputPath,
|
|
2188
|
-
result
|
|
2189
|
-
});
|
|
2190
|
-
}
|
|
2200
|
+
const reportReceipt = jsonOutputPath ? await writeAiTrainResearchSnapshot({ outputPath: jsonOutputPath, result }) : null;
|
|
2191
2201
|
if (jsonOutput) {
|
|
2192
|
-
if (
|
|
2202
|
+
if (reportReceipt) {
|
|
2203
|
+
console.log(JSON.stringify(reportReceipt));
|
|
2204
|
+
} else if (printJsonOutput) {
|
|
2193
2205
|
console.log(JSON.stringify(result));
|
|
2194
2206
|
}
|
|
2195
2207
|
process.exit(failed === selectedRows ? 1 : 0);
|
|
@@ -46,7 +46,7 @@ var import_node_path = __toESM(require("path"));
|
|
|
46
46
|
|
|
47
47
|
// src/lib/coreResearch/types.ts
|
|
48
48
|
var CORE_RESEARCH_SCHEMA = "tradejs-core-research/v1";
|
|
49
|
-
var CORE_RESEARCH_RESULT_SCHEMA = "tradejs-core-research-result/
|
|
49
|
+
var CORE_RESEARCH_RESULT_SCHEMA = "tradejs-core-research-result/v2";
|
|
50
50
|
var CORE_RESEARCH_LEDGER_SCHEMA = "tradejs-core-research-ledger/v1";
|
|
51
51
|
|
|
52
52
|
// src/lib/coreResearch/io.ts
|
|
@@ -731,6 +731,14 @@ var writeCoreResearchTrades = async (params) => {
|
|
|
731
731
|
}
|
|
732
732
|
await closeOutput(output);
|
|
733
733
|
};
|
|
734
|
+
var summarizeCoreResearchComparison = (comparison) => {
|
|
735
|
+
const { matchedPairs, ...summary } = comparison;
|
|
736
|
+
return {
|
|
737
|
+
...summary,
|
|
738
|
+
exitReasonChanges: matchedPairs.filter((pair) => pair.exitReasonChanged).length,
|
|
739
|
+
exitCodeChanges: matchedPairs.filter((pair) => pair.exitCodeChanged).length
|
|
740
|
+
};
|
|
741
|
+
};
|
|
734
742
|
|
|
735
743
|
// src/lib/coreResearch/metrics.ts
|
|
736
744
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -1806,7 +1814,7 @@ var buildCurveSvg = (tradesByVariant, cohort, curve) => {
|
|
|
1806
1814
|
)}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml(entry.id)}</span>`).join("")}</div>`;
|
|
1807
1815
|
};
|
|
1808
1816
|
var comparisonCards = (comparisons) => comparisons.map(
|
|
1809
|
-
(comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.
|
|
1817
|
+
(comparison) => `<article class="card"><h3>${escapeHtml(comparison.candidateId)} vs ${escapeHtml(comparison.controlId)}</h3><p class="verdict ${comparison.selection.passed ? "positive" : "negative"}">${comparison.selection.passed ? "PASS selection" : "REJECT selection"}</p><dl><dt>Matched</dt><dd>${comparison.matched}</dd><dt>Control only</dt><dd>${comparison.controlOnly}</dd><dt>Candidate only</dt><dd>${comparison.candidateOnly}</dd><dt>Identity retained</dt><dd>${format(comparison.matchedIdentityPctOfControl)}%</dd><dt>Exit reason changes</dt><dd>${comparison.exitReasonChanges}</dd><dt>Exit code changes</dt><dd>${comparison.exitCodeChanges}</dd><dt>Matched \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.matchedPnlDelta)}</dd><dt>Removed control PnL</dt><dd>${format(comparison.cohorts.ALL.controlOnlyPnl)}</dd><dt>Added candidate PnL</dt><dd>${format(comparison.cohorts.ALL.candidateOnlyPnl)}</dd><dt>Aggregate \u0394PnL</dt><dd>${format(comparison.cohorts.ALL.delta.pnl)}</dd><dt>Aggregate \u0394DD</dt><dd>${format(comparison.cohorts.ALL.delta.realizedMaxDrawdown)}</dd><dt>Bootstrap P(\u0394>0)</dt><dd>${format(comparison.bootstrap.probabilityPositive, 4)}</dd><dt>Holm p</dt><dd>${format(comparison.bootstrap.holmAdjustedPValue, 4)}</dd></dl><p>${comparison.selection.failedRules.length ? escapeHtml(comparison.selection.failedRules.map((rule) => `${rule.scope}.${rule.metric} ${rule.expected}`).join("; ")) : "All preregistered rules passed."}</p></article>`
|
|
1810
1818
|
).join("");
|
|
1811
1819
|
var regimeRows = (variants) => {
|
|
1812
1820
|
const keys = [
|
|
@@ -2491,7 +2499,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2491
2499
|
regimeCausality: "signal-time payload.additionalIndicators.baseContext only"
|
|
2492
2500
|
},
|
|
2493
2501
|
variants: analyses,
|
|
2494
|
-
comparisons,
|
|
2502
|
+
comparisons: comparisons.map(summarizeCoreResearchComparison),
|
|
2495
2503
|
multipleTesting: {
|
|
2496
2504
|
family: spec.hypothesis.family,
|
|
2497
2505
|
hypotheses: Math.max(comparisons.length, familyHypotheses),
|
|
@@ -2524,7 +2532,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2524
2532
|
artifactHashes: sourceHashes
|
|
2525
2533
|
};
|
|
2526
2534
|
await writeJsonAtomic(paths.resultPath, result);
|
|
2527
|
-
await writeJsonAtomic(paths.comparisonPath, comparisons);
|
|
2535
|
+
await writeJsonAtomic(paths.comparisonPath, result.comparisons);
|
|
2528
2536
|
await writeCoreResearchMatches({ filePath: paths.matchesPath, comparisons });
|
|
2529
2537
|
await writeCoreResearchTrades({
|
|
2530
2538
|
filePath: paths.tradesPath,
|
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 = ({
|
|
@@ -5177,6 +5183,9 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5177
5183
|
let executionTimestamp = null;
|
|
5178
5184
|
const positionsBySymbol = /* @__PURE__ */ new Map();
|
|
5179
5185
|
const currentPriceBySymbol = /* @__PURE__ */ new Map();
|
|
5186
|
+
const markedAtBySymbol = /* @__PURE__ */ new Map();
|
|
5187
|
+
const entryFacts = /* @__PURE__ */ new Map();
|
|
5188
|
+
let portfolioObserved = false;
|
|
5180
5189
|
const orderLog = [];
|
|
5181
5190
|
const positionLog = [];
|
|
5182
5191
|
const orderLogByStrategy = /* @__PURE__ */ new Map();
|
|
@@ -5414,14 +5423,51 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5414
5423
|
const { strategyName: _strategyName, ...nextPosition } = position;
|
|
5415
5424
|
return nextPosition;
|
|
5416
5425
|
}),
|
|
5426
|
+
getPortfolioObservation: async (asOf, reservations) => {
|
|
5427
|
+
portfolioObserved = true;
|
|
5428
|
+
const pendingIds = new Set(reservations.map((r) => r.id));
|
|
5429
|
+
for (const id of entryFacts.keys())
|
|
5430
|
+
if (!pendingIds.has(id)) entryFacts.delete(id);
|
|
5431
|
+
return {
|
|
5432
|
+
asOf,
|
|
5433
|
+
positions: Array.from(positionsBySymbol.values()).map(
|
|
5434
|
+
({ position, stopLossPrice }) => ({
|
|
5435
|
+
id: position.orderId ?? `${position.strategyName}:${position.symbol}:${position.timestamp}`,
|
|
5436
|
+
strategy: position.strategyName === UNKNOWN_STRATEGY ? null : position.strategyName,
|
|
5437
|
+
symbol: position.symbol,
|
|
5438
|
+
direction: position.direction,
|
|
5439
|
+
qty: position.qty,
|
|
5440
|
+
entryPrice: position.price,
|
|
5441
|
+
markPrice: currentPriceBySymbol.get(position.symbol) ?? null,
|
|
5442
|
+
markedAt: markedAtBySymbol.get(position.symbol) ?? null,
|
|
5443
|
+
stopLossPrice
|
|
5444
|
+
})
|
|
5445
|
+
),
|
|
5446
|
+
orders: reservations.map(
|
|
5447
|
+
(r) => entryFacts.get(r.id) ?? {
|
|
5448
|
+
id: r.id,
|
|
5449
|
+
status: "pending",
|
|
5450
|
+
filledQty: 0
|
|
5451
|
+
}
|
|
5452
|
+
)
|
|
5453
|
+
};
|
|
5454
|
+
},
|
|
5417
5455
|
getOpenPositionPnl: async () => Array.from(positionsBySymbol.values()).map(
|
|
5418
|
-
({ position }) =>
|
|
5419
|
-
|
|
5420
|
-
|
|
5456
|
+
({ position, stopLossPrice }) => ({
|
|
5457
|
+
...getPositionPnlSnapshot({
|
|
5458
|
+
position,
|
|
5459
|
+
currentPrice: currentPriceBySymbol.get(position.symbol) ?? position.price
|
|
5460
|
+
}),
|
|
5461
|
+
observedAt: markedAtBySymbol.get(position.symbol),
|
|
5462
|
+
stopLossPrice
|
|
5421
5463
|
})
|
|
5422
5464
|
),
|
|
5423
5465
|
placeOrder: async (order) => {
|
|
5424
|
-
const {
|
|
5466
|
+
const {
|
|
5467
|
+
beforeSubmit,
|
|
5468
|
+
onRejected: _onRejected,
|
|
5469
|
+
...submittedOrder
|
|
5470
|
+
} = order;
|
|
5425
5471
|
if (positionsBySymbol.has(order.symbol)) {
|
|
5426
5472
|
return false;
|
|
5427
5473
|
}
|
|
@@ -5465,6 +5511,14 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5465
5511
|
stopLossPrice: null
|
|
5466
5512
|
};
|
|
5467
5513
|
positionsBySymbol.set(order.symbol, positionState);
|
|
5514
|
+
markedAtBySymbol.set(order.symbol, executionTimestamp ?? order.timestamp);
|
|
5515
|
+
if (portfolioObserved && order.orderId)
|
|
5516
|
+
entryFacts.set(order.orderId, {
|
|
5517
|
+
id: order.orderId,
|
|
5518
|
+
status: "filled",
|
|
5519
|
+
filledQty: order.qty,
|
|
5520
|
+
positionId: order.orderId
|
|
5521
|
+
});
|
|
5468
5522
|
logOrder({
|
|
5469
5523
|
positionState,
|
|
5470
5524
|
data: {
|
|
@@ -5548,6 +5602,7 @@ var createPortfolioReplayConnector = (connector) => {
|
|
|
5548
5602
|
getTickers: connector.getTickers,
|
|
5549
5603
|
advanceMarket: async ({ symbol, candle }) => {
|
|
5550
5604
|
executionTimestamp = candle.timestamp;
|
|
5605
|
+
markedAtBySymbol.set(symbol, candle.timestamp);
|
|
5551
5606
|
currentPriceBySymbol.set(symbol, candle.close);
|
|
5552
5607
|
await checkSl({ symbol, candle });
|
|
5553
5608
|
await checkTp({ symbol, candle });
|
|
@@ -5588,6 +5643,73 @@ var invokeAfterSignalsHooks = async (hooks, params) => {
|
|
|
5588
5643
|
}
|
|
5589
5644
|
};
|
|
5590
5645
|
|
|
5646
|
+
// src/lib/replay/historicalSignalsReplayDecisionTrace.ts
|
|
5647
|
+
var replayDecisionTraceKey = ({
|
|
5648
|
+
strategy,
|
|
5649
|
+
symbol,
|
|
5650
|
+
signalTimestamp
|
|
5651
|
+
}) => JSON.stringify([strategy, symbol, signalTimestamp]);
|
|
5652
|
+
var describeOutcome = (result) => {
|
|
5653
|
+
if (typeof result === "string") {
|
|
5654
|
+
return {
|
|
5655
|
+
stage: result.startsWith("BACKTEST_ENTRY_DELAY_") ? "entry_delay" : /(?:NO_PATTERN|POSITION_EXISTS)$/.test(result) ? "core" : "evaluation",
|
|
5656
|
+
reason: result
|
|
5657
|
+
};
|
|
5658
|
+
}
|
|
5659
|
+
if (!result) return { stage: "evaluation", reason: "NO_SIGNAL" };
|
|
5660
|
+
const reason = result.orderSkipReason || result.orderFailureReason || result.orderStatus || "SIGNAL";
|
|
5661
|
+
const patternContext = result.additionalIndicators?.tradingPatternsContext;
|
|
5662
|
+
return {
|
|
5663
|
+
stage: result.riskDecision?.status === "rejected" || /^ENTRY_(?:RR_BELOW|RISK_EXCEEDS)/.test(reason) ? "risk" : /^(?:AI_|ML_|HOOK_BEFORE_ENTRY_GATE|RESEARCH_ENTRY_POLICY)/.test(
|
|
5664
|
+
reason
|
|
5665
|
+
) ? "gate" : result.orderStatus ? "execution" : "evaluation",
|
|
5666
|
+
reason,
|
|
5667
|
+
direction: result.direction,
|
|
5668
|
+
orderStatus: result.orderStatus,
|
|
5669
|
+
...typeof patternContext?.selectedPattern === "string" ? { pattern: patternContext.selectedPattern } : {},
|
|
5670
|
+
...typeof patternContext?.sourceCode === "string" ? { patternCode: patternContext.sourceCode } : {}
|
|
5671
|
+
};
|
|
5672
|
+
};
|
|
5673
|
+
var createReplayDecisionTrace = (targets = []) => {
|
|
5674
|
+
const traces = /* @__PURE__ */ new Map();
|
|
5675
|
+
const symbols = new Set(targets.map(({ symbol }) => symbol));
|
|
5676
|
+
for (const target of targets) {
|
|
5677
|
+
traces.set(replayDecisionTraceKey(target), {
|
|
5678
|
+
...target,
|
|
5679
|
+
decision: {
|
|
5680
|
+
stage: "not_evaluated",
|
|
5681
|
+
reason: "REPLAY_CANDLE_NOT_EVALUATED"
|
|
5682
|
+
}
|
|
5683
|
+
});
|
|
5684
|
+
}
|
|
5685
|
+
const record = (strategy, symbol, candle, result, notEvaluatedReason) => {
|
|
5686
|
+
if (!symbols.has(symbol)) return;
|
|
5687
|
+
const { timestamp, open, high, low, close, volume } = candle;
|
|
5688
|
+
const trace = traces.get(
|
|
5689
|
+
replayDecisionTraceKey({ strategy, symbol, signalTimestamp: timestamp })
|
|
5690
|
+
);
|
|
5691
|
+
if (result && typeof result !== "string" && result.timestamp !== timestamp) {
|
|
5692
|
+
const original = traces.get(
|
|
5693
|
+
replayDecisionTraceKey({
|
|
5694
|
+
strategy,
|
|
5695
|
+
symbol,
|
|
5696
|
+
signalTimestamp: result.timestamp
|
|
5697
|
+
})
|
|
5698
|
+
);
|
|
5699
|
+
if (original)
|
|
5700
|
+
original.followUp = {
|
|
5701
|
+
...describeOutcome(result),
|
|
5702
|
+
candleTimestamp: timestamp
|
|
5703
|
+
};
|
|
5704
|
+
notEvaluatedReason = "REPLAY_PROCESSED_DELAYED_ENTRY";
|
|
5705
|
+
}
|
|
5706
|
+
if (!trace) return;
|
|
5707
|
+
trace.candle = { timestamp, open, high, low, close, volume };
|
|
5708
|
+
trace.decision = notEvaluatedReason ? { stage: "not_evaluated", reason: notEvaluatedReason } : describeOutcome(result);
|
|
5709
|
+
};
|
|
5710
|
+
return { record, values: () => [...traces.values()] };
|
|
5711
|
+
};
|
|
5712
|
+
|
|
5591
5713
|
// src/lib/replay/historicalSignalsReplaySkipEvidence.ts
|
|
5592
5714
|
var import_data2 = require("@tradejs/core/data");
|
|
5593
5715
|
var replayScopeKey = (strategy, symbol) => `${strategy}::${symbol}`;
|
|
@@ -5698,6 +5820,7 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5698
5820
|
context.plan.runtimeLineages,
|
|
5699
5821
|
context.collectSkipEvidence
|
|
5700
5822
|
);
|
|
5823
|
+
const decisionTrace = createReplayDecisionTrace(context.decisionTraceTargets);
|
|
5701
5824
|
const reportProgress = (timestamp) => {
|
|
5702
5825
|
if (adapters.progress.enabled === false) return;
|
|
5703
5826
|
adapters.progress.tick({
|
|
@@ -5726,6 +5849,20 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5726
5849
|
status: "completed",
|
|
5727
5850
|
durationMs: adapters.clock.now() - cycleStartedAt
|
|
5728
5851
|
});
|
|
5852
|
+
if (context.decisionTraceTargets?.length) {
|
|
5853
|
+
for (const symbolRuntime of cycleSymbols) {
|
|
5854
|
+
const candle = symbolRuntime.replayData[symbolRuntime.currentIndex];
|
|
5855
|
+
for (const strategy of symbolRuntime.strategies) {
|
|
5856
|
+
decisionTrace.record(
|
|
5857
|
+
strategy.strategyName,
|
|
5858
|
+
symbolRuntime.symbol,
|
|
5859
|
+
candle,
|
|
5860
|
+
void 0,
|
|
5861
|
+
"REPLAY_CYCLE_ABORTED"
|
|
5862
|
+
);
|
|
5863
|
+
}
|
|
5864
|
+
}
|
|
5865
|
+
}
|
|
5729
5866
|
advanceAbortedCycle(cycleSymbols, timestamp);
|
|
5730
5867
|
reportProgress(timestamp);
|
|
5731
5868
|
continue;
|
|
@@ -5736,6 +5873,17 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5736
5873
|
const btcCandle = symbolRuntime.btcReplayData[symbolRuntime.currentIndex];
|
|
5737
5874
|
const ethCandle = symbolRuntime.ethReplayData[symbolRuntime.currentIndex];
|
|
5738
5875
|
if (!candle || !btcCandle || candle.timestamp + (symbolRuntime.timestampOffsetMs ?? 0) !== timestamp || btcCandle.timestamp !== candle.timestamp) {
|
|
5876
|
+
if (candle && context.decisionTraceTargets?.length) {
|
|
5877
|
+
for (const strategy of symbolRuntime.strategies) {
|
|
5878
|
+
decisionTrace.record(
|
|
5879
|
+
strategy.strategyName,
|
|
5880
|
+
symbolRuntime.symbol,
|
|
5881
|
+
candle,
|
|
5882
|
+
void 0,
|
|
5883
|
+
"REPLAY_CANDLE_OR_REFERENCE_NOT_ALIGNED"
|
|
5884
|
+
);
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5739
5887
|
continue;
|
|
5740
5888
|
}
|
|
5741
5889
|
for (const strategyRuntime of symbolRuntime.strategies) {
|
|
@@ -5744,6 +5892,12 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5744
5892
|
btcCandle,
|
|
5745
5893
|
ethCandle?.timestamp === candle.timestamp ? ethCandle : void 0
|
|
5746
5894
|
);
|
|
5895
|
+
decisionTrace.record(
|
|
5896
|
+
strategyRuntime.strategyName,
|
|
5897
|
+
symbolRuntime.symbol,
|
|
5898
|
+
candle,
|
|
5899
|
+
result
|
|
5900
|
+
);
|
|
5747
5901
|
skipEvidence.record({
|
|
5748
5902
|
strategyName: strategyRuntime.strategyName,
|
|
5749
5903
|
symbol: symbolRuntime.symbol,
|
|
@@ -5775,7 +5929,8 @@ var executeHistoricalReplay = async (context, adapters) => {
|
|
|
5775
5929
|
return {
|
|
5776
5930
|
signals,
|
|
5777
5931
|
abortedCycles,
|
|
5778
|
-
replayLineageScopes: skipEvidence.values()
|
|
5932
|
+
replayLineageScopes: skipEvidence.values(),
|
|
5933
|
+
replayDecisionTraces: decisionTrace.values()
|
|
5779
5934
|
};
|
|
5780
5935
|
};
|
|
5781
5936
|
|
|
@@ -5826,7 +5981,8 @@ var collectHistoricalReplayResult = (context) => ({
|
|
|
5826
5981
|
cycleCount: context.cycleCount,
|
|
5827
5982
|
abortedCycles: context.abortedCycles,
|
|
5828
5983
|
runtimeLineages: context.runtimeLineages,
|
|
5829
|
-
replayLineageScopes: context.replayLineageScopes
|
|
5984
|
+
replayLineageScopes: context.replayLineageScopes,
|
|
5985
|
+
...context.replayDecisionTraces?.length ? { replayDecisionTraces: context.replayDecisionTraces } : {}
|
|
5830
5986
|
});
|
|
5831
5987
|
var compactHistoricalReplayResultForPortfolio = (result) => ({
|
|
5832
5988
|
...result,
|
|
@@ -5872,7 +6028,10 @@ var mergeHistoricalReplayResults = (parts) => {
|
|
|
5872
6028
|
cycleCount: parts.reduce((sum, part) => sum + part.cycleCount, 0),
|
|
5873
6029
|
abortedCycles: parts.reduce((sum, part) => sum + part.abortedCycles, 0),
|
|
5874
6030
|
runtimeLineages: parts.flatMap((part) => part.runtimeLineages),
|
|
5875
|
-
replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes)
|
|
6031
|
+
replayLineageScopes: parts.flatMap((part) => part.replayLineageScopes),
|
|
6032
|
+
replayDecisionTraces: parts.flatMap(
|
|
6033
|
+
(part) => part.replayDecisionTraces ?? []
|
|
6034
|
+
)
|
|
5876
6035
|
};
|
|
5877
6036
|
};
|
|
5878
6037
|
|
|
@@ -6023,7 +6182,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6023
6182
|
references,
|
|
6024
6183
|
showProgress = true,
|
|
6025
6184
|
collectSkipEvidence = true,
|
|
6026
|
-
collectSignals = true
|
|
6185
|
+
collectSignals = true,
|
|
6186
|
+
decisionTraceTargets
|
|
6027
6187
|
}) => {
|
|
6028
6188
|
const memory = createReplayMemoryProfile();
|
|
6029
6189
|
memory?.("prepare:start");
|
|
@@ -6110,6 +6270,7 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6110
6270
|
connector: replayConnector,
|
|
6111
6271
|
collectSkipEvidence,
|
|
6112
6272
|
collectSignals,
|
|
6273
|
+
decisionTraceTargets,
|
|
6113
6274
|
hooks: projectHooks,
|
|
6114
6275
|
hookContext
|
|
6115
6276
|
},
|
|
@@ -6146,7 +6307,8 @@ var runHistoricalSignalsReplay = async ({
|
|
|
6146
6307
|
cycleCount: plan.orderedTimestamps.length,
|
|
6147
6308
|
abortedCycles: execution.abortedCycles,
|
|
6148
6309
|
runtimeLineages: plan.runtimeLineages,
|
|
6149
|
-
replayLineageScopes: execution.replayLineageScopes
|
|
6310
|
+
replayLineageScopes: execution.replayLineageScopes,
|
|
6311
|
+
replayDecisionTraces: execution.replayDecisionTraces
|
|
6150
6312
|
});
|
|
6151
6313
|
memory?.("result:collected");
|
|
6152
6314
|
return result;
|
|
@@ -6374,6 +6536,7 @@ var extractRuntimeParityEntries = (trades) => trades.filter(
|
|
|
6374
6536
|
direction: trade.direction,
|
|
6375
6537
|
qty: toFiniteNumberOrNull2(trade.qty),
|
|
6376
6538
|
timestamp: trade.entryTimestamp,
|
|
6539
|
+
...Number.isFinite(trade.signalTimestamp) ? { signalTimestamp: trade.signalTimestamp } : {},
|
|
6377
6540
|
price: toFiniteNumberOrNull2(trade.entryPrice),
|
|
6378
6541
|
orderId: trade.orderId,
|
|
6379
6542
|
signalId: typeof trade.signalId === "string" ? trade.signalId : void 0,
|
|
@@ -7119,6 +7282,7 @@ var toRuntimeParityDetail = (entry) => ({
|
|
|
7119
7282
|
direction: entry.direction,
|
|
7120
7283
|
qty: entry.qty ?? null,
|
|
7121
7284
|
timestamp: entry.timestamp,
|
|
7285
|
+
...entry.signalTimestamp != null ? { signalTimestamp: entry.signalTimestamp } : {},
|
|
7122
7286
|
price: entry.price,
|
|
7123
7287
|
exitType: entry.exitType ?? null,
|
|
7124
7288
|
exitTimestamp: entry.exitTimestamp ?? null,
|
|
@@ -7192,7 +7356,8 @@ var findNearestEvaluation = ({
|
|
|
7192
7356
|
evaluations,
|
|
7193
7357
|
toleranceMs,
|
|
7194
7358
|
timestampOffsetMs,
|
|
7195
|
-
entryTimestampOffsetMs = 0
|
|
7359
|
+
entryTimestampOffsetMs = 0,
|
|
7360
|
+
matchDirection = false
|
|
7196
7361
|
}) => {
|
|
7197
7362
|
const entryStrategy2 = getEntryStrategy(entry);
|
|
7198
7363
|
if (!entryStrategy2) {
|
|
@@ -7202,7 +7367,7 @@ var findNearestEvaluation = ({
|
|
|
7202
7367
|
let bestDiff = Number.POSITIVE_INFINITY;
|
|
7203
7368
|
const entryTimestamp = entry.timestamp + entryTimestampOffsetMs;
|
|
7204
7369
|
for (const evaluation of evaluations) {
|
|
7205
|
-
if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol) {
|
|
7370
|
+
if (evaluation.strategy !== entryStrategy2 || evaluation.symbol !== entry.symbol || matchDirection && evaluation.direction != null && evaluation.direction !== entry.direction) {
|
|
7206
7371
|
continue;
|
|
7207
7372
|
}
|
|
7208
7373
|
const diff = Math.abs(
|
|
@@ -7548,6 +7713,7 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7548
7713
|
replaySignals = [],
|
|
7549
7714
|
replaySignalEvaluations = [],
|
|
7550
7715
|
replayLineageScopes = [],
|
|
7716
|
+
replayDecisionTraces = [],
|
|
7551
7717
|
runtimeLineageScopes = [],
|
|
7552
7718
|
toleranceMs,
|
|
7553
7719
|
backtestTimestampOffsetMs,
|
|
@@ -7556,48 +7722,66 @@ var buildReplayMismatchDrilldown = ({
|
|
|
7556
7722
|
const nearestByEntry = new Map(
|
|
7557
7723
|
nearestCandidates.map((candidate) => [candidate.entry, candidate])
|
|
7558
7724
|
);
|
|
7725
|
+
const traceByCandle = new Map(
|
|
7726
|
+
replayDecisionTraces.map((trace) => [replayDecisionTraceKey(trace), trace])
|
|
7727
|
+
);
|
|
7559
7728
|
const artifactLimit = Math.min(limit, resolveReplayParityArtifactLimit());
|
|
7560
7729
|
let runtimeOnlyArtifactsIncluded = 0;
|
|
7561
7730
|
let backtestOnlyArtifactsIncluded = 0;
|
|
7562
7731
|
const runtimeOnlyDiagnostics = runtimeOnly.map((entry, index) => {
|
|
7563
|
-
const
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
7732
|
+
const isRuntimeTrade = entry.source === "runtime";
|
|
7733
|
+
const decisionEntry = isRuntimeTrade ? entry.signalTimestamp == null ? null : { ...entry, timestamp: entry.signalTimestamp } : entry;
|
|
7734
|
+
const decisionClock = {
|
|
7735
|
+
toleranceMs: isRuntimeTrade ? 0 : toleranceMs,
|
|
7736
|
+
timestampOffsetMs: isRuntimeTrade ? 0 : resolveTradeParityTimestampOffset(
|
|
7737
|
+
getEntryStrategy(entry) ?? "",
|
|
7738
|
+
backtestTimestampOffsetMs
|
|
7739
|
+
)
|
|
7740
|
+
};
|
|
7567
7741
|
const includeArtifacts = index < artifactLimit;
|
|
7568
7742
|
const nearestCandidate = nearestByEntry.get(entry);
|
|
7569
|
-
const nearestReplaySignal = findNearestSignal({
|
|
7570
|
-
entry,
|
|
7743
|
+
const nearestReplaySignal = decisionEntry && findNearestSignal({
|
|
7744
|
+
entry: decisionEntry,
|
|
7571
7745
|
signals: replaySignals,
|
|
7572
|
-
|
|
7573
|
-
timestampOffsetMs: entryOffsetMs
|
|
7746
|
+
...decisionClock
|
|
7574
7747
|
});
|
|
7575
|
-
const nearestReplayEvaluation = findNearestEvaluation({
|
|
7576
|
-
entry,
|
|
7748
|
+
const nearestReplayEvaluation = decisionEntry && findNearestEvaluation({
|
|
7749
|
+
entry: decisionEntry,
|
|
7577
7750
|
evaluations: replaySignalEvaluations,
|
|
7578
|
-
|
|
7579
|
-
|
|
7751
|
+
matchDirection: isRuntimeTrade,
|
|
7752
|
+
...decisionClock
|
|
7580
7753
|
});
|
|
7581
|
-
const nearestReplayEvaluationOutcome = findNearestReplayEvaluationOutcome({
|
|
7582
|
-
entry,
|
|
7754
|
+
const nearestReplayEvaluationOutcome = decisionEntry && findNearestReplayEvaluationOutcome({
|
|
7755
|
+
entry: decisionEntry,
|
|
7583
7756
|
lineageScopes: replayLineageScopes,
|
|
7584
|
-
|
|
7585
|
-
timestampOffsetMs: entryOffsetMs
|
|
7757
|
+
...decisionClock
|
|
7586
7758
|
});
|
|
7759
|
+
const strategy = getEntryStrategy(entry);
|
|
7760
|
+
const replayDecisionTrace = isRuntimeTrade && strategy && decisionEntry ? traceByCandle.get(
|
|
7761
|
+
replayDecisionTraceKey({
|
|
7762
|
+
strategy,
|
|
7763
|
+
symbol: entry.symbol,
|
|
7764
|
+
signalTimestamp: decisionEntry.timestamp
|
|
7765
|
+
})
|
|
7766
|
+
) : void 0;
|
|
7767
|
+
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
7768
|
const classification = nearestReplaySignal ? classifySignalDiagnostic(nearestReplaySignal.signal) : nearestReplayEvaluation ? classifyEvaluationDiagnostic(nearestReplayEvaluation.evaluation) : nearestReplayEvaluationOutcome ? {
|
|
7588
|
-
classification:
|
|
7769
|
+
classification: nearestReplayEvaluationOutcome.reason.startsWith(
|
|
7770
|
+
"BACKTEST_ENTRY_DELAY_"
|
|
7771
|
+
) ? "entry_delayed" : "core_skipped",
|
|
7589
7772
|
reason: nearestReplayEvaluationOutcome.reason
|
|
7590
|
-
} : nearestCandidate?.nearest ? {
|
|
7773
|
+
} : !isRuntimeTrade && nearestCandidate?.nearest ? {
|
|
7591
7774
|
classification: "timing_or_price_drift",
|
|
7592
7775
|
reason: nearestCandidate.reason
|
|
7593
7776
|
} : {
|
|
7594
7777
|
classification: "no_replay_candidate",
|
|
7595
|
-
reason:
|
|
7778
|
+
reason: unavailableReason
|
|
7596
7779
|
};
|
|
7597
7780
|
return {
|
|
7598
7781
|
entry,
|
|
7599
7782
|
...classification,
|
|
7600
7783
|
...nearestCandidate ? { nearestCandidate } : {},
|
|
7784
|
+
...replayDecisionTrace ? { replayDecisionTrace } : {},
|
|
7601
7785
|
...nearestReplaySignal ? {
|
|
7602
7786
|
replaySignal: toSignalDiagnostic({
|
|
7603
7787
|
signal: nearestReplaySignal.signal,
|
|
@@ -7723,6 +7907,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7723
7907
|
replaySignals,
|
|
7724
7908
|
replaySignalEvaluations,
|
|
7725
7909
|
replayLineageScopes,
|
|
7910
|
+
replayDecisionTraces,
|
|
7726
7911
|
runtimeLineageScopes
|
|
7727
7912
|
}) => {
|
|
7728
7913
|
const runtimeDetails = runtimeEntries.map(toRuntimeParityDetail);
|
|
@@ -7783,6 +7968,7 @@ var buildReplayRuntimeComparisonDetails = ({
|
|
|
7783
7968
|
replaySignals,
|
|
7784
7969
|
replaySignalEvaluations,
|
|
7785
7970
|
replayLineageScopes,
|
|
7971
|
+
replayDecisionTraces,
|
|
7786
7972
|
runtimeLineageScopes,
|
|
7787
7973
|
toleranceMs,
|
|
7788
7974
|
backtestTimestampOffsetMs,
|
|
@@ -8808,6 +8994,7 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8808
8994
|
replaySignals,
|
|
8809
8995
|
replayLineages,
|
|
8810
8996
|
replayLineageScopes,
|
|
8997
|
+
replayDecisionTraces,
|
|
8811
8998
|
runtimeEvidencePath
|
|
8812
8999
|
}) => {
|
|
8813
9000
|
const { connector, connectorName, window } = getRuntimeCompareContext();
|
|
@@ -8959,7 +9146,8 @@ var saveAndPrintReplayRuntimeComparison = async ({
|
|
|
8959
9146
|
replaySignals,
|
|
8960
9147
|
replaySignalEvaluations,
|
|
8961
9148
|
runtimeLineageScopes: comparable.runtimeLineageScopes,
|
|
8962
|
-
replayLineageScopes
|
|
9149
|
+
replayLineageScopes,
|
|
9150
|
+
replayDecisionTraces
|
|
8963
9151
|
});
|
|
8964
9152
|
const parityRows = summarizeTradeParityByStrategy({
|
|
8965
9153
|
runtimeEntries: runtimeDedupe.entries,
|
|
@@ -9886,6 +10074,7 @@ var finishReplay = async ({
|
|
|
9886
10074
|
replaySignals: replayResult.signals,
|
|
9887
10075
|
replayLineages: replayResult.runtimeLineages,
|
|
9888
10076
|
replayLineageScopes: replayResult.replayLineageScopes,
|
|
10077
|
+
replayDecisionTraces: replayResult.replayDecisionTraces,
|
|
9889
10078
|
runtimeEvidencePath: replayRuntimeEvidencePath
|
|
9890
10079
|
});
|
|
9891
10080
|
const finishedAt = /* @__PURE__ */ new Date();
|
|
@@ -10235,7 +10424,8 @@ var replayBacktest = async () => {
|
|
|
10235
10424
|
references: historicalReplayReferences,
|
|
10236
10425
|
showProgress: !replayFlags.portfolioReport,
|
|
10237
10426
|
collectSkipEvidence: !replayFlags.portfolioReport,
|
|
10238
|
-
collectSignals: !replayFlags.portfolioReport
|
|
10427
|
+
collectSignals: !replayFlags.portfolioReport,
|
|
10428
|
+
decisionTraceTargets: replayFlags.portfolioReport ? void 0 : evidenceMetadata?.decisionTraceTargets
|
|
10239
10429
|
});
|
|
10240
10430
|
return replayFlags.portfolioReport ? compactHistoricalReplayResultForPortfolio(result) : result;
|
|
10241
10431
|
};
|