@tradejs/node 3.1.28 → 3.1.29-beta.259

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/jev.js ADDED
@@ -0,0 +1,931 @@
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 __esm = (fn, res, err) => function __init() {
9
+ if (err) throw err[0];
10
+ try {
11
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err = [e], e;
14
+ }
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+ var __copyProps = (to, from, except, desc) => {
21
+ if (from && typeof from === "object" || typeof from === "function") {
22
+ for (let key of __getOwnPropNames(from))
23
+ if (!__hasOwnProp.call(to, key) && key !== except)
24
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
25
+ }
26
+ return to;
27
+ };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
29
+ // If the importer is in node compatibility mode or this is not an ESM
30
+ // file that has been converted to a CommonJS file using a Babel-
31
+ // compatible transform (i.e. "__esModule" has not been set), then set
32
+ // "default" to the CommonJS "module.exports" for node compatibility.
33
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
34
+ mod
35
+ ));
36
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
37
+
38
+ // src/jevInput.ts
39
+ var import_data, import_jev, object, at, finite, fact, buildJevInput, criteria, JEV_QUESTIONS, questionsForJevInput, stateForJevInput;
40
+ var init_jevInput = __esm({
41
+ "src/jevInput.ts"() {
42
+ "use strict";
43
+ import_data = require("@tradejs/core/data");
44
+ import_jev = require("@tradejs/core/jev");
45
+ object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
46
+ at = (value, ...path2) => path2.reduce((current, key) => object(current)[key], value);
47
+ finite = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
48
+ fact = (value) => {
49
+ if (value == null || typeof value === "boolean") return value ?? null;
50
+ if (typeof value === "number") return finite(value);
51
+ if (typeof value === "string" && value.length <= 60) return value;
52
+ throw new Error("Invalid Jev evidence fact");
53
+ };
54
+ buildJevInput = (signal) => {
55
+ if (!["LONG", "SHORT"].includes(signal.direction ?? "") || !Number.isFinite(signal.timestamp))
56
+ throw new Error("Jev requires a dated directional signal");
57
+ const base = object(signal.additionalIndicators?.baseContext);
58
+ const baseTimestamp = at(base, "candle", "timestamp");
59
+ const baseKnownAt = finite(baseTimestamp);
60
+ const baseProblem = !Object.keys(base).length ? "no_base_context" : baseKnownAt == null ? "missing_timestamp" : baseKnownAt > signal.timestamp ? "future" : signal.timestamp - baseKnownAt > (0, import_data.intervalToMs)(signal.interval) ? "stale" : null;
61
+ const evidence = signal.jevEvidence;
62
+ const features = {};
63
+ const provenance = {};
64
+ const missing = {};
65
+ const put = (key, value, details, missingReason = "missing_value") => {
66
+ const normalized = fact(value);
67
+ if (normalized != null) {
68
+ features[key] = normalized;
69
+ provenance[key] = details;
70
+ } else {
71
+ missing[key] = missingReason;
72
+ }
73
+ };
74
+ const signalFact = {
75
+ knownAt: signal.timestamp,
76
+ scope: "target",
77
+ source: "signal"
78
+ };
79
+ put("signal.direction", signal.direction, signalFact);
80
+ const marketKeys = [
81
+ "market.trendBias",
82
+ "market.swingBias",
83
+ "market.volumeRel20",
84
+ "market.deltaPct",
85
+ "market.fastMaDistanceAtr"
86
+ ];
87
+ if (baseProblem) {
88
+ for (const key of marketKeys) missing[key] = baseProblem;
89
+ } else {
90
+ const marketFact = {
91
+ knownAt: baseKnownAt,
92
+ scope: "target",
93
+ source: "baseContext"
94
+ };
95
+ put("market.trendBias", at(base, "regime", "trend", "bias"), marketFact);
96
+ put("market.swingBias", at(base, "structure", "swing", "bias"), marketFact);
97
+ put(
98
+ "market.volumeRel20",
99
+ at(base, "participation", "volume", "volumeRel20"),
100
+ { ...marketFact, unit: "ratio" }
101
+ );
102
+ const deltaSource = at(base, "participation", "delta", "source");
103
+ put(
104
+ "market.deltaPct",
105
+ at(base, "participation", "delta", "deltaPct"),
106
+ {
107
+ ...marketFact,
108
+ source: typeof deltaSource === "string" ? `baseContext.participation.delta.${deltaSource}` : "baseContext.participation.delta",
109
+ unit: "ratio"
110
+ },
111
+ deltaSource === "ohlcv_proxy" ? "ohlcv_proxy_no_taker_volume" : "missing_value"
112
+ );
113
+ put(
114
+ "market.fastMaDistanceAtr",
115
+ at(base, "regime", "trend", "priceDistanceToMaFastAtr"),
116
+ { ...marketFact, unit: "ATR" }
117
+ );
118
+ }
119
+ const price = finite(signal.prices.currentPrice);
120
+ const stop = finite(signal.prices.stopLossPrice);
121
+ const target = finite(signal.prices.takeProfitPrice);
122
+ const atr = baseProblem ? null : finite(at(base, "raw", "volatility", "atr"));
123
+ if (price == null || stop == null || target == null)
124
+ throw new Error("Invalid Jev signal prices");
125
+ put(
126
+ "signal.validLevels",
127
+ signal.direction === "LONG" ? stop < price && price < target : target < price && price < stop,
128
+ signalFact
129
+ );
130
+ if (atr != null && atr > 0) {
131
+ put("signal.stopDistanceAtr", Math.abs(price - stop) / atr, {
132
+ ...signalFact,
133
+ unit: "ATR"
134
+ });
135
+ put("signal.targetDistanceAtr", Math.abs(price - target) / atr, {
136
+ ...signalFact,
137
+ unit: "ATR"
138
+ });
139
+ } else {
140
+ missing["signal.stopDistanceAtr"] = baseProblem ?? "missing_atr";
141
+ missing["signal.targetDistanceAtr"] = baseProblem ?? "missing_atr";
142
+ }
143
+ let geometryStatus = "absent";
144
+ const figures = signal.figures;
145
+ const groups = [figures?.lines, figures?.points, figures?.zones];
146
+ const points = [
147
+ ...figures?.trendLine?.points ?? [],
148
+ ...figures?.trendLine?.touches ?? [],
149
+ ...groups.flatMap(
150
+ (group) => Array.isArray(group) ? group.flatMap(
151
+ (item) => "points" in item ? item.points : "start" in item ? [item.start, item.end] : []
152
+ ) : []
153
+ )
154
+ ];
155
+ if (points.length > 128 || groups.some(
156
+ (group) => group && (!Array.isArray(group) || group.length > 24)
157
+ ) || points.some(
158
+ (point) => !Number.isFinite(point.timestamp) || !Number.isFinite(point.value) || point.timestamp > signal.timestamp || object(point).knownAt != null && (!Number.isFinite(object(point).knownAt) || Number(object(point).knownAt) > signal.timestamp)
159
+ ))
160
+ geometryStatus = "invalid";
161
+ else if (points.length) geometryStatus = "available";
162
+ if (evidence) {
163
+ if (typeof evidence !== "object" || Array.isArray(evidence) || !evidence.facts || typeof evidence.facts !== "object" || Array.isArray(evidence.facts) || evidence.geometry != null && (typeof evidence.geometry !== "object" || Array.isArray(evidence.geometry)))
164
+ throw new Error("Invalid Jev evidence");
165
+ if (!/^[a-zA-Z0-9._-]{1,40}$/.test(evidence.version) || !Number.isFinite(evidence.knownAt) || evidence.knownAt > signal.timestamp || Object.keys(evidence.facts ?? {}).length > 16 || Object.keys(evidence.geometry ?? {}).length > 16)
166
+ throw new Error("Invalid or oversized Jev evidence");
167
+ const strategyFact = {
168
+ knownAt: evidence.knownAt,
169
+ scope: "strategy",
170
+ source: "strategy.jevEvidence"
171
+ };
172
+ const detailFor = (key) => {
173
+ const detail = evidence.factDetails?.[key] ?? strategyFact;
174
+ if (!Number.isFinite(detail.knownAt) || detail.knownAt > signal.timestamp || !["target", "strategy"].includes(detail.scope) || detail.unit != null && !/^[a-zA-Z0-9%._/-]{1,20}$/.test(detail.unit) || detail.source != null && !/^[a-zA-Z0-9._/-]{1,60}$/.test(detail.source))
175
+ throw new Error("Invalid Jev fact provenance");
176
+ return { ...strategyFact, ...detail };
177
+ };
178
+ put("setup.version", evidence.version, strategyFact);
179
+ for (const [key, value] of Object.entries(evidence.facts)) {
180
+ if (!/^[a-zA-Z][a-zA-Z0-9_]{0,39}$/.test(key) || /^(?:profit|pnl|outcome|label|score|scores|quality|verdict|approval|decision|result)$/i.test(
181
+ key
182
+ ))
183
+ throw new Error("Invalid Jev evidence key");
184
+ put(`setup.${key}`, value, detailFor(`setup.${key}`));
185
+ }
186
+ for (const [key, value] of Object.entries(evidence.geometry ?? {})) {
187
+ if (!/^[a-zA-Z][a-zA-Z0-9_]{0,39}$/.test(key))
188
+ throw new Error("Invalid Jev geometry key");
189
+ put(`geometry.${key}`, value, detailFor(`geometry.${key}`));
190
+ }
191
+ if (Object.keys(evidence.factDetails ?? {}).some(
192
+ (key) => !(key in provenance)
193
+ ))
194
+ throw new Error("Jev provenance references an unavailable fact");
195
+ }
196
+ if (evidence?.geometry && Object.keys(evidence.geometry).length && geometryStatus === "absent")
197
+ geometryStatus = "facts_only";
198
+ if (geometryStatus === "absent") missing["geometry.figures"] = "no_geometry";
199
+ if (geometryStatus === "invalid")
200
+ missing["geometry.figures"] = "invalid_geometry";
201
+ if (Object.keys(features).length > 40 || JSON.stringify(features).length > 4e3)
202
+ throw new Error("Jev input exceeds the microdecision budget");
203
+ const has = (key) => key in features;
204
+ const setupKeys = Object.keys(features).filter(
205
+ (key) => key.startsWith("setup.")
206
+ );
207
+ const questions = import_jev.JEV_DIMENSIONS.filter((dimension) => {
208
+ switch (dimension) {
209
+ case "trend":
210
+ return has("market.trendBias");
211
+ case "swing":
212
+ return has("market.swingBias");
213
+ case "participation":
214
+ return has("market.volumeRel20") || has("market.deltaPct");
215
+ case "setupParticipation":
216
+ return setupKeys.some(
217
+ (key) => /volume|delta|flow|liquidity|turnover/i.test(key)
218
+ );
219
+ case "extension":
220
+ return has("market.fastMaDistanceAtr") || has("setup.breakoutDistanceAtr") || has("signal.stopDistanceAtr");
221
+ case "confirmation":
222
+ return setupKeys.some(
223
+ (key) => /stage|confirm|retest|accept/i.test(key)
224
+ );
225
+ case "setupStrength":
226
+ return setupKeys.some(
227
+ (key) => key !== "setup.version" && !/volume|delta|flow|liquidity|turnover|stage|confirm|retest|accept|breakoutDistance/i.test(
228
+ key
229
+ )
230
+ );
231
+ case "geometry":
232
+ return geometryStatus !== "invalid" && Object.keys(features).some((key) => key.startsWith("geometry."));
233
+ }
234
+ });
235
+ return {
236
+ schema: "tradejs-jev-input/v4",
237
+ strategy: signal.strategy,
238
+ symbol: signal.symbol,
239
+ interval: String(signal.interval),
240
+ timestamp: signal.timestamp,
241
+ direction: signal.direction,
242
+ features,
243
+ provenance,
244
+ missing,
245
+ questions,
246
+ geometryStatus
247
+ };
248
+ };
249
+ criteria = (subject) => [
250
+ `${subject} is clearly contradicted by the supplied facts.`,
251
+ `${subject} has a material conflict.`,
252
+ `${subject} is uncertain from the supplied facts.`,
253
+ `${subject} has adequate support.`,
254
+ `${subject} has strong support without a material conflict.`
255
+ ];
256
+ JEV_QUESTIONS = {
257
+ trend: {
258
+ type: "score",
259
+ instructions: "Does market.trendBias support signal.direction? Judge only the target-asset trend, not swings or setup quality.",
260
+ criteria: criteria("Target-asset trend alignment")
261
+ },
262
+ swing: {
263
+ type: "score",
264
+ instructions: "Does market.swingBias support signal.direction? Judge only the target-asset swing structure, not the broader trend.",
265
+ criteria: criteria("Target-asset swing alignment")
266
+ },
267
+ participation: {
268
+ type: "score",
269
+ instructions: "Does the supplied market.volumeRel20 or market.deltaPct show target-asset participation supportive of this direction? Ignore strategy setup volume and do not infer missing flow.",
270
+ criteria: criteria("Current target-asset participation")
271
+ },
272
+ setupParticipation: {
273
+ type: "score",
274
+ instructions: "Do the strategy-defined setup volume, flow or liquidity facts support the named setup? Use only relevant setup.* facts, not market.volumeRel20; do not infer missing data.",
275
+ criteria: criteria("Participation within the setup")
276
+ },
277
+ extension: {
278
+ type: "score",
279
+ instructions: "Do the supplied ATR-normalized entry-distance facts suggest the entry is not excessively extended? Use only distance facts; do not calculate new ratios or judge confirmation.",
280
+ criteria: criteria("Entry location without excessive extension")
281
+ },
282
+ confirmation: {
283
+ type: "score",
284
+ instructions: "Do the supplied setup stage and confirmation facts indicate a confirmed rather than premature entry? Do not judge distance or predict the outcome.",
285
+ criteria: criteria("Setup confirmation at entry")
286
+ },
287
+ setupStrength: {
288
+ type: "score",
289
+ instructions: "Do the strategy-defined setup strength and quality facts support this candidate? Exclude volume, confirmation, entry distance and geometry; do not assume missing facts.",
290
+ criteria: criteria("Non-geometric setup strength")
291
+ },
292
+ geometry: {
293
+ type: "score",
294
+ instructions: "Do the supplied geometry.* facts support the named setup? Do not infer unseen figure points or certify mathematical validity.",
295
+ criteria: criteria("Setup geometry")
296
+ }
297
+ };
298
+ questionsForJevInput = (input) => Object.fromEntries(input.questions.map((key) => [key, JEV_QUESTIONS[key]]));
299
+ stateForJevInput = (input) => ({
300
+ schema: input.schema,
301
+ strategy: input.strategy,
302
+ direction: input.direction,
303
+ facts: input.features,
304
+ missing: input.missing,
305
+ geometryStatus: input.geometryStatus,
306
+ units: Object.fromEntries(
307
+ Object.entries(input.provenance).filter(([, detail]) => detail.unit != null).map(([key, detail]) => [key, detail.unit])
308
+ ),
309
+ sources: Object.fromEntries(
310
+ Object.entries(input.provenance).filter(([, detail]) => detail.source != null).map(([key, detail]) => [key, detail.source])
311
+ )
312
+ });
313
+ }
314
+ });
315
+
316
+ // src/jevTraining.ts
317
+ var jevTraining_exports = {};
318
+ __export(jevTraining_exports, {
319
+ compareJevGate: () => compareJevGate,
320
+ trainJevGate: () => trainJevGate,
321
+ validateJevGateModel: () => validateJevGateModel,
322
+ validateJevStudy: () => validateJevStudy
323
+ });
324
+ var import_jev2, import_jev3, mean, variance, validateJevStudy, trainTree, validateJevGateModel, predict, economics, compareJevGate, trainJevGate;
325
+ var init_jevTraining = __esm({
326
+ "src/jevTraining.ts"() {
327
+ "use strict";
328
+ import_jev2 = require("@tradejs/core/jev");
329
+ import_jev3 = require("@tradejs/infra/jev");
330
+ init_jevInput();
331
+ mean = (values) => values.reduce((sum, value) => sum + value, 0) / values.length;
332
+ variance = (values) => {
333
+ const average = mean(values);
334
+ return values.reduce((sum, value) => sum + (value - average) ** 2, 0);
335
+ };
336
+ validateJevStudy = (rows) => {
337
+ const unique = /* @__PURE__ */ new Map();
338
+ if (rows.length > 2e4)
339
+ throw new Error(
340
+ "Jev training is bounded to 20000 rows; select a frozen sample first"
341
+ );
342
+ for (const row of rows) {
343
+ const record = row?.record;
344
+ if (row?.schema !== "tradejs-jev-study/v4" || !row.signalId || record?.schema !== "tradejs-jev-record/v4" || record.input?.schema !== "tradejs-jev-input/v4" || !Number.isFinite(record.input.timestamp) || record.inputHash !== (0, import_jev3.jevHash)(record.input) || record.questionsHash !== (0, import_jev3.jevHash)(questionsForJevInput(record.input)) || record.id !== (0, import_jev3.jevHash)({
345
+ inputHash: record.inputHash,
346
+ questionsHash: record.questionsHash,
347
+ provider: record.provider
348
+ }))
349
+ throw new Error("Invalid Jev study provenance");
350
+ (0, import_jev3.validateJevResponse)(record.response, record.input.questions);
351
+ for (const dimension of import_jev2.JEV_DIMENSIONS) {
352
+ const expected = record.input.questions.includes(dimension) ? record.response.answers[dimension].score / 4 : null;
353
+ if (record.scores[dimension] !== expected)
354
+ throw new Error("Jev study scores do not match the teacher response");
355
+ }
356
+ if (row.profit != null && !Number.isFinite(row.profit))
357
+ throw new Error("Invalid study outcome");
358
+ const prior = unique.get(record.inputHash);
359
+ if (prior && (prior.record.id !== record.id || prior.profit !== row.profit || (0, import_jev3.jevHash)(prior.record.response) !== (0, import_jev3.jevHash)(record.response)))
360
+ throw new Error("Conflicting duplicated Jev sample");
361
+ unique.set(record.inputHash, row);
362
+ }
363
+ const ordered = [...unique.values()].sort(
364
+ (a, b) => a.record.input.timestamp - b.record.input.timestamp || a.record.id.localeCompare(b.record.id)
365
+ );
366
+ if (!ordered.length) throw new Error("Empty Jev study");
367
+ const first = ordered[0].record;
368
+ if (ordered.some(
369
+ (row) => row.record.input.strategy !== first.input.strategy || row.record.response.model !== first.response.model || (0, import_jev3.jevHash)(row.record.provider) !== (0, import_jev3.jevHash)(first.provider)
370
+ ))
371
+ throw new Error(
372
+ "Train one strategy and one frozen teacher/provider lineage at a time"
373
+ );
374
+ return ordered;
375
+ };
376
+ trainTree = (rows, dimension, depth, minLeaf) => {
377
+ const values = rows.map((row) => row.record.scores[dimension]);
378
+ const leaf = { value: mean(values), samples: rows.length };
379
+ if (depth === 0 || rows.length < minLeaf * 2 || variance(values) < 1e-8)
380
+ return leaf;
381
+ const keys = [
382
+ ...new Set(rows.flatMap((row) => Object.keys(row.record.input.features)))
383
+ ].sort().filter(
384
+ (key) => new Set(rows.map((row) => row.record.input.features[key])).size > 1
385
+ );
386
+ if (keys.length > 2e3) throw new Error("Too many Jev student features");
387
+ let best;
388
+ let bestLoss = variance(values);
389
+ for (const feature of keys) {
390
+ const observed = [
391
+ ...new Set(
392
+ rows.map((row) => row.record.input.features[feature]).filter((value) => value != null)
393
+ )
394
+ ];
395
+ const numeric = observed.every((value) => typeof value === "number");
396
+ const sorted = numeric ? observed.sort((a, b) => a - b) : observed.sort((a, b) => String(a).localeCompare(String(b)));
397
+ const candidates = sorted.filter(
398
+ (_, index) => index % Math.max(1, Math.ceil(sorted.length / 12)) === 0
399
+ ).slice(0, 12);
400
+ for (const value of candidates)
401
+ for (const missingLeft of [true, false]) {
402
+ const left = [], right = [];
403
+ for (const row of rows) {
404
+ const input = row.record.input.features[feature];
405
+ const goesLeft = input == null ? missingLeft : numeric ? typeof input === "number" && input <= value : input === value;
406
+ (goesLeft ? left : right).push(row);
407
+ }
408
+ if (left.length < minLeaf || right.length < minLeaf) continue;
409
+ const loss = variance(left.map((row) => row.record.scores[dimension])) + variance(right.map((row) => row.record.scores[dimension]));
410
+ if (loss < bestLoss - 1e-8) {
411
+ bestLoss = loss;
412
+ best = {
413
+ feature,
414
+ operator: numeric ? "lte" : "eq",
415
+ value,
416
+ missingLeft,
417
+ left,
418
+ right
419
+ };
420
+ }
421
+ }
422
+ }
423
+ if (!best) return leaf;
424
+ return {
425
+ feature: best.feature,
426
+ operator: best.operator,
427
+ value: best.value,
428
+ missingLeft: best.missingLeft,
429
+ left: trainTree(best.left, dimension, depth - 1, minLeaf),
430
+ right: trainTree(best.right, dimension, depth - 1, minLeaf)
431
+ };
432
+ };
433
+ validateJevGateModel = (value) => {
434
+ const model = value;
435
+ if (model?.schema !== "tradejs-jev-gate/v2" || model.inputSchema !== "tradejs-jev-input/v4" || typeof model.strategy !== "string" || !model.strategy || !model.teacherModel || !model.trees || model.questionsHash !== (0, import_jev3.jevHash)(JEV_QUESTIONS) || !model.training || !/^[a-f0-9]{64}$/.test(model.training.datasetHash))
436
+ throw new Error("Invalid local Jev gate");
437
+ const { trainEnd, validationEnd, testEnd } = model.training;
438
+ if (![trainEnd, validationEnd, testEnd].every(Number.isFinite) || !(trainEnd < validationEnd && validationEnd < testEnd))
439
+ throw new Error("Invalid local Jev gate partitions");
440
+ const check = (tree, depth) => {
441
+ if (!tree || depth > 8) throw new Error("Invalid local Jev tree");
442
+ if ("samples" in tree) {
443
+ if (!Number.isFinite(tree.value) || tree.value < 0 || tree.value > 1 || !Number.isInteger(tree.samples) || tree.samples < 1)
444
+ throw new Error("Invalid local Jev leaf");
445
+ return;
446
+ }
447
+ if (typeof tree.feature !== "string" || !["lte", "eq"].includes(tree.operator) || typeof tree.missingLeft !== "boolean" || tree.operator === "lte" && !Number.isFinite(tree.value) || !["number", "boolean", "string"].includes(typeof tree.value))
448
+ throw new Error("Invalid local Jev split");
449
+ check(tree.left, depth + 1);
450
+ check(tree.right, depth + 1);
451
+ };
452
+ for (const dimension of import_jev2.JEV_DIMENSIONS) {
453
+ if (model.trees[dimension] != null) check(model.trees[dimension], 0);
454
+ }
455
+ return model;
456
+ };
457
+ predict = (model, row) => Object.fromEntries(
458
+ import_jev2.JEV_DIMENSIONS.map((key) => [
459
+ key,
460
+ model.trees[key] && row.record.input.questions.includes(key) ? (0, import_jev2.predictJevTree)(model.trees[key], row.record.input.features) : null
461
+ ])
462
+ );
463
+ economics = (rows) => {
464
+ const known = rows.filter((row) => row.profit != null);
465
+ const profits = known.map((row) => row.profit);
466
+ const wins = profits.filter((value) => value > 0).reduce((sum, value) => sum + value, 0);
467
+ const losses = -profits.filter((value) => value < 0).reduce((sum, value) => sum + value, 0);
468
+ return {
469
+ rows: rows.length,
470
+ knownOutcomes: known.length,
471
+ pnl: profits.reduce((a, b) => a + b, 0),
472
+ pnlPerTrade: profits.length ? mean(profits) : null,
473
+ profitFactor: losses ? wins / losses : null,
474
+ winRate: profits.length ? profits.filter((value) => value > 0).length / profits.length : null
475
+ };
476
+ };
477
+ compareJevGate = (model, input, minScores = {}) => {
478
+ validateJevGateModel(model);
479
+ const rows = validateJevStudy(input);
480
+ if (rows.some(
481
+ (row) => row.record.input.strategy !== model.strategy || row.record.response.model !== model.teacherModel
482
+ ))
483
+ throw new Error("Model and study lineage do not match");
484
+ const decisions = rows.map((row) => {
485
+ const policy = {
486
+ source: "local",
487
+ mode: "gate",
488
+ minScores
489
+ };
490
+ const scores = predict(model, row);
491
+ const student = (0, import_jev2.decideJev)(
492
+ scores,
493
+ policy,
494
+ row.record.input.geometryStatus,
495
+ row.record.input.features,
496
+ row.record.input.questions
497
+ ).allowed;
498
+ const teacher = (0, import_jev2.decideJev)(
499
+ row.record.scores,
500
+ policy,
501
+ row.record.input.geometryStatus,
502
+ row.record.input.features,
503
+ row.record.input.questions
504
+ ).allowed;
505
+ return { row, scores, student, teacher };
506
+ });
507
+ const dimensionErrors = Object.fromEntries(
508
+ import_jev2.JEV_DIMENSIONS.map((key) => {
509
+ const pairs = decisions.filter(
510
+ (item) => item.row.record.scores[key] != null && item.scores[key] != null
511
+ );
512
+ return [
513
+ key,
514
+ {
515
+ n: pairs.length,
516
+ mae: pairs.length ? mean(
517
+ pairs.map(
518
+ (item) => Math.abs(item.scores[key] - item.row.record.scores[key])
519
+ )
520
+ ) : null
521
+ }
522
+ ];
523
+ })
524
+ );
525
+ const selected = (predicate) => decisions.filter(predicate).map((value) => value.row);
526
+ return {
527
+ rows: rows.length,
528
+ start: rows[0].record.input.timestamp,
529
+ end: rows[rows.length - 1].record.input.timestamp,
530
+ agreement: decisions.filter((item) => item.student === item.teacher).length / rows.length,
531
+ teacherOnly: decisions.filter((item) => item.teacher && !item.student).length,
532
+ studentOnly: decisions.filter((item) => item.student && !item.teacher).length,
533
+ dimensionErrors,
534
+ outcomeScope: "Matched completed outcomes only; this is not a sequential portfolio backtest. Unexecuted candidates have no observed outcome.",
535
+ cohorts: Object.fromEntries(
536
+ ["ALL", "LONG", "SHORT"].map((side) => [
537
+ side,
538
+ {
539
+ core: economics(
540
+ selected(
541
+ (item) => side === "ALL" || item.row.record.input.direction === side
542
+ )
543
+ ),
544
+ baseline: economics(
545
+ selected(
546
+ (item) => item.row.baselineAllowed === true && (side === "ALL" || item.row.record.input.direction === side)
547
+ )
548
+ ),
549
+ jev: economics(
550
+ selected(
551
+ (item) => item.teacher && (side === "ALL" || item.row.record.input.direction === side)
552
+ )
553
+ ),
554
+ local: economics(
555
+ selected(
556
+ (item) => item.student && (side === "ALL" || item.row.record.input.direction === side)
557
+ )
558
+ )
559
+ }
560
+ ])
561
+ )
562
+ };
563
+ };
564
+ trainJevGate = (input, options = {}) => {
565
+ const rows = validateJevStudy(input);
566
+ const maxDepth = options.maxDepth ?? 3, minLeaf = options.minLeaf ?? 10;
567
+ if (!Number.isInteger(maxDepth) || maxDepth < 1 || maxDepth > 6 || !Number.isInteger(minLeaf) || minLeaf < 2)
568
+ throw new Error("Invalid Jev training limits");
569
+ const timestamps = [
570
+ ...new Set(rows.map((row) => row.record.input.timestamp))
571
+ ];
572
+ if (rows.length < 30 || timestamps.length < 10)
573
+ throw new Error("Need at least 30 distinct samples across 10 signal times");
574
+ const trainEnd = timestamps[Math.floor(timestamps.length * 0.6) - 1];
575
+ const validationEnd = timestamps[Math.floor(timestamps.length * 0.8) - 1];
576
+ const train = rows.filter((row) => row.record.input.timestamp <= trainEnd);
577
+ const validation = rows.filter(
578
+ (row) => row.record.input.timestamp > trainEnd && row.record.input.timestamp <= validationEnd
579
+ );
580
+ const test = rows.filter((row) => row.record.input.timestamp > validationEnd);
581
+ const fit = (samples) => Object.fromEntries(
582
+ import_jev2.JEV_DIMENSIONS.map((key) => {
583
+ const eligible = samples.filter(
584
+ (row) => row.record.scores[key] != null
585
+ );
586
+ return [
587
+ key,
588
+ eligible.length ? trainTree(eligible, key, maxDepth, minLeaf) : null
589
+ ];
590
+ })
591
+ );
592
+ const first = rows[0].record;
593
+ const model = {
594
+ schema: "tradejs-jev-gate/v2",
595
+ inputSchema: "tradejs-jev-input/v4",
596
+ strategy: first.input.strategy,
597
+ questionsHash: (0, import_jev3.jevHash)(JEV_QUESTIONS),
598
+ teacherModel: first.response.model,
599
+ trees: fit(train),
600
+ training: {
601
+ datasetHash: (0, import_jev3.jevHash)(
602
+ rows.map((row) => ({
603
+ id: row.record.id,
604
+ response: row.record.response
605
+ }))
606
+ ),
607
+ trainEnd,
608
+ validationEnd,
609
+ testEnd: timestamps[timestamps.length - 1],
610
+ maxDepth,
611
+ minLeaf
612
+ }
613
+ };
614
+ validateJevGateModel(model);
615
+ const walkForward = [0.4, 0.6, 0.8].map((fraction) => {
616
+ const trainThrough = timestamps[Math.max(0, Math.floor(timestamps.length * (fraction - 0.1)) - 1)];
617
+ const validationThrough = timestamps[Math.floor(timestamps.length * fraction) - 1];
618
+ const testThrough = timestamps[Math.min(
619
+ timestamps.length - 1,
620
+ Math.round(timestamps.length * (fraction + 0.2)) - 1
621
+ )];
622
+ const fitting = rows.filter(
623
+ (row) => row.record.input.timestamp <= trainThrough
624
+ );
625
+ const evaluation = rows.filter(
626
+ (row) => row.record.input.timestamp > validationThrough && row.record.input.timestamp <= testThrough
627
+ );
628
+ const fold = {
629
+ ...model,
630
+ trees: fit(fitting),
631
+ training: {
632
+ ...model.training,
633
+ trainEnd: trainThrough,
634
+ validationEnd: validationThrough,
635
+ testEnd: testThrough
636
+ }
637
+ };
638
+ return {
639
+ trainThrough,
640
+ validationThrough,
641
+ testThrough,
642
+ trainRows: fitting.length,
643
+ test: compareJevGate(fold, evaluation)
644
+ };
645
+ });
646
+ return {
647
+ model,
648
+ report: {
649
+ train: compareJevGate(model, train),
650
+ validation: compareJevGate(model, validation),
651
+ test: compareJevGate(model, test),
652
+ walkForward,
653
+ promotion: "research-only; validate a sequential backtest and prospective signals before runtime admission"
654
+ }
655
+ };
656
+ };
657
+ }
658
+ });
659
+
660
+ // src/jev.ts
661
+ var jev_exports = {};
662
+ __export(jev_exports, {
663
+ JEV_QUESTIONS: () => JEV_QUESTIONS,
664
+ assessSignalWithJev: () => assessSignalWithJev,
665
+ buildJevInput: () => buildJevInput,
666
+ compareJevGate: () => compareJevGate,
667
+ evaluateJevInput: () => evaluateJevInput,
668
+ resolveJevProvider: () => resolveJevProvider,
669
+ trainJevGate: () => trainJevGate,
670
+ validateJevGateModel: () => validateJevGateModel
671
+ });
672
+ module.exports = __toCommonJS(jev_exports);
673
+ var import_promises = __toESM(require("fs/promises"));
674
+ var import_node_path = __toESM(require("path"));
675
+ var import_jev4 = require("@tradejs/core/jev");
676
+ var import_jev5 = require("@tradejs/infra/jev");
677
+ var import_userSettings = require("@tradejs/infra/userSettings");
678
+ init_jevInput();
679
+ init_jevInput();
680
+ init_jevTraining();
681
+ var resolveJevProvider = async (userName) => {
682
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
683
+ const provider = {
684
+ endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
685
+ model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
686
+ };
687
+ (0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
688
+ if (!settings.JEV_API_KEY)
689
+ throw new Error("Configure the Jev API key in Account settings");
690
+ return provider;
691
+ };
692
+ var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
693
+ var pending = /* @__PURE__ */ new Map();
694
+ var pendingResponses = /* @__PURE__ */ new Map();
695
+ var evaluateJevInput = async ({
696
+ input,
697
+ config,
698
+ userName,
699
+ projectRoot
700
+ }) => {
701
+ config = (0, import_jev4.parseJevConfig)(config);
702
+ const inputHash = (0, import_jev5.jevHash)(input);
703
+ const questions = questionsForJevInput(input);
704
+ const questionsHash = (0, import_jev5.jevHash)(questions);
705
+ const base = {
706
+ schema: "tradejs-signal-assessment/v5",
707
+ source: config.source,
708
+ mode: config.mode,
709
+ inputHash,
710
+ questionsHash,
711
+ geometryStatus: input.geometryStatus,
712
+ levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
713
+ };
714
+ if (!input.questions.length)
715
+ return {
716
+ assessment: {
717
+ ...base,
718
+ status: "unavailable",
719
+ model: "",
720
+ scores: emptyScores(),
721
+ confidence: emptyScores()
722
+ }
723
+ };
724
+ if (config.source === "local") {
725
+ const contents = await import_promises.default.readFile(
726
+ import_node_path.default.resolve(projectRoot, config.modelFile),
727
+ "utf8"
728
+ );
729
+ if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
730
+ throw new Error("Local Jev gate checksum mismatch");
731
+ const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
732
+ const model = validateJevGateModel2(JSON.parse(contents));
733
+ if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
734
+ throw new Error("Local Jev gate strategy or question version mismatch");
735
+ const scores = Object.fromEntries(
736
+ import_jev4.JEV_DIMENSIONS.map((key) => [
737
+ key,
738
+ input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
739
+ ])
740
+ );
741
+ return {
742
+ assessment: {
743
+ ...base,
744
+ status: "available",
745
+ model: config.modelSha256,
746
+ scores,
747
+ confidence: emptyScores()
748
+ }
749
+ };
750
+ }
751
+ const provider = config.provider ?? await resolveJevProvider(userName);
752
+ const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
753
+ const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
754
+ const file = import_node_path.default.join(dir, "records", `${id}.json`);
755
+ const getRecord = async () => {
756
+ const existing = await (0, import_jev5.readJevArtifact)(file);
757
+ if (existing) {
758
+ if (existing.schema !== "tradejs-jev-record/v4" || existing.id !== id || existing.inputHash !== inputHash || existing.questionsHash !== questionsHash || (0, import_jev5.jevHash)(existing.input) !== inputHash || (0, import_jev5.jevHash)(existing.provider) !== (0, import_jev5.jevHash)(provider))
759
+ throw new Error("Jev recording identity mismatch");
760
+ (0, import_jev5.validateJevResponse)(existing.response, input.questions);
761
+ for (const key of import_jev4.JEV_DIMENSIONS) {
762
+ const expected = input.questions.includes(key) ? existing.response.answers[key].score / 4 : null;
763
+ if (existing.scores[key] !== expected)
764
+ throw new Error("Jev recording scores do not match the response");
765
+ }
766
+ return existing;
767
+ }
768
+ if (config.source === "recorded")
769
+ throw new Error(
770
+ `Missing Jev recording ${id} for ${input.strategy} ${input.symbol} at ${new Date(input.timestamp).toISOString()}. Recorded mode requires the same signal-time facts and provider; a shorter backtest can change warm-up indicators or prior trade state.`
771
+ );
772
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
773
+ if (!settings.JEV_API_KEY)
774
+ throw new Error("Configure the Jev API key in Account settings");
775
+ const apiKey = settings.JEV_API_KEY;
776
+ const configuredProvider = await resolveJevProvider(userName);
777
+ if (configuredProvider.endpoint !== provider.endpoint)
778
+ throw new Error(
779
+ "Jev account endpoint changed; update the frozen provider configuration"
780
+ );
781
+ const started = Date.now();
782
+ const state = stateForJevInput(input);
783
+ const responseId = (0, import_jev5.jevHash)({
784
+ state,
785
+ questionsHash,
786
+ provider,
787
+ user: (0, import_jev5.jevHash)(userName)
788
+ });
789
+ const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
790
+ let responsePromise = pendingResponses.get(responseFile);
791
+ if (!responsePromise) {
792
+ responsePromise = (async () => {
793
+ const saved = await (0, import_jev5.readJevArtifact)(responseFile);
794
+ if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
795
+ const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
796
+ await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
797
+ return fresh;
798
+ })();
799
+ pendingResponses.set(responseFile, responsePromise);
800
+ }
801
+ let response;
802
+ try {
803
+ response = await responsePromise;
804
+ } finally {
805
+ pendingResponses.delete(responseFile);
806
+ }
807
+ const scores = Object.fromEntries(
808
+ import_jev4.JEV_DIMENSIONS.map((key) => [
809
+ key,
810
+ input.questions.includes(key) ? response.answers[key].score / 4 : null
811
+ ])
812
+ );
813
+ const value = {
814
+ schema: "tradejs-jev-record/v4",
815
+ id,
816
+ inputHash,
817
+ questionsHash,
818
+ provider,
819
+ input,
820
+ response,
821
+ scores,
822
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
823
+ elapsedMs: Date.now() - started
824
+ };
825
+ await (0, import_jev5.writeJevArtifact)(file, value);
826
+ return await (0, import_jev5.readJevArtifact)(file);
827
+ };
828
+ const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
829
+ let promise = pending.get(pendingKey);
830
+ if (!promise) {
831
+ promise = getRecord();
832
+ pending.set(pendingKey, promise);
833
+ }
834
+ let record;
835
+ try {
836
+ record = await promise;
837
+ } finally {
838
+ pending.delete(pendingKey);
839
+ }
840
+ return {
841
+ record,
842
+ assessment: {
843
+ ...base,
844
+ status: "available",
845
+ recordId: id,
846
+ model: record.response.model,
847
+ scores: record.scores,
848
+ confidence: Object.fromEntries(
849
+ import_jev4.JEV_DIMENSIONS.map((key) => [
850
+ key,
851
+ record.response.answers[key]?.confidence ?? null
852
+ ])
853
+ )
854
+ }
855
+ };
856
+ };
857
+ var assessSignalWithJev = async ({
858
+ signal,
859
+ config,
860
+ userName,
861
+ projectRoot,
862
+ strict,
863
+ scope = "runtime"
864
+ }) => {
865
+ let assessment;
866
+ try {
867
+ const input = buildJevInput(signal);
868
+ ({ assessment } = await evaluateJevInput({
869
+ input,
870
+ config,
871
+ userName,
872
+ projectRoot
873
+ }));
874
+ } catch (error) {
875
+ if (strict) throw error;
876
+ assessment = {
877
+ schema: "tradejs-signal-assessment/v5",
878
+ source: config.source,
879
+ mode: config.mode,
880
+ status: "unavailable",
881
+ inputHash: "",
882
+ questionsHash: "",
883
+ geometryStatus: "absent",
884
+ levelsValid: null,
885
+ model: config.provider?.model ?? config.modelSha256 ?? "",
886
+ scores: emptyScores(),
887
+ confidence: emptyScores()
888
+ };
889
+ }
890
+ signal.assessment = assessment;
891
+ signal.additionalIndicators = {
892
+ ...signal.additionalIndicators,
893
+ jev: {
894
+ schema: "tradejs-jev-features/v2",
895
+ status: assessment.status,
896
+ scores: assessment.scores,
897
+ confidence: assessment.confidence
898
+ }
899
+ };
900
+ const identity = {
901
+ signalId: signal.signalId,
902
+ strategy: signal.strategy,
903
+ symbol: signal.symbol,
904
+ timestamp: signal.timestamp,
905
+ scope,
906
+ user: (0, import_jev5.jevHash)(userName),
907
+ config,
908
+ assessment
909
+ };
910
+ await (0, import_jev5.writeJevArtifact)(
911
+ import_node_path.default.resolve(
912
+ projectRoot,
913
+ config.recordsDir ?? "data/ai/jev",
914
+ "decisions",
915
+ `${(0, import_jev5.jevHash)(identity)}.json`
916
+ ),
917
+ identity
918
+ );
919
+ return assessment;
920
+ };
921
+ // Annotate the CommonJS export names for ESM import in node:
922
+ 0 && (module.exports = {
923
+ JEV_QUESTIONS,
924
+ assessSignalWithJev,
925
+ buildJevInput,
926
+ compareJevGate,
927
+ evaluateJevInput,
928
+ resolveJevProvider,
929
+ trainJevGate,
930
+ validateJevGateModel
931
+ });