@tradejs/cli 3.1.29-beta.290 → 3.1.29-beta.292
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 +12283 -10394
- 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 +273 -119
- package/dist/lib/researchMetricSnapshot.js +74 -0
- package/dist/lib/researchVisuals.js +931 -0
- package/dist/lib/strategyRelease.js +8 -2
- package/dist/lib/strategyResearch.js +334 -199
- package/dist/lib/tickerProfiles.js +76 -0
- package/dist/scripts/aiPocketSearch.js +2959 -2610
- package/dist/scripts/aiTrain.js +29 -17
- package/dist/scripts/coreResearch.js +275 -119
- package/dist/scripts/replay.js +533 -115
- package/dist/scripts/replayRunner.js +533 -115
- package/dist/scripts/researchVisuals.js +976 -0
- package/dist/scripts/strategyRelease.js +59 -7
- package/dist/scripts/strategyResearch.js +339 -203
- package/dist/scripts/tickerProfiles.js +114 -0
- package/package.json +8 -7
package/dist/lib/coreResearch.js
CHANGED
|
@@ -52,21 +52,172 @@ var import_node_crypto5 = require("crypto");
|
|
|
52
52
|
var import_coreResearch = require("@tradejs/infra/coreResearch");
|
|
53
53
|
|
|
54
54
|
// src/lib/coreResearch/artifacts.ts
|
|
55
|
-
var
|
|
55
|
+
var import_node_fs3 = require("fs");
|
|
56
56
|
var import_node_events = require("events");
|
|
57
57
|
var import_promises2 = __toESM(require("fs/promises"));
|
|
58
|
-
var
|
|
58
|
+
var import_node_path3 = __toESM(require("path"));
|
|
59
59
|
|
|
60
60
|
// src/lib/coreResearch/io.ts
|
|
61
61
|
var import_node_crypto = require("crypto");
|
|
62
|
-
var
|
|
62
|
+
var import_node_fs2 = require("fs");
|
|
63
63
|
var import_promises = __toESM(require("fs/promises"));
|
|
64
64
|
var import_backtest = require("@tradejs/core/backtest");
|
|
65
|
+
var import_node_path2 = __toESM(require("path"));
|
|
66
|
+
|
|
67
|
+
// src/lib/coreResearch/trace.ts
|
|
68
|
+
var import_node_fs = require("fs");
|
|
69
|
+
var import_node_readline = __toESM(require("readline"));
|
|
65
70
|
var import_node_path = __toESM(require("path"));
|
|
71
|
+
async function* readCoreResearchTrace(filePaths) {
|
|
72
|
+
for (const inputPath of filePaths) {
|
|
73
|
+
const filePath = import_node_path.default.resolve(inputPath);
|
|
74
|
+
const input = (0, import_node_fs.createReadStream)(filePath);
|
|
75
|
+
const lines = import_node_readline.default.createInterface({ input, crlfDelay: Infinity });
|
|
76
|
+
let index = -1;
|
|
77
|
+
try {
|
|
78
|
+
for await (const line of lines) {
|
|
79
|
+
index++;
|
|
80
|
+
if (!line.trim()) continue;
|
|
81
|
+
let event;
|
|
82
|
+
try {
|
|
83
|
+
event = JSON.parse(line);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
yield event;
|
|
90
|
+
}
|
|
91
|
+
} finally {
|
|
92
|
+
lines.close();
|
|
93
|
+
input.destroy();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
98
|
+
const events = /* @__PURE__ */ Object.create(null);
|
|
99
|
+
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
100
|
+
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
101
|
+
const policyRejections = {};
|
|
102
|
+
const policyExamples = [];
|
|
103
|
+
for await (const event of readCoreResearchTrace(filePaths)) {
|
|
104
|
+
events[event.event] = (events[event.event] ?? 0) + 1;
|
|
105
|
+
if (event.event === "skip_summary") {
|
|
106
|
+
for (const [reason, count] of Object.entries(event.skipCounts)) {
|
|
107
|
+
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (event.event === "position_exited" && event.exitCode) {
|
|
111
|
+
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
112
|
+
}
|
|
113
|
+
if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
|
|
114
|
+
const reason = event.researchEntryDecision.reason;
|
|
115
|
+
policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
|
|
116
|
+
if (policyExamples.length < 20)
|
|
117
|
+
policyExamples.push({
|
|
118
|
+
timestamp: event.timestamp,
|
|
119
|
+
symbol: event.symbol,
|
|
120
|
+
decision: event.researchEntryDecision
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
events: Object.fromEntries(Object.entries(events).sort()),
|
|
126
|
+
skipCounts: Object.fromEntries(
|
|
127
|
+
Object.entries(skipCounts).sort(
|
|
128
|
+
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
129
|
+
)
|
|
130
|
+
),
|
|
131
|
+
exitCodes: Object.fromEntries(
|
|
132
|
+
Object.entries(exitCodes).sort(
|
|
133
|
+
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
134
|
+
)
|
|
135
|
+
),
|
|
136
|
+
...policyExamples.length ? {
|
|
137
|
+
researchPolicy: {
|
|
138
|
+
rejections: policyRejections,
|
|
139
|
+
examples: policyExamples
|
|
140
|
+
}
|
|
141
|
+
} : {}
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/lib/coreResearch/support.ts
|
|
146
|
+
async function assessCoreResearchSupport(spec, criteria) {
|
|
147
|
+
const symbols = new Set(spec.universe.symbols);
|
|
148
|
+
const blockMs = spec.robustness.clusterDays * 864e5;
|
|
149
|
+
const variants = [];
|
|
150
|
+
for (const variant of spec.variants) {
|
|
151
|
+
if (!variant.traceFiles?.length) continue;
|
|
152
|
+
const setups = /* @__PURE__ */ new Set();
|
|
153
|
+
const timestamps = /* @__PURE__ */ new Set();
|
|
154
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
155
|
+
const seenSymbols = /* @__PURE__ */ new Set();
|
|
156
|
+
const runs = /* @__PURE__ */ new Set();
|
|
157
|
+
let fallbackRows = 0;
|
|
158
|
+
for await (const event of readCoreResearchTrace(variant.traceFiles)) {
|
|
159
|
+
if (variant.runId && event.backtestRunId !== variant.runId) continue;
|
|
160
|
+
runs.add(event.backtestRunId);
|
|
161
|
+
if (event.event !== "signal_emitted" && event.event !== "entry_rejected" || event.strategy !== spec.strategy || !symbols.has(event.symbol) || spec.hypothesis.target !== "ALL" && event.direction !== spec.hypothesis.target || !Number.isSafeInteger(event.timestamp) || event.timestamp < spec.window.start || event.timestamp >= spec.window.end)
|
|
162
|
+
continue;
|
|
163
|
+
timestamps.add(event.timestamp);
|
|
164
|
+
seenSymbols.add(event.symbol);
|
|
165
|
+
if (event.setupIdentitySource === "strategy-context" && event.setupIdentity) {
|
|
166
|
+
setups.add(
|
|
167
|
+
JSON.stringify([event.symbol, event.direction, event.setupIdentity])
|
|
168
|
+
);
|
|
169
|
+
} else fallbackRows++;
|
|
170
|
+
const block = Math.floor((event.timestamp - spec.window.start) / blockMs);
|
|
171
|
+
const times = blocks.get(block) ?? /* @__PURE__ */ new Set();
|
|
172
|
+
times.add(event.timestamp);
|
|
173
|
+
blocks.set(block, times);
|
|
174
|
+
}
|
|
175
|
+
if (runs.size > 1)
|
|
176
|
+
throw new Error(`Support traces mix runs for ${variant.id}`);
|
|
177
|
+
variants.push({
|
|
178
|
+
id: variant.id,
|
|
179
|
+
role: variant.role,
|
|
180
|
+
setups,
|
|
181
|
+
events: timestamps.size,
|
|
182
|
+
symbols: seenSymbols.size,
|
|
183
|
+
fallbackRows,
|
|
184
|
+
blocks: [...blocks].sort(([a], [b]) => a - b).map(([index, times]) => ({
|
|
185
|
+
start: spec.window.start + index * blockMs,
|
|
186
|
+
events: times.size
|
|
187
|
+
})),
|
|
188
|
+
passed: timestamps.size >= criteria.minimumEvents && blocks.size >= criteria.minimumBlocks
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const control = variants.find((v) => v.role === "control");
|
|
192
|
+
const candidates = variants.filter((v) => v.role === "candidate");
|
|
193
|
+
const comparisons = control ? candidates.map((candidate) => ({
|
|
194
|
+
candidate: candidate.id,
|
|
195
|
+
commonSetups: [...candidate.setups].filter(
|
|
196
|
+
(id) => control.setups.has(id)
|
|
197
|
+
).length
|
|
198
|
+
})) : [];
|
|
199
|
+
const missing = !control || criteria.minimumCommonSetups !== void 0 && candidates.length !== spec.variants.filter((v) => v.role === "candidate").length;
|
|
200
|
+
const insufficient = variants.some((v) => !v.passed) || comparisons.some(
|
|
201
|
+
(c) => c.commonSetups < (criteria.minimumCommonSetups ?? 0)
|
|
202
|
+
);
|
|
203
|
+
return {
|
|
204
|
+
schema: "tradejs-core-research-support/v1",
|
|
205
|
+
window: { start: spec.window.start, end: spec.window.end },
|
|
206
|
+
target: spec.hypothesis.target,
|
|
207
|
+
blockDays: spec.robustness.clusterDays,
|
|
208
|
+
criteria,
|
|
209
|
+
status: missing ? "missing-evidence" : insufficient ? "insufficient-support" : "supported",
|
|
210
|
+
variants: variants.map(({ setups, ...variant }) => ({
|
|
211
|
+
...variant,
|
|
212
|
+
explicitSetups: setups.size
|
|
213
|
+
})),
|
|
214
|
+
comparisons
|
|
215
|
+
};
|
|
216
|
+
}
|
|
66
217
|
|
|
67
218
|
// src/lib/coreResearch/types.ts
|
|
68
219
|
var CORE_RESEARCH_SCHEMA = "tradejs-core-research/v1";
|
|
69
|
-
var CORE_RESEARCH_RESULT_SCHEMA = "tradejs-core-research-result/
|
|
220
|
+
var CORE_RESEARCH_RESULT_SCHEMA = "tradejs-core-research-result/v2";
|
|
70
221
|
var CORE_RESEARCH_LEDGER_SCHEMA = "tradejs-core-research-ledger/v1";
|
|
71
222
|
|
|
72
223
|
// src/lib/coreResearch/io.ts
|
|
@@ -88,7 +239,7 @@ var sha256Text = (value) => (0, import_node_crypto.createHash)("sha256").update(
|
|
|
88
239
|
var sha256Json = (value) => sha256Text(canonicalJson(value));
|
|
89
240
|
var sha256File = async (filePath) => {
|
|
90
241
|
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
91
|
-
const input = (0,
|
|
242
|
+
const input = (0, import_node_fs2.createReadStream)(filePath);
|
|
92
243
|
for await (const chunk of input) hash.update(chunk);
|
|
93
244
|
return hash.digest("hex");
|
|
94
245
|
};
|
|
@@ -327,33 +478,49 @@ var validateCoreResearchSpec = (value) => {
|
|
|
327
478
|
Boolean(spec.artifacts?.ledgerPath?.trim()),
|
|
328
479
|
"artifacts.ledgerPath is required"
|
|
329
480
|
);
|
|
481
|
+
if (spec.support !== void 0) {
|
|
482
|
+
assert(
|
|
483
|
+
spec.support && typeof spec.support === "object",
|
|
484
|
+
"support must be an object"
|
|
485
|
+
);
|
|
486
|
+
for (const [key, value2] of [
|
|
487
|
+
["minimumEvents", spec.support.minimumEvents],
|
|
488
|
+
["minimumBlocks", spec.support.minimumBlocks],
|
|
489
|
+
...spec.support.minimumCommonSetups === void 0 ? [] : [["minimumCommonSetups", spec.support.minimumCommonSetups]]
|
|
490
|
+
]) {
|
|
491
|
+
assert(
|
|
492
|
+
typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0,
|
|
493
|
+
`support.${key} must be a positive integer`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
330
497
|
return spec;
|
|
331
498
|
};
|
|
332
499
|
var loadCoreResearchSpec = async (specPath) => {
|
|
333
|
-
const resolvedPath =
|
|
500
|
+
const resolvedPath = import_node_path2.default.resolve(specPath);
|
|
334
501
|
const parsed = JSON.parse(await import_promises.default.readFile(resolvedPath, "utf8"));
|
|
335
502
|
const spec = validateCoreResearchSpec(parsed);
|
|
336
503
|
return { resolvedPath, spec, specSha256: sha256Json(spec) };
|
|
337
504
|
};
|
|
338
505
|
var resolveCoreResearchPaths = (spec) => {
|
|
339
|
-
const rootDir =
|
|
340
|
-
const researchDir =
|
|
506
|
+
const rootDir = import_node_path2.default.resolve(spec.artifacts.rootDir);
|
|
507
|
+
const researchDir = import_node_path2.default.join(rootDir, spec.researchId);
|
|
341
508
|
return {
|
|
342
509
|
rootDir,
|
|
343
510
|
researchDir,
|
|
344
|
-
ledgerPath:
|
|
345
|
-
specPath:
|
|
346
|
-
manifestPath:
|
|
347
|
-
resultPath:
|
|
348
|
-
comparisonPath:
|
|
349
|
-
matchesPath:
|
|
350
|
-
tradesPath:
|
|
351
|
-
reportPath:
|
|
352
|
-
runLogDir:
|
|
511
|
+
ledgerPath: import_node_path2.default.resolve(spec.artifacts.ledgerPath),
|
|
512
|
+
specPath: import_node_path2.default.join(researchDir, "spec.json"),
|
|
513
|
+
manifestPath: import_node_path2.default.join(researchDir, "manifest.json"),
|
|
514
|
+
resultPath: import_node_path2.default.join(researchDir, "result.json"),
|
|
515
|
+
comparisonPath: import_node_path2.default.join(researchDir, "comparison.json"),
|
|
516
|
+
matchesPath: import_node_path2.default.join(researchDir, "matches.csv"),
|
|
517
|
+
tradesPath: import_node_path2.default.join(researchDir, "trades.jsonl"),
|
|
518
|
+
reportPath: import_node_path2.default.join(researchDir, "report.html"),
|
|
519
|
+
runLogDir: import_node_path2.default.join(researchDir, "runs")
|
|
353
520
|
};
|
|
354
521
|
};
|
|
355
522
|
var writeJsonAtomic = async (filePath, value) => {
|
|
356
|
-
await import_promises.default.mkdir(
|
|
523
|
+
await import_promises.default.mkdir(import_node_path2.default.dirname(filePath), { recursive: true });
|
|
357
524
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
358
525
|
await import_promises.default.writeFile(temporaryPath, `${canonicalJson(value)}
|
|
359
526
|
`, "utf8");
|
|
@@ -400,7 +567,7 @@ var verifyCoreResearchLedger = (records) => {
|
|
|
400
567
|
};
|
|
401
568
|
var appendCoreResearchLedger = async (params) => {
|
|
402
569
|
const { ledgerPath } = params;
|
|
403
|
-
await import_promises.default.mkdir(
|
|
570
|
+
await import_promises.default.mkdir(import_node_path2.default.dirname(ledgerPath), { recursive: true });
|
|
404
571
|
const lockPath = `${ledgerPath}.lock`;
|
|
405
572
|
let lock = null;
|
|
406
573
|
try {
|
|
@@ -439,14 +606,14 @@ var appendCoreResearchLedger = async (params) => {
|
|
|
439
606
|
var validateCoreResearchParentLineage = async (spec) => {
|
|
440
607
|
if (spec.stage === "screen") return;
|
|
441
608
|
const expectedParentStage = spec.stage === "isolated_long" ? "screen" : "isolated_long";
|
|
442
|
-
const rootDir =
|
|
609
|
+
const rootDir = import_node_path2.default.resolve(spec.artifacts.rootDir);
|
|
443
610
|
const passedParentCandidateConfigShas = /* @__PURE__ */ new Set();
|
|
444
611
|
for (const parentResearchId of spec.parentResearchIds ?? []) {
|
|
445
|
-
const parentDir =
|
|
612
|
+
const parentDir = import_node_path2.default.join(rootDir, parentResearchId);
|
|
446
613
|
const [parentSpec, parentManifest, parentResult] = await Promise.all([
|
|
447
|
-
import_promises.default.readFile(
|
|
448
|
-
import_promises.default.readFile(
|
|
449
|
-
import_promises.default.readFile(
|
|
614
|
+
import_promises.default.readFile(import_node_path2.default.join(parentDir, "spec.json"), "utf8").then((text) => JSON.parse(text)),
|
|
615
|
+
import_promises.default.readFile(import_node_path2.default.join(parentDir, "manifest.json"), "utf8").then((text) => JSON.parse(text)),
|
|
616
|
+
import_promises.default.readFile(import_node_path2.default.join(parentDir, "result.json"), "utf8").then((text) => JSON.parse(text))
|
|
450
617
|
]);
|
|
451
618
|
validateCoreResearchSpec(parentSpec);
|
|
452
619
|
if (parentManifest.status !== "completed") {
|
|
@@ -520,15 +687,46 @@ var prepareCoreResearch = async (spec) => {
|
|
|
520
687
|
return { paths, specSha256, manifest: existingManifest };
|
|
521
688
|
}
|
|
522
689
|
}
|
|
690
|
+
const supportSources = spec.support ? await Promise.all(
|
|
691
|
+
[
|
|
692
|
+
...new Set(
|
|
693
|
+
spec.variants.flatMap((variant) => variant.traceFiles ?? [])
|
|
694
|
+
)
|
|
695
|
+
].map(async (file) => ({
|
|
696
|
+
file: import_node_path2.default.resolve(file),
|
|
697
|
+
sha256: await sha256File(file).catch((error) => {
|
|
698
|
+
if (error.code === "ENOENT") return null;
|
|
699
|
+
throw error;
|
|
700
|
+
})
|
|
701
|
+
}))
|
|
702
|
+
) : [];
|
|
703
|
+
const missingSources = supportSources.filter(
|
|
704
|
+
(source) => source.sha256 === null
|
|
705
|
+
);
|
|
706
|
+
const support = spec.support ? missingSources.length ? {
|
|
707
|
+
schema: "tradejs-core-research-support/v1",
|
|
708
|
+
status: "missing-evidence",
|
|
709
|
+
criteria: spec.support
|
|
710
|
+
} : await assessCoreResearchSupport(spec, spec.support) : void 0;
|
|
711
|
+
const supportPath = import_node_path2.default.join(paths.researchDir, "support.json");
|
|
712
|
+
if (support) {
|
|
713
|
+
for (const source of supportSources) {
|
|
714
|
+
if (source.sha256 !== null && source.sha256 !== await sha256File(source.file))
|
|
715
|
+
throw new Error("Support trace changed during preparation");
|
|
716
|
+
}
|
|
717
|
+
await writeJsonAtomic(supportPath, { ...support, sources: supportSources });
|
|
718
|
+
}
|
|
523
719
|
await writeJsonAtomic(paths.specPath, spec);
|
|
524
720
|
const manifest = {
|
|
525
721
|
schema: "tradejs-core-research-manifest/v1",
|
|
526
722
|
researchId: spec.researchId,
|
|
527
723
|
specSha256,
|
|
528
724
|
status: "prepared",
|
|
725
|
+
...support ? { support: support.status } : {},
|
|
529
726
|
createdAt: spec.createdAt,
|
|
530
727
|
artifactHashes: {
|
|
531
|
-
"spec.json": await sha256File(paths.specPath)
|
|
728
|
+
"spec.json": await sha256File(paths.specPath),
|
|
729
|
+
...support ? { "support.json": await sha256File(supportPath) } : {}
|
|
532
730
|
}
|
|
533
731
|
};
|
|
534
732
|
await writeJsonAtomic(paths.manifestPath, manifest);
|
|
@@ -547,6 +745,24 @@ var prepareCoreResearch = async (spec) => {
|
|
|
547
745
|
}
|
|
548
746
|
return { paths, specSha256, manifest };
|
|
549
747
|
};
|
|
748
|
+
var verifyPreparedSupport = async (prepared) => {
|
|
749
|
+
if (!prepared.manifest.support) return;
|
|
750
|
+
if (prepared.manifest.support !== "supported") {
|
|
751
|
+
throw new Error(
|
|
752
|
+
`Research preflight: ${prepared.manifest.support}; see support.json`
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
const file = import_node_path2.default.join(prepared.paths.researchDir, "support.json");
|
|
756
|
+
const content = await import_promises.default.readFile(file);
|
|
757
|
+
if ((0, import_node_crypto.createHash)("sha256").update(content).digest("hex") !== prepared.manifest.artifactHashes["support.json"]) {
|
|
758
|
+
throw new Error("Support report checksum mismatch");
|
|
759
|
+
}
|
|
760
|
+
const { sources } = JSON.parse(content.toString());
|
|
761
|
+
for (const source of sources) {
|
|
762
|
+
if (source.sha256 !== await sha256File(source.file))
|
|
763
|
+
throw new Error("Support trace changed after preparation");
|
|
764
|
+
}
|
|
765
|
+
};
|
|
550
766
|
var verifyCoreResearchArtifacts = async (spec) => {
|
|
551
767
|
const paths = resolveCoreResearchPaths(spec);
|
|
552
768
|
const manifest = JSON.parse(
|
|
@@ -559,7 +775,7 @@ var verifyCoreResearchArtifacts = async (spec) => {
|
|
|
559
775
|
manifest.artifactHashes
|
|
560
776
|
)) {
|
|
561
777
|
const actualHash = await sha256File(
|
|
562
|
-
|
|
778
|
+
import_node_path2.default.join(paths.researchDir, relativePath)
|
|
563
779
|
);
|
|
564
780
|
if (actualHash !== expectedHash) {
|
|
565
781
|
throw new Error(`Artifact hash mismatch: ${relativePath}`);
|
|
@@ -575,7 +791,7 @@ var verifyCoreResearchArtifacts = async (spec) => {
|
|
|
575
791
|
};
|
|
576
792
|
};
|
|
577
793
|
var scanCoreResearchStages = async (rootDir) => {
|
|
578
|
-
const resolvedRoot =
|
|
794
|
+
const resolvedRoot = import_node_path2.default.resolve(rootDir);
|
|
579
795
|
let entries = [];
|
|
580
796
|
try {
|
|
581
797
|
entries = await import_promises.default.readdir(resolvedRoot);
|
|
@@ -593,15 +809,15 @@ var scanCoreResearchStages = async (rootDir) => {
|
|
|
593
809
|
for (const entry of entries.sort(
|
|
594
810
|
(left, right) => left < right ? -1 : left > right ? 1 : 0
|
|
595
811
|
)) {
|
|
596
|
-
const researchDir =
|
|
812
|
+
const researchDir = import_node_path2.default.join(resolvedRoot, entry);
|
|
597
813
|
const stats = await import_promises.default.stat(researchDir);
|
|
598
814
|
if (!stats.isDirectory()) continue;
|
|
599
815
|
let spec;
|
|
600
816
|
let manifest;
|
|
601
817
|
try {
|
|
602
818
|
[spec, manifest] = await Promise.all([
|
|
603
|
-
import_promises.default.readFile(
|
|
604
|
-
import_promises.default.readFile(
|
|
819
|
+
import_promises.default.readFile(import_node_path2.default.join(researchDir, "spec.json"), "utf8").then((text) => JSON.parse(text)),
|
|
820
|
+
import_promises.default.readFile(import_node_path2.default.join(researchDir, "manifest.json"), "utf8").then((text) => JSON.parse(text))
|
|
605
821
|
]);
|
|
606
822
|
} catch (error) {
|
|
607
823
|
if (error.code === "ENOENT") continue;
|
|
@@ -611,7 +827,7 @@ var scanCoreResearchStages = async (rootDir) => {
|
|
|
611
827
|
if (manifest.specSha256 !== sha256Json(spec)) {
|
|
612
828
|
throw new Error(`Stage index found a spec hash mismatch for ${entry}`);
|
|
613
829
|
}
|
|
614
|
-
const result = manifest.status === "completed" ? await import_promises.default.readFile(
|
|
830
|
+
const result = manifest.status === "completed" ? await import_promises.default.readFile(import_node_path2.default.join(researchDir, "result.json"), "utf8").then((text) => JSON.parse(text)) : null;
|
|
615
831
|
familyByResearch.set(spec.researchId, spec.hypothesis.family);
|
|
616
832
|
experiments.push({
|
|
617
833
|
researchId: spec.researchId,
|
|
@@ -642,7 +858,7 @@ var scanCoreResearchStages = async (rootDir) => {
|
|
|
642
858
|
return { experiments, familyByResearch };
|
|
643
859
|
};
|
|
644
860
|
var writeCoreResearchStageIndex = async (rootDir) => {
|
|
645
|
-
const resolvedRoot =
|
|
861
|
+
const resolvedRoot = import_node_path2.default.resolve(rootDir);
|
|
646
862
|
const { experiments, familyByResearch } = await scanCoreResearchStages(resolvedRoot);
|
|
647
863
|
const grouped = /* @__PURE__ */ new Map();
|
|
648
864
|
for (const experiment of experiments) {
|
|
@@ -659,7 +875,7 @@ var writeCoreResearchStageIndex = async (rootDir) => {
|
|
|
659
875
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
660
876
|
families: [...grouped.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([family, experiments2]) => ({ family, experiments: experiments2 }))
|
|
661
877
|
};
|
|
662
|
-
await writeJsonAtomic(
|
|
878
|
+
await writeJsonAtomic(import_node_path2.default.join(resolvedRoot, "index.json"), index);
|
|
663
879
|
return index;
|
|
664
880
|
};
|
|
665
881
|
|
|
@@ -676,8 +892,8 @@ var closeOutput = async (output) => {
|
|
|
676
892
|
await (0, import_node_events.once)(output, "close");
|
|
677
893
|
};
|
|
678
894
|
var writeCoreResearchMatches = async (params) => {
|
|
679
|
-
await import_promises2.default.mkdir(
|
|
680
|
-
const output = (0,
|
|
895
|
+
await import_promises2.default.mkdir(import_node_path3.default.dirname(params.filePath), { recursive: true });
|
|
896
|
+
const output = (0, import_node_fs3.createWriteStream)(params.filePath, { encoding: "utf8" });
|
|
681
897
|
await writeLine(
|
|
682
898
|
output,
|
|
683
899
|
[
|
|
@@ -727,8 +943,8 @@ var writeCoreResearchMatches = async (params) => {
|
|
|
727
943
|
await closeOutput(output);
|
|
728
944
|
};
|
|
729
945
|
var writeCoreResearchTrades = async (params) => {
|
|
730
|
-
await import_promises2.default.mkdir(
|
|
731
|
-
const output = (0,
|
|
946
|
+
await import_promises2.default.mkdir(import_node_path3.default.dirname(params.filePath), { recursive: true });
|
|
947
|
+
const output = (0, import_node_fs3.createWriteStream)(params.filePath, { encoding: "utf8" });
|
|
732
948
|
for (const variant of params.variants) {
|
|
733
949
|
for (const trade of variant.trades) {
|
|
734
950
|
await writeLine(
|
|
@@ -740,6 +956,14 @@ var writeCoreResearchTrades = async (params) => {
|
|
|
740
956
|
}
|
|
741
957
|
await closeOutput(output);
|
|
742
958
|
};
|
|
959
|
+
var summarizeCoreResearchComparison = (comparison) => {
|
|
960
|
+
const { matchedPairs, ...summary } = comparison;
|
|
961
|
+
return {
|
|
962
|
+
...summary,
|
|
963
|
+
exitReasonChanges: matchedPairs.filter((pair) => pair.exitReasonChanged).length,
|
|
964
|
+
exitCodeChanges: matchedPairs.filter((pair) => pair.exitCodeChanged).length
|
|
965
|
+
};
|
|
966
|
+
};
|
|
743
967
|
|
|
744
968
|
// src/lib/coreResearch/metrics.ts
|
|
745
969
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -1453,9 +1677,9 @@ var compareCoreResearchVariants = (params) => {
|
|
|
1453
1677
|
|
|
1454
1678
|
// src/lib/coreResearch/dataset.ts
|
|
1455
1679
|
var import_node_crypto3 = require("crypto");
|
|
1456
|
-
var
|
|
1457
|
-
var
|
|
1458
|
-
var
|
|
1680
|
+
var import_node_fs4 = require("fs");
|
|
1681
|
+
var import_node_path4 = __toESM(require("path"));
|
|
1682
|
+
var import_node_readline2 = __toESM(require("readline"));
|
|
1459
1683
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1460
1684
|
var finiteNumber = (value, field) => {
|
|
1461
1685
|
const numeric = Number(value);
|
|
@@ -1665,11 +1889,11 @@ var readCoreResearchVariant = async (variant) => {
|
|
|
1665
1889
|
const seen = /* @__PURE__ */ new Map();
|
|
1666
1890
|
let duplicateRowsDropped = 0;
|
|
1667
1891
|
for (const inputPath of variant.files) {
|
|
1668
|
-
const filePath =
|
|
1669
|
-
const input = (0,
|
|
1892
|
+
const filePath = import_node_path4.default.resolve(inputPath);
|
|
1893
|
+
const input = (0, import_node_fs4.createReadStream)(filePath);
|
|
1670
1894
|
const hash = (0, import_node_crypto3.createHash)("sha256");
|
|
1671
1895
|
input.on("data", (chunk) => hash.update(chunk));
|
|
1672
|
-
const lines =
|
|
1896
|
+
const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
|
|
1673
1897
|
const fileTrades = [];
|
|
1674
1898
|
let lineNumber = 0;
|
|
1675
1899
|
let rows = 0;
|
|
@@ -1815,7 +2039,7 @@ var buildCurveSvg = (tradesByVariant, cohort, curve) => {
|
|
|
1815
2039
|
)}</svg><div class="legend">${series.map((entry) => `<span><i style="background:${entry.color}"></i>${escapeHtml(entry.id)}</span>`).join("")}</div>`;
|
|
1816
2040
|
};
|
|
1817
2041
|
var comparisonCards = (comparisons) => comparisons.map(
|
|
1818
|
-
(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.
|
|
2042
|
+
(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>`
|
|
1819
2043
|
).join("");
|
|
1820
2044
|
var regimeRows = (variants) => {
|
|
1821
2045
|
const keys = [
|
|
@@ -2157,79 +2381,6 @@ var applyCoreResearchRobustnessGuardrails = (params) => {
|
|
|
2157
2381
|
}
|
|
2158
2382
|
};
|
|
2159
2383
|
|
|
2160
|
-
// src/lib/coreResearch/trace.ts
|
|
2161
|
-
var import_node_fs4 = require("fs");
|
|
2162
|
-
var import_node_readline2 = __toESM(require("readline"));
|
|
2163
|
-
var import_node_path4 = __toESM(require("path"));
|
|
2164
|
-
var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
2165
|
-
const events = /* @__PURE__ */ Object.create(null);
|
|
2166
|
-
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
2167
|
-
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
2168
|
-
const policyRejections = {};
|
|
2169
|
-
const policyExamples = [];
|
|
2170
|
-
for (const inputPath of filePaths) {
|
|
2171
|
-
const filePath = import_node_path4.default.resolve(inputPath);
|
|
2172
|
-
const input = (0, import_node_fs4.createReadStream)(filePath);
|
|
2173
|
-
const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
|
|
2174
|
-
let index = -1;
|
|
2175
|
-
try {
|
|
2176
|
-
for await (const line of lines) {
|
|
2177
|
-
index++;
|
|
2178
|
-
if (!line.trim()) continue;
|
|
2179
|
-
let event;
|
|
2180
|
-
try {
|
|
2181
|
-
event = JSON.parse(line);
|
|
2182
|
-
} catch (error) {
|
|
2183
|
-
throw new Error(
|
|
2184
|
-
`${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
|
|
2185
|
-
);
|
|
2186
|
-
}
|
|
2187
|
-
events[event.event] = (events[event.event] ?? 0) + 1;
|
|
2188
|
-
if (event.event === "skip_summary") {
|
|
2189
|
-
for (const [reason, count] of Object.entries(event.skipCounts)) {
|
|
2190
|
-
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
if (event.event === "position_exited" && event.exitCode) {
|
|
2194
|
-
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
2195
|
-
}
|
|
2196
|
-
if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
|
|
2197
|
-
const reason = event.researchEntryDecision.reason;
|
|
2198
|
-
policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
|
|
2199
|
-
if (policyExamples.length < 20)
|
|
2200
|
-
policyExamples.push({
|
|
2201
|
-
timestamp: event.timestamp,
|
|
2202
|
-
symbol: event.symbol,
|
|
2203
|
-
decision: event.researchEntryDecision
|
|
2204
|
-
});
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
} finally {
|
|
2208
|
-
lines.close();
|
|
2209
|
-
input.destroy();
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2212
|
-
return {
|
|
2213
|
-
events: Object.fromEntries(Object.entries(events).sort()),
|
|
2214
|
-
skipCounts: Object.fromEntries(
|
|
2215
|
-
Object.entries(skipCounts).sort(
|
|
2216
|
-
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2217
|
-
)
|
|
2218
|
-
),
|
|
2219
|
-
exitCodes: Object.fromEntries(
|
|
2220
|
-
Object.entries(exitCodes).sort(
|
|
2221
|
-
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2222
|
-
)
|
|
2223
|
-
),
|
|
2224
|
-
...policyExamples.length ? {
|
|
2225
|
-
researchPolicy: {
|
|
2226
|
-
rejections: policyRejections,
|
|
2227
|
-
examples: policyExamples
|
|
2228
|
-
}
|
|
2229
|
-
} : {}
|
|
2230
|
-
};
|
|
2231
|
-
};
|
|
2232
|
-
|
|
2233
2384
|
// src/lib/researchRoots.ts
|
|
2234
2385
|
var import_node_path5 = __toESM(require("path"));
|
|
2235
2386
|
var SOURCE_REPOSITORY_ROOT_ENV = "TRADEJS_SOURCE_REPOSITORY_ROOT";
|
|
@@ -2253,6 +2404,7 @@ var summarizeSupplementalFiles = async (params) => {
|
|
|
2253
2404
|
var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
2254
2405
|
const prepared = options.prepared ?? await prepareCoreResearch(spec);
|
|
2255
2406
|
const { paths, specSha256 } = prepared;
|
|
2407
|
+
await verifyPreparedSupport(prepared);
|
|
2256
2408
|
if (prepared.manifest.status === "completed") {
|
|
2257
2409
|
throw new Error(
|
|
2258
2410
|
`Research ${spec.researchId} is already completed and immutable; use verify or create a new researchId`
|
|
@@ -2472,7 +2624,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2472
2624
|
regimeCausality: "signal-time payload.additionalIndicators.baseContext only"
|
|
2473
2625
|
},
|
|
2474
2626
|
variants: analyses,
|
|
2475
|
-
comparisons,
|
|
2627
|
+
comparisons: comparisons.map(summarizeCoreResearchComparison),
|
|
2476
2628
|
multipleTesting: {
|
|
2477
2629
|
family: spec.hypothesis.family,
|
|
2478
2630
|
hypotheses: Math.max(comparisons.length, familyHypotheses),
|
|
@@ -2505,7 +2657,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2505
2657
|
artifactHashes: sourceHashes
|
|
2506
2658
|
};
|
|
2507
2659
|
await writeJsonAtomic(paths.resultPath, result);
|
|
2508
|
-
await writeJsonAtomic(paths.comparisonPath, comparisons);
|
|
2660
|
+
await writeJsonAtomic(paths.comparisonPath, result.comparisons);
|
|
2509
2661
|
await writeCoreResearchMatches({ filePath: paths.matchesPath, comparisons });
|
|
2510
2662
|
await writeCoreResearchTrades({
|
|
2511
2663
|
filePath: paths.tradesPath,
|
|
@@ -2523,6 +2675,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2523
2675
|
"utf8"
|
|
2524
2676
|
);
|
|
2525
2677
|
const artifactHashes = {
|
|
2678
|
+
...prepared.manifest.artifactHashes,
|
|
2526
2679
|
"spec.json": await sha256File(paths.specPath),
|
|
2527
2680
|
"result.json": await sha256File(paths.resultPath),
|
|
2528
2681
|
"comparison.json": await sha256File(paths.comparisonPath),
|
|
@@ -2813,6 +2966,7 @@ var runCoreResearch = async (params) => {
|
|
|
2813
2966
|
const { spec } = params;
|
|
2814
2967
|
const cwd = import_node_path6.default.resolve(params.cwd ?? process.cwd());
|
|
2815
2968
|
const prepared = await prepareCoreResearch(spec);
|
|
2969
|
+
await verifyPreparedSupport(prepared);
|
|
2816
2970
|
const resolvedVariants = [];
|
|
2817
2971
|
for (const variant of spec.variants) {
|
|
2818
2972
|
if (!variant.command?.length) {
|