@tradejs/cli 2.0.18 → 2.0.20
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 +14522 -15704
- package/dist/lib/aiExport.js +15 -1608
- package/dist/lib/aiPocketSearch.js +499 -67
- package/dist/lib/aiPocketSearchCli.js +113 -0
- package/dist/lib/aiTrainCharts.js +14 -1
- package/dist/lib/aiTrainMetrics.js +4 -1
- package/dist/lib/aiTrainResearch.js +88 -54
- package/dist/lib/binanceMarketContextBackfill.js +7 -6
- package/dist/lib/coinMarketCapContextBackfill.js +19 -18
- package/dist/lib/coreResearch.js +2738 -0
- package/dist/lib/derivativesContextBackfill.js +10 -9
- package/dist/lib/executionCalibration.js +6 -3
- package/dist/lib/hyperliquidWhaleBackfill.js +8 -8
- package/dist/lib/hyperliquidWhaleStream.js +4 -4
- package/dist/lib/marketContextPrepare.js +83 -1686
- package/dist/lib/runEnvironment.js +14 -1666
- package/dist/lib/runFormatting.js +1 -1607
- package/dist/lib/runtimeDebugEvidence.js +2 -48
- package/dist/lib/runtimeLineage.js +81 -48
- package/dist/lib/runtimeRedis.js +6 -57
- package/dist/lib/runtimeScorecard.js +153 -10
- package/dist/lib/runtimeStrategyBacktest.js +2 -48
- package/dist/lib/strategyGateFingerprint.js +80 -0
- package/dist/lib/strategyRelease.js +1365 -0
- package/dist/scripts/aiExport.js +17 -1610
- package/dist/scripts/aiPocketSearch.js +1070 -1770
- package/dist/scripts/aiTrain.js +108 -1688
- package/dist/scripts/backtest.js +120 -1760
- package/dist/scripts/binanceMarketIngest.js +5 -4
- package/dist/scripts/candlesMigrateProvider.js +6 -5186
- package/dist/scripts/cleanupMarketContext.js +4 -3
- package/dist/scripts/continuity.js +4 -3
- package/dist/scripts/coreResearch.js +2830 -0
- package/dist/scripts/derivativesIngest.js +6 -4
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +5 -4
- package/dist/scripts/doctor.js +2 -5184
- package/dist/scripts/executionCalibration.js +6 -3
- package/dist/scripts/hyperliquidWhaleBackfill.js +8 -8
- package/dist/scripts/hyperliquidWhaleIngest.js +4 -4
- package/dist/scripts/hyperliquidWhalesUpdate.js +1 -1
- package/dist/scripts/migration.js +3 -3
- package/dist/scripts/mlExport.js +1 -1607
- package/dist/scripts/replay.js +239 -1855
- package/dist/scripts/replayRunner.js +239 -1855
- package/dist/scripts/replayRuntimeEvidence.js +29 -62
- package/dist/scripts/researchAuto.js +3 -49
- package/dist/scripts/results.js +1 -1607
- package/dist/scripts/runtimeEvidence.js +18 -56
- package/dist/scripts/runtimeParity.js +121 -1746
- package/dist/scripts/runtimeScorecard.js +883 -29
- package/dist/scripts/signals.js +1178 -2724
- package/dist/scripts/signalsDaemon.js +1183 -2696
- package/dist/scripts/signalsSummary.js +4 -26
- package/dist/scripts/strategyRelease.js +1469 -0
- package/dist/workers/testerWorker.js +2 -2
- package/package.json +13 -9
|
@@ -0,0 +1,1365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/lib/strategyRelease.ts
|
|
31
|
+
var strategyRelease_exports = {};
|
|
32
|
+
__export(strategyRelease_exports, {
|
|
33
|
+
buildStrategyLiveDiagnosis: () => buildStrategyLiveDiagnosis,
|
|
34
|
+
buildStrategyLiveDiagnosisFromScorecard: () => buildStrategyLiveDiagnosisFromScorecard,
|
|
35
|
+
buildStrategyMonitoringProfile: () => buildStrategyMonitoringProfile,
|
|
36
|
+
canonicalStrategyReleaseJson: () => canonicalStrategyReleaseJson,
|
|
37
|
+
collectReleaseEvidenceReferences: () => collectReleaseEvidenceReferences,
|
|
38
|
+
createStrategyEvidenceMarkerEnvelope: () => import_strategyReleaseEvidence.createStrategyEvidenceMarkerEnvelope,
|
|
39
|
+
createStrategyReleaseEnvelope: () => createStrategyReleaseEnvelope,
|
|
40
|
+
createStrategyReleaseManifest: () => createStrategyReleaseManifest,
|
|
41
|
+
deriveStrategyReleaseResearchDecision: () => deriveStrategyReleaseResearchDecision,
|
|
42
|
+
planStrategyEvidenceRetention: () => planStrategyEvidenceRetention,
|
|
43
|
+
publishStrategyLiveDiagnosis: () => publishStrategyLiveDiagnosis,
|
|
44
|
+
publishStrategyRelease: () => publishStrategyRelease,
|
|
45
|
+
strategyReleaseSha256: () => strategyReleaseSha256,
|
|
46
|
+
verifyStrategyEvidenceMarkerEnvelope: () => verifyStrategyEvidenceMarkerEnvelope,
|
|
47
|
+
verifyStrategyReleaseEnvelope: () => verifyStrategyReleaseEnvelope
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(strategyRelease_exports);
|
|
50
|
+
var import_node_crypto = require("crypto");
|
|
51
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
52
|
+
var import_node_path = __toESM(require("path"));
|
|
53
|
+
var import_types = require("@tradejs/types");
|
|
54
|
+
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
55
|
+
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
56
|
+
var LINEAGE_FINGERPRINT_RE = /^[a-f0-9]{16}$/;
|
|
57
|
+
var DAY_MS = 864e5;
|
|
58
|
+
var REQUIRED_LONG_WINDOWS = [1095, 1460, 1825];
|
|
59
|
+
var REQUIRED_STABLE_TERMINAL_WINDOWS = [365, 180, 90];
|
|
60
|
+
var MIN_RECENT_DIRECTION_REPAIR_TRADES = 20;
|
|
61
|
+
var safeSegment = import_strategyReleaseEvidence.safeStrategyEvidenceSegment;
|
|
62
|
+
var compactTimestamp = import_strategyReleaseEvidence.compactStrategyEvidenceTimestamp;
|
|
63
|
+
var verifyFullPeriodChartArtifact = async (input) => {
|
|
64
|
+
const artifact = input.chartArtifact;
|
|
65
|
+
if (!artifact?.path || !SHA256_RE.test(artifact.sha256)) return false;
|
|
66
|
+
try {
|
|
67
|
+
const artifactPath = import_node_path.default.resolve(artifact.path);
|
|
68
|
+
if (await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(artifactPath) !== artifact.sha256) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const report = JSON.parse(await import_promises.default.readFile(artifactPath, "utf8"));
|
|
72
|
+
return Boolean(
|
|
73
|
+
report.chart?.persisted === true && report.chart.cardIds?.length && report.errors?.failed === 0 && report.run?.strategy === input.strategy && report.run.mode === "local-deterministic" && report.run.recent === 0 && report.run.since == null && report.run.until == null && Number(report.run.sourceRows) > 0
|
|
74
|
+
);
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var hasExactRuntimeTarget = (target) => Boolean(
|
|
80
|
+
target?.userName.trim() && target.deploymentId.trim() && target.accountId.trim() && target.strategyConfigName.trim()
|
|
81
|
+
);
|
|
82
|
+
var deriveStrategyReleaseResearchDecision = async (input) => {
|
|
83
|
+
const blockers = [];
|
|
84
|
+
const byDays = new Map(
|
|
85
|
+
input.historicalWindows.map((entry) => [entry.days, entry])
|
|
86
|
+
);
|
|
87
|
+
if (REQUIRED_LONG_WINDOWS.some((days) => !byDays.has(days))) {
|
|
88
|
+
blockers.push("HISTORICAL_MATRIX_INCOMPLETE");
|
|
89
|
+
}
|
|
90
|
+
if (REQUIRED_STABLE_TERMINAL_WINDOWS.some((days) => !byDays.has(days))) {
|
|
91
|
+
blockers.push("HISTORICAL_MATRIX_INCOMPLETE");
|
|
92
|
+
}
|
|
93
|
+
const longWindows = REQUIRED_LONG_WINDOWS.map(
|
|
94
|
+
(days) => byDays.get(days)
|
|
95
|
+
).filter((entry) => Boolean(entry));
|
|
96
|
+
if (longWindows.some(
|
|
97
|
+
(entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
|
|
98
|
+
)) {
|
|
99
|
+
blockers.push("HISTORICAL_EDGE_FAILED");
|
|
100
|
+
}
|
|
101
|
+
const stableTerminalWindows = REQUIRED_STABLE_TERMINAL_WINDOWS.map(
|
|
102
|
+
(days) => byDays.get(days)
|
|
103
|
+
).filter((entry) => Boolean(entry));
|
|
104
|
+
if (stableTerminalWindows.some(
|
|
105
|
+
(entry) => entry.pnl <= 0 || entry.profitFactor < 1 || entry.long.pnl <= 0 || entry.long.profitFactor < 1 || entry.short.pnl <= 0 || entry.short.profitFactor < 1
|
|
106
|
+
)) {
|
|
107
|
+
blockers.push("HISTORICAL_EDGE_FAILED");
|
|
108
|
+
}
|
|
109
|
+
if (blockers.length) {
|
|
110
|
+
return {
|
|
111
|
+
strategy: input.strategy,
|
|
112
|
+
action: "STOP_RESEARCH",
|
|
113
|
+
repairAllowed: false,
|
|
114
|
+
targetDirection: input.recentFailure?.direction ?? null,
|
|
115
|
+
maxLossValue: null,
|
|
116
|
+
blockers,
|
|
117
|
+
summary: "The final composition does not have complete positive ALL/LONG/SHORT evidence on the frozen 3y/4y/max-window matrix."
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const repairAllowed = Boolean(
|
|
121
|
+
input.recentFailure && input.recentFailure.closedTrades >= MIN_RECENT_DIRECTION_REPAIR_TRADES && input.recentFailure.causalMechanismIdentified && input.recentFailure.repairRoundsUsed === 0 && !input.exposedEvaluation
|
|
122
|
+
);
|
|
123
|
+
if (repairAllowed) {
|
|
124
|
+
return {
|
|
125
|
+
strategy: input.strategy,
|
|
126
|
+
action: "REPAIR_RECENT_DIRECTION",
|
|
127
|
+
repairAllowed: true,
|
|
128
|
+
targetDirection: input.recentFailure.direction,
|
|
129
|
+
maxLossValue: null,
|
|
130
|
+
blockers: [],
|
|
131
|
+
summary: "Spend the single terminal-direction repair round on the preregistered causal mechanism, then rebuild the full evidence matrix."
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (!input.candidateImplemented) blockers.push("CANDIDATE_NOT_IMPLEMENTED");
|
|
135
|
+
if (!await verifyFullPeriodChartArtifact(input)) {
|
|
136
|
+
blockers.push("FULL_PERIOD_CHART_MISSING");
|
|
137
|
+
}
|
|
138
|
+
if (input.forwardTest.maxLossValue !== 1) {
|
|
139
|
+
blockers.push("FORWARD_RISK_MUST_BE_ONE");
|
|
140
|
+
}
|
|
141
|
+
if (blockers.length) {
|
|
142
|
+
return {
|
|
143
|
+
strategy: input.strategy,
|
|
144
|
+
action: "FORWARD_BLOCKED",
|
|
145
|
+
repairAllowed: false,
|
|
146
|
+
targetDirection: input.recentFailure?.direction ?? null,
|
|
147
|
+
maxLossValue: null,
|
|
148
|
+
blockers,
|
|
149
|
+
summary: "Complete the frozen candidate implementation and full-period chart before any micro-forward execution."
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (!input.forwardTest.authorized) {
|
|
153
|
+
return {
|
|
154
|
+
strategy: input.strategy,
|
|
155
|
+
action: "MICRO_FORWARD_READY",
|
|
156
|
+
repairAllowed: false,
|
|
157
|
+
targetDirection: input.recentFailure?.direction ?? null,
|
|
158
|
+
maxLossValue: 1,
|
|
159
|
+
blockers: ["FORWARD_NOT_AUTHORIZED"],
|
|
160
|
+
summary: "The candidate is ready for a separately authorized micro-forward test at MAX_LOSS_VALUE=1."
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (!hasExactRuntimeTarget(input.forwardTest.runtimeTarget)) {
|
|
164
|
+
return {
|
|
165
|
+
strategy: input.strategy,
|
|
166
|
+
action: "FORWARD_BLOCKED",
|
|
167
|
+
repairAllowed: false,
|
|
168
|
+
targetDirection: input.recentFailure?.direction ?? null,
|
|
169
|
+
maxLossValue: 1,
|
|
170
|
+
blockers: ["RUNTIME_TARGET_UNRESOLVED"],
|
|
171
|
+
summary: "Forward testing is authorized, but an exact runtime deployment/account target must be resolved first."
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
strategy: input.strategy,
|
|
176
|
+
action: "START_MICRO_FORWARD",
|
|
177
|
+
repairAllowed: false,
|
|
178
|
+
targetDirection: input.recentFailure?.direction ?? null,
|
|
179
|
+
maxLossValue: 1,
|
|
180
|
+
blockers: [],
|
|
181
|
+
summary: "Start the exact frozen composition as a micro-forward test at MAX_LOSS_VALUE=1; do not retune an underpowered or exposed recent tail."
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
var canonicalStrategyReleaseJson = (value) => (0, import_strategyReleaseEvidence.canonicalStrategyEvidenceJson)(value);
|
|
185
|
+
var strategyReleaseSha256 = (value) => (0, import_strategyReleaseEvidence.strategyEvidenceSha256)(value);
|
|
186
|
+
function assert(condition, message) {
|
|
187
|
+
if (!condition) throw new Error(`Invalid strategy release: ${message}`);
|
|
188
|
+
}
|
|
189
|
+
var assertFingerprint = (value, label) => assert(SHA256_RE.test(value), `${label} must be a lowercase SHA-256`);
|
|
190
|
+
var assertLineageFingerprint = (value, label) => assert(
|
|
191
|
+
LINEAGE_FINGERPRINT_RE.test(value),
|
|
192
|
+
`${label} must be a 16-character lowercase lineage fingerprint`
|
|
193
|
+
);
|
|
194
|
+
var releaseReasons = (input) => {
|
|
195
|
+
const reasons = [];
|
|
196
|
+
const requiredEvidenceKinds = [
|
|
197
|
+
"core_research",
|
|
198
|
+
"ai_gate",
|
|
199
|
+
"runtime_parity",
|
|
200
|
+
"execution_calibration"
|
|
201
|
+
];
|
|
202
|
+
if (input.evidence.some((entry) => !entry.verified) || requiredEvidenceKinds.some(
|
|
203
|
+
(kind) => !input.evidence.some((entry) => entry.kind === kind)
|
|
204
|
+
)) {
|
|
205
|
+
reasons.push("EVIDENCE_INCOMPLETE");
|
|
206
|
+
}
|
|
207
|
+
if (!input.gates.coreEdgeVerified) reasons.push("NO_VERIFIED_CORE_EDGE");
|
|
208
|
+
if (!input.gates.aiGateAddsValue) reasons.push("AI_GATE_ADDS_NO_VALUE");
|
|
209
|
+
if (!input.gates.currentMarketSuitable) {
|
|
210
|
+
reasons.push("CURRENT_REGIME_UNSUITABLE");
|
|
211
|
+
}
|
|
212
|
+
if (!input.gates.runtimeParityVerified) {
|
|
213
|
+
reasons.push("RUNTIME_PARITY_BLOCKED");
|
|
214
|
+
}
|
|
215
|
+
if (!input.gates.executionModelVerified) {
|
|
216
|
+
reasons.push("EXECUTION_MODEL_UNSAFE");
|
|
217
|
+
}
|
|
218
|
+
if (input.researchBudget.hypothesisFamilies >= 3 && !input.gates.coreEdgeVerified) {
|
|
219
|
+
reasons.push("RESEARCH_BUDGET_EXHAUSTED");
|
|
220
|
+
}
|
|
221
|
+
return [...new Set(reasons)];
|
|
222
|
+
};
|
|
223
|
+
var deriveReleaseGates = (evidence) => {
|
|
224
|
+
const asserted = (name) => {
|
|
225
|
+
const values = evidence.filter((entry) => entry.verified).map((entry) => entry.releaseAssertions?.[name]).filter((value) => typeof value === "boolean");
|
|
226
|
+
return values.length > 0 && values.every(Boolean);
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
coreEdgeVerified: asserted("coreEdgeVerified"),
|
|
230
|
+
aiGateAddsValue: asserted("aiGateAddsValue"),
|
|
231
|
+
currentMarketSuitable: asserted("currentMarketSuitable"),
|
|
232
|
+
runtimeParityVerified: asserted("runtimeParityVerified"),
|
|
233
|
+
executionModelVerified: asserted("executionModelVerified")
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
var verifyEvidenceComposition = (strategy, composition, evidence) => {
|
|
237
|
+
const requireEqual = (actual, expected, label, artifactId) => assert(
|
|
238
|
+
actual === expected,
|
|
239
|
+
`evidence ${artifactId} ${label} does not match the frozen composition`
|
|
240
|
+
);
|
|
241
|
+
for (const reference of evidence) {
|
|
242
|
+
const lineage = reference.lineage;
|
|
243
|
+
if (reference.kind === "core_research") {
|
|
244
|
+
requireEqual(
|
|
245
|
+
lineage?.strategy,
|
|
246
|
+
strategy,
|
|
247
|
+
"strategy",
|
|
248
|
+
reference.artifactId
|
|
249
|
+
);
|
|
250
|
+
requireEqual(
|
|
251
|
+
lineage?.coreConfigSha256,
|
|
252
|
+
composition.coreConfigSha256,
|
|
253
|
+
"coreConfigSha256",
|
|
254
|
+
reference.artifactId
|
|
255
|
+
);
|
|
256
|
+
requireEqual(
|
|
257
|
+
lineage?.gitSha,
|
|
258
|
+
composition.gitSha,
|
|
259
|
+
"gitSha",
|
|
260
|
+
reference.artifactId
|
|
261
|
+
);
|
|
262
|
+
requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
|
|
263
|
+
requireEqual(
|
|
264
|
+
lineage?.maxLossValue,
|
|
265
|
+
composition.maxLossValue,
|
|
266
|
+
"MAX_LOSS_VALUE",
|
|
267
|
+
reference.artifactId
|
|
268
|
+
);
|
|
269
|
+
assert(
|
|
270
|
+
lineage?.sourceSha256s.includes(composition.coreExportSha256),
|
|
271
|
+
`evidence ${reference.artifactId} does not include the frozen core export`
|
|
272
|
+
);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (reference.kind === "ai_gate") {
|
|
276
|
+
requireEqual(
|
|
277
|
+
lineage?.strategy,
|
|
278
|
+
strategy,
|
|
279
|
+
"strategy",
|
|
280
|
+
reference.artifactId
|
|
281
|
+
);
|
|
282
|
+
requireEqual(
|
|
283
|
+
lineage?.gitSha,
|
|
284
|
+
composition.gitSha,
|
|
285
|
+
"gitSha",
|
|
286
|
+
reference.artifactId
|
|
287
|
+
);
|
|
288
|
+
requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
|
|
289
|
+
requireEqual(
|
|
290
|
+
lineage?.gateConfigIdsFingerprint,
|
|
291
|
+
composition.gateConfigIdsFingerprint,
|
|
292
|
+
"gateConfigIdsFingerprint",
|
|
293
|
+
reference.artifactId
|
|
294
|
+
);
|
|
295
|
+
requireEqual(
|
|
296
|
+
lineage?.gateFingerprint,
|
|
297
|
+
composition.gateFingerprint,
|
|
298
|
+
"gateFingerprint",
|
|
299
|
+
reference.artifactId
|
|
300
|
+
);
|
|
301
|
+
requireEqual(
|
|
302
|
+
lineage?.gateContextFingerprint,
|
|
303
|
+
composition.gateContextFingerprint,
|
|
304
|
+
"gateContextFingerprint",
|
|
305
|
+
reference.artifactId
|
|
306
|
+
);
|
|
307
|
+
assert(
|
|
308
|
+
lineage?.sourceSha256s.includes(composition.coreExportSha256),
|
|
309
|
+
`evidence ${reference.artifactId} does not use the frozen core export`
|
|
310
|
+
);
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (reference.kind === "runtime_parity") {
|
|
314
|
+
requireEqual(
|
|
315
|
+
lineage?.strategy,
|
|
316
|
+
strategy,
|
|
317
|
+
"strategy",
|
|
318
|
+
reference.artifactId
|
|
319
|
+
);
|
|
320
|
+
requireEqual(
|
|
321
|
+
lineage?.gitSha,
|
|
322
|
+
composition.gitSha,
|
|
323
|
+
"gitSha",
|
|
324
|
+
reference.artifactId
|
|
325
|
+
);
|
|
326
|
+
requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
|
|
327
|
+
requireEqual(
|
|
328
|
+
lineage?.gateFingerprint,
|
|
329
|
+
composition.gateFingerprint,
|
|
330
|
+
"gateFingerprint",
|
|
331
|
+
reference.artifactId
|
|
332
|
+
);
|
|
333
|
+
requireEqual(
|
|
334
|
+
lineage?.runtimeConfigFingerprint,
|
|
335
|
+
composition.runtimeConfigFingerprint,
|
|
336
|
+
"runtimeConfigFingerprint",
|
|
337
|
+
reference.artifactId
|
|
338
|
+
);
|
|
339
|
+
requireEqual(
|
|
340
|
+
lineage?.runtimeContextFingerprint,
|
|
341
|
+
composition.runtimeContextFingerprint,
|
|
342
|
+
"runtimeContextFingerprint",
|
|
343
|
+
reference.artifactId
|
|
344
|
+
);
|
|
345
|
+
requireEqual(
|
|
346
|
+
lineage?.maxLossValue,
|
|
347
|
+
composition.maxLossValue,
|
|
348
|
+
"MAX_LOSS_VALUE",
|
|
349
|
+
reference.artifactId
|
|
350
|
+
);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (reference.kind === "execution_calibration") {
|
|
354
|
+
requireEqual(
|
|
355
|
+
lineage?.strategy,
|
|
356
|
+
strategy,
|
|
357
|
+
"strategy",
|
|
358
|
+
reference.artifactId
|
|
359
|
+
);
|
|
360
|
+
requireEqual(
|
|
361
|
+
lineage?.gitSha,
|
|
362
|
+
composition.gitSha,
|
|
363
|
+
"gitSha",
|
|
364
|
+
reference.artifactId
|
|
365
|
+
);
|
|
366
|
+
requireEqual(lineage?.gitDirty, false, "gitDirty", reference.artifactId);
|
|
367
|
+
requireEqual(
|
|
368
|
+
lineage?.gateFingerprint,
|
|
369
|
+
composition.gateFingerprint,
|
|
370
|
+
"gateFingerprint",
|
|
371
|
+
reference.artifactId
|
|
372
|
+
);
|
|
373
|
+
requireEqual(
|
|
374
|
+
lineage?.runtimeConfigFingerprint,
|
|
375
|
+
composition.runtimeConfigFingerprint,
|
|
376
|
+
"runtimeConfigFingerprint",
|
|
377
|
+
reference.artifactId
|
|
378
|
+
);
|
|
379
|
+
requireEqual(
|
|
380
|
+
lineage?.runtimeContextFingerprint,
|
|
381
|
+
composition.runtimeContextFingerprint,
|
|
382
|
+
"runtimeContextFingerprint",
|
|
383
|
+
reference.artifactId
|
|
384
|
+
);
|
|
385
|
+
requireEqual(
|
|
386
|
+
lineage?.maxLossValue,
|
|
387
|
+
composition.maxLossValue,
|
|
388
|
+
"MAX_LOSS_VALUE",
|
|
389
|
+
reference.artifactId
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
var createStrategyReleaseManifest = (input) => {
|
|
395
|
+
assert(input.strategy.trim(), "strategy is required");
|
|
396
|
+
assert(
|
|
397
|
+
Number.isFinite(input.createdAt),
|
|
398
|
+
"createdAt must be a finite timestamp"
|
|
399
|
+
);
|
|
400
|
+
assert(
|
|
401
|
+
input.researchBudget.hypothesisFamilies <= 3,
|
|
402
|
+
"at most 3 hypothesis families are allowed"
|
|
403
|
+
);
|
|
404
|
+
assert(
|
|
405
|
+
input.researchBudget.maximumVariantsPerFamily <= 5,
|
|
406
|
+
"at most 5 variants per family are allowed"
|
|
407
|
+
);
|
|
408
|
+
assert(
|
|
409
|
+
input.researchBudget.isolatedLongFinalists <= 1,
|
|
410
|
+
"at most one isolated-long finalist is allowed"
|
|
411
|
+
);
|
|
412
|
+
assert(
|
|
413
|
+
input.researchBudget.aiGateTuningRounds <= 1,
|
|
414
|
+
"at most one AI-gate tuning round is allowed"
|
|
415
|
+
);
|
|
416
|
+
assert(input.marketWindow.cacheOnly, "market window must be cache-only");
|
|
417
|
+
assert(
|
|
418
|
+
input.marketWindow.endTime > input.marketWindow.startTime,
|
|
419
|
+
"market window is invalid"
|
|
420
|
+
);
|
|
421
|
+
assert(input.composition.maxLossValue > 0, "MAX_LOSS_VALUE must be positive");
|
|
422
|
+
assert(
|
|
423
|
+
input.composition.longEnabled && input.composition.shortEnabled,
|
|
424
|
+
"LONG and SHORT must both remain enabled"
|
|
425
|
+
);
|
|
426
|
+
assert(
|
|
427
|
+
Number.isInteger(input.monitoring.minimumProspectiveClosedTrades) && input.monitoring.minimumProspectiveClosedTrades > 0,
|
|
428
|
+
"monitoring.minimumProspectiveClosedTrades must be positive"
|
|
429
|
+
);
|
|
430
|
+
assert(
|
|
431
|
+
Number.isFinite(input.monitoring.minimumParityRatio) && input.monitoring.minimumParityRatio >= 0 && input.monitoring.minimumParityRatio <= 1,
|
|
432
|
+
"monitoring.minimumParityRatio must be between 0 and 1"
|
|
433
|
+
);
|
|
434
|
+
assert(
|
|
435
|
+
Number.isFinite(input.monitoring.maximumOrderFailureRate) && input.monitoring.maximumOrderFailureRate >= 0 && input.monitoring.maximumOrderFailureRate <= 1,
|
|
436
|
+
"monitoring.maximumOrderFailureRate must be between 0 and 1"
|
|
437
|
+
);
|
|
438
|
+
assert(
|
|
439
|
+
Number.isFinite(input.monitoring.minimumRegimeCoverage) && input.monitoring.minimumRegimeCoverage >= 0 && input.monitoring.minimumRegimeCoverage <= 1,
|
|
440
|
+
"monitoring.minimumRegimeCoverage must be between 0 and 1"
|
|
441
|
+
);
|
|
442
|
+
assert(
|
|
443
|
+
input.monitoring.drawdownEnvelopes.length > 0 && input.monitoring.drawdownEnvelopes.every(
|
|
444
|
+
(entry) => Number.isInteger(entry.days) && entry.days > 0 && Number.isFinite(entry.p95) && Number.isFinite(entry.maximum) && entry.p95 >= 0 && entry.maximum >= entry.p95
|
|
445
|
+
),
|
|
446
|
+
"monitoring drawdown envelopes are invalid"
|
|
447
|
+
);
|
|
448
|
+
assertFingerprint(input.composition.coreConfigSha256, "coreConfigSha256");
|
|
449
|
+
assertFingerprint(input.composition.coreExportSha256, "coreExportSha256");
|
|
450
|
+
assertLineageFingerprint(
|
|
451
|
+
input.composition.gateConfigIdsFingerprint,
|
|
452
|
+
"gateConfigIdsFingerprint"
|
|
453
|
+
);
|
|
454
|
+
assertLineageFingerprint(
|
|
455
|
+
input.composition.runtimeConfigFingerprint,
|
|
456
|
+
"runtimeConfigFingerprint"
|
|
457
|
+
);
|
|
458
|
+
assertLineageFingerprint(
|
|
459
|
+
input.composition.gateFingerprint,
|
|
460
|
+
"gateFingerprint"
|
|
461
|
+
);
|
|
462
|
+
assertLineageFingerprint(
|
|
463
|
+
input.composition.gateContextFingerprint,
|
|
464
|
+
"gateContextFingerprint"
|
|
465
|
+
);
|
|
466
|
+
assertLineageFingerprint(
|
|
467
|
+
input.composition.runtimeContextFingerprint,
|
|
468
|
+
"runtimeContextFingerprint"
|
|
469
|
+
);
|
|
470
|
+
assertFingerprint(input.marketWindow.universeSha256, "universeSha256");
|
|
471
|
+
for (const entry of input.evidence) {
|
|
472
|
+
assertFingerprint(entry.sha256, `evidence ${entry.artifactId}`);
|
|
473
|
+
}
|
|
474
|
+
verifyEvidenceComposition(input.strategy, input.composition, input.evidence);
|
|
475
|
+
const evidenceGates = deriveReleaseGates(input.evidence);
|
|
476
|
+
assert(
|
|
477
|
+
canonicalStrategyReleaseJson(input.gates) === canonicalStrategyReleaseJson(evidenceGates),
|
|
478
|
+
"release gates must be derived from verified evidence assertions"
|
|
479
|
+
);
|
|
480
|
+
const compositionIdentity = {
|
|
481
|
+
strategy: input.strategy,
|
|
482
|
+
gitSha: input.composition.gitSha,
|
|
483
|
+
coreConfigSha256: input.composition.coreConfigSha256,
|
|
484
|
+
coreExportSha256: input.composition.coreExportSha256,
|
|
485
|
+
gateConfigIdsFingerprint: input.composition.gateConfigIdsFingerprint,
|
|
486
|
+
runtimeConfigFingerprint: input.composition.runtimeConfigFingerprint,
|
|
487
|
+
gateFingerprint: input.composition.gateFingerprint,
|
|
488
|
+
gateContextFingerprint: input.composition.gateContextFingerprint,
|
|
489
|
+
runtimeContextFingerprint: input.composition.runtimeContextFingerprint,
|
|
490
|
+
maxLossValue: input.composition.maxLossValue
|
|
491
|
+
};
|
|
492
|
+
const compositionId = `${safeSegment(input.strategy, "strategy")}_${strategyReleaseSha256(compositionIdentity).slice(0, 16)}`;
|
|
493
|
+
const reasons = releaseReasons({ ...input, gates: evidenceGates });
|
|
494
|
+
const economicFailure = reasons.some(
|
|
495
|
+
(reason) => [
|
|
496
|
+
"NO_VERIFIED_CORE_EDGE",
|
|
497
|
+
"AI_GATE_ADDS_NO_VALUE",
|
|
498
|
+
"CURRENT_REGIME_UNSUITABLE",
|
|
499
|
+
"RESEARCH_BUDGET_EXHAUSTED"
|
|
500
|
+
].includes(reason)
|
|
501
|
+
);
|
|
502
|
+
const evidenceFailure = reasons.some(
|
|
503
|
+
(reason) => [
|
|
504
|
+
"EVIDENCE_INCOMPLETE",
|
|
505
|
+
"RUNTIME_PARITY_BLOCKED",
|
|
506
|
+
"EXECUTION_MODEL_UNSAFE"
|
|
507
|
+
].includes(reason)
|
|
508
|
+
);
|
|
509
|
+
const verdict = evidenceFailure ? "INSUFFICIENT_EVIDENCE" : economicFailure ? "UNSUITABLE_FOR_CURRENT_MARKET" : "READY_FOR_RUNTIME";
|
|
510
|
+
const releaseIdentity = {
|
|
511
|
+
strategy: input.strategy,
|
|
512
|
+
createdAt: input.createdAt,
|
|
513
|
+
compositionId,
|
|
514
|
+
evidence: input.evidence.map(({ kind, artifactId, sha256 }) => ({
|
|
515
|
+
kind,
|
|
516
|
+
artifactId,
|
|
517
|
+
sha256
|
|
518
|
+
})),
|
|
519
|
+
verdict
|
|
520
|
+
};
|
|
521
|
+
return {
|
|
522
|
+
schema: import_types.STRATEGY_RELEASE_SCHEMA,
|
|
523
|
+
releaseId: `${safeSegment(input.strategy, "strategy")}_${compactTimestamp(input.createdAt)}_${strategyReleaseSha256(releaseIdentity).slice(0, 16)}`,
|
|
524
|
+
strategy: input.strategy,
|
|
525
|
+
createdAt: input.createdAt,
|
|
526
|
+
composition: { ...input.composition, compositionId },
|
|
527
|
+
marketWindow: input.marketWindow,
|
|
528
|
+
researchBudget: input.researchBudget,
|
|
529
|
+
evidence: input.evidence,
|
|
530
|
+
gates: evidenceGates,
|
|
531
|
+
monitoring: input.monitoring,
|
|
532
|
+
verdict: {
|
|
533
|
+
status: verdict,
|
|
534
|
+
reasons,
|
|
535
|
+
summary: input.summary
|
|
536
|
+
},
|
|
537
|
+
prospective: input.prospective
|
|
538
|
+
};
|
|
539
|
+
};
|
|
540
|
+
var createStrategyReleaseEnvelope = (manifest) => ({
|
|
541
|
+
schema: "tradejs-strategy-release-envelope/v1",
|
|
542
|
+
releaseId: manifest.releaseId,
|
|
543
|
+
manifestSha256: strategyReleaseSha256(manifest),
|
|
544
|
+
manifest
|
|
545
|
+
});
|
|
546
|
+
var verifyStrategyReleaseEnvelope = async (valueOrPath) => {
|
|
547
|
+
const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises.default.readFile(valueOrPath, "utf8")) : valueOrPath;
|
|
548
|
+
const envelope = value;
|
|
549
|
+
assert(
|
|
550
|
+
envelope?.schema === "tradejs-strategy-release-envelope/v1",
|
|
551
|
+
"release envelope schema mismatch"
|
|
552
|
+
);
|
|
553
|
+
assert(
|
|
554
|
+
envelope.releaseId === envelope.manifest?.releaseId,
|
|
555
|
+
"releaseId mismatch"
|
|
556
|
+
);
|
|
557
|
+
assert(
|
|
558
|
+
strategyReleaseSha256(envelope.manifest) === envelope.manifestSha256,
|
|
559
|
+
"release manifest checksum mismatch"
|
|
560
|
+
);
|
|
561
|
+
const manifest = envelope.manifest;
|
|
562
|
+
assert(
|
|
563
|
+
manifest.schema === import_types.STRATEGY_RELEASE_SCHEMA,
|
|
564
|
+
"manifest schema mismatch"
|
|
565
|
+
);
|
|
566
|
+
const rebuilt = createStrategyReleaseManifest({
|
|
567
|
+
strategy: manifest.strategy,
|
|
568
|
+
createdAt: manifest.createdAt,
|
|
569
|
+
composition: {
|
|
570
|
+
gitSha: manifest.composition.gitSha,
|
|
571
|
+
coreConfigSha256: manifest.composition.coreConfigSha256,
|
|
572
|
+
coreExportSha256: manifest.composition.coreExportSha256,
|
|
573
|
+
gateConfigIdsFingerprint: manifest.composition.gateConfigIdsFingerprint,
|
|
574
|
+
runtimeConfigFingerprint: manifest.composition.runtimeConfigFingerprint,
|
|
575
|
+
gateFingerprint: manifest.composition.gateFingerprint,
|
|
576
|
+
gateContextFingerprint: manifest.composition.gateContextFingerprint,
|
|
577
|
+
runtimeContextFingerprint: manifest.composition.runtimeContextFingerprint,
|
|
578
|
+
maxLossValue: manifest.composition.maxLossValue,
|
|
579
|
+
longEnabled: manifest.composition.longEnabled,
|
|
580
|
+
shortEnabled: manifest.composition.shortEnabled
|
|
581
|
+
},
|
|
582
|
+
marketWindow: manifest.marketWindow,
|
|
583
|
+
researchBudget: manifest.researchBudget,
|
|
584
|
+
evidence: manifest.evidence,
|
|
585
|
+
gates: manifest.gates,
|
|
586
|
+
monitoring: manifest.monitoring,
|
|
587
|
+
summary: manifest.verdict.summary,
|
|
588
|
+
prospective: manifest.prospective
|
|
589
|
+
});
|
|
590
|
+
assert(
|
|
591
|
+
canonicalStrategyReleaseJson(rebuilt) === canonicalStrategyReleaseJson(manifest),
|
|
592
|
+
"release manifest contains self-declared or inconsistent derived fields"
|
|
593
|
+
);
|
|
594
|
+
return envelope;
|
|
595
|
+
};
|
|
596
|
+
var verifyStrategyEvidenceMarkerEnvelope = async (valueOrPath) => {
|
|
597
|
+
const value = typeof valueOrPath === "string" ? JSON.parse(await import_promises.default.readFile(valueOrPath, "utf8")) : valueOrPath;
|
|
598
|
+
return (0, import_strategyReleaseEvidence.verifyStrategyEvidenceMarkerEnvelope)(value);
|
|
599
|
+
};
|
|
600
|
+
var releaseMarkers = (manifest) => {
|
|
601
|
+
const common = {
|
|
602
|
+
timestamp: manifest.createdAt,
|
|
603
|
+
artifactId: manifest.releaseId,
|
|
604
|
+
artifactSha256: strategyReleaseSha256(manifest),
|
|
605
|
+
compositionId: manifest.composition.compositionId,
|
|
606
|
+
gitSha: manifest.composition.gitSha,
|
|
607
|
+
gateFingerprint: manifest.composition.gateFingerprint,
|
|
608
|
+
configFingerprint: manifest.composition.runtimeConfigFingerprint,
|
|
609
|
+
contextFingerprint: manifest.composition.runtimeContextFingerprint,
|
|
610
|
+
maxLossValue: manifest.composition.maxLossValue
|
|
611
|
+
};
|
|
612
|
+
return [
|
|
613
|
+
{
|
|
614
|
+
...common,
|
|
615
|
+
id: `${manifest.releaseId}:evidence`,
|
|
616
|
+
type: "E",
|
|
617
|
+
label: "Evidence frozen",
|
|
618
|
+
summary: `${manifest.evidence.length} verified release artifacts`,
|
|
619
|
+
coverage: {
|
|
620
|
+
startTime: manifest.marketWindow.startTime,
|
|
621
|
+
endTime: manifest.marketWindow.endTime
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
...common,
|
|
626
|
+
id: `${manifest.releaseId}:gate`,
|
|
627
|
+
type: "G",
|
|
628
|
+
label: "Composition frozen",
|
|
629
|
+
summary: manifest.composition.compositionId
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
...common,
|
|
633
|
+
id: `${manifest.releaseId}:loss`,
|
|
634
|
+
type: "L",
|
|
635
|
+
label: "Trade loss value",
|
|
636
|
+
summary: `MAX_LOSS_VALUE ${manifest.composition.maxLossValue}`,
|
|
637
|
+
maxLossValue: manifest.composition.maxLossValue
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
...common,
|
|
641
|
+
id: `${manifest.releaseId}:deployment`,
|
|
642
|
+
type: "D",
|
|
643
|
+
label: "Release manifest",
|
|
644
|
+
summary: manifest.verdict.status,
|
|
645
|
+
severity: manifest.verdict.status === "READY_FOR_RUNTIME" ? "info" : "blocking"
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
...common,
|
|
649
|
+
id: `${manifest.releaseId}:recommendation`,
|
|
650
|
+
type: "R",
|
|
651
|
+
label: "Release recommendation",
|
|
652
|
+
summary: manifest.verdict.summary,
|
|
653
|
+
severity: manifest.verdict.status === "READY_FOR_RUNTIME" ? "info" : "warning"
|
|
654
|
+
}
|
|
655
|
+
];
|
|
656
|
+
};
|
|
657
|
+
var writeJsonAtomic = async (filePath, value) => {
|
|
658
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
659
|
+
await import_promises.default.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
|
|
660
|
+
await import_promises.default.writeFile(
|
|
661
|
+
temporaryPath,
|
|
662
|
+
`${canonicalStrategyReleaseJson(value)}
|
|
663
|
+
`,
|
|
664
|
+
"utf8"
|
|
665
|
+
);
|
|
666
|
+
await import_promises.default.rename(temporaryPath, filePath);
|
|
667
|
+
};
|
|
668
|
+
var publishStrategyRelease = async ({
|
|
669
|
+
rootDir,
|
|
670
|
+
manifest
|
|
671
|
+
}) => {
|
|
672
|
+
const envelope = createStrategyReleaseEnvelope(manifest);
|
|
673
|
+
const markerEnvelope = (0, import_strategyReleaseEvidence.createStrategyEvidenceMarkerEnvelope)({
|
|
674
|
+
strategy: manifest.strategy,
|
|
675
|
+
createdAt: manifest.createdAt,
|
|
676
|
+
markers: releaseMarkers(manifest),
|
|
677
|
+
sourceArtifacts: manifest.evidence.map((entry) => ({
|
|
678
|
+
artifactId: entry.artifactId,
|
|
679
|
+
sha256: entry.sha256,
|
|
680
|
+
path: entry.path
|
|
681
|
+
}))
|
|
682
|
+
});
|
|
683
|
+
const releasePath = import_node_path.default.join(
|
|
684
|
+
rootDir,
|
|
685
|
+
"releases",
|
|
686
|
+
safeSegment(manifest.strategy, "strategy"),
|
|
687
|
+
`${manifest.releaseId}.json`
|
|
688
|
+
);
|
|
689
|
+
const markerPath = import_node_path.default.join(
|
|
690
|
+
rootDir,
|
|
691
|
+
"markers",
|
|
692
|
+
safeSegment(manifest.strategy, "strategy"),
|
|
693
|
+
`${markerEnvelope.artifactId}.json`
|
|
694
|
+
);
|
|
695
|
+
await Promise.all([
|
|
696
|
+
writeJsonAtomic(releasePath, envelope),
|
|
697
|
+
writeJsonAtomic(markerPath, markerEnvelope)
|
|
698
|
+
]);
|
|
699
|
+
await Promise.all([
|
|
700
|
+
verifyStrategyReleaseEnvelope(releasePath),
|
|
701
|
+
verifyStrategyEvidenceMarkerEnvelope(markerPath)
|
|
702
|
+
]);
|
|
703
|
+
return { releasePath, markerPath, envelope, markerEnvelope };
|
|
704
|
+
};
|
|
705
|
+
var buildStrategyLiveDiagnosis = (input) => {
|
|
706
|
+
const minimumClosedTrades = input.minimumClosedTrades ?? 20;
|
|
707
|
+
const minimumParityRatio = input.minimumParityRatio ?? 0.95;
|
|
708
|
+
const maximumOrderFailureRate = input.maximumOrderFailureRate ?? 0.05;
|
|
709
|
+
const minimumRegimeCoverage = input.minimumRegimeCoverage ?? 0.5;
|
|
710
|
+
const hasExplicitRiskScale = input.riskScaleComparable != null;
|
|
711
|
+
const comparisonObservedDrawdown = hasExplicitRiskScale ? input.riskScaleComparable ? input.normalizedObservedDrawdown ?? null : null : input.observedDrawdown;
|
|
712
|
+
const runtimeDivergence = !input.lineageComparable || input.parityRatio != null && input.parityRatio < minimumParityRatio || input.orderFailureRate != null && input.orderFailureRate > maximumOrderFailureRate;
|
|
713
|
+
const insufficient = input.closedTrades < minimumClosedTrades || input.parityRatio == null || comparisonObservedDrawdown == null || input.historicalDrawdownP95 == null;
|
|
714
|
+
const expected = !runtimeDivergence && !insufficient && comparisonObservedDrawdown <= input.historicalDrawdownP95;
|
|
715
|
+
const verdict = runtimeDivergence ? "RUNTIME_DIVERGENCE" : insufficient ? "INSUFFICIENT_EVIDENCE" : expected ? "EXPECTED_DRAWDOWN" : "GENERALIZATION_FAILURE";
|
|
716
|
+
const attributionIncomplete = input.rawCoreExpectancyDelta == null || input.aiGateAddedValue == null || input.regimeCoverage == null;
|
|
717
|
+
const attributedVerdict = verdict === "GENERALIZATION_FAILURE" && attributionIncomplete ? "INSUFFICIENT_EVIDENCE" : verdict;
|
|
718
|
+
const subtype = attributedVerdict !== "GENERALIZATION_FAILURE" ? null : input.rawCoreExpectancyDelta < 0 && input.aiGateAddedValue >= 0 ? "RAW_CORE_DECAY" : input.aiGateAddedValue < 0 ? "AI_GATE_FAILURE" : input.regimeCoverage < minimumRegimeCoverage ? "REGIME_SHIFT" : (input.overfitProbability ?? 0) >= 0.5 ? "SUSPECTED_HISTORICAL_OVERFIT" : "RAW_CORE_DECAY";
|
|
719
|
+
const explanation = attributedVerdict === "RUNTIME_DIVERGENCE" ? "Runtime cannot be compared economically until lineage, replay parity, and order execution agree with the frozen composition." : attributedVerdict === "EXPECTED_DRAWDOWN" ? "The observed drawdown is within the preregistered equal-length historical drawdown envelope." : attributedVerdict === "GENERALIZATION_FAILURE" ? `Runtime is comparable, but the observed drawdown exceeds the historical envelope (${subtype}).` : attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "The drawdown breaches its historical envelope, but matched shadow raw-core, deterministic-gate, or causal regime evidence is missing for attribution." : "There are not enough comparable closed trades and historical drawdown observations for attribution.";
|
|
720
|
+
const recommendations = attributedVerdict === "RUNTIME_DIVERGENCE" ? [
|
|
721
|
+
"Resolve lineage and replay parity mismatches before evaluating strategy economics.",
|
|
722
|
+
"Inspect order failures and execution residuals against the frozen execution model."
|
|
723
|
+
] : attributedVerdict === "EXPECTED_DRAWDOWN" ? [
|
|
724
|
+
"Keep the frozen composition unchanged and continue prospective evidence collection."
|
|
725
|
+
] : attributedVerdict === "GENERALIZATION_FAILURE" ? [
|
|
726
|
+
"Do not retune the live cohort; open a new immutable research lineage.",
|
|
727
|
+
"Compare raw-core decay, AI-gate added value, and causal regime coverage."
|
|
728
|
+
] : [
|
|
729
|
+
attributionIncomplete && verdict === "GENERALIZATION_FAILURE" ? "Collect matched shadow raw-core, deterministic-gate, and causal regime evidence without changing the composition." : "Collect more verified prospective outcomes without changing the composition."
|
|
730
|
+
];
|
|
731
|
+
return {
|
|
732
|
+
schema: import_types.STRATEGY_LIVE_DIAGNOSIS_SCHEMA,
|
|
733
|
+
strategy: input.strategy,
|
|
734
|
+
compositionId: input.compositionId,
|
|
735
|
+
createdAt: input.createdAt,
|
|
736
|
+
verdict: attributedVerdict,
|
|
737
|
+
subtype,
|
|
738
|
+
confidence: runtimeDivergence || input.closedTrades >= 50 ? "high" : input.closedTrades >= 20 ? "medium" : "low",
|
|
739
|
+
evidence: {
|
|
740
|
+
lineageComparable: input.lineageComparable,
|
|
741
|
+
riskScaleComparable: input.riskScaleComparable ?? false,
|
|
742
|
+
releaseMaxLossValue: input.releaseMaxLossValue ?? null,
|
|
743
|
+
runtimeMaxLossValue: input.runtimeMaxLossValue ?? null,
|
|
744
|
+
riskScaleRatio: input.riskScaleRatio ?? null,
|
|
745
|
+
parityRatio: input.parityRatio,
|
|
746
|
+
orderFailureRate: input.orderFailureRate,
|
|
747
|
+
observedDrawdown: input.observedDrawdown,
|
|
748
|
+
normalizedObservedDrawdown: input.normalizedObservedDrawdown ?? null,
|
|
749
|
+
historicalDrawdownP95: input.historicalDrawdownP95,
|
|
750
|
+
historicalDrawdownMaximum: input.historicalDrawdownMaximum,
|
|
751
|
+
closedTrades: input.closedTrades,
|
|
752
|
+
rawCoreExpectancyDelta: input.rawCoreExpectancyDelta,
|
|
753
|
+
aiGateAddedValue: input.aiGateAddedValue,
|
|
754
|
+
regimeCoverage: input.regimeCoverage,
|
|
755
|
+
overfitProbability: input.overfitProbability
|
|
756
|
+
},
|
|
757
|
+
explanation,
|
|
758
|
+
recommendations
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
var buildStrategyLiveDiagnosisFromScorecard = ({
|
|
762
|
+
manifest,
|
|
763
|
+
scorecard,
|
|
764
|
+
days
|
|
765
|
+
}) => {
|
|
766
|
+
const rolling = scorecard.rolling.find((entry) => entry.days === days);
|
|
767
|
+
const envelope = manifest.monitoring.drawdownEnvelopes.find(
|
|
768
|
+
(entry) => entry.days === days
|
|
769
|
+
);
|
|
770
|
+
const orderFailureRate = scorecard.funnel.orderAttempts ? scorecard.funnel.orderFailures / scorecard.funnel.orderAttempts : null;
|
|
771
|
+
const lineage = scorecard.lineage;
|
|
772
|
+
const lineageComparable = scorecard.parity.lineageReason == null && lineage?.complete === true && lineage.conflicts === false && lineage.compositionId === manifest.composition.compositionId && lineage.gitDirty === false && lineage.gitSha === manifest.composition.gitSha && lineage.configFingerprint === manifest.composition.runtimeConfigFingerprint && lineage.gateFingerprint === manifest.composition.gateFingerprint && lineage.contextFingerprint === manifest.composition.runtimeContextFingerprint;
|
|
773
|
+
const releaseMaxLossValue = manifest.composition.maxLossValue;
|
|
774
|
+
const runtimeMaxLossValue = lineage?.maxLossValue ?? null;
|
|
775
|
+
const riskScaleComparable = Number.isFinite(releaseMaxLossValue) && releaseMaxLossValue > 0 && runtimeMaxLossValue != null && Number.isFinite(runtimeMaxLossValue) && runtimeMaxLossValue > 0;
|
|
776
|
+
const riskScaleRatio = riskScaleComparable ? runtimeMaxLossValue / releaseMaxLossValue : null;
|
|
777
|
+
const normalizedObservedDrawdown = rolling != null && riskScaleRatio != null ? rolling.maxDrawdown / riskScaleRatio : null;
|
|
778
|
+
const normalizeRuntimeEconomics = (value) => value != null && riskScaleRatio != null ? value / riskScaleRatio : null;
|
|
779
|
+
return buildStrategyLiveDiagnosis({
|
|
780
|
+
strategy: manifest.strategy,
|
|
781
|
+
compositionId: manifest.composition.compositionId,
|
|
782
|
+
createdAt: scorecard.generatedAt,
|
|
783
|
+
lineageComparable,
|
|
784
|
+
riskScaleComparable,
|
|
785
|
+
releaseMaxLossValue,
|
|
786
|
+
runtimeMaxLossValue,
|
|
787
|
+
riskScaleRatio,
|
|
788
|
+
parityRatio: scorecard.parity.ratio,
|
|
789
|
+
orderFailureRate,
|
|
790
|
+
observedDrawdown: rolling?.maxDrawdown ?? null,
|
|
791
|
+
normalizedObservedDrawdown,
|
|
792
|
+
historicalDrawdownP95: envelope?.p95 ?? null,
|
|
793
|
+
historicalDrawdownMaximum: envelope?.maximum ?? null,
|
|
794
|
+
closedTrades: rolling?.closedTrades ?? 0,
|
|
795
|
+
rawCoreExpectancyDelta: scorecard.prospective?.rawCoreExpectancy != null && manifest.monitoring.rawCoreExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) - manifest.monitoring.rawCoreExpectancy : null,
|
|
796
|
+
aiGateAddedValue: scorecard.prospective?.rawCoreExpectancy != null && scorecard.prospective.aiGateExpectancy != null ? normalizeRuntimeEconomics(scorecard.prospective.aiGateExpectancy) - normalizeRuntimeEconomics(scorecard.prospective.rawCoreExpectancy) : null,
|
|
797
|
+
regimeCoverage: scorecard.prospective?.regimeCoverage ?? null,
|
|
798
|
+
overfitProbability: manifest.monitoring.overfitProbability,
|
|
799
|
+
minimumClosedTrades: manifest.monitoring.minimumProspectiveClosedTrades,
|
|
800
|
+
minimumParityRatio: manifest.monitoring.minimumParityRatio,
|
|
801
|
+
maximumOrderFailureRate: manifest.monitoring.maximumOrderFailureRate,
|
|
802
|
+
minimumRegimeCoverage: manifest.monitoring.minimumRegimeCoverage
|
|
803
|
+
});
|
|
804
|
+
};
|
|
805
|
+
var realizedDrawdown = (trades, startIndex = 0, endIndex = trades.length) => {
|
|
806
|
+
let equity = 0;
|
|
807
|
+
let peak = 0;
|
|
808
|
+
let maximum = 0;
|
|
809
|
+
for (let index = startIndex; index < endIndex; index += 1) {
|
|
810
|
+
const trade = trades[index];
|
|
811
|
+
equity += trade.netProfit;
|
|
812
|
+
peak = Math.max(peak, equity);
|
|
813
|
+
maximum = Math.max(maximum, peak - equity);
|
|
814
|
+
}
|
|
815
|
+
return maximum;
|
|
816
|
+
};
|
|
817
|
+
var lowerBoundExitTimestamp = (trades, timestamp) => {
|
|
818
|
+
let left = 0;
|
|
819
|
+
let right = trades.length;
|
|
820
|
+
while (left < right) {
|
|
821
|
+
const middle = Math.floor((left + right) / 2);
|
|
822
|
+
if (trades[middle].exitTimestamp < timestamp) left = middle + 1;
|
|
823
|
+
else right = middle;
|
|
824
|
+
}
|
|
825
|
+
return left;
|
|
826
|
+
};
|
|
827
|
+
var percentile = (values, probability) => {
|
|
828
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
829
|
+
const position = (sorted.length - 1) * probability;
|
|
830
|
+
const lower = Math.floor(position);
|
|
831
|
+
const upper = Math.ceil(position);
|
|
832
|
+
if (lower === upper) return sorted[lower];
|
|
833
|
+
return sorted[lower] * (upper - position) + sorted[upper] * (position - lower);
|
|
834
|
+
};
|
|
835
|
+
var buildStrategyMonitoringProfile = ({
|
|
836
|
+
trades,
|
|
837
|
+
startTime,
|
|
838
|
+
endTime,
|
|
839
|
+
days,
|
|
840
|
+
minimumProspectiveClosedTrades = 20,
|
|
841
|
+
minimumParityRatio = 0.95,
|
|
842
|
+
maximumOrderFailureRate = 0.05,
|
|
843
|
+
minimumRegimeCoverage = 0.5,
|
|
844
|
+
aiGateExpectancy = null,
|
|
845
|
+
overfitProbability = null
|
|
846
|
+
}) => {
|
|
847
|
+
assert(endTime > startTime, "monitoring profile window is invalid");
|
|
848
|
+
assert(days.length > 0, "monitoring profile days are required");
|
|
849
|
+
const ordered = trades.filter(
|
|
850
|
+
(trade) => Number.isFinite(trade.exitTimestamp) && Number.isFinite(trade.netProfit) && trade.exitTimestamp >= startTime && trade.exitTimestamp < endTime
|
|
851
|
+
).sort((left, right) => left.exitTimestamp - right.exitTimestamp);
|
|
852
|
+
const drawdownEnvelopes = [...new Set(days)].sort((left, right) => left - right).map((windowDays) => {
|
|
853
|
+
assert(
|
|
854
|
+
Number.isInteger(windowDays) && windowDays > 0,
|
|
855
|
+
"monitoring profile days must be positive integers"
|
|
856
|
+
);
|
|
857
|
+
const duration = windowDays * DAY_MS;
|
|
858
|
+
assert(
|
|
859
|
+
endTime - startTime >= duration,
|
|
860
|
+
`${windowDays}d monitoring window exceeds historical coverage`
|
|
861
|
+
);
|
|
862
|
+
const drawdowns = [];
|
|
863
|
+
for (let windowStart = startTime; windowStart + duration <= endTime; windowStart += DAY_MS) {
|
|
864
|
+
const windowEnd = windowStart + duration;
|
|
865
|
+
drawdowns.push(
|
|
866
|
+
realizedDrawdown(
|
|
867
|
+
ordered,
|
|
868
|
+
lowerBoundExitTimestamp(ordered, windowStart),
|
|
869
|
+
lowerBoundExitTimestamp(ordered, windowEnd)
|
|
870
|
+
)
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
days: windowDays,
|
|
875
|
+
p95: percentile(drawdowns, 0.95),
|
|
876
|
+
maximum: Math.max(...drawdowns)
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
return {
|
|
880
|
+
minimumProspectiveClosedTrades,
|
|
881
|
+
minimumParityRatio,
|
|
882
|
+
maximumOrderFailureRate,
|
|
883
|
+
minimumRegimeCoverage,
|
|
884
|
+
drawdownEnvelopes,
|
|
885
|
+
rawCoreExpectancy: ordered.length ? ordered.reduce((sum, trade) => sum + trade.netProfit, 0) / ordered.length : null,
|
|
886
|
+
aiGateExpectancy,
|
|
887
|
+
overfitProbability
|
|
888
|
+
};
|
|
889
|
+
};
|
|
890
|
+
var publishStrategyLiveDiagnosis = async ({
|
|
891
|
+
rootDir,
|
|
892
|
+
diagnosis,
|
|
893
|
+
sourceArtifacts,
|
|
894
|
+
composition
|
|
895
|
+
}) => {
|
|
896
|
+
const diagnosisSha256 = strategyReleaseSha256(diagnosis);
|
|
897
|
+
const diagnosisId = `${safeSegment(diagnosis.strategy, "strategy")}_${compactTimestamp(diagnosis.createdAt)}_${diagnosisSha256.slice(0, 16)}`;
|
|
898
|
+
const envelope = {
|
|
899
|
+
schema: "tradejs-strategy-live-diagnosis-envelope/v1",
|
|
900
|
+
diagnosisId,
|
|
901
|
+
diagnosisSha256,
|
|
902
|
+
diagnosis
|
|
903
|
+
};
|
|
904
|
+
const common = {
|
|
905
|
+
timestamp: diagnosis.createdAt,
|
|
906
|
+
artifactId: diagnosisId,
|
|
907
|
+
artifactSha256: diagnosisSha256,
|
|
908
|
+
compositionId: diagnosis.compositionId,
|
|
909
|
+
...composition ? {
|
|
910
|
+
gitSha: composition.gitSha,
|
|
911
|
+
configFingerprint: composition.runtimeConfigFingerprint,
|
|
912
|
+
gateFingerprint: composition.gateFingerprint,
|
|
913
|
+
contextFingerprint: composition.runtimeContextFingerprint,
|
|
914
|
+
maxLossValue: composition.maxLossValue
|
|
915
|
+
} : {}
|
|
916
|
+
};
|
|
917
|
+
const markers = [
|
|
918
|
+
...diagnosis.evidence.runtimeMaxLossValue != null && diagnosis.evidence.runtimeMaxLossValue !== diagnosis.evidence.releaseMaxLossValue ? [
|
|
919
|
+
{
|
|
920
|
+
...common,
|
|
921
|
+
id: `${diagnosisId}:loss`,
|
|
922
|
+
type: "L",
|
|
923
|
+
label: "Trade loss value changed",
|
|
924
|
+
summary: `MAX_LOSS_VALUE ${diagnosis.evidence.runtimeMaxLossValue}`,
|
|
925
|
+
maxLossValue: diagnosis.evidence.runtimeMaxLossValue,
|
|
926
|
+
severity: "info"
|
|
927
|
+
}
|
|
928
|
+
] : [],
|
|
929
|
+
...diagnosis.verdict === "RUNTIME_DIVERGENCE" ? [
|
|
930
|
+
{
|
|
931
|
+
...common,
|
|
932
|
+
id: `${diagnosisId}:parity`,
|
|
933
|
+
type: "P",
|
|
934
|
+
label: "Runtime parity mismatch",
|
|
935
|
+
summary: diagnosis.explanation,
|
|
936
|
+
severity: "blocking"
|
|
937
|
+
}
|
|
938
|
+
] : [],
|
|
939
|
+
{
|
|
940
|
+
...common,
|
|
941
|
+
id: `${diagnosisId}:recommendation`,
|
|
942
|
+
type: "R",
|
|
943
|
+
label: "Live diagnosis",
|
|
944
|
+
summary: `${diagnosis.verdict}: ${diagnosis.explanation}`,
|
|
945
|
+
severity: diagnosis.verdict === "EXPECTED_DRAWDOWN" ? "info" : "warning"
|
|
946
|
+
}
|
|
947
|
+
];
|
|
948
|
+
const markerEnvelope = (0, import_strategyReleaseEvidence.createStrategyEvidenceMarkerEnvelope)({
|
|
949
|
+
strategy: diagnosis.strategy,
|
|
950
|
+
createdAt: diagnosis.createdAt,
|
|
951
|
+
markers,
|
|
952
|
+
sourceArtifacts
|
|
953
|
+
});
|
|
954
|
+
const diagnosisPath = import_node_path.default.join(
|
|
955
|
+
rootDir,
|
|
956
|
+
"diagnoses",
|
|
957
|
+
safeSegment(diagnosis.strategy, "strategy"),
|
|
958
|
+
`${diagnosisId}.json`
|
|
959
|
+
);
|
|
960
|
+
const markerPath = import_node_path.default.join(
|
|
961
|
+
rootDir,
|
|
962
|
+
"markers",
|
|
963
|
+
safeSegment(diagnosis.strategy, "strategy"),
|
|
964
|
+
`${markerEnvelope.artifactId}.json`
|
|
965
|
+
);
|
|
966
|
+
await Promise.all([
|
|
967
|
+
writeJsonAtomic(diagnosisPath, envelope),
|
|
968
|
+
writeJsonAtomic(markerPath, markerEnvelope)
|
|
969
|
+
]);
|
|
970
|
+
await verifyStrategyEvidenceMarkerEnvelope(markerPath);
|
|
971
|
+
return { diagnosisPath, markerPath, envelope, markerEnvelope };
|
|
972
|
+
};
|
|
973
|
+
var planStrategyEvidenceRetention = ({
|
|
974
|
+
now,
|
|
975
|
+
entries,
|
|
976
|
+
retentionDays = {
|
|
977
|
+
operational_redis: 3,
|
|
978
|
+
verbose_payload: 14,
|
|
979
|
+
verified_runtime_bundle: 90,
|
|
980
|
+
compact_ledger: null
|
|
981
|
+
}
|
|
982
|
+
}) => {
|
|
983
|
+
const keep = [];
|
|
984
|
+
const remove = [];
|
|
985
|
+
for (const entry of entries) {
|
|
986
|
+
const days = retentionDays[entry.kind];
|
|
987
|
+
if (days == null || !entry.verified || !entry.aggregated || now - entry.createdAt <= days * DAY_MS) {
|
|
988
|
+
keep.push(entry);
|
|
989
|
+
} else {
|
|
990
|
+
remove.push(entry);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return {
|
|
994
|
+
keep,
|
|
995
|
+
delete: remove,
|
|
996
|
+
bytesReclaimable: remove.reduce((total, entry) => total + entry.bytes, 0)
|
|
997
|
+
};
|
|
998
|
+
};
|
|
999
|
+
var finiteString = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1000
|
+
var finiteNumber = (value) => {
|
|
1001
|
+
const number = Number(value);
|
|
1002
|
+
return Number.isFinite(number) ? number : null;
|
|
1003
|
+
};
|
|
1004
|
+
var uniqueStrings = (values) => [
|
|
1005
|
+
...new Set(
|
|
1006
|
+
values.map(finiteString).filter((value) => value != null)
|
|
1007
|
+
)
|
|
1008
|
+
];
|
|
1009
|
+
var incompleteEvidenceLineage = () => ({
|
|
1010
|
+
strategy: null,
|
|
1011
|
+
gitSha: null,
|
|
1012
|
+
gitDirty: null,
|
|
1013
|
+
coreConfigSha256: null,
|
|
1014
|
+
gateConfigIdsFingerprint: null,
|
|
1015
|
+
gateFingerprint: null,
|
|
1016
|
+
runtimeConfigFingerprint: null,
|
|
1017
|
+
gateContextFingerprint: null,
|
|
1018
|
+
runtimeContextFingerprint: null,
|
|
1019
|
+
maxLossValue: null,
|
|
1020
|
+
sourceSha256s: []
|
|
1021
|
+
});
|
|
1022
|
+
var extractEvidenceLineage = ({
|
|
1023
|
+
artifact,
|
|
1024
|
+
reference,
|
|
1025
|
+
nested,
|
|
1026
|
+
array
|
|
1027
|
+
}) => {
|
|
1028
|
+
const empty = incompleteEvidenceLineage();
|
|
1029
|
+
if (reference.kind === "core_research") {
|
|
1030
|
+
const variants = array(artifact.variants).map(nested).filter((entry) => entry != null);
|
|
1031
|
+
const selectedCandidateIds = uniqueStrings(
|
|
1032
|
+
array(artifact.comparisons).map(nested).filter(
|
|
1033
|
+
(comparison) => comparison != null && nested(comparison.selection)?.passed === true
|
|
1034
|
+
).map((comparison) => comparison.candidateId)
|
|
1035
|
+
);
|
|
1036
|
+
const selectedVariants = selectedCandidateIds.length === 1 ? variants.filter(
|
|
1037
|
+
(variant) => finiteString(nested(variant.variant)?.id) === selectedCandidateIds[0]
|
|
1038
|
+
) : [];
|
|
1039
|
+
const configShas = uniqueStrings(
|
|
1040
|
+
selectedVariants.map((variant) => nested(variant.variant)?.configSha256)
|
|
1041
|
+
);
|
|
1042
|
+
const maxLossValues = [
|
|
1043
|
+
...new Set(
|
|
1044
|
+
selectedVariants.map(
|
|
1045
|
+
(variant) => finiteNumber(
|
|
1046
|
+
nested(nested(variant.variant)?.resolvedConfig)?.MAX_LOSS_VALUE
|
|
1047
|
+
)
|
|
1048
|
+
).filter((value) => value != null)
|
|
1049
|
+
)
|
|
1050
|
+
];
|
|
1051
|
+
const sourceSha256s = uniqueStrings(
|
|
1052
|
+
selectedVariants.flatMap(
|
|
1053
|
+
(variant) => array(variant.files).map((file) => nested(file)?.sha256)
|
|
1054
|
+
)
|
|
1055
|
+
);
|
|
1056
|
+
const resultLineage = nested(artifact.lineage);
|
|
1057
|
+
const strategies = uniqueStrings(
|
|
1058
|
+
selectedVariants.map(
|
|
1059
|
+
(variant) => finiteString(nested(variant.variant)?.configName)?.split(":")[0]
|
|
1060
|
+
)
|
|
1061
|
+
);
|
|
1062
|
+
return {
|
|
1063
|
+
...empty,
|
|
1064
|
+
strategy: strategies.length === 1 ? strategies[0] : null,
|
|
1065
|
+
gitSha: finiteString(resultLineage?.gitSha),
|
|
1066
|
+
gitDirty: resultLineage?.gitSha != null ? finiteString(resultLineage?.dirtyDiffSha256) != null : null,
|
|
1067
|
+
coreConfigSha256: configShas.length === 1 ? configShas[0] : null,
|
|
1068
|
+
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
|
|
1069
|
+
sourceSha256s
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if (reference.kind === "ai_gate") {
|
|
1073
|
+
const lineage = nested(nested(artifact.research)?.lineage);
|
|
1074
|
+
return {
|
|
1075
|
+
...empty,
|
|
1076
|
+
strategy: finiteString(nested(artifact.run)?.strategy),
|
|
1077
|
+
gitSha: finiteString(lineage?.gitSha),
|
|
1078
|
+
gitDirty: typeof lineage?.gitDirty === "boolean" ? lineage.gitDirty : null,
|
|
1079
|
+
gateConfigIdsFingerprint: finiteString(lineage?.configIdsFingerprint),
|
|
1080
|
+
gateFingerprint: finiteString(lineage?.gateFingerprint),
|
|
1081
|
+
gateContextFingerprint: finiteString(lineage?.contextFingerprint),
|
|
1082
|
+
sourceSha256s: uniqueStrings(array(lineage?.sourceSha256s))
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
if (reference.kind === "runtime_parity") {
|
|
1086
|
+
const comparison = nested(nested(artifact.replay)?.runtimeComparison);
|
|
1087
|
+
const replayLineages = array(nested(comparison?.lineage)?.replay).map(nested).map((entry) => nested(entry?.lineage)).filter((entry) => entry != null);
|
|
1088
|
+
const one = (key) => {
|
|
1089
|
+
const values = uniqueStrings(replayLineages.map((entry) => entry[key]));
|
|
1090
|
+
return values.length === 1 ? values[0] : null;
|
|
1091
|
+
};
|
|
1092
|
+
const maxLossValues = [
|
|
1093
|
+
...new Set(
|
|
1094
|
+
replayLineages.map((entry) => finiteNumber(entry.maxLossValue)).filter((value) => value != null)
|
|
1095
|
+
)
|
|
1096
|
+
];
|
|
1097
|
+
const dirtyValues = [
|
|
1098
|
+
...new Set(
|
|
1099
|
+
replayLineages.map((entry) => entry.gitDirty).filter((value) => typeof value === "boolean")
|
|
1100
|
+
)
|
|
1101
|
+
];
|
|
1102
|
+
return {
|
|
1103
|
+
...empty,
|
|
1104
|
+
strategy: (() => {
|
|
1105
|
+
const strategies = uniqueStrings(
|
|
1106
|
+
array(nested(comparison?.lineage)?.replay).map(
|
|
1107
|
+
(entry) => nested(entry)?.strategy
|
|
1108
|
+
)
|
|
1109
|
+
);
|
|
1110
|
+
return strategies.length === 1 ? strategies[0] : null;
|
|
1111
|
+
})(),
|
|
1112
|
+
gitSha: one("gitSha"),
|
|
1113
|
+
gitDirty: dirtyValues.length === 1 ? dirtyValues[0] : null,
|
|
1114
|
+
gateFingerprint: one("gateFingerprint"),
|
|
1115
|
+
runtimeConfigFingerprint: one("configFingerprint"),
|
|
1116
|
+
runtimeContextFingerprint: one("contextFingerprint"),
|
|
1117
|
+
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
if (reference.kind === "execution_calibration") {
|
|
1121
|
+
const samples = [
|
|
1122
|
+
...array(artifact.samples),
|
|
1123
|
+
...array(artifact.replayOnlySamples)
|
|
1124
|
+
].map(nested);
|
|
1125
|
+
const lineages = samples.map((sample) => nested(sample?.runtimeLineage)).filter((lineage) => lineage != null);
|
|
1126
|
+
const one = (key) => {
|
|
1127
|
+
const values = uniqueStrings(lineages.map((lineage) => lineage[key]));
|
|
1128
|
+
return values.length === 1 ? values[0] : null;
|
|
1129
|
+
};
|
|
1130
|
+
const dirtyValues = [
|
|
1131
|
+
...new Set(
|
|
1132
|
+
lineages.map((lineage) => lineage.gitDirty).filter((value) => typeof value === "boolean")
|
|
1133
|
+
)
|
|
1134
|
+
];
|
|
1135
|
+
const maxLossValues = [
|
|
1136
|
+
...new Set(
|
|
1137
|
+
lineages.map((lineage) => finiteNumber(lineage.maxLossValue)).filter((value) => value != null)
|
|
1138
|
+
)
|
|
1139
|
+
];
|
|
1140
|
+
return {
|
|
1141
|
+
...empty,
|
|
1142
|
+
strategy: (() => {
|
|
1143
|
+
const strategies = uniqueStrings(
|
|
1144
|
+
samples.map((sample) => sample?.strategy)
|
|
1145
|
+
);
|
|
1146
|
+
return strategies.length === 1 ? strategies[0] : null;
|
|
1147
|
+
})(),
|
|
1148
|
+
gitSha: one("gitSha"),
|
|
1149
|
+
gitDirty: dirtyValues.length === 1 ? dirtyValues[0] : null,
|
|
1150
|
+
gateFingerprint: one("gateFingerprint"),
|
|
1151
|
+
runtimeConfigFingerprint: one("configFingerprint"),
|
|
1152
|
+
runtimeContextFingerprint: one("contextFingerprint"),
|
|
1153
|
+
maxLossValue: maxLossValues.length === 1 ? maxLossValues[0] : null,
|
|
1154
|
+
sourceSha256s: uniqueStrings([
|
|
1155
|
+
...array(nested(artifact.sources)?.sha256s),
|
|
1156
|
+
...array(artifact.sourceSha256s)
|
|
1157
|
+
])
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
return empty;
|
|
1161
|
+
};
|
|
1162
|
+
var verifyCompletedCoreResearchBundle = async ({
|
|
1163
|
+
artifact,
|
|
1164
|
+
resultPath,
|
|
1165
|
+
resultSha256
|
|
1166
|
+
}) => {
|
|
1167
|
+
const researchId = finiteString(artifact.researchId);
|
|
1168
|
+
const specSha256 = finiteString(artifact.specSha256);
|
|
1169
|
+
assert(researchId != null, "core result researchId is required");
|
|
1170
|
+
assert(specSha256 != null, "core result specSha256 is required");
|
|
1171
|
+
const researchDir = import_node_path.default.dirname(resultPath);
|
|
1172
|
+
const manifestPath = import_node_path.default.join(researchDir, "manifest.json");
|
|
1173
|
+
const specPath = import_node_path.default.join(researchDir, "spec.json");
|
|
1174
|
+
const manifestBytes = await import_promises.default.readFile(manifestPath).catch(() => null);
|
|
1175
|
+
const specBytes = await import_promises.default.readFile(specPath).catch(() => null);
|
|
1176
|
+
assert(
|
|
1177
|
+
manifestBytes != null && specBytes != null,
|
|
1178
|
+
"core result must belong to a completed core research bundle"
|
|
1179
|
+
);
|
|
1180
|
+
const manifest = JSON.parse(manifestBytes.toString("utf8"));
|
|
1181
|
+
const artifactHashes = manifest.artifactHashes != null && typeof manifest.artifactHashes === "object" && !Array.isArray(manifest.artifactHashes) ? manifest.artifactHashes : null;
|
|
1182
|
+
const parsedSpec = JSON.parse(specBytes.toString("utf8"));
|
|
1183
|
+
assert(
|
|
1184
|
+
manifest.schema === "tradejs-core-research-manifest/v1" && manifest.status === "completed" && manifest.researchId === researchId && manifest.specSha256 === specSha256 && artifactHashes?.["result.json"] === resultSha256 && (0, import_node_crypto.createHash)("sha256").update(canonicalStrategyReleaseJson(parsedSpec)).digest("hex") === specSha256,
|
|
1185
|
+
"core result is not bound by its completed core research manifest"
|
|
1186
|
+
);
|
|
1187
|
+
for (const [relativePath, expectedSha256] of Object.entries(
|
|
1188
|
+
artifactHashes ?? {}
|
|
1189
|
+
)) {
|
|
1190
|
+
assert(
|
|
1191
|
+
!import_node_path.default.isAbsolute(relativePath) && !relativePath.split(/[\\/]/).includes(".."),
|
|
1192
|
+
`core bundle artifact ${relativePath} escapes its research directory`
|
|
1193
|
+
);
|
|
1194
|
+
assert(
|
|
1195
|
+
typeof expectedSha256 === "string" && SHA256_RE.test(expectedSha256),
|
|
1196
|
+
`core bundle artifact ${relativePath} has an invalid checksum`
|
|
1197
|
+
);
|
|
1198
|
+
const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(
|
|
1199
|
+
import_node_path.default.join(researchDir, relativePath)
|
|
1200
|
+
);
|
|
1201
|
+
assert(
|
|
1202
|
+
actualSha256 === expectedSha256,
|
|
1203
|
+
`core bundle artifact ${relativePath} checksum mismatch`
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
async function collectReleaseEvidenceReferences(references) {
|
|
1208
|
+
return Promise.all(
|
|
1209
|
+
references.map(async (reference) => {
|
|
1210
|
+
const actualSha256 = await (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(reference.path);
|
|
1211
|
+
assert(
|
|
1212
|
+
actualSha256 === reference.sha256,
|
|
1213
|
+
`evidence ${reference.artifactId} checksum mismatch`
|
|
1214
|
+
);
|
|
1215
|
+
let artifact;
|
|
1216
|
+
try {
|
|
1217
|
+
const parsed = JSON.parse(
|
|
1218
|
+
await import_promises.default.readFile(reference.path, "utf8")
|
|
1219
|
+
);
|
|
1220
|
+
assert(
|
|
1221
|
+
parsed != null && typeof parsed === "object" && !Array.isArray(parsed),
|
|
1222
|
+
`evidence ${reference.artifactId} must be a JSON object`
|
|
1223
|
+
);
|
|
1224
|
+
artifact = parsed;
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
throw new Error(
|
|
1227
|
+
`Invalid strategy release: evidence ${reference.artifactId} is not valid JSON: ${error.message}`
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
const nested = (value) => value != null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1231
|
+
const array = (value) => Array.isArray(value) ? value : [];
|
|
1232
|
+
const semanticallyValid = reference.kind === "core_research" ? artifact.schema === "tradejs-core-research-result/v1" || artifact.schema === "tradejs-core-research-manifest/v1" && artifact.status === "completed" : reference.kind === "ai_gate" ? nested(artifact.run)?.mode === "local-deterministic" && nested(artifact.research)?.lineage != null && nested(artifact.outcome) != null : reference.kind === "runtime_parity" ? artifact.reportType === "replay-runtime-evidence" && nested(nested(artifact.replay)?.runtimeComparison) != null : reference.kind === "execution_calibration" ? artifact.reportType === "execution-calibration" && nested(artifact.summary) != null : reference.kind === "runtime_evidence" ? artifact.reportType === "runtime-evidence" : artifact.reportType === "runtime-scorecard";
|
|
1233
|
+
assert(
|
|
1234
|
+
semanticallyValid,
|
|
1235
|
+
`evidence ${reference.artifactId} does not match ${reference.kind}`
|
|
1236
|
+
);
|
|
1237
|
+
if (reference.kind === "core_research" && artifact.schema === "tradejs-core-research-result/v1") {
|
|
1238
|
+
await verifyCompletedCoreResearchBundle({
|
|
1239
|
+
artifact,
|
|
1240
|
+
resultPath: reference.path,
|
|
1241
|
+
resultSha256: actualSha256
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
const releaseAssertions = (() => {
|
|
1245
|
+
if (reference.kind === "core_research") {
|
|
1246
|
+
const evidence = nested(artifact.evidence);
|
|
1247
|
+
const comparisons = array(artifact.comparisons).map(nested).filter((entry) => entry != null);
|
|
1248
|
+
const variants = array(artifact.variants).map(nested).filter((entry) => entry != null);
|
|
1249
|
+
const hasCohorts = (value) => {
|
|
1250
|
+
const cohorts = nested(value);
|
|
1251
|
+
return ["ALL", "LONG", "SHORT"].every(
|
|
1252
|
+
(cohort) => nested(cohorts?.[cohort]) != null
|
|
1253
|
+
);
|
|
1254
|
+
};
|
|
1255
|
+
const directionalEvidenceComplete = variants.length > 0 && variants.every(
|
|
1256
|
+
(variant) => hasCohorts(nested(variant.full)?.cohorts) && array(variant.terminal).every(
|
|
1257
|
+
(window) => hasCohorts(nested(window)?.cohorts)
|
|
1258
|
+
) && array(variant.folds).every(
|
|
1259
|
+
(window) => hasCohorts(nested(window)?.cohorts)
|
|
1260
|
+
)
|
|
1261
|
+
);
|
|
1262
|
+
const evidenceComplete = [
|
|
1263
|
+
"terminals",
|
|
1264
|
+
"folds",
|
|
1265
|
+
"coldStart",
|
|
1266
|
+
"costStress",
|
|
1267
|
+
"delayStress",
|
|
1268
|
+
"fastNonFast",
|
|
1269
|
+
"runtimeParity"
|
|
1270
|
+
].every((key) => evidence?.[key] === "present");
|
|
1271
|
+
const reconciled = variants.length > 0 && variants.every(
|
|
1272
|
+
(variant) => nested(variant.reconciliation)?.status === "match"
|
|
1273
|
+
);
|
|
1274
|
+
const selectedCandidateIds = uniqueStrings(
|
|
1275
|
+
comparisons.filter(
|
|
1276
|
+
(comparison) => nested(comparison.selection)?.passed === true
|
|
1277
|
+
).map((comparison) => comparison.candidateId)
|
|
1278
|
+
);
|
|
1279
|
+
const selected = selectedCandidateIds.length === 1;
|
|
1280
|
+
const finalStage = artifact.stage === "isolated_long" || artifact.stage === "confirmation";
|
|
1281
|
+
const verified = finalStage && evidenceComplete && directionalEvidenceComplete && reconciled && selected;
|
|
1282
|
+
return {
|
|
1283
|
+
coreEdgeVerified: verified,
|
|
1284
|
+
currentMarketSuitable: verified
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
if (reference.kind === "ai_gate") {
|
|
1288
|
+
const outcome = nested(artifact.outcome);
|
|
1289
|
+
const approvedRisk = nested(outcome?.approvedRisk);
|
|
1290
|
+
const terminalWindows = array(
|
|
1291
|
+
nested(artifact.research)?.terminalWindows
|
|
1292
|
+
).map(nested).filter((entry) => entry != null);
|
|
1293
|
+
const hasLongShortDirections = (value) => {
|
|
1294
|
+
const directions = new Map(
|
|
1295
|
+
array(value).map(nested).filter(
|
|
1296
|
+
(entry) => entry != null
|
|
1297
|
+
).map((entry) => [finiteString(entry.direction), entry])
|
|
1298
|
+
);
|
|
1299
|
+
return ["LONG", "SHORT"].every(
|
|
1300
|
+
(direction) => nested(directions.get(direction)?.summary) != null
|
|
1301
|
+
);
|
|
1302
|
+
};
|
|
1303
|
+
const directionalEvidenceComplete = hasLongShortDirections(artifact.byDirection) && terminalWindows.every(
|
|
1304
|
+
(window) => hasLongShortDirections(window.byDirection)
|
|
1305
|
+
);
|
|
1306
|
+
const terminalsPass = terminalWindows.length > 0 && terminalWindows.every((window) => {
|
|
1307
|
+
const terminalOutcome = nested(window.outcome);
|
|
1308
|
+
return window.complete === true && Number(terminalOutcome?.approved ?? 0) > 0 && Number(
|
|
1309
|
+
nested(terminalOutcome?.approvedRisk)?.totalProfit ?? Number.NEGATIVE_INFINITY
|
|
1310
|
+
) > 0;
|
|
1311
|
+
});
|
|
1312
|
+
return {
|
|
1313
|
+
aiGateAddsValue: Number(outcome?.expectancyDelta ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.totalProfit ?? Number.NEGATIVE_INFINITY) > 0 && Number(approvedRisk?.profitFactor ?? Number.NEGATIVE_INFINITY) > 1 && directionalEvidenceComplete && terminalsPass
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
if (reference.kind === "runtime_parity") {
|
|
1317
|
+
const comparison = nested(nested(artifact.replay)?.runtimeComparison);
|
|
1318
|
+
const counts = nested(comparison?.counts);
|
|
1319
|
+
const matched = Number(counts?.matched ?? 0);
|
|
1320
|
+
const backtestOnly = Number(counts?.backtestOnly ?? 0);
|
|
1321
|
+
const runtimeOnly = Number(counts?.runtimeOnly ?? 0);
|
|
1322
|
+
return {
|
|
1323
|
+
runtimeParityVerified: matched > 0 && backtestOnly === 0 && runtimeOnly === 0 && nested(comparison?.lineage)?.reason == null
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
if (reference.kind === "execution_calibration") {
|
|
1327
|
+
const counts = nested(artifact.counts);
|
|
1328
|
+
const all = nested(nested(artifact.summary)?.all);
|
|
1329
|
+
const residual = Number(
|
|
1330
|
+
nested(all?.residualVsCurrentModelBps)?.avg ?? Number.NaN
|
|
1331
|
+
);
|
|
1332
|
+
return {
|
|
1333
|
+
executionModelVerified: Number(counts?.fullTelemetryTrades ?? 0) > 0 && Number.isFinite(residual) && Math.abs(residual) <= 3
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
return {};
|
|
1337
|
+
})();
|
|
1338
|
+
const lineage = extractEvidenceLineage({
|
|
1339
|
+
artifact,
|
|
1340
|
+
reference,
|
|
1341
|
+
nested,
|
|
1342
|
+
array
|
|
1343
|
+
});
|
|
1344
|
+
return { ...reference, verified: true, lineage, releaseAssertions };
|
|
1345
|
+
})
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1349
|
+
0 && (module.exports = {
|
|
1350
|
+
buildStrategyLiveDiagnosis,
|
|
1351
|
+
buildStrategyLiveDiagnosisFromScorecard,
|
|
1352
|
+
buildStrategyMonitoringProfile,
|
|
1353
|
+
canonicalStrategyReleaseJson,
|
|
1354
|
+
collectReleaseEvidenceReferences,
|
|
1355
|
+
createStrategyEvidenceMarkerEnvelope,
|
|
1356
|
+
createStrategyReleaseEnvelope,
|
|
1357
|
+
createStrategyReleaseManifest,
|
|
1358
|
+
deriveStrategyReleaseResearchDecision,
|
|
1359
|
+
planStrategyEvidenceRetention,
|
|
1360
|
+
publishStrategyLiveDiagnosis,
|
|
1361
|
+
publishStrategyRelease,
|
|
1362
|
+
strategyReleaseSha256,
|
|
1363
|
+
verifyStrategyEvidenceMarkerEnvelope,
|
|
1364
|
+
verifyStrategyReleaseEnvelope
|
|
1365
|
+
});
|