@tradejs/cli 3.1.28 → 3.1.29-beta.260

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.
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/lib/jevResearch.ts
31
+ var jevResearch_exports = {};
32
+ __export(jevResearch_exports, {
33
+ readJevJsonl: () => readJevJsonl,
34
+ runJevResearch: () => runJevResearch
35
+ });
36
+ module.exports = __toCommonJS(jevResearch_exports);
37
+ var import_promises = __toESM(require("fs/promises"));
38
+ var import_node_fs = require("fs");
39
+ var import_node_readline = __toESM(require("readline"));
40
+ var import_node_path = __toESM(require("path"));
41
+ var import_node_crypto = require("crypto");
42
+ var import_jev = require("@tradejs/infra/jev");
43
+ var import_jev2 = require("@tradejs/node/jev");
44
+ var readJevJsonl = async (file) => {
45
+ const rows = [];
46
+ const stream = (0, import_node_fs.createReadStream)(file);
47
+ const reader = import_node_readline.default.createInterface({
48
+ input: stream,
49
+ crlfDelay: Infinity
50
+ });
51
+ try {
52
+ for await (const line of reader) {
53
+ if (!line.trim()) continue;
54
+ if (rows.length >= 2e4)
55
+ throw new Error("Select at most 20000 rows for this bounded Jev study");
56
+ rows.push(JSON.parse(line));
57
+ }
58
+ } finally {
59
+ reader.close();
60
+ stream.destroy();
61
+ }
62
+ return rows;
63
+ };
64
+ var writeNew = async (file, contents) => {
65
+ await import_promises.default.mkdir(import_node_path.default.dirname(file), { recursive: true });
66
+ await import_promises.default.writeFile(file, contents, { flag: "wx", mode: 384 });
67
+ };
68
+ var runJevResearch = async (options) => {
69
+ const resolve = (file) => import_node_path.default.resolve(options.projectRoot, file);
70
+ const out = resolve(options.out);
71
+ try {
72
+ await import_promises.default.access(out);
73
+ throw new Error("Jev output already exists; choose a new output path");
74
+ } catch (error) {
75
+ if (error.code !== "ENOENT") throw error;
76
+ }
77
+ if (options.action === "evaluate") {
78
+ if (!options.input)
79
+ throw new Error("Provide an AI dataset JSONL with --input");
80
+ const rows2 = await readJevJsonl(resolve(options.input));
81
+ const selected = rows2.filter(
82
+ (row) => !options.strategy || row.strategyName === options.strategy
83
+ );
84
+ if (!selected.length) throw new Error("No matching Jev input rows");
85
+ for (const row of selected) {
86
+ if (!row.assessment?.recordId)
87
+ throw new Error(
88
+ `Signal ${row.signalId} has no Jev recording. Run the backtest with --ai --jev and use its --jevRecordsDir; an --ai-only export cannot restore the signal-time Jev facts.`
89
+ );
90
+ if (!/^[a-f0-9]{64}$/.test(row.assessment.recordId))
91
+ throw new Error(`Invalid Jev recording id for signal ${row.signalId}`);
92
+ }
93
+ await import_promises.default.mkdir(import_node_path.default.dirname(out), { recursive: true });
94
+ const temp = `${out}.${(0, import_node_crypto.randomUUID)()}.tmp`;
95
+ try {
96
+ const handle = await import_promises.default.open(temp, "wx", 384);
97
+ try {
98
+ for (const row of selected) {
99
+ const recordId = row.assessment.recordId;
100
+ const saved = await (0, import_jev.readJevArtifact)(
101
+ resolve(
102
+ import_node_path.default.join(options.recordsDir, "records", `${recordId}.json`)
103
+ )
104
+ );
105
+ if (!saved)
106
+ throw new Error(
107
+ `Jev recording ${recordId} for signal ${row.signalId} is missing from --jevRecordsDir. Use the recordings from the original --ai --jev backtest.`
108
+ );
109
+ if (saved.schema !== "tradejs-jev-record/v4")
110
+ throw new Error(
111
+ `Jev recording ${recordId} uses an older schema; rerun the backtest with the current --jev integration.`
112
+ );
113
+ if (saved.id !== recordId || saved.inputHash !== row.assessment?.inputHash || saved.questionsHash !== row.assessment?.questionsHash)
114
+ throw new Error(
115
+ `Jev recording ${recordId} does not match signal ${row.signalId}`
116
+ );
117
+ const { record } = await (0, import_jev2.evaluateJevInput)({
118
+ input: saved.input,
119
+ config: {
120
+ source: "recorded",
121
+ mode: "observe",
122
+ provider: saved.provider,
123
+ recordsDir: options.recordsDir
124
+ },
125
+ userName: options.userName,
126
+ projectRoot: options.projectRoot
127
+ });
128
+ if (!record)
129
+ throw new Error(
130
+ `No eligible Jev questions for signal ${row.signalId}`
131
+ );
132
+ const study = {
133
+ schema: "tradejs-jev-study/v4",
134
+ signalId: row.signalId,
135
+ record,
136
+ ...Number.isFinite(row.profit) ? { profit: row.profit } : {}
137
+ };
138
+ await handle.write(`${JSON.stringify(study)}
139
+ `);
140
+ }
141
+ } finally {
142
+ await handle.close();
143
+ }
144
+ await import_promises.default.link(temp, out);
145
+ } finally {
146
+ await import_promises.default.rm(temp, { force: true });
147
+ }
148
+ return { rows: selected.length, out };
149
+ }
150
+ if (options.action === "export") {
151
+ const dir = resolve(import_node_path.default.join(options.recordsDir, "records"));
152
+ const rows2 = [];
153
+ const outcomes = options.input ? await readJevJsonl(resolve(options.input)) : [];
154
+ const byRecord = /* @__PURE__ */ new Map();
155
+ for (const row of outcomes) {
156
+ const id = row.assessment?.recordId;
157
+ if (!id) continue;
158
+ if (!/^[a-f0-9]{64}$/.test(id))
159
+ throw new Error("Invalid Jev recording id");
160
+ const prior = byRecord.get(id);
161
+ if (prior && (prior.profit !== row.profit || prior.signalId !== row.signalId))
162
+ throw new Error("Conflicting outcomes for one Jev recording");
163
+ byRecord.set(id, row);
164
+ }
165
+ for (const file of (await import_promises.default.readdir(dir)).filter((name) => /^[a-f0-9]{64}\.json$/.test(name)).sort()) {
166
+ const record = await (0, import_jev.readJevArtifact)(import_node_path.default.join(dir, file));
167
+ if (!record || options.strategy && record.input.strategy !== options.strategy)
168
+ continue;
169
+ if (rows2.length >= 2e4)
170
+ throw new Error(
171
+ "Export exceeds 20000 samples; select a bounded recording directory"
172
+ );
173
+ const outcome = byRecord.get(record.id);
174
+ rows2.push({
175
+ schema: "tradejs-jev-study/v4",
176
+ signalId: outcome?.signalId ?? record.id,
177
+ record,
178
+ ...outcome && Number.isFinite(outcome.profit) ? { profit: outcome.profit } : {}
179
+ });
180
+ }
181
+ if (!rows2.length) throw new Error("No Jev recordings to export");
182
+ await writeNew(
183
+ out,
184
+ rows2.map((row) => JSON.stringify(row)).join("\n") + "\n"
185
+ );
186
+ return { rows: rows2.length, out };
187
+ }
188
+ if (!options.input) throw new Error("Provide a Jev study JSONL with --input");
189
+ const rows = await readJevJsonl(resolve(options.input));
190
+ if (options.action === "train") {
191
+ const { model, report } = (0, import_jev2.trainJevGate)(rows, options);
192
+ const contents = `${JSON.stringify(model, null, 2)}
193
+ `;
194
+ await writeNew(
195
+ `${out}.report.json`,
196
+ `${JSON.stringify(report, null, 2)}
197
+ `
198
+ );
199
+ await writeNew(out, contents);
200
+ return {
201
+ model: out,
202
+ modelSha256: (0, import_jev.jevFileHash)(contents),
203
+ report: `${out}.report.json`,
204
+ trainEnd: model.training.trainEnd,
205
+ validationEnd: model.training.validationEnd
206
+ };
207
+ }
208
+ if (options.action === "compare") {
209
+ if (!options.model) throw new Error("Provide --model");
210
+ const model = (0, import_jev2.validateJevGateModel)(
211
+ JSON.parse(await import_promises.default.readFile(resolve(options.model), "utf8"))
212
+ );
213
+ const heldOut = rows.filter(
214
+ (row) => row.record.input.timestamp > model.training.validationEnd
215
+ );
216
+ if (!heldOut.length)
217
+ throw new Error("No held-out rows after model validationEnd");
218
+ const report = (0, import_jev2.compareJevGate)(model, heldOut);
219
+ await writeNew(out, `${JSON.stringify(report, null, 2)}
220
+ `);
221
+ return report;
222
+ }
223
+ throw new Error("Jev action must be evaluate, export, train, or compare");
224
+ };
225
+ // Annotate the CommonJS export names for ESM import in node:
226
+ 0 && (module.exports = {
227
+ readJevJsonl,
228
+ runJevResearch
229
+ });
@@ -402,7 +402,14 @@ var discoverRuntimeEvidenceCompositionSnapshots = async (evidenceRoot) => {
402
402
  };
