@tradejs/cli 3.1.29-beta.291 → 3.1.29-beta.296
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 +7150 -6352
- package/dist/lib/binanceMarketContextBackfill.js +2 -2
- package/dist/lib/coinMarketCapContextBackfill.js +1 -1
- package/dist/lib/coreResearch.js +261 -115
- package/dist/lib/marketContextPrepare.js +3 -3
- package/dist/lib/researchVisuals.js +27 -6
- package/dist/lib/strategyResearch.js +234 -245
- package/dist/scripts/aiPocketSearch.js +3343 -3029
- package/dist/scripts/backtest.js +192 -69
- package/dist/scripts/binanceMarketIngest.js +3 -3
- package/dist/scripts/coreResearch.js +263 -115
- package/dist/scripts/replay.js +483 -114
- package/dist/scripts/replayRunner.js +483 -114
- package/dist/scripts/researchVisuals.js +27 -6
- package/dist/scripts/runtimeParity.js +3 -3
- package/dist/scripts/signals.js +3 -3
- package/dist/scripts/signalsDaemon.js +3 -3
- package/dist/scripts/strategyRelease.js +6 -37
- package/dist/scripts/strategyResearch.js +239 -249
- package/package.json +7 -7
- package/dist/lib/researchMetricSnapshot.js +0 -74
|
@@ -35,6 +35,98 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
35
35
|
));
|
|
36
36
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
37
|
|
|
38
|
+
// src/lib/coreResearch/trace.ts
|
|
39
|
+
async function* readCoreResearchTrace(filePaths) {
|
|
40
|
+
for (const inputPath of filePaths) {
|
|
41
|
+
const filePath = import_node_path.default.resolve(inputPath);
|
|
42
|
+
const input = (0, import_node_fs.createReadStream)(filePath);
|
|
43
|
+
const lines = import_node_readline.default.createInterface({ input, crlfDelay: Infinity });
|
|
44
|
+
let index = -1;
|
|
45
|
+
try {
|
|
46
|
+
for await (const line of lines) {
|
|
47
|
+
index++;
|
|
48
|
+
if (!line.trim()) continue;
|
|
49
|
+
let event;
|
|
50
|
+
try {
|
|
51
|
+
event = JSON.parse(line);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
yield event;
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
lines.close();
|
|
61
|
+
input.destroy();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
var import_node_fs, import_node_readline, import_node_path, summarizeCoreResearchTrace;
|
|
66
|
+
var init_trace = __esm({
|
|
67
|
+
"src/lib/coreResearch/trace.ts"() {
|
|
68
|
+
"use strict";
|
|
69
|
+
import_node_fs = require("fs");
|
|
70
|
+
import_node_readline = __toESM(require("readline"));
|
|
71
|
+
import_node_path = __toESM(require("path"));
|
|
72
|
+
summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
73
|
+
const events = /* @__PURE__ */ Object.create(null);
|
|
74
|
+
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
75
|
+
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
76
|
+
const policyRejections = {};
|
|
77
|
+
const policyExamples = [];
|
|
78
|
+
for await (const event of readCoreResearchTrace(filePaths)) {
|
|
79
|
+
events[event.event] = (events[event.event] ?? 0) + 1;
|
|
80
|
+
if (event.event === "skip_summary") {
|
|
81
|
+
for (const [reason, count] of Object.entries(event.skipCounts)) {
|
|
82
|
+
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (event.event === "position_exited" && event.exitCode) {
|
|
86
|
+
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
87
|
+
}
|
|
88
|
+
if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
|
|
89
|
+
const reason = event.researchEntryDecision.reason;
|
|
90
|
+
policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
|
|
91
|
+
if (policyExamples.length < 20)
|
|
92
|
+
policyExamples.push({
|
|
93
|
+
timestamp: event.timestamp,
|
|
94
|
+
symbol: event.symbol,
|
|
95
|
+
decision: event.researchEntryDecision
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
events: Object.fromEntries(Object.entries(events).sort()),
|
|
101
|
+
skipCounts: Object.fromEntries(
|
|
102
|
+
Object.entries(skipCounts).sort(
|
|
103
|
+
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
104
|
+
)
|
|
105
|
+
),
|
|
106
|
+
exitCodes: Object.fromEntries(
|
|
107
|
+
Object.entries(exitCodes).sort(
|
|
108
|
+
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
109
|
+
)
|
|
110
|
+
),
|
|
111
|
+
...policyExamples.length ? {
|
|
112
|
+
researchPolicy: {
|
|
113
|
+
rejections: policyRejections,
|
|
114
|
+
examples: policyExamples
|
|
115
|
+
}
|
|
116
|
+
} : {}
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// src/lib/coreResearch/support.ts
|
|
123
|
+
var init_support = __esm({
|
|
124
|
+
"src/lib/coreResearch/support.ts"() {
|
|
125
|
+
"use strict";
|
|
126
|
+
init_trace();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
38
130
|
// src/lib/coreResearch/types.ts
|
|
39
131
|
var CORE_RESEARCH_SCHEMA;
|
|
40
132
|
var init_types = __esm({
|
|
@@ -48,15 +140,16 @@ var init_types = __esm({
|
|
|
48
140
|
function assert(condition, message) {
|
|
49
141
|
if (!condition) throw new Error(`Invalid core research spec: ${message}`);
|
|
50
142
|
}
|
|
51
|
-
var import_node_crypto,
|
|
143
|
+
var import_node_crypto, import_node_fs2, import_promises, import_backtest, import_node_path2, RESEARCH_ID_RE, SHA256_RE, canonicalJson, sha256Text, sha256Json, sha256File, assertFinite, validateCoreResearchSpec, writeJsonAtomic;
|
|
52
144
|
var init_io = __esm({
|
|
53
145
|
"src/lib/coreResearch/io.ts"() {
|
|
54
146
|
"use strict";
|
|
55
147
|
import_node_crypto = require("crypto");
|
|
56
|
-
|
|
148
|
+
import_node_fs2 = require("fs");
|
|
57
149
|
import_promises = __toESM(require("fs/promises"));
|
|
58
150
|
import_backtest = require("@tradejs/core/backtest");
|
|
59
|
-
|
|
151
|
+
import_node_path2 = __toESM(require("path"));
|
|
152
|
+
init_support();
|
|
60
153
|
init_types();
|
|
61
154
|
RESEARCH_ID_RE = /^[a-z0-9][a-z0-9._-]{2,120}$/;
|
|
62
155
|
SHA256_RE = /^[a-f0-9]{64}$/;
|
|
@@ -75,10 +168,10 @@ var init_io = __esm({
|
|
|
75
168
|
sha256Text = (value) => (0, import_node_crypto.createHash)("sha256").update(value).digest("hex");
|
|
76
169
|
sha256Json = (value) => sha256Text(canonicalJson(value));
|
|
77
170
|
sha256File = async (filePath) => {
|
|
78
|
-
const
|
|
79
|
-
const input = (0,
|
|
80
|
-
for await (const chunk of input)
|
|
81
|
-
return
|
|
171
|
+
const hash = (0, import_node_crypto.createHash)("sha256");
|
|
172
|
+
const input = (0, import_node_fs2.createReadStream)(filePath);
|
|
173
|
+
for await (const chunk of input) hash.update(chunk);
|
|
174
|
+
return hash.digest("hex");
|
|
82
175
|
};
|
|
83
176
|
assertFinite = (value, name) => {
|
|
84
177
|
assert(typeof value === "number" && Number.isFinite(value), name);
|
|
@@ -312,10 +405,26 @@ var init_io = __esm({
|
|
|
312
405
|
Boolean(spec.artifacts?.ledgerPath?.trim()),
|
|
313
406
|
"artifacts.ledgerPath is required"
|
|
314
407
|
);
|
|
408
|
+
if (spec.support !== void 0) {
|
|
409
|
+
assert(
|
|
410
|
+
spec.support && typeof spec.support === "object",
|
|
411
|
+
"support must be an object"
|
|
412
|
+
);
|
|
413
|
+
for (const [key, value2] of [
|
|
414
|
+
["minimumEvents", spec.support.minimumEvents],
|
|
415
|
+
["minimumBlocks", spec.support.minimumBlocks],
|
|
416
|
+
...spec.support.minimumCommonSetups === void 0 ? [] : [["minimumCommonSetups", spec.support.minimumCommonSetups]]
|
|
417
|
+
]) {
|
|
418
|
+
assert(
|
|
419
|
+
typeof value2 === "number" && Number.isSafeInteger(value2) && value2 > 0,
|
|
420
|
+
`support.${key} must be a positive integer`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
315
424
|
return spec;
|
|
316
425
|
};
|
|
317
426
|
writeJsonAtomic = async (filePath, value) => {
|
|
318
|
-
await import_promises.default.mkdir(
|
|
427
|
+
await import_promises.default.mkdir(import_node_path2.default.dirname(filePath), { recursive: true });
|
|
319
428
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
320
429
|
await import_promises.default.writeFile(temporaryPath, `${canonicalJson(value)}
|
|
321
430
|
`, "utf8");
|
|
@@ -336,30 +445,30 @@ __export(io_exports, {
|
|
|
336
445
|
verifyResearchState: () => verifyResearchState,
|
|
337
446
|
withResearchLock: () => withResearchLock
|
|
338
447
|
});
|
|
339
|
-
var import_promises2,
|
|
448
|
+
var import_promises2, import_node_path3, import_node_os, import_node_crypto2, researchDirectory, readJson, boundResearchPath, withResearchLock, verifyResearchState, verifyCompletedResearch, initializeResearchState, isRetainedDeletion;
|
|
340
449
|
var init_io2 = __esm({
|
|
341
450
|
"src/lib/strategyResearch/io.ts"() {
|
|
342
451
|
"use strict";
|
|
343
452
|
import_promises2 = __toESM(require("fs/promises"));
|
|
344
|
-
|
|
453
|
+
import_node_path3 = __toESM(require("path"));
|
|
345
454
|
import_node_os = __toESM(require("os"));
|
|
346
|
-
|
|
455
|
+
import_node_crypto2 = require("crypto");
|
|
347
456
|
init_io();
|
|
348
|
-
researchDirectory = (projectRoot, spec) =>
|
|
457
|
+
researchDirectory = (projectRoot, spec) => import_node_path3.default.join(projectRoot, "data/research/strategy", spec.core.researchId);
|
|
349
458
|
readJson = async (file) => JSON.parse(await import_promises2.default.readFile(file, "utf8"));
|
|
350
459
|
boundResearchPath = async (root, relative) => {
|
|
351
|
-
const target =
|
|
460
|
+
const target = import_node_path3.default.resolve(root, relative);
|
|
352
461
|
const realRoot = await import_promises2.default.realpath(root);
|
|
353
462
|
let ancestor = target;
|
|
354
463
|
while (true) {
|
|
355
464
|
try {
|
|
356
465
|
const real = await import_promises2.default.realpath(ancestor);
|
|
357
|
-
if (real !== realRoot && !real.startsWith(`${realRoot}${
|
|
466
|
+
if (real !== realRoot && !real.startsWith(`${realRoot}${import_node_path3.default.sep}`))
|
|
358
467
|
throw new Error(`Research path escapes Project: ${relative}`);
|
|
359
468
|
break;
|
|
360
469
|
} catch (error) {
|
|
361
470
|
if (error.code !== "ENOENT") throw error;
|
|
362
|
-
const parent =
|
|
471
|
+
const parent = import_node_path3.default.dirname(ancestor);
|
|
363
472
|
if (parent === ancestor) throw error;
|
|
364
473
|
ancestor = parent;
|
|
365
474
|
}
|
|
@@ -371,8 +480,8 @@ var init_io2 = __esm({
|
|
|
371
480
|
projectRoot,
|
|
372
481
|
"data/research/strategy/.lock"
|
|
373
482
|
);
|
|
374
|
-
await import_promises2.default.mkdir(
|
|
375
|
-
const token = (0,
|
|
483
|
+
await import_promises2.default.mkdir(import_node_path3.default.dirname(file), { recursive: true });
|
|
484
|
+
const token = (0, import_node_crypto2.randomUUID)();
|
|
376
485
|
const acquire = () => import_promises2.default.writeFile(
|
|
377
486
|
file,
|
|
378
487
|
JSON.stringify({ pid: process.pid, host: import_node_os.default.hostname(), token }),
|
|
@@ -405,7 +514,7 @@ var init_io2 = __esm({
|
|
|
405
514
|
}
|
|
406
515
|
};
|
|
407
516
|
verifyResearchState = async (directory, state) => {
|
|
408
|
-
const frozenSpec = await readJson(
|
|
517
|
+
const frozenSpec = await readJson(import_node_path3.default.join(directory, "spec.json"));
|
|
409
518
|
if (sha256Json(frozenSpec) !== state.specSha256)
|
|
410
519
|
throw new Error("Frozen research spec changed");
|
|
411
520
|
for (const [name, stage] of Object.entries(state.stages)) {
|
|
@@ -424,14 +533,14 @@ var init_io2 = __esm({
|
|
|
424
533
|
};
|
|
425
534
|
verifyCompletedResearch = async (directory, state) => {
|
|
426
535
|
await verifyResearchState(directory, state);
|
|
427
|
-
const manifest = await readJson(
|
|
536
|
+
const manifest = await readJson(import_node_path3.default.join(directory, "manifest.json"));
|
|
428
537
|
if (manifest.specSha256 !== state.specSha256)
|
|
429
538
|
throw new Error("Final manifest spec mismatch");
|
|
430
539
|
for (const [relative, expected] of Object.entries(manifest.artifacts))
|
|
431
540
|
if (await sha256File(await boundResearchPath(directory, relative)) !== expected)
|
|
432
541
|
throw new Error(`Changed final research artifact: ${relative}`);
|
|
433
|
-
const inventory = await readJson(
|
|
434
|
-
const projectRoot =
|
|
542
|
+
const inventory = await readJson(import_node_path3.default.join(directory, "retention.json"));
|
|
543
|
+
const projectRoot = import_node_path3.default.resolve(directory, "../../../..");
|
|
435
544
|
for (const artifact of inventory.research.permanentArtifacts) {
|
|
436
545
|
const file = await boundResearchPath(projectRoot, artifact.path);
|
|
437
546
|
if (await sha256File(file) !== artifact.sha256)
|
|
@@ -440,7 +549,7 @@ var init_io2 = __esm({
|
|
|
440
549
|
};
|
|
441
550
|
initializeResearchState = async (directory, spec, lineage) => {
|
|
442
551
|
await import_promises2.default.mkdir(directory, { recursive: true });
|
|
443
|
-
const file =
|
|
552
|
+
const file = import_node_path3.default.join(directory, "state.json");
|
|
444
553
|
let previous;
|
|
445
554
|
try {
|
|
446
555
|
previous = await readJson(file);
|
|
@@ -464,19 +573,19 @@ var init_io2 = __esm({
|
|
|
464
573
|
status: "running",
|
|
465
574
|
stages: {}
|
|
466
575
|
};
|
|
467
|
-
await writeJsonAtomic(
|
|
576
|
+
await writeJsonAtomic(import_node_path3.default.join(directory, "spec.json"), spec);
|
|
468
577
|
await writeJsonAtomic(file, state);
|
|
469
578
|
return state;
|
|
470
579
|
};
|
|
471
|
-
isRetainedDeletion = async (directory, file,
|
|
580
|
+
isRetainedDeletion = async (directory, file, hash) => {
|
|
472
581
|
try {
|
|
473
|
-
const inventory = await readJson(
|
|
582
|
+
const inventory = await readJson(import_node_path3.default.join(directory, "retention.json"));
|
|
474
583
|
const index = inventory.entries.findIndex(
|
|
475
|
-
(entry) => entry.path === file && entry.sha256 ===
|
|
584
|
+
(entry) => entry.path === file && entry.sha256 === hash
|
|
476
585
|
);
|
|
477
586
|
if (index < 0) return false;
|
|
478
|
-
const receipt = await readJson(
|
|
479
|
-
return ["applying", "completed"].includes(receipt.status) && receipt.files[0]?.path === file && receipt.files[0]?.sha256 ===
|
|
587
|
+
const receipt = await readJson(import_node_path3.default.join(directory, "retention", `${index}.json`));
|
|
588
|
+
return ["applying", "completed"].includes(receipt.status) && receipt.files[0]?.path === file && receipt.files[0]?.sha256 === hash;
|
|
480
589
|
} catch (error) {
|
|
481
590
|
if (error.code === "ENOENT") return false;
|
|
482
591
|
throw error;
|
|
@@ -1813,15 +1922,15 @@ var createPocketResult = (rows, predicates, mask, validationRows, testRows, obje
|
|
|
1813
1922
|
};
|
|
1814
1923
|
};
|
|
1815
1924
|
var hashMask = (mask) => {
|
|
1816
|
-
let
|
|
1925
|
+
let hash = 2166136261;
|
|
1817
1926
|
for (let index = 0; index < mask.length; index += 1) {
|
|
1818
1927
|
if (mask[index] !== 1) {
|
|
1819
1928
|
continue;
|
|
1820
1929
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1930
|
+
hash ^= index + 1;
|
|
1931
|
+
hash = Math.imul(hash, 16777619);
|
|
1823
1932
|
}
|
|
1824
|
-
return (
|
|
1933
|
+
return (hash >>> 0).toString(36);
|
|
1825
1934
|
};
|
|
1826
1935
|
var isBetterRepresentativePocket = (candidate, existing, compare) => candidate.depth < existing.depth || candidate.depth === existing.depth && candidate.condition.length < existing.condition.length || candidate.depth === existing.depth && candidate.condition.length === existing.condition.length && compare(candidate, existing) < 0;
|
|
1827
1936
|
var estimateCombinationCount = (poolSize, maxDepth, maxCombinations) => {
|
|
@@ -2518,52 +2627,8 @@ var policyVariant = (spec, coreId, policy, id, jev) => {
|
|
|
2518
2627
|
|
|
2519
2628
|
// src/lib/strategyResearch/orchestrator.ts
|
|
2520
2629
|
var import_promises5 = __toESM(require("fs/promises"));
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
var import_node_crypto2 = require("crypto");
|
|
2524
|
-
var import_node_zlib = require("zlib");
|
|
2525
|
-
var MAX_BYTES = 256 * 1024 * 1024;
|
|
2526
|
-
var hash = (bytes) => (0, import_node_crypto2.createHash)("sha256").update(bytes).digest("hex");
|
|
2527
|
-
var validatePermanentResearchMetrics = (value) => {
|
|
2528
|
-
if (!value || typeof value !== "object") return;
|
|
2529
|
-
for (const [key, child] of Object.entries(value)) {
|
|
2530
|
-
if (["matchedPairs", "tradeRows", "allProjectedLeaves"].includes(key) && Array.isArray(child) && child.length)
|
|
2531
|
-
throw new Error(`Temporary payload ${key} is not a permanent metric`);
|
|
2532
|
-
if (key === "trades" && Array.isArray(child) && child.some(
|
|
2533
|
-
(row) => row && typeof row === "object" && ("signalId" in row || "entryTimestamp" in row)
|
|
2534
|
-
))
|
|
2535
|
-
throw new Error("Temporary trade records are not permanent metrics");
|
|
2536
|
-
validatePermanentResearchMetrics(child);
|
|
2537
|
-
}
|
|
2538
|
-
};
|
|
2539
|
-
var encodeResearchMetricSnapshot = (metrics) => {
|
|
2540
|
-
validatePermanentResearchMetrics(metrics);
|
|
2541
|
-
const bytes = Buffer.from(JSON.stringify(metrics));
|
|
2542
|
-
if (bytes.length > MAX_BYTES)
|
|
2543
|
-
throw new Error("Metric snapshot exceeds decoded size limit");
|
|
2544
|
-
return {
|
|
2545
|
-
schema: "tradejs-metric-snapshot/v1",
|
|
2546
|
-
encoding: "gzip+base64",
|
|
2547
|
-
decodedBytes: bytes.length,
|
|
2548
|
-
sha256: hash(bytes),
|
|
2549
|
-
payload: (0, import_node_zlib.gzipSync)(bytes, { level: 9 }).toString("base64")
|
|
2550
|
-
};
|
|
2551
|
-
};
|
|
2552
|
-
var decodeResearchMetricSnapshot = (snapshot) => {
|
|
2553
|
-
if (!snapshot || typeof snapshot !== "object" || !("schema" in snapshot) || snapshot.schema !== "tradejs-metric-snapshot/v1" || !("encoding" in snapshot) || snapshot.encoding !== "gzip+base64" || !("decodedBytes" in snapshot) || typeof snapshot.decodedBytes !== "number" || !Number.isSafeInteger(snapshot.decodedBytes) || snapshot.decodedBytes <= 0 || snapshot.decodedBytes > MAX_BYTES || !("sha256" in snapshot) || typeof snapshot.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(snapshot.sha256) || !("payload" in snapshot) || typeof snapshot.payload !== "string")
|
|
2554
|
-
throw new Error("Invalid metric snapshot envelope");
|
|
2555
|
-
const bytes = (0, import_node_zlib.gunzipSync)(Buffer.from(snapshot.payload, "base64"), {
|
|
2556
|
-
maxOutputLength: snapshot.decodedBytes
|
|
2557
|
-
});
|
|
2558
|
-
if (bytes.length !== snapshot.decodedBytes || hash(bytes) !== snapshot.sha256)
|
|
2559
|
-
throw new Error("Metric snapshot checksum or size mismatch");
|
|
2560
|
-
const metrics = JSON.parse(bytes.toString("utf8"));
|
|
2561
|
-
validatePermanentResearchMetrics(metrics);
|
|
2562
|
-
return metrics;
|
|
2563
|
-
};
|
|
2564
|
-
|
|
2565
|
-
// src/lib/strategyResearch/orchestrator.ts
|
|
2566
|
-
var import_node_path6 = __toESM(require("path"));
|
|
2630
|
+
var import_researchMetricSnapshot2 = require("@tradejs/infra/researchMetricSnapshot");
|
|
2631
|
+
var import_node_path7 = __toESM(require("path"));
|
|
2567
2632
|
|
|
2568
2633
|
// src/lib/coreResearch/metrics.ts
|
|
2569
2634
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
@@ -3764,16 +3829,17 @@ var testCharts = (conclusion) => {
|
|
|
3764
3829
|
|
|
3765
3830
|
// src/lib/strategyResearch/retention.ts
|
|
3766
3831
|
var import_promises4 = __toESM(require("fs/promises"));
|
|
3767
|
-
var
|
|
3832
|
+
var import_node_path6 = __toESM(require("path"));
|
|
3768
3833
|
|
|
3769
3834
|
// src/lib/strategyRelease/researchRetention.ts
|
|
3770
3835
|
var import_promises3 = __toESM(require("fs/promises"));
|
|
3771
|
-
var
|
|
3772
|
-
var
|
|
3836
|
+
var import_researchMetricSnapshot = require("@tradejs/infra/researchMetricSnapshot");
|
|
3837
|
+
var import_node_path5 = __toESM(require("path"));
|
|
3838
|
+
var import_node_crypto3 = require("crypto");
|
|
3773
3839
|
var import_strategyReleaseEvidence = require("@tradejs/infra/strategyReleaseEvidence");
|
|
3774
3840
|
|
|
3775
3841
|
// src/lib/strategyRelease/researchFileConsumers.ts
|
|
3776
|
-
var
|
|
3842
|
+
var import_node_path4 = __toESM(require("path"));
|
|
3777
3843
|
var import_node_child_process = require("child_process");
|
|
3778
3844
|
var import_node_util = require("util");
|
|
3779
3845
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
@@ -3804,15 +3870,15 @@ print(json.dumps(sorted(opened)))
|
|
|
3804
3870
|
`;
|
|
3805
3871
|
var assertNoResearchFileConsumers = async (files, run = runCommand) => {
|
|
3806
3872
|
const virtualPids = /* @__PURE__ */ new Set();
|
|
3807
|
-
const wanted = new Set(files.map((file) =>
|
|
3808
|
-
let commonRoot = files.length ?
|
|
3873
|
+
const wanted = new Set(files.map((file) => import_node_path4.default.resolve(file)));
|
|
3874
|
+
let commonRoot = files.length ? import_node_path4.default.dirname(files[0]) : "";
|
|
3809
3875
|
while (commonRoot && files.some((file) => {
|
|
3810
|
-
const relative =
|
|
3811
|
-
return relative === ".." || relative.startsWith(`..${
|
|
3876
|
+
const relative = import_node_path4.default.relative(commonRoot, file);
|
|
3877
|
+
return relative === ".." || relative.startsWith(`..${import_node_path4.default.sep}`) || import_node_path4.default.isAbsolute(relative);
|
|
3812
3878
|
}))
|
|
3813
|
-
commonRoot =
|
|
3879
|
+
commonRoot = import_node_path4.default.dirname(commonRoot);
|
|
3814
3880
|
const selectors = [];
|
|
3815
|
-
if (files.length > 1e3 && commonRoot !==
|
|
3881
|
+
if (files.length > 1e3 && commonRoot !== import_node_path4.default.parse(commonRoot).root) {
|
|
3816
3882
|
selectors.push(["+D", commonRoot]);
|
|
3817
3883
|
} else {
|
|
3818
3884
|
for (let offset = 0; offset < files.length; offset += 100)
|
|
@@ -3836,7 +3902,7 @@ var assertNoResearchFileConsumers = async (files, run = runCommand) => {
|
|
|
3836
3902
|
if (line.startsWith("c"))
|
|
3837
3903
|
virtual = line.startsWith("ccom.apple.Virtualization.");
|
|
3838
3904
|
if (line.startsWith("n")) {
|
|
3839
|
-
if (!wanted.has(
|
|
3905
|
+
if (!wanted.has(import_node_path4.default.resolve(line.slice(1)))) continue;
|
|
3840
3906
|
if (!virtual || !/^\d+$/.test(pid))
|
|
3841
3907
|
throw new Error(
|
|
3842
3908
|
`Research cleanup found open files: ${line.slice(1)}`
|
|
@@ -3870,9 +3936,9 @@ var assertNoResearchFileConsumers = async (files, run = runCommand) => {
|
|
|
3870
3936
|
for (const mount of container.Mounts) {
|
|
3871
3937
|
if (mount.Type !== "bind") continue;
|
|
3872
3938
|
for (const file of files) {
|
|
3873
|
-
const relative =
|
|
3874
|
-
if (relative === "" || !relative.startsWith(`..${
|
|
3875
|
-
guestFiles.add(
|
|
3939
|
+
const relative = import_node_path4.default.relative(mount.Source, file);
|
|
3940
|
+
if (relative === "" || !relative.startsWith(`..${import_node_path4.default.sep}`) && relative !== ".." && !import_node_path4.default.isAbsolute(relative)) {
|
|
3941
|
+
guestFiles.add(import_node_path4.default.posix.join(mount.Destination, relative));
|
|
3876
3942
|
covered.add(file);
|
|
3877
3943
|
}
|
|
3878
3944
|
}
|
|
@@ -3918,15 +3984,15 @@ var mapFiles = async (entries, visit) => {
|
|
|
3918
3984
|
if (worker.status === "rejected") throw worker.reason;
|
|
3919
3985
|
return results;
|
|
3920
3986
|
};
|
|
3921
|
-
var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0,
|
|
3987
|
+
var fileSha256 = async (file, bytes) => bytes <= 1024 * 1024 ? (0, import_node_crypto3.createHash)("sha256").update(await import_promises3.default.readFile(file)).digest("hex") : (0, import_strategyReleaseEvidence.strategyEvidenceFileSha256)(file);
|
|
3922
3988
|
var inside = (root, file) => {
|
|
3923
|
-
const relative =
|
|
3924
|
-
return relative !== "" && !relative.startsWith(`..${
|
|
3989
|
+
const relative = import_node_path5.default.relative(root, file);
|
|
3990
|
+
return relative !== "" && !relative.startsWith(`..${import_node_path5.default.sep}`) && relative !== ".." && !import_node_path5.default.isAbsolute(relative);
|
|
3925
3991
|
};
|
|
3926
3992
|
var applyResearchRetention = async (options) => {
|
|
3927
3993
|
const projectRoot = await import_promises3.default.realpath(options.projectRoot);
|
|
3928
|
-
const dataRoot =
|
|
3929
|
-
const resolve = (file) =>
|
|
3994
|
+
const dataRoot = import_node_path5.default.join(projectRoot, "data");
|
|
3995
|
+
const resolve = (file) => import_node_path5.default.resolve(projectRoot, file);
|
|
3930
3996
|
const receiptPath = resolve(options.receiptPath);
|
|
3931
3997
|
const journalPath = `${receiptPath}.deleted.jsonl`;
|
|
3932
3998
|
const permanent = new Set(
|
|
@@ -3981,7 +4047,7 @@ var applyResearchRetention = async (options) => {
|
|
|
3981
4047
|
throw new Error(
|
|
3982
4048
|
"Complete v2 note needs one verified metric snapshot"
|
|
3983
4049
|
);
|
|
3984
|
-
decodeResearchMetricSnapshot(JSON.parse(blocks[0][1]));
|
|
4050
|
+
(0, import_researchMetricSnapshot.decodeResearchMetricSnapshot)(JSON.parse(blocks[0][1]));
|
|
3985
4051
|
}
|
|
3986
4052
|
} finally {
|
|
3987
4053
|
await handle.close();
|
|
@@ -4025,8 +4091,8 @@ var applyResearchRetention = async (options) => {
|
|
|
4025
4091
|
}))
|
|
4026
4092
|
};
|
|
4027
4093
|
if (!options.apply) return receipt;
|
|
4028
|
-
await import_promises3.default.mkdir(
|
|
4029
|
-
if (await import_promises3.default.realpath(
|
|
4094
|
+
await import_promises3.default.mkdir(import_node_path5.default.dirname(receiptPath), { recursive: true });
|
|
4095
|
+
if (await import_promises3.default.realpath(import_node_path5.default.dirname(receiptPath)) !== import_node_path5.default.dirname(receiptPath)) {
|
|
4030
4096
|
throw new Error("Cleanup receipt refuses symlinked directories");
|
|
4031
4097
|
}
|
|
4032
4098
|
const lock = await import_promises3.default.open(`${receiptPath}.lock`, "wx");
|
|
@@ -4072,17 +4138,17 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
|
|
|
4072
4138
|
const permanentArtifacts = [];
|
|
4073
4139
|
for (const [role, file] of [
|
|
4074
4140
|
["note", notePath],
|
|
4075
|
-
["metrics",
|
|
4076
|
-
["candidates",
|
|
4077
|
-
["candidates",
|
|
4078
|
-
["ledger",
|
|
4079
|
-
["chart",
|
|
4141
|
+
["metrics", import_node_path6.default.join(directory, "result.json")],
|
|
4142
|
+
["candidates", import_node_path6.default.join(directory, "03-freeze-gates/result.json")],
|
|
4143
|
+
["candidates", import_node_path6.default.join(directory, "resolved-spec.json")],
|
|
4144
|
+
["ledger", import_node_path6.default.join(directory, "05-freeze-primary/result.json")],
|
|
4145
|
+
["chart", import_node_path6.default.join(directory, "report.html")],
|
|
4080
4146
|
...[
|
|
4081
4147
|
"source.patch",
|
|
4082
4148
|
"source-baseline.tar",
|
|
4083
4149
|
"source-untracked.json",
|
|
4084
4150
|
"source-jev-models.json"
|
|
4085
|
-
].map((file2) => ["source",
|
|
4151
|
+
].map((file2) => ["source", import_node_path6.default.join(directory, file2)])
|
|
4086
4152
|
]) {
|
|
4087
4153
|
try {
|
|
4088
4154
|
permanentArtifacts.push({
|
|
@@ -4096,11 +4162,11 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
|
|
|
4096
4162
|
}
|
|
4097
4163
|
const files = /* @__PURE__ */ new Map();
|
|
4098
4164
|
for (const stage of Object.values(state.stages)) {
|
|
4099
|
-
for (const [relative,
|
|
4165
|
+
for (const [relative, hash] of Object.entries(stage.artifacts)) {
|
|
4100
4166
|
const file = await boundResearchPath(directory, relative);
|
|
4101
4167
|
if (relative.endsWith("/result.json")) {
|
|
4102
4168
|
const result = await readJson(file);
|
|
4103
|
-
if (result?.dataset) files.set(file,
|
|
4169
|
+
if (result?.dataset) files.set(file, hash);
|
|
4104
4170
|
}
|
|
4105
4171
|
if (relative.endsWith("/sources.json") || relative.endsWith("/payloads.json")) {
|
|
4106
4172
|
const sources = await readJson(file);
|
|
@@ -4117,13 +4183,13 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
|
|
|
4117
4183
|
}
|
|
4118
4184
|
}
|
|
4119
4185
|
const entries = [];
|
|
4120
|
-
for (const [file,
|
|
4186
|
+
for (const [file, hash] of files) {
|
|
4121
4187
|
const stat = await import_promises4.default.stat(file);
|
|
4122
4188
|
entries.push({
|
|
4123
4189
|
path: file,
|
|
4124
4190
|
kind: "research_payload",
|
|
4125
4191
|
researchId: spec.core.researchId,
|
|
4126
|
-
sha256:
|
|
4192
|
+
sha256: hash,
|
|
4127
4193
|
bytes: stat.size,
|
|
4128
4194
|
createdAt: stat.birthtimeMs,
|
|
4129
4195
|
verified: true,
|
|
@@ -4142,13 +4208,13 @@ var createResearchRetentionInventory = async (projectRoot, directory, spec, stat
|
|
|
4142
4208
|
};
|
|
4143
4209
|
var applyRetentionInventory = async (projectRoot, directory) => {
|
|
4144
4210
|
const inventory = await readJson(
|
|
4145
|
-
|
|
4211
|
+
import_node_path6.default.join(directory, "retention.json")
|
|
4146
4212
|
);
|
|
4147
|
-
const receipts =
|
|
4213
|
+
const receipts = import_node_path6.default.join(directory, "retention");
|
|
4148
4214
|
await import_promises4.default.mkdir(receipts, { recursive: true });
|
|
4149
4215
|
for (let index = 0; index < inventory.entries.length; index++) {
|
|
4150
4216
|
const entry = inventory.entries[index];
|
|
4151
|
-
const receiptPath =
|
|
4217
|
+
const receiptPath = import_node_path6.default.join(receipts, `${index}.json`);
|
|
4152
4218
|
let receipt;
|
|
4153
4219
|
try {
|
|
4154
4220
|
receipt = await readJson(receiptPath);
|
|
@@ -4184,13 +4250,13 @@ var applyRetentionInventory = async (projectRoot, directory) => {
|
|
|
4184
4250
|
entries: [entry],
|
|
4185
4251
|
receiptPath,
|
|
4186
4252
|
apply: true,
|
|
4187
|
-
protectedPaths: [
|
|
4253
|
+
protectedPaths: [import_node_path6.default.join(directory, "retention.json")]
|
|
4188
4254
|
});
|
|
4189
4255
|
}
|
|
4190
4256
|
return { status: "completed", files: inventory.entries.length };
|
|
4191
4257
|
};
|
|
4192
4258
|
var finishResearchRetention = async (projectRoot, directory) => {
|
|
4193
|
-
const statusPath =
|
|
4259
|
+
const statusPath = import_node_path6.default.join(directory, "retention-status.json");
|
|
4194
4260
|
try {
|
|
4195
4261
|
const result = await applyRetentionInventory(projectRoot, directory);
|
|
4196
4262
|
await writeJsonAtomic(statusPath, result);
|
|
@@ -4217,7 +4283,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4217
4283
|
const directory = researchDirectory(projectRoot, spec);
|
|
4218
4284
|
const lineage = await backend.lineage();
|
|
4219
4285
|
const state = await initializeResearchState(directory, spec, lineage);
|
|
4220
|
-
const statePath =
|
|
4286
|
+
const statePath = import_node_path7.default.join(directory, "state.json");
|
|
4221
4287
|
const elapsedBefore = state.elapsedMs ?? 0;
|
|
4222
4288
|
const activeStart = Date.now();
|
|
4223
4289
|
const elapsed = () => elapsedBefore + Date.now() - activeStart;
|
|
@@ -4227,7 +4293,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4227
4293
|
directory,
|
|
4228
4294
|
state,
|
|
4229
4295
|
conclusion: await readJson(
|
|
4230
|
-
|
|
4296
|
+
import_node_path7.default.join(directory, "result.json")
|
|
4231
4297
|
)
|
|
4232
4298
|
};
|
|
4233
4299
|
}
|
|
@@ -4237,13 +4303,13 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4237
4303
|
return writeJsonAtomic(statePath, state);
|
|
4238
4304
|
};
|
|
4239
4305
|
const event = async (stage2, status) => import_promises5.default.appendFile(
|
|
4240
|
-
|
|
4306
|
+
import_node_path7.default.join(directory, "events.jsonl"),
|
|
4241
4307
|
`${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), stage: stage2, status, specSha256: state.specSha256 })}
|
|
4242
4308
|
`
|
|
4243
4309
|
);
|
|
4244
4310
|
const stage = async (name, work) => {
|
|
4245
4311
|
const stageDirectory = await boundResearchPath(directory, name);
|
|
4246
|
-
const resultPath =
|
|
4312
|
+
const resultPath = import_node_path7.default.join(stageDirectory, "result.json");
|
|
4247
4313
|
if (state.stages[name]?.status === "completed")
|
|
4248
4314
|
return readJson(resultPath);
|
|
4249
4315
|
if (elapsed() > spec.budget.maxMinutes * 6e4)
|
|
@@ -4262,7 +4328,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4262
4328
|
await save();
|
|
4263
4329
|
await event(name, "started");
|
|
4264
4330
|
await import_promises5.default.writeFile(
|
|
4265
|
-
|
|
4331
|
+
import_node_path7.default.join(directory, "report.html"),
|
|
4266
4332
|
buildStrategyResearchReport({ spec, state, diagnostics })
|
|
4267
4333
|
);
|
|
4268
4334
|
try {
|
|
@@ -4270,7 +4336,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4270
4336
|
await writeJsonAtomic(resultPath, result);
|
|
4271
4337
|
const artifacts = {};
|
|
4272
4338
|
for (const file of await import_promises5.default.readdir(stageDirectory)) {
|
|
4273
|
-
const full =
|
|
4339
|
+
const full = import_node_path7.default.join(stageDirectory, file);
|
|
4274
4340
|
if ((await import_promises5.default.stat(full)).isFile())
|
|
4275
4341
|
artifacts[`${name}/${file}`] = await sha256File(full);
|
|
4276
4342
|
}
|
|
@@ -4282,7 +4348,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4282
4348
|
"source-jev-models.json"
|
|
4283
4349
|
]) {
|
|
4284
4350
|
try {
|
|
4285
|
-
artifacts[file] = await sha256File(
|
|
4351
|
+
artifacts[file] = await sha256File(import_node_path7.default.join(directory, file));
|
|
4286
4352
|
} catch (error) {
|
|
4287
4353
|
if (error.code !== "ENOENT")
|
|
4288
4354
|
throw error;
|
|
@@ -4315,14 +4381,14 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4315
4381
|
let previousRunId;
|
|
4316
4382
|
try {
|
|
4317
4383
|
previousRunId = (await readJson(
|
|
4318
|
-
|
|
4384
|
+
import_node_path7.default.join(folder, "native-run.json")
|
|
4319
4385
|
)).runId;
|
|
4320
4386
|
} catch (error) {
|
|
4321
4387
|
if (error.code !== "ENOENT") throw error;
|
|
4322
4388
|
}
|
|
4323
4389
|
if (!previousRunId) {
|
|
4324
4390
|
try {
|
|
4325
|
-
if ((await import_promises5.default.stat(
|
|
4391
|
+
if ((await import_promises5.default.stat(import_node_path7.default.join(folder, "backtest.log"))).size)
|
|
4326
4392
|
throw new Error(
|
|
4327
4393
|
"Previous native attempt has no recorded run identity; inspect its log before another launch"
|
|
4328
4394
|
);
|
|
@@ -4343,7 +4409,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4343
4409
|
},
|
|
4344
4410
|
variant,
|
|
4345
4411
|
folder,
|
|
4346
|
-
async (runId) => writeJsonAtomic(
|
|
4412
|
+
async (runId) => writeJsonAtomic(import_node_path7.default.join(folder, "native-run.json"), { runId }),
|
|
4347
4413
|
previousRunId,
|
|
4348
4414
|
confirmation
|
|
4349
4415
|
);
|
|
@@ -4358,15 +4424,15 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4358
4424
|
try {
|
|
4359
4425
|
const inventory = await stage("01-preflight", async () => {
|
|
4360
4426
|
const prior = [];
|
|
4361
|
-
const root =
|
|
4427
|
+
const root = import_node_path7.default.dirname(directory);
|
|
4362
4428
|
for (const id of await import_promises5.default.readdir(root)) {
|
|
4363
4429
|
if (id === spec.core.researchId || id.startsWith(".")) continue;
|
|
4364
4430
|
try {
|
|
4365
4431
|
const previous = await readJson(
|
|
4366
|
-
|
|
4432
|
+
import_node_path7.default.join(root, id, "spec.json")
|
|
4367
4433
|
);
|
|
4368
4434
|
const previousState = await readJson(
|
|
4369
|
-
|
|
4435
|
+
import_node_path7.default.join(root, id, "state.json")
|
|
4370
4436
|
);
|
|
4371
4437
|
const overlap = previous.core.strategy === spec.core.strategy && previous.core.window.start < spec.core.window.end && previous.core.window.end > (spec.testSince || spec.core.window.start);
|
|
4372
4438
|
if (overlap && previousState.holdoutOpenedAt)
|
|
@@ -4383,7 +4449,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4383
4449
|
});
|
|
4384
4450
|
let partitionControl;
|
|
4385
4451
|
if (spec.testSince === 0) {
|
|
4386
|
-
const
|
|
4452
|
+
const control = spec.core.variants.find(
|
|
4387
4453
|
(variant) => variant.role === "control"
|
|
4388
4454
|
);
|
|
4389
4455
|
partitionControl = await stage(
|
|
@@ -4392,7 +4458,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4392
4458
|
let previous;
|
|
4393
4459
|
try {
|
|
4394
4460
|
previous = (await readJson(
|
|
4395
|
-
|
|
4461
|
+
import_node_path7.default.join(folder, "native-run.json")
|
|
4396
4462
|
)).runId;
|
|
4397
4463
|
} catch (error) {
|
|
4398
4464
|
if (error.code !== "ENOENT")
|
|
@@ -4400,9 +4466,9 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4400
4466
|
}
|
|
4401
4467
|
return backend.runVariant(
|
|
4402
4468
|
spec,
|
|
4403
|
-
|
|
4469
|
+
control,
|
|
4404
4470
|
folder,
|
|
4405
|
-
async (runId) => writeJsonAtomic(
|
|
4471
|
+
async (runId) => writeJsonAtomic(import_node_path7.default.join(folder, "native-run.json"), {
|
|
4406
4472
|
runId
|
|
4407
4473
|
}),
|
|
4408
4474
|
previous
|
|
@@ -4416,15 +4482,12 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4416
4482
|
);
|
|
4417
4483
|
spec = { ...spec, testSince: partition.testSince };
|
|
4418
4484
|
}
|
|
4419
|
-
await writeJsonAtomic(
|
|
4485
|
+
await writeJsonAtomic(import_node_path7.default.join(directory, "resolved-spec.json"), spec);
|
|
4420
4486
|
let gates = [];
|
|
4421
4487
|
let primaryId = null;
|
|
4422
4488
|
let frozenSelection;
|
|
4423
4489
|
const raw = {};
|
|
4424
4490
|
const discovered = [];
|
|
4425
|
-
const control = spec.core.variants.find(
|
|
4426
|
-
(variant) => variant.role === "control"
|
|
4427
|
-
);
|
|
4428
4491
|
const currentPolicy = {
|
|
4429
4492
|
schema: "tradejs-research-entry-policy/v1",
|
|
4430
4493
|
strategy: spec.core.strategy,
|
|
@@ -4475,10 +4538,10 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4475
4538
|
core: { ...spec.core, researchId: spec.frozenFrom }
|
|
4476
4539
|
});
|
|
4477
4540
|
const parentState = await readJson(
|
|
4478
|
-
|
|
4541
|
+
import_node_path7.default.join(parentDirectory, "state.json")
|
|
4479
4542
|
);
|
|
4480
4543
|
const parentSpec = await readJson(
|
|
4481
|
-
|
|
4544
|
+
import_node_path7.default.join(parentDirectory, "spec.json")
|
|
4482
4545
|
);
|
|
4483
4546
|
const { verifyCompletedResearch: verifyCompletedResearch2 } = await Promise.resolve().then(() => (init_io2(), io_exports));
|
|
4484
4547
|
await verifyCompletedResearch2(parentDirectory, parentState);
|
|
@@ -4505,10 +4568,10 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4505
4568
|
"Frozen revalidation requires complete, unchanged rules/runtime and a strictly new period"
|
|
4506
4569
|
);
|
|
4507
4570
|
gates = await readJson(
|
|
4508
|
-
|
|
4571
|
+
import_node_path7.default.join(parentDirectory, "03-freeze-gates/result.json")
|
|
4509
4572
|
);
|
|
4510
4573
|
frozenSelection = await readJson(
|
|
4511
|
-
|
|
4574
|
+
import_node_path7.default.join(parentDirectory, "05-freeze-primary/result.json")
|
|
4512
4575
|
);
|
|
4513
4576
|
if (frozenSelection.schema !== "tradejs-research-selection/v1" || !Array.isArray(frozenSelection.compositions))
|
|
4514
4577
|
throw new Error(
|
|
@@ -4637,7 +4700,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4637
4700
|
`06-raw-metrics-${variant.id}`,
|
|
4638
4701
|
async (folder) => {
|
|
4639
4702
|
const resolved = spec.frozenFrom ? (await run(spec, variant, `06-raw-revalidation-${variant.id}`)).variant : (await readJson(
|
|
4640
|
-
|
|
4703
|
+
import_node_path7.default.join(directory, `02-raw-${variant.id}`, "result.json")
|
|
4641
4704
|
)).variant;
|
|
4642
4705
|
const dataset = await backend.loadDataset(
|
|
4643
4706
|
spec,
|
|
@@ -4814,7 +4877,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4814
4877
|
for (const name of Object.keys(state.stages)) {
|
|
4815
4878
|
try {
|
|
4816
4879
|
const result = await readJson(
|
|
4817
|
-
|
|
4880
|
+
import_node_path7.default.join(directory, name, "result.json")
|
|
4818
4881
|
);
|
|
4819
4882
|
if (result?.variant)
|
|
4820
4883
|
executions.push({ stage: name, ...result.variant });
|
|
@@ -4822,7 +4885,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4822
4885
|
if (error.code !== "ENOENT") throw error;
|
|
4823
4886
|
}
|
|
4824
4887
|
}
|
|
4825
|
-
await writeJsonAtomic(
|
|
4888
|
+
await writeJsonAtomic(import_node_path7.default.join(directory, "result.json"), {
|
|
4826
4889
|
...conclusion,
|
|
4827
4890
|
executions,
|
|
4828
4891
|
rawCore: Object.entries(rawReports).map(([id, windows]) => ({
|
|
@@ -4836,7 +4899,7 @@ var runStrategyResearch = async (parameters) => {
|
|
|
4836
4899
|
delete state.error;
|
|
4837
4900
|
await save();
|
|
4838
4901
|
await import_promises5.default.writeFile(
|
|
4839
|
-
|
|
4902
|
+
import_node_path7.default.join(directory, "report.html"),
|
|
4840
4903
|
buildStrategyResearchReport({
|
|
4841
4904
|
spec,
|
|
4842
4905
|
state,
|
|
@@ -4900,7 +4963,7 @@ ${JSON.stringify(spec, null, 2)}
|
|
|
4900
4963
|
## Metrics snapshot (machine-readable)
|
|
4901
4964
|
|
|
4902
4965
|
\`\`\`json
|
|
4903
|
-
${JSON.stringify(encodeResearchMetricSnapshot(compact))}
|
|
4966
|
+
${JSON.stringify((0, import_researchMetricSnapshot2.encodeResearchMetricSnapshot)(compact))}
|
|
4904
4967
|
\`\`\`
|
|
4905
4968
|
|
|
4906
4969
|
## Reported metrics
|
|
@@ -4924,7 +4987,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
|
|
|
4924
4987
|
"Compact permanent note exceeds 8 MiB; archive source separately, never truncate metrics"
|
|
4925
4988
|
);
|
|
4926
4989
|
await import_promises5.default.writeFile(
|
|
4927
|
-
|
|
4990
|
+
import_node_path7.default.join(
|
|
4928
4991
|
noteDirectory,
|
|
4929
4992
|
`${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
|
|
4930
4993
|
),
|
|
@@ -4933,7 +4996,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
|
|
|
4933
4996
|
).catch(async (error) => {
|
|
4934
4997
|
if (error.code !== "EEXIST") throw error;
|
|
4935
4998
|
const existing = await import_promises5.default.readFile(
|
|
4936
|
-
|
|
4999
|
+
import_node_path7.default.join(
|
|
4937
5000
|
noteDirectory,
|
|
4938
5001
|
`${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
|
|
4939
5002
|
),
|
|
@@ -4945,19 +5008,19 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
|
|
|
4945
5008
|
);
|
|
4946
5009
|
});
|
|
4947
5010
|
await writeJsonAtomic(
|
|
4948
|
-
|
|
5011
|
+
import_node_path7.default.join(directory, "retention.json"),
|
|
4949
5012
|
await createResearchRetentionInventory(
|
|
4950
5013
|
projectRoot,
|
|
4951
5014
|
directory,
|
|
4952
5015
|
spec,
|
|
4953
5016
|
state,
|
|
4954
|
-
|
|
5017
|
+
import_node_path7.default.join(
|
|
4955
5018
|
noteDirectory,
|
|
4956
5019
|
`${spec.core.createdAt.slice(0, 10)}-${spec.core.researchId}.md`
|
|
4957
5020
|
)
|
|
4958
5021
|
)
|
|
4959
5022
|
);
|
|
4960
|
-
await writeJsonAtomic(
|
|
5023
|
+
await writeJsonAtomic(import_node_path7.default.join(directory, "manifest.json"), {
|
|
4961
5024
|
schema: "tradejs-strategy-research-manifest/v1",
|
|
4962
5025
|
specSha256: state.specSha256,
|
|
4963
5026
|
artifacts: Object.fromEntries(
|
|
@@ -4971,7 +5034,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
|
|
|
4971
5034
|
"retention.json"
|
|
4972
5035
|
].map(async (file) => [
|
|
4973
5036
|
file,
|
|
4974
|
-
await sha256File(
|
|
5037
|
+
await sha256File(import_node_path7.default.join(directory, file))
|
|
4975
5038
|
])
|
|
4976
5039
|
)
|
|
4977
5040
|
)
|
|
@@ -4984,7 +5047,7 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
|
|
|
4984
5047
|
);
|
|
4985
5048
|
}
|
|
4986
5049
|
parameters.onProgress?.(
|
|
4987
|
-
`\u0418\u0442\u043E\u0433: ${conclusion.status}. \u041E\u0442\u0447\u0451\u0442: ${
|
|
5050
|
+
`\u0418\u0442\u043E\u0433: ${conclusion.status}. \u041E\u0442\u0447\u0451\u0442: ${import_node_path7.default.join(directory, "report.html")}`
|
|
4988
5051
|
);
|
|
4989
5052
|
return { directory, state, conclusion };
|
|
4990
5053
|
} catch (error) {
|
|
@@ -5005,9 +5068,9 @@ ${conclusion.nextAction} Current-snapshot universe; no deployment authorization.
|
|
|
5005
5068
|
frozenGates: 0
|
|
5006
5069
|
}
|
|
5007
5070
|
};
|
|
5008
|
-
await writeJsonAtomic(
|
|
5071
|
+
await writeJsonAtomic(import_node_path7.default.join(directory, "result.json"), conclusion);
|
|
5009
5072
|
await import_promises5.default.writeFile(
|
|
5010
|
-
|
|
5073
|
+
import_node_path7.default.join(directory, "report.html"),
|
|
5011
5074
|
buildStrategyResearchReport({ spec, state, conclusion, diagnostics })
|
|
5012
5075
|
);
|
|
5013
5076
|
throw error;
|
|
@@ -5030,10 +5093,10 @@ var import_registry = require("@tradejs/node/registry");
|
|
|
5030
5093
|
var import_connectors = require("@tradejs/node/connectors");
|
|
5031
5094
|
|
|
5032
5095
|
// src/lib/coreResearch/dataset.ts
|
|
5033
|
-
var
|
|
5034
|
-
var
|
|
5035
|
-
var
|
|
5036
|
-
var
|
|
5096
|
+
var import_node_crypto4 = require("crypto");
|
|
5097
|
+
var import_node_fs3 = require("fs");
|
|
5098
|
+
var import_node_path8 = __toESM(require("path"));
|
|
5099
|
+
var import_node_readline2 = __toESM(require("readline"));
|
|
5037
5100
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5038
5101
|
var finiteNumber = (value, field) => {
|
|
5039
5102
|
const numeric = Number(value);
|
|
@@ -5243,11 +5306,11 @@ var readCoreResearchVariant = async (variant) => {
|
|
|
5243
5306
|
const seen = /* @__PURE__ */ new Map();
|
|
5244
5307
|
let duplicateRowsDropped = 0;
|
|
5245
5308
|
for (const inputPath of variant.files) {
|
|
5246
|
-
const filePath =
|
|
5247
|
-
const input = (0,
|
|
5248
|
-
const
|
|
5249
|
-
input.on("data", (chunk) =>
|
|
5250
|
-
const lines =
|
|
5309
|
+
const filePath = import_node_path8.default.resolve(inputPath);
|
|
5310
|
+
const input = (0, import_node_fs3.createReadStream)(filePath);
|
|
5311
|
+
const hash = (0, import_node_crypto4.createHash)("sha256");
|
|
5312
|
+
input.on("data", (chunk) => hash.update(chunk));
|
|
5313
|
+
const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
|
|
5251
5314
|
const fileTrades = [];
|
|
5252
5315
|
let lineNumber = 0;
|
|
5253
5316
|
let rows = 0;
|
|
@@ -5296,7 +5359,7 @@ var readCoreResearchVariant = async (variant) => {
|
|
|
5296
5359
|
trades.push(trade);
|
|
5297
5360
|
fileTrades.push(trade);
|
|
5298
5361
|
}
|
|
5299
|
-
const fileSha2562 =
|
|
5362
|
+
const fileSha2562 = hash.digest("hex");
|
|
5300
5363
|
for (const trade of fileTrades) trade.sourceSha256 = fileSha2562;
|
|
5301
5364
|
sources.push({
|
|
5302
5365
|
path: filePath,
|
|
@@ -5316,7 +5379,7 @@ var readCoreResearchVariant = async (variant) => {
|
|
|
5316
5379
|
};
|
|
5317
5380
|
|
|
5318
5381
|
// src/lib/backtest/checkpoint.ts
|
|
5319
|
-
var
|
|
5382
|
+
var import_node_crypto5 = require("crypto");
|
|
5320
5383
|
var import_constants = require("@tradejs/core/constants");
|
|
5321
5384
|
var import_backtest2 = require("@tradejs/core/backtest");
|
|
5322
5385
|
var import_redis = require("@tradejs/infra/redis");
|
|
@@ -5530,9 +5593,9 @@ init_io2();
|
|
|
5530
5593
|
|
|
5531
5594
|
// src/lib/aiTrainResearch.ts
|
|
5532
5595
|
var import_node_child_process2 = require("child_process");
|
|
5533
|
-
var
|
|
5596
|
+
var import_node_crypto7 = require("crypto");
|
|
5534
5597
|
var import_promises7 = __toESM(require("fs/promises"));
|
|
5535
|
-
var
|
|
5598
|
+
var import_node_path10 = __toESM(require("path"));
|
|
5536
5599
|
|
|
5537
5600
|
// src/lib/aiTrainMetrics.ts
|
|
5538
5601
|
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
@@ -5542,10 +5605,10 @@ var import_strategies = require("@tradejs/node/strategies");
|
|
|
5542
5605
|
var import_types2 = require("@tradejs/types");
|
|
5543
5606
|
|
|
5544
5607
|
// src/lib/strategyGateFingerprint.ts
|
|
5545
|
-
var
|
|
5608
|
+
var import_node_crypto6 = require("crypto");
|
|
5546
5609
|
var import_node_module = require("module");
|
|
5547
5610
|
var import_promises6 = __toESM(require("fs/promises"));
|
|
5548
|
-
var
|
|
5611
|
+
var import_node_path9 = __toESM(require("path"));
|
|
5549
5612
|
|
|
5550
5613
|
// src/lib/aiTrainResearch.ts
|
|
5551
5614
|
var DAY_MS4 = 24 * 60 * 60 * 1e3;
|
|
@@ -5586,7 +5649,7 @@ var normalizeForStableJson = (value) => {
|
|
|
5586
5649
|
}
|
|
5587
5650
|
return value;
|
|
5588
5651
|
};
|
|
5589
|
-
var fingerprintResearchValue = (value) => (0,
|
|
5652
|
+
var fingerprintResearchValue = (value) => (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(normalizeForStableJson(value))).digest("hex").slice(0, 16);
|
|
5590
5653
|
var fingerprintResearchContextEnvironment = (env = process.env) => fingerprintResearchValue(
|
|
5591
5654
|
Object.fromEntries(
|
|
5592
5655
|
RESEARCH_CONTEXT_ENV_KEYS.map((key) => [key, env[key] ?? null])
|
|
@@ -5596,81 +5659,7 @@ var fingerprintResearchContextEnvironment = (env = process.env) => fingerprintRe
|
|
|
5596
5659
|
// src/lib/strategyResearch/native.ts
|
|
5597
5660
|
var import_data = require("@tradejs/core/data");
|
|
5598
5661
|
var import_coreResearch = require("@tradejs/infra/coreResearch");
|
|
5599
|
-
|
|
5600
|
-
// src/lib/coreResearch/trace.ts
|
|
5601
|
-
var import_node_fs3 = require("fs");
|
|
5602
|
-
var import_node_readline2 = __toESM(require("readline"));
|
|
5603
|
-
var import_node_path10 = __toESM(require("path"));
|
|
5604
|
-
var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
5605
|
-
const events = /* @__PURE__ */ Object.create(null);
|
|
5606
|
-
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
5607
|
-
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
5608
|
-
const policyRejections = {};
|
|
5609
|
-
const policyExamples = [];
|
|
5610
|
-
for (const inputPath of filePaths) {
|
|
5611
|
-
const filePath = import_node_path10.default.resolve(inputPath);
|
|
5612
|
-
const input = (0, import_node_fs3.createReadStream)(filePath);
|
|
5613
|
-
const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
|
|
5614
|
-
let index = -1;
|
|
5615
|
-
try {
|
|
5616
|
-
for await (const line of lines) {
|
|
5617
|
-
index++;
|
|
5618
|
-
if (!line.trim()) continue;
|
|
5619
|
-
let event;
|
|
5620
|
-
try {
|
|
5621
|
-
event = JSON.parse(line);
|
|
5622
|
-
} catch (error) {
|
|
5623
|
-
throw new Error(
|
|
5624
|
-
`${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
|
|
5625
|
-
);
|
|
5626
|
-
}
|
|
5627
|
-
events[event.event] = (events[event.event] ?? 0) + 1;
|
|
5628
|
-
if (event.event === "skip_summary") {
|
|
5629
|
-
for (const [reason, count] of Object.entries(event.skipCounts)) {
|
|
5630
|
-
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
5631
|
-
}
|
|
5632
|
-
}
|
|
5633
|
-
if (event.event === "position_exited" && event.exitCode) {
|
|
5634
|
-
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
5635
|
-
}
|
|
5636
|
-
if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
|
|
5637
|
-
const reason = event.researchEntryDecision.reason;
|
|
5638
|
-
policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
|
|
5639
|
-
if (policyExamples.length < 20)
|
|
5640
|
-
policyExamples.push({
|
|
5641
|
-
timestamp: event.timestamp,
|
|
5642
|
-
symbol: event.symbol,
|
|
5643
|
-
decision: event.researchEntryDecision
|
|
5644
|
-
});
|
|
5645
|
-
}
|
|
5646
|
-
}
|
|
5647
|
-
} finally {
|
|
5648
|
-
lines.close();
|
|
5649
|
-
input.destroy();
|
|
5650
|
-
}
|
|
5651
|
-
}
|
|
5652
|
-
return {
|
|
5653
|
-
events: Object.fromEntries(Object.entries(events).sort()),
|
|
5654
|
-
skipCounts: Object.fromEntries(
|
|
5655
|
-
Object.entries(skipCounts).sort(
|
|
5656
|
-
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
5657
|
-
)
|
|
5658
|
-
),
|
|
5659
|
-
exitCodes: Object.fromEntries(
|
|
5660
|
-
Object.entries(exitCodes).sort(
|
|
5661
|
-
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
5662
|
-
)
|
|
5663
|
-
),
|
|
5664
|
-
...policyExamples.length ? {
|
|
5665
|
-
researchPolicy: {
|
|
5666
|
-
rejections: policyRejections,
|
|
5667
|
-
examples: policyExamples
|
|
5668
|
-
}
|
|
5669
|
-
} : {}
|
|
5670
|
-
};
|
|
5671
|
-
};
|
|
5672
|
-
|
|
5673
|
-
// src/lib/strategyResearch/native.ts
|
|
5662
|
+
init_trace();
|
|
5674
5663
|
var import_jev4 = require("@tradejs/core/jev");
|
|
5675
5664
|
|
|
5676
5665
|
// src/lib/backtest/jevOptions.ts
|