@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.
@@ -5,6 +5,14 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
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
+ };
8
16
  var __export = (target, all) => {
9
17
  for (var name in all)
10
18
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +35,628 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
35
  ));
28
36
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
37
 
38
+ // src/jevInput.ts
39
+ var import_data2, 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_data2 = 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, ...path4) => path4.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_data2.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
+ (_2, 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
+
30
660
  // src/runtimeDashboard.ts
31
661
  var runtimeDashboard_exports = {};
32
662
  __export(runtimeDashboard_exports, {
@@ -1333,12 +1963,266 @@ var syncRuntimeTrades = async ({
1333
1963
  };
1334
1964
 
1335
1965
  // src/runtimeStrategies.ts
1966
+ var import_jev8 = require("@tradejs/core/jev");
1336
1967
  var import_node_crypto4 = require("crypto");
1337
- var import_promises = require("fs/promises");
1338
- var import_node_path = __toESM(require("path"));
1968
+ var import_promises2 = require("fs/promises");
1969
+ var import_node_path2 = __toESM(require("path"));
1339
1970
  var import_runtimeControls = require("@tradejs/infra/runtimeControls");
1340
1971
  var import_tradingAccounts2 = require("@tradejs/infra/tradingAccounts");
1341
1972
 
1973
+ // src/strategyRuntime.ts
1974
+ var import_jev6 = require("@tradejs/core/jev");
1975
+
1976
+ // src/jev.ts
1977
+ var import_promises = __toESM(require("fs/promises"));
1978
+ var import_node_path = __toESM(require("path"));
1979
+ var import_jev4 = require("@tradejs/core/jev");
1980
+ var import_jev5 = require("@tradejs/infra/jev");
1981
+ var import_userSettings = require("@tradejs/infra/userSettings");
1982
+ init_jevInput();
1983
+ init_jevInput();
1984
+ init_jevTraining();
1985
+ var resolveJevProvider = async (userName) => {
1986
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
1987
+ const provider = {
1988
+ endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
1989
+ model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
1990
+ };
1991
+ (0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
1992
+ if (!settings.JEV_API_KEY)
1993
+ throw new Error("Configure the Jev API key in Account settings");
1994
+ return provider;
1995
+ };
1996
+ var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
1997
+ var pending = /* @__PURE__ */ new Map();
1998
+ var pendingResponses = /* @__PURE__ */ new Map();
1999
+ var evaluateJevInput = async ({
2000
+ input,
2001
+ config,
2002
+ userName,
2003
+ projectRoot
2004
+ }) => {
2005
+ config = (0, import_jev4.parseJevConfig)(config);
2006
+ const inputHash = (0, import_jev5.jevHash)(input);
2007
+ const questions = questionsForJevInput(input);
2008
+ const questionsHash = (0, import_jev5.jevHash)(questions);
2009
+ const base = {
2010
+ schema: "tradejs-signal-assessment/v5",
2011
+ source: config.source,
2012
+ mode: config.mode,
2013
+ inputHash,
2014
+ questionsHash,
2015
+ geometryStatus: input.geometryStatus,
2016
+ levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
2017
+ };
2018
+ if (!input.questions.length)
2019
+ return {
2020
+ assessment: {
2021
+ ...base,
2022
+ status: "unavailable",
2023
+ model: "",
2024
+ scores: emptyScores(),
2025
+ confidence: emptyScores()
2026
+ }
2027
+ };
2028
+ if (config.source === "local") {
2029
+ const contents = await import_promises.default.readFile(
2030
+ import_node_path.default.resolve(projectRoot, config.modelFile),
2031
+ "utf8"
2032
+ );
2033
+ if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
2034
+ throw new Error("Local Jev gate checksum mismatch");
2035
+ const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
2036
+ const model = validateJevGateModel2(JSON.parse(contents));
2037
+ if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
2038
+ throw new Error("Local Jev gate strategy or question version mismatch");
2039
+ const scores = Object.fromEntries(
2040
+ import_jev4.JEV_DIMENSIONS.map((key) => [
2041
+ key,
2042
+ input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
2043
+ ])
2044
+ );
2045
+ return {
2046
+ assessment: {
2047
+ ...base,
2048
+ status: "available",
2049
+ model: config.modelSha256,
2050
+ scores,
2051
+ confidence: emptyScores()
2052
+ }
2053
+ };
2054
+ }
2055
+ const provider = config.provider ?? await resolveJevProvider(userName);
2056
+ const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
2057
+ const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
2058
+ const file = import_node_path.default.join(dir, "records", `${id}.json`);
2059
+ const getRecord = async () => {
2060
+ const existing = await (0, import_jev5.readJevArtifact)(file);
2061
+ if (existing) {
2062
+ 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))
2063
+ throw new Error("Jev recording identity mismatch");
2064
+ (0, import_jev5.validateJevResponse)(existing.response, input.questions);
2065
+ for (const key of import_jev4.JEV_DIMENSIONS) {
2066
+ const expected = input.questions.includes(key) ? existing.response.answers[key].score / 4 : null;
2067
+ if (existing.scores[key] !== expected)
2068
+ throw new Error("Jev recording scores do not match the response");
2069
+ }
2070
+ return existing;
2071
+ }
2072
+ if (config.source === "recorded")
2073
+ throw new Error(
2074
+ `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.`
2075
+ );
2076
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
2077
+ if (!settings.JEV_API_KEY)
2078
+ throw new Error("Configure the Jev API key in Account settings");
2079
+ const apiKey = settings.JEV_API_KEY;
2080
+ const configuredProvider = await resolveJevProvider(userName);
2081
+ if (configuredProvider.endpoint !== provider.endpoint)
2082
+ throw new Error(
2083
+ "Jev account endpoint changed; update the frozen provider configuration"
2084
+ );
2085
+ const started = Date.now();
2086
+ const state = stateForJevInput(input);
2087
+ const responseId = (0, import_jev5.jevHash)({
2088
+ state,
2089
+ questionsHash,
2090
+ provider,
2091
+ user: (0, import_jev5.jevHash)(userName)
2092
+ });
2093
+ const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
2094
+ let responsePromise = pendingResponses.get(responseFile);
2095
+ if (!responsePromise) {
2096
+ responsePromise = (async () => {
2097
+ const saved = await (0, import_jev5.readJevArtifact)(responseFile);
2098
+ if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
2099
+ const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
2100
+ await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
2101
+ return fresh;
2102
+ })();
2103
+ pendingResponses.set(responseFile, responsePromise);
2104
+ }
2105
+ let response;
2106
+ try {
2107
+ response = await responsePromise;
2108
+ } finally {
2109
+ pendingResponses.delete(responseFile);
2110
+ }
2111
+ const scores = Object.fromEntries(
2112
+ import_jev4.JEV_DIMENSIONS.map((key) => [
2113
+ key,
2114
+ input.questions.includes(key) ? response.answers[key].score / 4 : null
2115
+ ])
2116
+ );
2117
+ const value = {
2118
+ schema: "tradejs-jev-record/v4",
2119
+ id,
2120
+ inputHash,
2121
+ questionsHash,
2122
+ provider,
2123
+ input,
2124
+ response,
2125
+ scores,
2126
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2127
+ elapsedMs: Date.now() - started
2128
+ };
2129
+ await (0, import_jev5.writeJevArtifact)(file, value);
2130
+ return await (0, import_jev5.readJevArtifact)(file);
2131
+ };
2132
+ const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
2133
+ let promise = pending.get(pendingKey);
2134
+ if (!promise) {
2135
+ promise = getRecord();
2136
+ pending.set(pendingKey, promise);
2137
+ }
2138
+ let record;
2139
+ try {
2140
+ record = await promise;
2141
+ } finally {
2142
+ pending.delete(pendingKey);
2143
+ }
2144
+ return {
2145
+ record,
2146
+ assessment: {
2147
+ ...base,
2148
+ status: "available",
2149
+ recordId: id,
2150
+ model: record.response.model,
2151
+ scores: record.scores,
2152
+ confidence: Object.fromEntries(
2153
+ import_jev4.JEV_DIMENSIONS.map((key) => [
2154
+ key,
2155
+ record.response.answers[key]?.confidence ?? null
2156
+ ])
2157
+ )
2158
+ }
2159
+ };
2160
+ };
2161
+ var assessSignalWithJev = async ({
2162
+ signal,
2163
+ config,
2164
+ userName,
2165
+ projectRoot,
2166
+ strict,
2167
+ scope = "runtime"
2168
+ }) => {
2169
+ let assessment;
2170
+ try {
2171
+ const input = buildJevInput(signal);
2172
+ ({ assessment } = await evaluateJevInput({
2173
+ input,
2174
+ config,
2175
+ userName,
2176
+ projectRoot
2177
+ }));
2178
+ } catch (error) {
2179
+ if (strict) throw error;
2180
+ assessment = {
2181
+ schema: "tradejs-signal-assessment/v5",
2182
+ source: config.source,
2183
+ mode: config.mode,
2184
+ status: "unavailable",
2185
+ inputHash: "",
2186
+ questionsHash: "",
2187
+ geometryStatus: "absent",
2188
+ levelsValid: null,
2189
+ model: config.provider?.model ?? config.modelSha256 ?? "",
2190
+ scores: emptyScores(),
2191
+ confidence: emptyScores()
2192
+ };
2193
+ }
2194
+ signal.assessment = assessment;
2195
+ signal.additionalIndicators = {
2196
+ ...signal.additionalIndicators,
2197
+ jev: {
2198
+ schema: "tradejs-jev-features/v2",
2199
+ status: assessment.status,
2200
+ scores: assessment.scores,
2201
+ confidence: assessment.confidence
2202
+ }
2203
+ };
2204
+ const identity = {
2205
+ signalId: signal.signalId,
2206
+ strategy: signal.strategy,
2207
+ symbol: signal.symbol,
2208
+ timestamp: signal.timestamp,
2209
+ scope,
2210
+ user: (0, import_jev5.jevHash)(userName),
2211
+ config,
2212
+ assessment
2213
+ };
2214
+ await (0, import_jev5.writeJevArtifact)(
2215
+ import_node_path.default.resolve(
2216
+ projectRoot,
2217
+ config.recordsDir ?? "data/ai/jev",
2218
+ "decisions",
2219
+ `${(0, import_jev5.jevHash)(identity)}.json`
2220
+ ),
2221
+ identity
2222
+ );
2223
+ return assessment;
2224
+ };
2225
+
1342
2226
  // src/strategyRuntime.ts
1343
2227
  var import_constants7 = require("@tradejs/core/constants");
1344
2228
  var import_strategies6 = require("@tradejs/core/strategies");
@@ -2330,8 +3214,11 @@ var toRecord2 = (value) => {
2330
3214
  return value;
2331
3215
  };
2332
3216
  var buildBaseAiPayload = (signal) => {
3217
+ const { jevEvidence: _jevEvidence, ...signalAdditionalIndicators } = toRecord2(
3218
+ signal.additionalIndicators
3219
+ );
2333
3220
  const additionalIndicators = {
2334
- ...toRecord2(signal.additionalIndicators),
3221
+ ...signalAdditionalIndicators,
2335
3222
  marketContext: buildAiMarketContext(signal)
2336
3223
  };
2337
3224
  return {
@@ -2385,7 +3272,7 @@ var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiP
2385
3272
  var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
2386
3273
  var import_aiModels = require("@tradejs/core/aiModels");
2387
3274
  var import_aiLanguages = require("@tradejs/core/aiLanguages");
2388
- var import_userSettings = require("@tradejs/infra/userSettings");
3275
+ var import_userSettings2 = require("@tradejs/infra/userSettings");
2389
3276
  var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
2390
3277
  var userSettingsCache = /* @__PURE__ */ new Map();
2391
3278
  var aiModelCache = /* @__PURE__ */ new Map();
@@ -2433,7 +3320,7 @@ var getOpenRouterModelKwargs = (apiEndpoint) => {
2433
3320
  var getAiUserSettings = async (userName = "root") => {
2434
3321
  let settingsPromise = userSettingsCache.get(userName);
2435
3322
  if (!settingsPromise) {
2436
- settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
3323
+ settingsPromise = (0, import_userSettings2.getUserSettings)(userName).then((settings2) => {
2437
3324
  const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
2438
3325
  return {
2439
3326
  ...settings2,
@@ -4393,7 +5280,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
4393
5280
 
4394
5281
  // src/strategyHelpers/derivativesContext.ts
4395
5282
  var import_indicators2 = require("@tradejs/core/indicators");
4396
- var import_data2 = require("@tradejs/core/data");
5283
+ var import_data3 = require("@tradejs/core/data");
4397
5284
  var import_strategies3 = require("@tradejs/core/strategies");
4398
5285
  var import_constants4 = require("@tradejs/core/constants");
4399
5286
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
@@ -4553,7 +5440,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4553
5440
  const referenceSymbols = getDerivativesContextReferenceSymbols();
4554
5441
  const targetSymbol = normalizeSymbol(signal.symbol);
4555
5442
  const lookbackMs = parseLookbackMs();
4556
- const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
5443
+ const decisionTimeMs = signal.timestamp + (0, import_data3.intervalToMs)(signal.interval);
4557
5444
  const derivativesEndMs = (0, import_indicators2.getLastClosedDerivativesBarStartMs)(
4558
5445
  decisionTimeMs,
4559
5446
  "15m"
@@ -4638,7 +5525,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
4638
5525
  };
4639
5526
 
4640
5527
  // src/strategyHelpers/hyperliquidWhaleContext.ts
4641
- var import_data3 = require("@tradejs/core/data");
5528
+ var import_data4 = require("@tradejs/core/data");
4642
5529
  var import_strategies4 = require("@tradejs/core/strategies");
4643
5530
  var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
4644
5531
  var import_logger9 = require("@tradejs/infra/logger");
@@ -5112,7 +5999,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
5112
5999
  const symbol = resolveHyperliquidPerpFromSignalSymbol(params.symbol);
5113
6000
  if (!symbol) return null;
5114
6001
  const interval = params.marketInterval ?? signalIntervalToMarketInterval2(params.interval);
5115
- const decisionTimeMs = params.timestamp + (0, import_data3.intervalToMs)(params.interval);
6002
+ const decisionTimeMs = params.timestamp + (0, import_data4.intervalToMs)(params.interval);
5116
6003
  const maxAgeMs = params.maxAgeMs ?? DEFAULT_MAX_AGE_BY_INTERVAL2[interval];
5117
6004
  const universe = getHyperliquidPerpUniverseSnapshot();
5118
6005
  const whales = getHyperliquidWhaleRegistrySnapshot();
@@ -5139,9 +6026,9 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
5139
6026
  abortSignal: params.abortSignal
5140
6027
  });
5141
6028
  } else {
5142
- let pending = contextCache.get(cacheKey);
5143
- if (!pending) {
5144
- pending = (0, import_hyperliquidWhales2.getHyperliquidWhaleFlowAggregate)({
6029
+ let pending2 = contextCache.get(cacheKey);
6030
+ if (!pending2) {
6031
+ pending2 = (0, import_hyperliquidWhales2.getHyperliquidWhaleFlowAggregate)({
5145
6032
  symbol,
5146
6033
  interval,
5147
6034
  decisionTimeMs,
@@ -5150,9 +6037,9 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
5150
6037
  whaleRegistryFingerprint: whales.fingerprint,
5151
6038
  ...params.abortSignal ? { signal: params.abortSignal } : {}
5152
6039
  });
5153
- setBoundedCache2(cacheKey, pending);
6040
+ setBoundedCache2(cacheKey, pending2);
5154
6041
  }
5155
- row = await pending;
6042
+ row = await pending2;
5156
6043
  }
5157
6044
  if (!row) return null;
5158
6045
  return toBaseHyperliquidWhaleFlowContext(row, minimumCoveragePct);
@@ -5713,7 +6600,8 @@ var executeEntryOrder = async ({
5713
6600
  runtimeConfigId: signal.runtimeConfigId,
5714
6601
  strategyRevision: signal.strategyRevision,
5715
6602
  runtimeLineage: signal.runtimeLineage,
5716
- ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {}
6603
+ ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
6604
+ ...signal.assessment ? { assessment: signal.assessment } : {}
5717
6605
  });
5718
6606
  }
5719
6607
  if (orderPlaced && recordRuntimeTrade && isPositionIncrease) {
@@ -5786,7 +6674,7 @@ var resolveStrategyConfig = async ({
5786
6674
 
5787
6675
  // src/strategy/runtimeBacktestDelay.ts
5788
6676
  var import_constants6 = require("@tradejs/core/constants");
5789
- var import_data4 = require("@tradejs/core/data");
6677
+ var import_data5 = require("@tradejs/core/data");
5790
6678
  var import_strategies5 = require("@tradejs/core/strategies");
5791
6679
  var resolveBacktestEntryDelayBars = (value) => {
5792
6680
  if (value == null || value === "") {
@@ -5814,7 +6702,7 @@ var resolveBacktestExecutionDelayMs = (value, fallbackDelayMs) => {
5814
6702
  };
5815
6703
  var safeIntervalToMs = (interval) => {
5816
6704
  try {
5817
- return (0, import_data4.intervalToMs)(interval);
6705
+ return (0, import_data5.intervalToMs)(interval);
5818
6706
  } catch {
5819
6707
  return null;
5820
6708
  }
@@ -6438,6 +7326,7 @@ var handleExitDecision = async ({
6438
7326
  onRuntimeClose?.({
6439
7327
  userName,
6440
7328
  strategy: strategyName,
7329
+ deploymentId: connector.deploymentId,
6441
7330
  openedByStrategy: trade.strategy,
6442
7331
  symbol,
6443
7332
  direction: trade.direction,
@@ -6730,6 +7619,11 @@ var createStrategyRuntime = ({
6730
7619
  const projectConfig = await loadTradejsConfig(projectRoot);
6731
7620
  const projectHooks = projectConfig.hooks;
6732
7621
  const env = String(config.ENV ?? "BACKTEST");
7622
+ const jev = (0, import_jev6.parseJevConfig)(config.JEV);
7623
+ if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
7624
+ throw new Error(
7625
+ "Runtime JEV requires a frozen provider endpoint and model"
7626
+ );
6733
7627
  const backtestPriceMode = config.BACKTEST_PRICE_MODE ?? "open";
6734
7628
  const backtestEntryDelayBars = env === "BACKTEST" ? resolveBacktestEntryDelayBars(config.BACKTEST_ENTRY_DELAY_BARS) : 0;
6735
7629
  const resolvedBacktestExecutionInterval = config.BACKTEST_EXECUTION_INTERVAL ?? backtestExecutionMarketData?.interval ?? resolveBacktestExecutionIntervalForPrimary(config.INTERVAL ?? "15");
@@ -7150,44 +8044,44 @@ var createStrategyRuntime = ({
7150
8044
  if (pendingBacktestEntry.delayBarsRemaining > 0) {
7151
8045
  return `BACKTEST_ENTRY_DELAY_PENDING:${pendingBacktestEntry.delayBarsRemaining}`;
7152
8046
  }
7153
- const pending = pendingBacktestEntry;
8047
+ const pending2 = pendingBacktestEntry;
7154
8048
  pendingBacktestEntry = null;
7155
8049
  const executionCandleResolution = resolveBacktestExecutionCandle(
7156
8050
  candle,
7157
8051
  btcCandle
7158
8052
  );
7159
8053
  const execution = applyBacktestDelayedEntryExecution({
7160
- decision: pending.decision,
8054
+ decision: pending2.decision,
7161
8055
  execution: executionCandleResolution,
7162
8056
  backtestPriceMode: executionCandleResolution.source === "primary_timeframe" ? "open" : backtestPriceMode,
7163
- delayBars: pending.delayBars
8057
+ delayBars: pending2.delayBars
7164
8058
  });
7165
8059
  if (execution.skipReason) {
7166
- return pending.decision.signal ?? execution.skipReason;
8060
+ return pending2.decision.signal ?? execution.skipReason;
7167
8061
  }
7168
8062
  if (!execution.executionCandle || !execution.btcExecutionCandle) {
7169
- return pending.decision.signal ?? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING";
8063
+ return pending2.decision.signal ?? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING";
7170
8064
  }
7171
8065
  const market = {
7172
8066
  candle: execution.executionCandle,
7173
8067
  btcCandle: execution.btcExecutionCandle
7174
8068
  };
7175
8069
  const entry = buildHookEntry({
7176
- decision: pending.decision,
7177
- runtime: pending.runtime
8070
+ decision: pending2.decision,
8071
+ runtime: pending2.runtime
7178
8072
  });
7179
8073
  return executeEntryDecision({
7180
8074
  connector,
7181
8075
  symbol,
7182
- decision: pending.decision,
7183
- runtime: pending.runtime,
7184
- manifest: pending.manifest,
7185
- hookCtx: pending.hookCtx,
8076
+ decision: pending2.decision,
8077
+ runtime: pending2.runtime,
8078
+ manifest: pending2.manifest,
8079
+ hookCtx: pending2.hookCtx,
7186
8080
  market,
7187
8081
  entry,
7188
- policy: pending.policy,
7189
- ml: pending.ml,
7190
- ai: pending.ai,
8082
+ policy: pending2.policy,
8083
+ ml: pending2.ml,
8084
+ ai: pending2.ai,
7191
8085
  recordRuntimeJournal,
7192
8086
  invokeStageHooks,
7193
8087
  notifyRuntimeError
@@ -7437,6 +8331,16 @@ var createStrategyRuntime = ({
7437
8331
  env,
7438
8332
  includeHyperliquidWhales: false
7439
8333
  });
8334
+ if (jev) {
8335
+ await assessSignalWithJev({
8336
+ signal,
8337
+ config: jev,
8338
+ userName,
8339
+ projectRoot,
8340
+ strict: env === "BACKTEST",
8341
+ scope: runtimeLineage?.deploymentCompositionId ?? (env === "BACKTEST" ? "backtest" : "runtime")
8342
+ });
8343
+ }
7440
8344
  quality = await enrichSignalWithAi({
7441
8345
  signal,
7442
8346
  userName,
@@ -7506,6 +8410,7 @@ var createStrategyRuntime = ({
7506
8410
  if (!shouldMakeOrder) {
7507
8411
  if (signal) {
7508
8412
  const skipReason = getEntrySkipReason({
8413
+ signal,
7509
8414
  makeOrdersEnabled,
7510
8415
  env,
7511
8416
  ml,
@@ -7719,10 +8624,10 @@ var computeDeploymentCompositionId = (value) => revision("dc1", {
7719
8624
  }))
7720
8625
  });
7721
8626
  var readRuntimePackageManifest = async (projectRoot) => {
7722
- const manifestPath = process.env.TRADEJS_RUNTIME_PACKAGE_MANIFEST?.trim() || import_node_path.default.join(projectRoot, "runtime-package-manifest.json");
8627
+ const manifestPath = process.env.TRADEJS_RUNTIME_PACKAGE_MANIFEST?.trim() || import_node_path2.default.join(projectRoot, "runtime-package-manifest.json");
7723
8628
  let value;
7724
8629
  try {
7725
- value = JSON.parse(await (0, import_promises.readFile)(manifestPath, "utf8"));
8630
+ value = JSON.parse(await (0, import_promises2.readFile)(manifestPath, "utf8"));
7726
8631
  } catch (error) {
7727
8632
  throw new Error(
7728
8633
  `Unable to read runtime package manifest ${manifestPath}: ${String(error)}`
@@ -7761,7 +8666,7 @@ var verifyInstalledPackageName = async ({
7761
8666
  throw new Error("name is invalid");
7762
8667
  }
7763
8668
  const projectManifest = JSON.parse(
7764
- await (0, import_promises.readFile)(import_node_path.default.join(projectRoot, "package.json"), "utf8")
8669
+ await (0, import_promises2.readFile)(import_node_path2.default.join(projectRoot, "package.json"), "utf8")
7765
8670
  );
7766
8671
  const alias = parseNpmAliasReference(
7767
8672
  projectManifest.dependencies?.[packageName]
@@ -7775,14 +8680,14 @@ var readInstalledPackageMetadata = async ({
7775
8680
  packageName,
7776
8681
  projectPackage
7777
8682
  }) => {
7778
- const packageJsonPath = projectPackage ? import_node_path.default.join(projectRoot, "package.json") : import_node_path.default.join(
8683
+ const packageJsonPath = projectPackage ? import_node_path2.default.join(projectRoot, "package.json") : import_node_path2.default.join(
7779
8684
  projectRoot,
7780
8685
  "node_modules",
7781
8686
  ...packageName.split("/"),
7782
8687
  "package.json"
7783
8688
  );
7784
8689
  try {
7785
- const packageJson = JSON.parse(await (0, import_promises.readFile)(packageJsonPath, "utf8"));
8690
+ const packageJson = JSON.parse(await (0, import_promises2.readFile)(packageJsonPath, "utf8"));
7786
8691
  if (typeof packageJson.version !== "string" || !packageJson.version.trim()) {
7787
8692
  throw new Error("version is invalid");
7788
8693
  }
@@ -7857,12 +8762,12 @@ var resolveStrategyPackage = async ({
7857
8762
  projectRoot
7858
8763
  }) => {
7859
8764
  if (!pluginSource) return null;
7860
- if (!pluginSource.startsWith(".") && !import_node_path.default.isAbsolute(pluginSource)) {
8765
+ if (!pluginSource.startsWith(".") && !import_node_path2.default.isAbsolute(pluginSource)) {
7861
8766
  return { name: pluginSource, projectPackage: false };
7862
8767
  }
7863
8768
  try {
7864
8769
  const packageJson = JSON.parse(
7865
- await (0, import_promises.readFile)(import_node_path.default.join(projectRoot, "package.json"), "utf8")
8770
+ await (0, import_promises2.readFile)(import_node_path2.default.join(projectRoot, "package.json"), "utf8")
7866
8771
  );
7867
8772
  return typeof packageJson.name === "string" && packageJson.name.trim() ? { name: packageJson.name, projectPackage: true } : null;
7868
8773
  } catch {
@@ -7966,7 +8871,15 @@ var resolveStrategyComposition = async ({
7966
8871
  `Strategy config parser returned a non-object: ${strategyName}`
7967
8872
  );
7968
8873
  }
7969
- const strategyConfig = parsedConfig;
8874
+ const jev = (0, import_jev8.parseJevConfig)(declaration.config.JEV);
8875
+ if (jev && jev.source === "provider" && !jev.provider)
8876
+ throw new Error(
8877
+ "Runtime JEV requires a frozen provider endpoint and model"
8878
+ );
8879
+ const strategyConfig = {
8880
+ ...parsedConfig,
8881
+ ...jev ? { JEV: jev } : {}
8882
+ };
7970
8883
  const selection = resolveStrategySelection({
7971
8884
  deployment,
7972
8885
  strategy: declaration