403
403
 
404
404
  // src/lib/runtimeEvidenceCompositions.ts
405
- var stableSnapshotIdentity = (deployment) => JSON.stringify({ ...deployment, tickers: void 0 });
405
+ var stableSnapshotIdentity = (deployment) => JSON.stringify({
406
+ ...deployment,
407
+ tickers: void 0,
408
+ strategies: deployment.strategies.map((strategy) => ({
409
+ ...strategy,
410
+ controlState: void 0
411
+ }))
412
+ });
406
413
  var producerIdentity = (producer) => JSON.stringify(producer);
407
414
  var loadRuntimeEvidenceCompositionSnapshots = async ({
408
415
  publishRoot,
@@ -65,6 +65,52 @@ var import_data = require("@tradejs/core/data");
65
65
  var import_redis5 = require("@tradejs/infra/redis");
66
66
  var import_constants6 = require("@tradejs/core/constants");
67
67
 
68
+ // src/lib/backtest/jevOptions.ts
69
+ var import_promises = __toESM(require("fs/promises"));
70
+ var import_node_path = __toESM(require("path"));
71
+ var import_jev = require("@tradejs/core/jev");
72
+ var import_jev2 = require("@tradejs/infra/jev");
73
+ var import_userSettings = require("@tradejs/infra/userSettings");
74
+ var import_jev3 = require("@tradejs/node/jev");
75
+ var resolveBacktestAiExportEnabled = (flags2) => Boolean(flags2.ai);
76
+ var resolveBacktestJev = async (flags2, userName2, projectRoot2) => {
77
+ if (!flags2.jev) {
78
+ if (flags2.jevRecorded || flags2.jevModelFile)
79
+ throw new Error("Jev options require --jev");
80
+ return void 0;
81
+ }
82
+ if (flags2.jevRecorded && flags2.jevModelFile)
83
+ throw new Error("Choose --jevRecorded or --jevModelFile");
84
+ const common = {
85
+ mode: "observe",
86
+ recordsDir: String(flags2.jevRecordsDir || "data/ai/jev")
87
+ };
88
+ if (flags2.jevModelFile) {
89
+ const modelFile = String(flags2.jevModelFile);
90
+ const contents = await import_promises.default.readFile(
91
+ import_node_path.default.resolve(projectRoot2, modelFile),
92
+ "utf8"
93
+ );
94
+ (0, import_jev3.validateJevGateModel)(JSON.parse(contents));
95
+ return (0, import_jev.parseJevConfig)({
96
+ ...common,
97
+ source: "local",
98
+ modelFile,
99
+ modelSha256: (0, import_jev2.jevFileHash)(contents)
100
+ });
101
+ }
102
+ const settings = flags2.jevRecorded ? await (0, import_userSettings.getUserSettings)(userName2) : null;
103
+ const provider = settings ? {
104
+ endpoint: settings.JEV_API_ENDPOINT || import_jev.JEV_PROVIDERS[0].endpoint,
105
+ model: settings.JEV_MODEL || import_jev.JEV_PROVIDERS[0].model
106
+ } : await (0, import_jev3.resolveJevProvider)(userName2);
107
+ return (0, import_jev.parseJevConfig)({
108
+ ...common,
109
+ source: flags2.jevRecorded ? "recorded" : "provider",
110
+ provider
111
+ });
112
+ };
113
+
68
114
  // src/lib/backtest/runnerCore.ts
69
115
  var import_fs = __toESM(require("fs"));
70
116
  var import_path = __toESM(require("path"));
@@ -906,7 +952,7 @@ var import_constants = require("@tradejs/core/constants");
906
952
  var import_indicators = require("@tradejs/core/indicators");
907
953
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
908
954
  var import_client2 = require("@tradejs/infra/timescale/client");
909
- var import_userSettings = require("@tradejs/infra/userSettings");
955
+ var import_userSettings2 = require("@tradejs/infra/userSettings");
910
956
  var HOUR_MS = 60 * 60 * 1e3;
911
957
  var DAY_MS2 = 24 * HOUR_MS;
912
958
  var DEFAULT_LOOKBACK_HOURS = 48;
@@ -1142,7 +1188,7 @@ var extendEdges = (current, fromMs, toMs) => ({
1142
1188
  max: current?.max == null ? toMs : Math.max(current.max, Math.trunc(toMs))
1143
1189
  });
1144
1190
  var getCoinalyzeApiKey = async (userName2) => {
1145
- const settings = await (0, import_userSettings.getUserSettings)(userName2);
1191
+ const settings = await (0, import_userSettings2.getUserSettings)(userName2);
1146
1192
  return settings.COINALYZE_API_KEY.trim() || String(process.env.COINALYZE_API_KEY ?? "").trim();
1147
1193
  };
1148
1194
  var getCoinalyzeBaseUrl = () => process.env.COINALYZE_BASE_URL?.trim() || "https://api.coinalyze.net/v1";
@@ -1881,7 +1927,7 @@ var import_chalk3 = __toESM(require("chalk"));
1881
1927
  var import_async2 = require("@tradejs/core/async");
1882
1928
  var import_marketContext2 = require("@tradejs/infra/timescale/marketContext");
1883
1929
  var import_client3 = require("@tradejs/infra/timescale/client");
1884
- var import_userSettings2 = require("@tradejs/infra/userSettings");
1930
+ var import_userSettings3 = require("@tradejs/infra/userSettings");
1885
1931
  var DAY_MS3 = 864e5;
1886
1932
  var DEFAULT_CONTEXT_MAX_AGE_MS = 48 * 60 * 6e4;
1887
1933
  var DEFAULT_FEAR_GREED_STALE_RETRY_MS = 60 * 6e4;
@@ -2105,27 +2151,27 @@ var createCoinMarketCapFetchStats = () => ({
2105
2151
  totalCredits: 0,
2106
2152
  byPath: /* @__PURE__ */ new Map()
2107
2153
  });
2108
- var getCoinMarketCapPathLabel = (path3) => {
2109
- if (path3.includes("global-metrics")) return "global";
2110
- if (path3.includes("cryptocurrency/quotes")) return "reference";
2111
- if (path3.includes("exchange/quotes")) return "exchangeLiquidity";
2112
- if (path3.includes("fear-and-greed")) return "fearGreed";
2113
- if (path3.includes("cmc100")) return "cmc100";
2114
- if (path3.includes("cmc20")) return "cmc20";
2115
- return path3;
2116
- };
2117
- var recordCoinMarketCapFetchStats = (stats, path3, creditCount) => {
2154
+ var getCoinMarketCapPathLabel = (path4) => {
2155
+ if (path4.includes("global-metrics")) return "global";
2156
+ if (path4.includes("cryptocurrency/quotes")) return "reference";
2157
+ if (path4.includes("exchange/quotes")) return "exchangeLiquidity";
2158
+ if (path4.includes("fear-and-greed")) return "fearGreed";
2159
+ if (path4.includes("cmc100")) return "cmc100";
2160
+ if (path4.includes("cmc20")) return "cmc20";
2161
+ return path4;
2162
+ };
2163
+ var recordCoinMarketCapFetchStats = (stats, path4, creditCount) => {
2118
2164
  if (!stats) return;
2119
- const existing = stats.byPath.get(path3) ?? { requests: 0, credits: 0 };
2165
+ const existing = stats.byPath.get(path4) ?? { requests: 0, credits: 0 };
2120
2166
  existing.requests += 1;
2121
2167
  existing.credits += creditCount ?? 0;
2122
2168
  stats.requests += 1;
2123
2169
  stats.totalCredits += creditCount ?? 0;
2124
- stats.byPath.set(path3, existing);
2170
+ stats.byPath.set(path4, existing);
2125
2171
  };
2126
2172
  var formatCoinMarketCapFetchStats = (stats) => {
2127
2173
  const byPath = [...stats.byPath.entries()].map(
2128
- ([path3, item]) => `${getCoinMarketCapPathLabel(path3)}=${item.requests}req/${item.credits}cr`
2174
+ ([path4, item]) => `${getCoinMarketCapPathLabel(path4)}=${item.requests}req/${item.credits}cr`
2129
2175
  ).join(" ");
2130
2176
  return `apiRequests=${stats.requests} apiCredits=${stats.totalCredits}${byPath ? ` ${byPath}` : ""}`;
2131
2177
  };
@@ -2698,7 +2744,7 @@ var backfillCoinMarketCapContext = async (params) => {
2698
2744
  );
2699
2745
  return skippedResult(true);
2700
2746
  }
2701
- const settings = await (0, import_userSettings2.getUserSettings)(params.userName);
2747
+ const settings = await (0, import_userSettings3.getUserSettings)(params.userName);
2702
2748
  const apiKey = settings.COINMARKETCAP_API_KEY.trim();
2703
2749
  if (!apiKey) {
2704
2750
  throw new Error(
@@ -4262,6 +4308,18 @@ import_args.default.option(
4262
4308
  "Write AI prompt rows to per-worker JSONL chunks",
4263
4309
  false
4264
4310
  );
4311
+ import_args.default.option("jev", "Enrich backtest signals with Jev assessments", false);
4312
+ import_args.default.option(
4313
+ ["Q", "jevRecorded"],
4314
+ "Use recorded Jev answers only; fail on a missing answer",
4315
+ false
4316
+ );
4317
+ import_args.default.option(["M", "jevModelFile"], "Use a trained local Jev gate JSON file");
4318
+ import_args.default.option(
4319
+ ["W", "jevRecordsDir"],
4320
+ "Jev answer and decision directory",
4321
+ "data/ai/jev"
4322
+ );
4265
4323
  import_args.default.option(
4266
4324
  "fast",
4267
4325
  "Skip per-test artifact persistence and keep only in-memory summary/AI-ML dataset output",
@@ -5095,7 +5153,8 @@ var buildPreparedTestSuite = async ({
5095
5153
  const executionCosts = flags.executionCosts == null ? void 0 : (0, import_backtest2.parseBacktestExecutionCosts)(JSON.parse(String(flags.executionCosts)));
5096
5154
  for (const test of testSuite)
5097
5155
  (0, import_backtest2.assertStrategyExecutionIsolation)(test.strategyConfig);
5098
- const aiEnabled = Boolean(flags.ai);
5156
+ const jev = await resolveBacktestJev(flags, userName, projectRoot);
5157
+ const aiEnabled = resolveBacktestAiExportEnabled(flags);
5099
5158
  const requestedTestsLimit = resolveRequestedTestsLimit({
5100
5159
  isLiveMode: isReplay,
5101
5160
  requestedLimit: testsLimit,
@@ -5108,6 +5167,7 @@ var buildPreparedTestSuite = async ({
5108
5167
  executionCostsCacheOnly: Boolean(flags.cacheOnly),
5109
5168
  strategyConfig: {
5110
5169
  ...test.strategyConfig,
5170
+ JEV: jev,
5111
5171
  ENV: "BACKTEST",
5112
5172
  INTERVAL: interval,
5113
5173
  MAKE_ORDERS: true,
@@ -5770,7 +5830,8 @@ var backtest = async () => {
5770
5830
  window: preparedRun.window,
5771
5831
  preloadStart: preparedRun.preloadStart,
5772
5832
  flags: {
5773
- ai: Boolean(flags.ai),
5833
+ ai: Boolean(flags.ai || flags.jev),
5834
+ jev: Boolean(flags.jev),
5774
5835
  backtestEntryDelayBars,
5775
5836
  backtestPriceMode,
5776
5837
  cacheOnly: Boolean(flags.cacheOnly),
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/scripts/jev.ts
31
+ var jev_exports = {};
32
+ __export(jev_exports, {
33
+ main: () => main
34
+ });
35
+ module.exports = __toCommonJS(jev_exports);
36
+ var import_args = __toESM(require("args"));
37
+
38
+ // src/lib/jevResearch.ts
39
+ var import_promises = __toESM(require("fs/promises"));
40
+ var import_node_fs = require("fs");
41
+ var import_node_readline = __toESM(require("readline"));
42
+ var import_node_path = __toESM(require("path"));
43
+ var import_node_crypto = require("crypto");
44
+ var import_jev = require("@tradejs/infra/jev");
45
+ var import_jev2 = require("@tradejs/node/jev");
46
+ var readJevJsonl = async (file) => {
47
+ const rows = [];
48
+ const stream = (0, import_node_fs.createReadStream)(file);
49
+ const reader = import_node_readline.default.createInterface({
50
+ input: stream,
51
+ crlfDelay: Infinity
52
+ });
53
+ try {
54
+ for await (const line of reader) {
55
+ if (!line.trim()) continue;
56
+ if (rows.length >= 2e4)
57
+ throw new Error("Select at most 20000 rows for this bounded Jev study");
58
+ rows.push(JSON.parse(line));
59
+ }
60
+ } finally {
61
+ reader.close();
62
+ stream.destroy();
63
+ }
64
+ return rows;
65
+ };
66
+ var writeNew = async (file, contents) => {
67
+ await import_promises.default.mkdir(import_node_path.default.dirname(file), { recursive: true });
68
+ await import_promises.default.writeFile(file, contents, { flag: "wx", mode: 384 });
69
+ };
70
+ var runJevResearch = async (options) => {
71
+ const resolve = (file) => import_node_path.default.resolve(options.projectRoot, file);
72
+ const out = resolve(options.out);
73
+ try {
74
+ await import_promises.default.access(out);
75
+ throw new Error("Jev output already exists; choose a new output path");
76
+ } catch (error) {
77
+ if (error.code !== "ENOENT") throw error;
78
+ }
79
+ if (options.action === "evaluate") {
80
+ if (!options.input)
81
+ throw new Error("Provide an AI dataset JSONL with --input");
82
+ const rows2 = await readJevJsonl(resolve(options.input));
83
+ const selected = rows2.filter(
84
+ (row) => !options.strategy || row.strategyName === options.strategy
85
+ );
86
+ if (!selected.length) throw new Error("No matching Jev input rows");
87
+ for (const row of selected) {
88
+ if (!row.assessment?.recordId)
89
+ throw new Error(
90
+ `Signal ${row.signalId} has no Jev recording. Run the backtest with --ai --jev and use its --jevRecordsDir; an --ai-only export cannot restore the signal-time Jev facts.`
91
+ );
92
+ if (!/^[a-f0-9]{64}$/.test(row.assessment.recordId))
93
+ throw new Error(`Invalid Jev recording id for signal ${row.signalId}`);
94
+ }
95
+ await import_promises.default.mkdir(import_node_path.default.dirname(out), { recursive: true });
96
+ const temp = `${out}.${(0, import_node_crypto.randomUUID)()}.tmp`;
97
+ try {
98
+ const handle = await import_promises.default.open(temp, "wx", 384);
99
+ try {
100
+ for (const row of selected) {
101
+ const recordId = row.assessment.recordId;
102
+ const saved = await (0, import_jev.readJevArtifact)(
103
+ resolve(
104
+ import_node_path.default.join(options.recordsDir, "records", `${recordId}.json`)
105
+ )
106
+ );
107
+ if (!saved)
108
+ throw new Error(
109
+ `Jev recording ${recordId} for signal ${row.signalId} is missing from --jevRecordsDir. Use the recordings from the original --ai --jev backtest.`
110
+ );
111
+ if (saved.schema !== "tradejs-jev-record/v4")
112
+ throw new Error(
113
+ `Jev recording ${recordId} uses an older schema; rerun the backtest with the current --jev integration.`
114
+ );
115
+ if (saved.id !== recordId || saved.inputHash !== row.assessment?.inputHash || saved.questionsHash !== row.assessment?.questionsHash)
116
+ throw new Error(
117
+ `Jev recording ${recordId} does not match signal ${row.signalId}`
118
+ );
119
+ const { record } = await (0, import_jev2.evaluateJevInput)({
120
+ input: saved.input,
121
+ config: {
122
+ source: "recorded",
123
+ mode: "observe",
124
+ provider: saved.provider,
125
+ recordsDir: options.recordsDir
126
+ },
127
+ userName: options.userName,
128
+ projectRoot: options.projectRoot
129
+ });
130
+ if (!record)
131
+ throw new Error(
132
+ `No eligible Jev questions for signal ${row.signalId}`
133
+ );
134
+ const study = {
135
+ schema: "tradejs-jev-study/v4",
136
+ signalId: row.signalId,
137
+ record,
138
+ ...Number.isFinite(row.profit) ? { profit: row.profit } : {}
139
+ };
140
+ await handle.write(`${JSON.stringify(study)}
141
+ `);
142
+ }
143
+ } finally {
144
+ await handle.close();
145
+ }
146
+ await import_promises.default.link(temp, out);
147
+ } finally {
148
+ await import_promises.default.rm(temp, { force: true });
149
+ }
150
+ return { rows: selected.length, out };
151
+ }
152
+ if (options.action === "export") {
153
+ const dir = resolve(import_node_path.default.join(options.recordsDir, "records"));
154
+ const rows2 = [];
155
+ const outcomes = options.input ? await readJevJsonl(resolve(options.input)) : [];
156
+ const byRecord = /* @__PURE__ */ new Map();
157
+ for (const row of outcomes) {
158
+ const id = row.assessment?.recordId;
159
+ if (!id) continue;
160
+ if (!/^[a-f0-9]{64}$/.test(id))
161
+ throw new Error("Invalid Jev recording id");
162
+ const prior = byRecord.get(id);
163
+ if (prior && (prior.profit !== row.profit || prior.signalId !== row.signalId))
164
+ throw new Error("Conflicting outcomes for one Jev recording");
165
+ byRecord.set(id, row);
166
+ }
167
+ for (const file of (await import_promises.default.readdir(dir)).filter((name) => /^[a-f0-9]{64}\.json$/.test(name)).sort()) {
168
+ const record = await (0, import_jev.readJevArtifact)(import_node_path.default.join(dir, file));
169
+ if (!record || options.strategy && record.input.strategy !== options.strategy)
170
+ continue;
171
+ if (rows2.length >= 2e4)
172
+ throw new Error(
173
+ "Export exceeds 20000 samples; select a bounded recording directory"
174
+ );
175
+ const outcome = byRecord.get(record.id);
176
+ rows2.push({
177
+ schema: "tradejs-jev-study/v4",
178
+ signalId: outcome?.signalId ?? record.id,
179
+ record,
180
+ ...outcome && Number.isFinite(outcome.profit) ? { profit: outcome.profit } : {}
181
+ });
182
+ }
183
+ if (!rows2.length) throw new Error("No Jev recordings to export");
184
+ await writeNew(
185
+ out,
186
+ rows2.map((row) => JSON.stringify(row)).join("\n") + "\n"
187
+ );
188
+ return { rows: rows2.length, out };
189
+ }
190
+ if (!options.input) throw new Error("Provide a Jev study JSONL with --input");
191
+ const rows = await readJevJsonl(resolve(options.input));
192
+ if (options.action === "train") {
193
+ const { model, report } = (0, import_jev2.trainJevGate)(rows, options);
194
+ const contents = `${JSON.stringify(model, null, 2)}
195
+ `;
196
+ await writeNew(
197
+ `${out}.report.json`,
198
+ `${JSON.stringify(report, null, 2)}
199
+ `
200
+ );
201
+ await writeNew(out, contents);
202
+ return {
203
+ model: out,
204
+ modelSha256: (0, import_jev.jevFileHash)(contents),
205
+ report: `${out}.report.json`,
206
+ trainEnd: model.training.trainEnd,
207
+ validationEnd: model.training.validationEnd
208
+ };
209
+ }
210
+ if (options.action === "compare") {
211
+ if (!options.model) throw new Error("Provide --model");
212
+ const model = (0, import_jev2.validateJevGateModel)(
213
+ JSON.parse(await import_promises.default.readFile(resolve(options.model), "utf8"))
214
+ );
215
+ const heldOut = rows.filter(
216
+ (row) => row.record.input.timestamp > model.training.validationEnd
217
+ );
218
+ if (!heldOut.length)
219
+ throw new Error("No held-out rows after model validationEnd");
220
+ const report = (0, import_jev2.compareJevGate)(model, heldOut);
221
+ await writeNew(out, `${JSON.stringify(report, null, 2)}
222
+ `);
223
+ return report;
224
+ }
225
+ throw new Error("Jev action must be evaluate, export, train, or compare");
226
+ };
227
+
228
+ // src/scripts/jev.ts
229
+ import_args.default.option(
230
+ "action",
231
+ "Jev workflow: evaluate recorded rows, export, train, compare"
232
+ );
233
+ import_args.default.option(
234
+ "input",
235
+ "Input JSONL; export optionally joins outcomes by exact assessment record"
236
+ );
237
+ import_args.default.option("out", "New output file (existing files are never overwritten)");
238
+ import_args.default.option("user", "Account settings user", "root");
239
+ import_args.default.option("strategy", "Select one strategy");
240
+ import_args.default.option("recordsDir", "Jev recordings directory", "data/ai/jev");
241
+ import_args.default.option("model", "Local Jev gate JSON for comparison");
242
+ import_args.default.option("maxDepth", "Maximum local rule tree depth", 3);
243
+ import_args.default.option(["l", "minLeaf"], "Minimum training samples per leaf", 10);
244
+ var main = async () => {
245
+ const flags = import_args.default.parse(process.argv);
246
+ if (!flags.out) throw new Error("Provide --out for the immutable Jev result");
247
+ console.log(
248
+ JSON.stringify(
249
+ await runJevResearch({
250
+ action: String(flags.action),
251
+ projectRoot: process.env.PROJECT_CWD || process.cwd(),
252
+ userName: String(flags.user),
253
+ input: flags.input,
254
+ out: String(flags.out),
255
+ strategy: flags.strategy,
256
+ recordsDir: String(flags.recordsDir),
257
+ model: flags.model,
258
+ maxDepth: Number(flags.maxDepth),
259
+ minLeaf: Number(flags.minLeaf)
260
+ }),
261
+ null,
262
+ 2
263
+ )
264
+ );
265
+ };
266
+ // Annotate the CommonJS export names for ESM import in node:
267
+ 0 && (module.exports = {
268
+ main
269
+ });