@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
|
@@ -380,13 +380,12 @@ var getReferenceSymbols = () => {
|
|
|
380
380
|
};
|
|
381
381
|
var intervalToMarketFeatureInterval = (interval) => {
|
|
382
382
|
const normalized = String(interval).trim().toLowerCase();
|
|
383
|
-
if (normalized === "1" || normalized === "1m") return
|
|
383
|
+
if (normalized === "1" || normalized === "1m") return null;
|
|
384
384
|
if (normalized === "5" || normalized === "5m") return "5m";
|
|
385
385
|
if (normalized === "60" || normalized === "1h") return "1h";
|
|
386
386
|
return "15m";
|
|
387
387
|
};
|
|
388
388
|
var marketIntervalToConnectorInterval = (interval) => {
|
|
389
|
-
if (interval === "1m") return "1";
|
|
390
389
|
if (interval === "5m") return "5";
|
|
391
390
|
if (interval === "1h") return "60";
|
|
392
391
|
return "15";
|
|
@@ -551,6 +550,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
551
550
|
return skippedBackfillResult();
|
|
552
551
|
}
|
|
553
552
|
const interval = intervalToMarketFeatureInterval(params.interval);
|
|
553
|
+
if (interval === null) return skippedBackfillResult();
|
|
554
554
|
const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
|
|
555
555
|
const { breadthStartMs, tradeFlowStartMs, endMs } = resolveBinanceMarketContextBackfillWindow(params);
|
|
556
556
|
if (endMs <= Math.min(breadthStartMs, tradeFlowStartMs)) {
|
package/dist/lib/coreResearch.js
CHANGED
|
@@ -52,17 +52,168 @@ 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";
|
|
@@ -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(
|
|
@@ -1461,9 +1677,9 @@ var compareCoreResearchVariants = (params) => {
|
|
|
1461
1677
|
|
|
1462
1678
|
// src/lib/coreResearch/dataset.ts
|
|
1463
1679
|
var import_node_crypto3 = require("crypto");
|
|
1464
|
-
var
|
|
1465
|
-
var
|
|
1466
|
-
var
|
|
1680
|
+
var import_node_fs4 = require("fs");
|
|
1681
|
+
var import_node_path4 = __toESM(require("path"));
|
|
1682
|
+
var import_node_readline2 = __toESM(require("readline"));
|
|
1467
1683
|
var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1468
1684
|
var finiteNumber = (value, field) => {
|
|
1469
1685
|
const numeric = Number(value);
|
|
@@ -1673,11 +1889,11 @@ var readCoreResearchVariant = async (variant) => {
|
|
|
1673
1889
|
const seen = /* @__PURE__ */ new Map();
|
|
1674
1890
|
let duplicateRowsDropped = 0;
|
|
1675
1891
|
for (const inputPath of variant.files) {
|
|
1676
|
-
const filePath =
|
|
1677
|
-
const input = (0,
|
|
1892
|
+
const filePath = import_node_path4.default.resolve(inputPath);
|
|
1893
|
+
const input = (0, import_node_fs4.createReadStream)(filePath);
|
|
1678
1894
|
const hash = (0, import_node_crypto3.createHash)("sha256");
|
|
1679
1895
|
input.on("data", (chunk) => hash.update(chunk));
|
|
1680
|
-
const lines =
|
|
1896
|
+
const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
|
|
1681
1897
|
const fileTrades = [];
|
|
1682
1898
|
let lineNumber = 0;
|
|
1683
1899
|
let rows = 0;
|
|
@@ -2165,79 +2381,6 @@ var applyCoreResearchRobustnessGuardrails = (params) => {
|
|
|
2165
2381
|
}
|
|
2166
2382
|
};
|
|
2167
2383
|
|
|
2168
|
-
// src/lib/coreResearch/trace.ts
|
|
2169
|
-
var import_node_fs4 = require("fs");
|
|
2170
|
-
var import_node_readline2 = __toESM(require("readline"));
|
|
2171
|
-
var import_node_path4 = __toESM(require("path"));
|
|
2172
|
-
var summarizeCoreResearchTrace = async (filePaths = []) => {
|
|
2173
|
-
const events = /* @__PURE__ */ Object.create(null);
|
|
2174
|
-
const skipCounts = /* @__PURE__ */ Object.create(null);
|
|
2175
|
-
const exitCodes = /* @__PURE__ */ Object.create(null);
|
|
2176
|
-
const policyRejections = {};
|
|
2177
|
-
const policyExamples = [];
|
|
2178
|
-
for (const inputPath of filePaths) {
|
|
2179
|
-
const filePath = import_node_path4.default.resolve(inputPath);
|
|
2180
|
-
const input = (0, import_node_fs4.createReadStream)(filePath);
|
|
2181
|
-
const lines = import_node_readline2.default.createInterface({ input, crlfDelay: Infinity });
|
|
2182
|
-
let index = -1;
|
|
2183
|
-
try {
|
|
2184
|
-
for await (const line of lines) {
|
|
2185
|
-
index++;
|
|
2186
|
-
if (!line.trim()) continue;
|
|
2187
|
-
let event;
|
|
2188
|
-
try {
|
|
2189
|
-
event = JSON.parse(line);
|
|
2190
|
-
} catch (error) {
|
|
2191
|
-
throw new Error(
|
|
2192
|
-
`${filePath}:${index + 1} contains invalid trace JSON: ${String(error)}`
|
|
2193
|
-
);
|
|
2194
|
-
}
|
|
2195
|
-
events[event.event] = (events[event.event] ?? 0) + 1;
|
|
2196
|
-
if (event.event === "skip_summary") {
|
|
2197
|
-
for (const [reason, count] of Object.entries(event.skipCounts)) {
|
|
2198
|
-
skipCounts[reason] = (skipCounts[reason] ?? 0) + count;
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
if (event.event === "position_exited" && event.exitCode) {
|
|
2202
|
-
exitCodes[event.exitCode] = (exitCodes[event.exitCode] ?? 0) + 1;
|
|
2203
|
-
}
|
|
2204
|
-
if ("researchEntryDecision" in event && event.researchEntryDecision && !event.researchEntryDecision.allowed) {
|
|
2205
|
-
const reason = event.researchEntryDecision.reason;
|
|
2206
|
-
policyRejections[reason] = (policyRejections[reason] ?? 0) + 1;
|
|
2207
|
-
if (policyExamples.length < 20)
|
|
2208
|
-
policyExamples.push({
|
|
2209
|
-
timestamp: event.timestamp,
|
|
2210
|
-
symbol: event.symbol,
|
|
2211
|
-
decision: event.researchEntryDecision
|
|
2212
|
-
});
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
} finally {
|
|
2216
|
-
lines.close();
|
|
2217
|
-
input.destroy();
|
|
2218
|
-
}
|
|
2219
|
-
}
|
|
2220
|
-
return {
|
|
2221
|
-
events: Object.fromEntries(Object.entries(events).sort()),
|
|
2222
|
-
skipCounts: Object.fromEntries(
|
|
2223
|
-
Object.entries(skipCounts).sort(
|
|
2224
|
-
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2225
|
-
)
|
|
2226
|
-
),
|
|
2227
|
-
exitCodes: Object.fromEntries(
|
|
2228
|
-
Object.entries(exitCodes).sort(
|
|
2229
|
-
([leftKey, leftValue], [rightKey, rightValue]) => rightValue - leftValue || (leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0)
|
|
2230
|
-
)
|
|
2231
|
-
),
|
|
2232
|
-
...policyExamples.length ? {
|
|
2233
|
-
researchPolicy: {
|
|
2234
|
-
rejections: policyRejections,
|
|
2235
|
-
examples: policyExamples
|
|
2236
|
-
}
|
|
2237
|
-
} : {}
|
|
2238
|
-
};
|
|
2239
|
-
};
|
|
2240
|
-
|
|
2241
2384
|
// src/lib/researchRoots.ts
|
|
2242
2385
|
var import_node_path5 = __toESM(require("path"));
|
|
2243
2386
|
var SOURCE_REPOSITORY_ROOT_ENV = "TRADEJS_SOURCE_REPOSITORY_ROOT";
|
|
@@ -2261,6 +2404,7 @@ var summarizeSupplementalFiles = async (params) => {
|
|
|
2261
2404
|
var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
2262
2405
|
const prepared = options.prepared ?? await prepareCoreResearch(spec);
|
|
2263
2406
|
const { paths, specSha256 } = prepared;
|
|
2407
|
+
await verifyPreparedSupport(prepared);
|
|
2264
2408
|
if (prepared.manifest.status === "completed") {
|
|
2265
2409
|
throw new Error(
|
|
2266
2410
|
`Research ${spec.researchId} is already completed and immutable; use verify or create a new researchId`
|
|
@@ -2531,6 +2675,7 @@ var analyzeCoreResearchPrepared = async (spec, options = {}) => {
|
|
|
2531
2675
|
"utf8"
|
|
2532
2676
|
);
|
|
2533
2677
|
const artifactHashes = {
|
|
2678
|
+
...prepared.manifest.artifactHashes,
|
|
2534
2679
|
"spec.json": await sha256File(paths.specPath),
|
|
2535
2680
|
"result.json": await sha256File(paths.resultPath),
|
|
2536
2681
|
"comparison.json": await sha256File(paths.comparisonPath),
|
|
@@ -2821,6 +2966,7 @@ var runCoreResearch = async (params) => {
|
|
|
2821
2966
|
const { spec } = params;
|
|
2822
2967
|
const cwd = import_node_path6.default.resolve(params.cwd ?? process.cwd());
|
|
2823
2968
|
const prepared = await prepareCoreResearch(spec);
|
|
2969
|
+
await verifyPreparedSupport(prepared);
|
|
2824
2970
|
const resolvedVariants = [];
|
|
2825
2971
|
for (const variant of spec.variants) {
|
|
2826
2972
|
if (!variant.command?.length) {
|
|
@@ -381,13 +381,12 @@ var getReferenceSymbols = () => {
|
|
|
381
381
|
};
|
|
382
382
|
var intervalToMarketFeatureInterval = (interval) => {
|
|
383
383
|
const normalized = String(interval).trim().toLowerCase();
|
|
384
|
-
if (normalized === "1" || normalized === "1m") return
|
|
384
|
+
if (normalized === "1" || normalized === "1m") return null;
|
|
385
385
|
if (normalized === "5" || normalized === "5m") return "5m";
|
|
386
386
|
if (normalized === "60" || normalized === "1h") return "1h";
|
|
387
387
|
return "15m";
|
|
388
388
|
};
|
|
389
389
|
var marketIntervalToConnectorInterval = (interval) => {
|
|
390
|
-
if (interval === "1m") return "1";
|
|
391
390
|
if (interval === "5m") return "5";
|
|
392
391
|
if (interval === "1h") return "60";
|
|
393
392
|
return "15";
|
|
@@ -552,6 +551,7 @@ var backfillBinanceMarketContext = async (params, enabled) => {
|
|
|
552
551
|
return skippedBackfillResult();
|
|
553
552
|
}
|
|
554
553
|
const interval = intervalToMarketFeatureInterval(params.interval);
|
|
554
|
+
if (interval === null) return skippedBackfillResult();
|
|
555
555
|
const intervalMs = MARKET_FEATURE_INTERVAL_MS[interval];
|
|
556
556
|
const { breadthStartMs, tradeFlowStartMs, endMs } = resolveBinanceMarketContextBackfillWindow(params);
|
|
557
557
|
if (endMs <= Math.min(breadthStartMs, tradeFlowStartMs)) {
|
|
@@ -2132,7 +2132,7 @@ var coinMarketCapFetch = async (params) => {
|
|
|
2132
2132
|
};
|
|
2133
2133
|
var fetchCoinMarketCapIndexRows = async (params) => {
|
|
2134
2134
|
const rows = [];
|
|
2135
|
-
const maxChunkDays =
|
|
2135
|
+
const maxChunkDays = 4;
|
|
2136
2136
|
const totalChunks = Math.max(
|
|
2137
2137
|
1,
|
|
2138
2138
|
Math.ceil(
|