@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_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, ...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_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
+ (_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/runtimeStrategies.ts
31
661
  var runtimeStrategies_exports = {};
32
662
  __export(runtimeStrategies_exports, {
@@ -41,12 +671,266 @@ __export(runtimeStrategies_exports, {
41
671
  verifyRuntimeDeclaration: () => verifyRuntimeDeclaration
42
672
  });
43
673
  module.exports = __toCommonJS(runtimeStrategies_exports);
674
+ var import_jev8 = require("@tradejs/core/jev");
44
675
  var import_node_crypto4 = require("crypto");
45
- var import_promises = require("fs/promises");
46
- var import_node_path = __toESM(require("path"));
676
+ var import_promises2 = require("fs/promises");
677
+ var import_node_path2 = __toESM(require("path"));
47
678
  var import_runtimeControls = require("@tradejs/infra/runtimeControls");
48
679
  var import_tradingAccounts = require("@tradejs/infra/tradingAccounts");
49
680
 
681
+ // src/strategyRuntime.ts
682
+ var import_jev6 = require("@tradejs/core/jev");
683
+
684
+ // src/jev.ts
685
+ var import_promises = __toESM(require("fs/promises"));
686
+ var import_node_path = __toESM(require("path"));
687
+ var import_jev4 = require("@tradejs/core/jev");
688
+ var import_jev5 = require("@tradejs/infra/jev");
689
+ var import_userSettings = require("@tradejs/infra/userSettings");
690
+ init_jevInput();
691
+ init_jevInput();
692
+ init_jevTraining();
693
+ var resolveJevProvider = async (userName) => {
694
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
695
+ const provider = {
696
+ endpoint: settings.JEV_API_ENDPOINT || import_jev4.JEV_PROVIDERS[0].endpoint,
697
+ model: settings.JEV_MODEL || import_jev4.JEV_PROVIDERS[0].model
698
+ };
699
+ (0, import_jev4.parseJevConfig)({ source: "provider", mode: "observe", provider });
700
+ if (!settings.JEV_API_KEY)
701
+ throw new Error("Configure the Jev API key in Account settings");
702
+ return provider;
703
+ };
704
+ var emptyScores = () => Object.fromEntries(import_jev4.JEV_DIMENSIONS.map((key) => [key, null]));
705
+ var pending = /* @__PURE__ */ new Map();
706
+ var pendingResponses = /* @__PURE__ */ new Map();
707
+ var evaluateJevInput = async ({
708
+ input,
709
+ config,
710
+ userName,
711
+ projectRoot
712
+ }) => {
713
+ config = (0, import_jev4.parseJevConfig)(config);
714
+ const inputHash = (0, import_jev5.jevHash)(input);
715
+ const questions = questionsForJevInput(input);
716
+ const questionsHash = (0, import_jev5.jevHash)(questions);
717
+ const base = {
718
+ schema: "tradejs-signal-assessment/v5",
719
+ source: config.source,
720
+ mode: config.mode,
721
+ inputHash,
722
+ questionsHash,
723
+ geometryStatus: input.geometryStatus,
724
+ levelsValid: typeof input.features["signal.validLevels"] === "boolean" ? input.features["signal.validLevels"] : null
725
+ };
726
+ if (!input.questions.length)
727
+ return {
728
+ assessment: {
729
+ ...base,
730
+ status: "unavailable",
731
+ model: "",
732
+ scores: emptyScores(),
733
+ confidence: emptyScores()
734
+ }
735
+ };
736
+ if (config.source === "local") {
737
+ const contents = await import_promises.default.readFile(
738
+ import_node_path.default.resolve(projectRoot, config.modelFile),
739
+ "utf8"
740
+ );
741
+ if ((0, import_jev5.jevFileHash)(contents) !== config.modelSha256)
742
+ throw new Error("Local Jev gate checksum mismatch");
743
+ const { validateJevGateModel: validateJevGateModel2 } = await Promise.resolve().then(() => (init_jevTraining(), jevTraining_exports));
744
+ const model = validateJevGateModel2(JSON.parse(contents));
745
+ if (model.strategy !== input.strategy || model.questionsHash !== (0, import_jev5.jevHash)(JEV_QUESTIONS))
746
+ throw new Error("Local Jev gate strategy or question version mismatch");
747
+ const scores = Object.fromEntries(
748
+ import_jev4.JEV_DIMENSIONS.map((key) => [
749
+ key,
750
+ input.questions.includes(key) && model.trees[key] ? (0, import_jev4.predictJevTree)(model.trees[key], input.features) : null
751
+ ])
752
+ );
753
+ return {
754
+ assessment: {
755
+ ...base,
756
+ status: "available",
757
+ model: config.modelSha256,
758
+ scores,
759
+ confidence: emptyScores()
760
+ }
761
+ };
762
+ }
763
+ const provider = config.provider ?? await resolveJevProvider(userName);
764
+ const id = (0, import_jev5.jevHash)({ inputHash, questionsHash, provider });
765
+ const dir = import_node_path.default.resolve(projectRoot, config.recordsDir ?? "data/ai/jev");
766
+ const file = import_node_path.default.join(dir, "records", `${id}.json`);
767
+ const getRecord = async () => {
768
+ const existing = await (0, import_jev5.readJevArtifact)(file);
769
+ if (existing) {
770
+ 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))
771
+ throw new Error("Jev recording identity mismatch");
772
+ (0, import_jev5.validateJevResponse)(existing.response, input.questions);
773
+ for (const key of import_jev4.JEV_DIMENSIONS) {
774
+ const expected = input.questions.includes(key) ? existing.response.answers[key].score / 4 : null;
775
+ if (existing.scores[key] !== expected)
776
+ throw new Error("Jev recording scores do not match the response");
777
+ }
778
+ return existing;
779
+ }
780
+ if (config.source === "recorded")
781
+ throw new Error(
782
+ `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.`
783
+ );
784
+ const settings = await (0, import_userSettings.getUserSettings)(userName);
785
+ if (!settings.JEV_API_KEY)
786
+ throw new Error("Configure the Jev API key in Account settings");
787
+ const apiKey = settings.JEV_API_KEY;
788
+ const configuredProvider = await resolveJevProvider(userName);
789
+ if (configuredProvider.endpoint !== provider.endpoint)
790
+ throw new Error(
791
+ "Jev account endpoint changed; update the frozen provider configuration"
792
+ );
793
+ const started = Date.now();
794
+ const state = stateForJevInput(input);
795
+ const responseId = (0, import_jev5.jevHash)({
796
+ state,
797
+ questionsHash,
798
+ provider,
799
+ user: (0, import_jev5.jevHash)(userName)
800
+ });
801
+ const responseFile = import_node_path.default.join(dir, "responses", `${responseId}.json`);
802
+ let responsePromise = pendingResponses.get(responseFile);
803
+ if (!responsePromise) {
804
+ responsePromise = (async () => {
805
+ const saved = await (0, import_jev5.readJevArtifact)(responseFile);
806
+ if (saved) return (0, import_jev5.validateJevResponse)(saved, input.questions);
807
+ const fresh = await (0, import_jev5.requestJev)(provider, apiKey, state, questions);
808
+ await (0, import_jev5.writeJevArtifact)(responseFile, fresh);
809
+ return fresh;
810
+ })();
811
+ pendingResponses.set(responseFile, responsePromise);
812
+ }
813
+ let response;
814
+ try {
815
+ response = await responsePromise;
816
+ } finally {
817
+ pendingResponses.delete(responseFile);
818
+ }
819
+ const scores = Object.fromEntries(
820
+ import_jev4.JEV_DIMENSIONS.map((key) => [
821
+ key,
822
+ input.questions.includes(key) ? response.answers[key].score / 4 : null
823
+ ])
824
+ );
825
+ const value = {
826
+ schema: "tradejs-jev-record/v4",
827
+ id,
828
+ inputHash,
829
+ questionsHash,
830
+ provider,
831
+ input,
832
+ response,
833
+ scores,
834
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
835
+ elapsedMs: Date.now() - started
836
+ };
837
+ await (0, import_jev5.writeJevArtifact)(file, value);
838
+ return await (0, import_jev5.readJevArtifact)(file);
839
+ };
840
+ const pendingKey = `${file}:${(0, import_jev5.jevHash)(userName)}`;
841
+ let promise = pending.get(pendingKey);
842
+ if (!promise) {
843
+ promise = getRecord();
844
+ pending.set(pendingKey, promise);
845
+ }
846
+ let record;
847
+ try {
848
+ record = await promise;
849
+ } finally {
850
+ pending.delete(pendingKey);
851
+ }
852
+ return {
853
+ record,
854
+ assessment: {
855
+ ...base,
856
+ status: "available",
857
+ recordId: id,
858
+ model: record.response.model,
859
+ scores: record.scores,
860
+ confidence: Object.fromEntries(
861
+ import_jev4.JEV_DIMENSIONS.map((key) => [
862
+ key,
863
+ record.response.answers[key]?.confidence ?? null
864
+ ])
865
+ )
866
+ }
867
+ };
868
+ };
869
+ var assessSignalWithJev = async ({
870
+ signal,
871
+ config,
872
+ userName,
873
+ projectRoot,
874
+ strict,
875
+ scope = "runtime"
876
+ }) => {
877
+ let assessment;
878
+ try {
879
+ const input = buildJevInput(signal);
880
+ ({ assessment } = await evaluateJevInput({
881
+ input,
882
+ config,
883
+ userName,
884
+ projectRoot
885
+ }));
886
+ } catch (error) {
887
+ if (strict) throw error;
888
+ assessment = {
889
+ schema: "tradejs-signal-assessment/v5",
890
+ source: config.source,
891
+ mode: config.mode,
892
+ status: "unavailable",
893
+ inputHash: "",
894
+ questionsHash: "",
895
+ geometryStatus: "absent",
896
+ levelsValid: null,
897
+ model: config.provider?.model ?? config.modelSha256 ?? "",
898
+ scores: emptyScores(),
899
+ confidence: emptyScores()
900
+ };
901
+ }
902
+ signal.assessment = assessment;
903
+ signal.additionalIndicators = {
904
+ ...signal.additionalIndicators,
905
+ jev: {
906
+ schema: "tradejs-jev-features/v2",
907
+ status: assessment.status,
908
+ scores: assessment.scores,
909
+ confidence: assessment.confidence
910
+ }
911
+ };
912
+ const identity = {
913
+ signalId: signal.signalId,
914
+ strategy: signal.strategy,
915
+ symbol: signal.symbol,
916
+ timestamp: signal.timestamp,
917
+ scope,
918
+ user: (0, import_jev5.jevHash)(userName),
919
+ config,
920
+ assessment
921
+ };
922
+ await (0, import_jev5.writeJevArtifact)(
923
+ import_node_path.default.resolve(
924
+ projectRoot,
925
+ config.recordsDir ?? "data/ai/jev",
926
+ "decisions",
927
+ `${(0, import_jev5.jevHash)(identity)}.json`
928
+ ),
929
+ identity
930
+ );
931
+ return assessment;
932
+ };
933
+
50
934
  // src/strategyRuntime.ts
51
935
  var import_constants5 = require("@tradejs/core/constants");
52
936
  var import_strategies6 = require("@tradejs/core/strategies");
@@ -1327,8 +2211,11 @@ var toRecord2 = (value) => {
1327
2211
  return value;
1328
2212
  };
1329
2213
  var buildBaseAiPayload = (signal) => {
2214
+ const { jevEvidence: _jevEvidence, ...signalAdditionalIndicators } = toRecord2(
2215
+ signal.additionalIndicators
2216
+ );
1330
2217
  const additionalIndicators = {
1331
- ...toRecord2(signal.additionalIndicators),
2218
+ ...signalAdditionalIndicators,
1332
2219
  marketContext: buildAiMarketContext(signal)
1333
2220
  };
1334
2221
  return {
@@ -1382,7 +2269,7 @@ var postProcessLocalAiAnalysisByStrategy = (signal, analysis, payload = buildAiP
1382
2269
  var import_aiEndpoints = require("@tradejs/core/aiEndpoints");
1383
2270
  var import_aiModels = require("@tradejs/core/aiModels");
1384
2271
  var import_aiLanguages = require("@tradejs/core/aiLanguages");
1385
- var import_userSettings = require("@tradejs/infra/userSettings");
2272
+ var import_userSettings2 = require("@tradejs/infra/userSettings");
1386
2273
  var DEFAULT_AI_MODEL = "openai/gpt-5-mini";
1387
2274
  var userSettingsCache = /* @__PURE__ */ new Map();
1388
2275
  var aiModelCache = /* @__PURE__ */ new Map();
@@ -1430,7 +2317,7 @@ var getOpenRouterModelKwargs = (apiEndpoint) => {
1430
2317
  var getAiUserSettings = async (userName = "root") => {
1431
2318
  let settingsPromise = userSettingsCache.get(userName);
1432
2319
  if (!settingsPromise) {
1433
- settingsPromise = (0, import_userSettings.getUserSettings)(userName).then((settings2) => {
2320
+ settingsPromise = (0, import_userSettings2.getUserSettings)(userName).then((settings2) => {
1434
2321
  const endpoint = (0, import_aiEndpoints.normalizeAiEndpoint)(settings2.AI_API_ENDPOINT);
1435
2322
  return {
1436
2323
  ...settings2,
@@ -3390,7 +4277,7 @@ var enrichSignalWithCoinMarketCapContext = async (params) => {
3390
4277
 
3391
4278
  // src/strategyHelpers/derivativesContext.ts
3392
4279
  var import_indicators2 = require("@tradejs/core/indicators");
3393
- var import_data = require("@tradejs/core/data");
4280
+ var import_data2 = require("@tradejs/core/data");
3394
4281
  var import_strategies3 = require("@tradejs/core/strategies");
3395
4282
  var import_constants2 = require("@tradejs/core/constants");
3396
4283
  var import_derivatives = require("@tradejs/infra/timescale/derivatives");
@@ -3550,7 +4437,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
3550
4437
  const referenceSymbols = getDerivativesContextReferenceSymbols();
3551
4438
  const targetSymbol = normalizeSymbol(signal.symbol);
3552
4439
  const lookbackMs = parseLookbackMs();
3553
- const decisionTimeMs = signal.timestamp + (0, import_data.intervalToMs)(signal.interval);
4440
+ const decisionTimeMs = signal.timestamp + (0, import_data2.intervalToMs)(signal.interval);
3554
4441
  const derivativesEndMs = (0, import_indicators2.getLastClosedDerivativesBarStartMs)(
3555
4442
  decisionTimeMs,
3556
4443
  "15m"
@@ -3635,7 +4522,7 @@ var enrichSignalWithDerivativesContext = async (params) => {
3635
4522
  };
3636
4523
 
3637
4524
  // src/strategyHelpers/hyperliquidWhaleContext.ts
3638
- var import_data2 = require("@tradejs/core/data");
4525
+ var import_data3 = require("@tradejs/core/data");
3639
4526
  var import_strategies4 = require("@tradejs/core/strategies");
3640
4527
  var import_hyperliquidWhales2 = require("@tradejs/infra/timescale/hyperliquidWhales");
3641
4528
  var import_logger6 = require("@tradejs/infra/logger");
@@ -4109,7 +4996,7 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
4109
4996
  const symbol = resolveHyperliquidPerpFromSignalSymbol(params.symbol);
4110
4997
  if (!symbol) return null;
4111
4998
  const interval = params.marketInterval ?? signalIntervalToMarketInterval2(params.interval);
4112
- const decisionTimeMs = params.timestamp + (0, import_data2.intervalToMs)(params.interval);
4999
+ const decisionTimeMs = params.timestamp + (0, import_data3.intervalToMs)(params.interval);
4113
5000
  const maxAgeMs = params.maxAgeMs ?? DEFAULT_MAX_AGE_BY_INTERVAL2[interval];
4114
5001
  const universe = getHyperliquidPerpUniverseSnapshot();
4115
5002
  const whales = getHyperliquidWhaleRegistrySnapshot();
@@ -4136,9 +5023,9 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
4136
5023
  abortSignal: params.abortSignal
4137
5024
  });
4138
5025
  } else {
4139
- let pending = contextCache.get(cacheKey);
4140
- if (!pending) {
4141
- pending = (0, import_hyperliquidWhales2.getHyperliquidWhaleFlowAggregate)({
5026
+ let pending2 = contextCache.get(cacheKey);
5027
+ if (!pending2) {
5028
+ pending2 = (0, import_hyperliquidWhales2.getHyperliquidWhaleFlowAggregate)({
4142
5029
  symbol,
4143
5030
  interval,
4144
5031
  decisionTimeMs,
@@ -4147,9 +5034,9 @@ var loadHyperliquidWhaleFlowContext = async (params) => {
4147
5034
  whaleRegistryFingerprint: whales.fingerprint,
4148
5035
  ...params.abortSignal ? { signal: params.abortSignal } : {}
4149
5036
  });
4150
- setBoundedCache2(cacheKey, pending);
5037
+ setBoundedCache2(cacheKey, pending2);
4151
5038
  }
4152
- row = await pending;
5039
+ row = await pending2;
4153
5040
  }
4154
5041
  if (!row) return null;
4155
5042
  return toBaseHyperliquidWhaleFlowContext(row, minimumCoveragePct);
@@ -4710,7 +5597,8 @@ var executeEntryOrder = async ({
4710
5597
  runtimeConfigId: signal.runtimeConfigId,
4711
5598
  strategyRevision: signal.strategyRevision,
4712
5599
  runtimeLineage: signal.runtimeLineage,
4713
- ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {}
5600
+ ...signal.aiAnalysis ? { aiAnalysis: signal.aiAnalysis } : {},
5601
+ ...signal.assessment ? { assessment: signal.assessment } : {}
4714
5602
  });
4715
5603
  }
4716
5604
  if (orderPlaced && recordRuntimeTrade && isPositionIncrease) {
@@ -4783,7 +5671,7 @@ var resolveStrategyConfig = async ({
4783
5671
 
4784
5672
  // src/strategy/runtimeBacktestDelay.ts
4785
5673
  var import_constants4 = require("@tradejs/core/constants");
4786
- var import_data3 = require("@tradejs/core/data");
5674
+ var import_data4 = require("@tradejs/core/data");
4787
5675
  var import_strategies5 = require("@tradejs/core/strategies");
4788
5676
  var resolveBacktestEntryDelayBars = (value) => {
4789
5677
  if (value == null || value === "") {
@@ -4811,7 +5699,7 @@ var resolveBacktestExecutionDelayMs = (value, fallbackDelayMs) => {
4811
5699
  };
4812
5700
  var safeIntervalToMs = (interval) => {
4813
5701
  try {
4814
- return (0, import_data3.intervalToMs)(interval);
5702
+ return (0, import_data4.intervalToMs)(interval);
4815
5703
  } catch {
4816
5704
  return null;
4817
5705
  }
@@ -5435,6 +6323,7 @@ var handleExitDecision = async ({
5435
6323
  onRuntimeClose?.({
5436
6324
  userName,
5437
6325
  strategy: strategyName,
6326
+ deploymentId: connector.deploymentId,
5438
6327
  openedByStrategy: trade.strategy,
5439
6328
  symbol,
5440
6329
  direction: trade.direction,
@@ -5727,6 +6616,11 @@ var createStrategyRuntime = ({
5727
6616
  const projectConfig = await loadTradejsConfig(projectRoot);
5728
6617
  const projectHooks = projectConfig.hooks;
5729
6618
  const env = String(config.ENV ?? "BACKTEST");
6619
+ const jev = (0, import_jev6.parseJevConfig)(config.JEV);
6620
+ if (jev && env !== "BACKTEST" && jev.source === "provider" && !jev.provider)
6621
+ throw new Error(
6622
+ "Runtime JEV requires a frozen provider endpoint and model"
6623
+ );
5730
6624
  const backtestPriceMode = config.BACKTEST_PRICE_MODE ?? "open";
5731
6625
  const backtestEntryDelayBars = env === "BACKTEST" ? resolveBacktestEntryDelayBars(config.BACKTEST_ENTRY_DELAY_BARS) : 0;
5732
6626
  const resolvedBacktestExecutionInterval = config.BACKTEST_EXECUTION_INTERVAL ?? backtestExecutionMarketData?.interval ?? resolveBacktestExecutionIntervalForPrimary(config.INTERVAL ?? "15");
@@ -6147,44 +7041,44 @@ var createStrategyRuntime = ({
6147
7041
  if (pendingBacktestEntry.delayBarsRemaining > 0) {
6148
7042
  return `BACKTEST_ENTRY_DELAY_PENDING:${pendingBacktestEntry.delayBarsRemaining}`;
6149
7043
  }
6150
- const pending = pendingBacktestEntry;
7044
+ const pending2 = pendingBacktestEntry;
6151
7045
  pendingBacktestEntry = null;
6152
7046
  const executionCandleResolution = resolveBacktestExecutionCandle(
6153
7047
  candle,
6154
7048
  btcCandle
6155
7049
  );
6156
7050
  const execution = applyBacktestDelayedEntryExecution({
6157
- decision: pending.decision,
7051
+ decision: pending2.decision,
6158
7052
  execution: executionCandleResolution,
6159
7053
  backtestPriceMode: executionCandleResolution.source === "primary_timeframe" ? "open" : backtestPriceMode,
6160
- delayBars: pending.delayBars
7054
+ delayBars: pending2.delayBars
6161
7055
  });
6162
7056
  if (execution.skipReason) {
6163
- return pending.decision.signal ?? execution.skipReason;
7057
+ return pending2.decision.signal ?? execution.skipReason;
6164
7058
  }
6165
7059
  if (!execution.executionCandle || !execution.btcExecutionCandle) {
6166
- return pending.decision.signal ?? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING";
7060
+ return pending2.decision.signal ?? "BACKTEST_LOWER_EXECUTION_CANDLE_MISSING";
6167
7061
  }
6168
7062
  const market = {
6169
7063
  candle: execution.executionCandle,
6170
7064
  btcCandle: execution.btcExecutionCandle
6171
7065
  };
6172
7066
  const entry = buildHookEntry({
6173
- decision: pending.decision,
6174
- runtime: pending.runtime
7067
+ decision: pending2.decision,
7068
+ runtime: pending2.runtime
6175
7069
  });
6176
7070
  return executeEntryDecision({
6177
7071
  connector,
6178
7072
  symbol,
6179
- decision: pending.decision,
6180
- runtime: pending.runtime,
6181
- manifest: pending.manifest,
6182
- hookCtx: pending.hookCtx,
7073
+ decision: pending2.decision,
7074
+ runtime: pending2.runtime,
7075
+ manifest: pending2.manifest,
7076
+ hookCtx: pending2.hookCtx,
6183
7077
  market,
6184
7078
  entry,
6185
- policy: pending.policy,
6186
- ml: pending.ml,
6187
- ai: pending.ai,
7079
+ policy: pending2.policy,
7080
+ ml: pending2.ml,
7081
+ ai: pending2.ai,
6188
7082
  recordRuntimeJournal,
6189
7083
  invokeStageHooks,
6190
7084
  notifyRuntimeError
@@ -6434,6 +7328,16 @@ var createStrategyRuntime = ({
6434
7328
  env,
6435
7329
  includeHyperliquidWhales: false
6436
7330
  });
7331
+ if (jev) {
7332
+ await assessSignalWithJev({
7333
+ signal,
7334
+ config: jev,
7335
+ userName,
7336
+ projectRoot,
7337
+ strict: env === "BACKTEST",
7338
+ scope: runtimeLineage?.deploymentCompositionId ?? (env === "BACKTEST" ? "backtest" : "runtime")
7339
+ });
7340
+ }
6437
7341
  quality = await enrichSignalWithAi({
6438
7342
  signal,
6439
7343
  userName,
@@ -6503,6 +7407,7 @@ var createStrategyRuntime = ({
6503
7407
  if (!shouldMakeOrder) {
6504
7408
  if (signal) {
6505
7409
  const skipReason = getEntrySkipReason({
7410
+ signal,
6506
7411
  makeOrdersEnabled,
6507
7412
  env,
6508
7413
  ml,
@@ -6716,10 +7621,10 @@ var computeDeploymentCompositionId = (value) => revision("dc1", {
6716
7621
  }))
6717
7622
  });
6718
7623
  var readRuntimePackageManifest = async (projectRoot) => {
6719
- const manifestPath = process.env.TRADEJS_RUNTIME_PACKAGE_MANIFEST?.trim() || import_node_path.default.join(projectRoot, "runtime-package-manifest.json");
7624
+ const manifestPath = process.env.TRADEJS_RUNTIME_PACKAGE_MANIFEST?.trim() || import_node_path2.default.join(projectRoot, "runtime-package-manifest.json");
6720
7625
  let value;
6721
7626
  try {
6722
- value = JSON.parse(await (0, import_promises.readFile)(manifestPath, "utf8"));
7627
+ value = JSON.parse(await (0, import_promises2.readFile)(manifestPath, "utf8"));
6723
7628
  } catch (error) {
6724
7629
  throw new Error(
6725
7630
  `Unable to read runtime package manifest ${manifestPath}: ${String(error)}`
@@ -6758,7 +7663,7 @@ var verifyInstalledPackageName = async ({
6758
7663
  throw new Error("name is invalid");
6759
7664
  }
6760
7665
  const projectManifest = JSON.parse(
6761
- await (0, import_promises.readFile)(import_node_path.default.join(projectRoot, "package.json"), "utf8")
7666
+ await (0, import_promises2.readFile)(import_node_path2.default.join(projectRoot, "package.json"), "utf8")
6762
7667
  );
6763
7668
  const alias = parseNpmAliasReference(
6764
7669
  projectManifest.dependencies?.[packageName]
@@ -6772,14 +7677,14 @@ var readInstalledPackageMetadata = async ({
6772
7677
  packageName,
6773
7678
  projectPackage
6774
7679
  }) => {
6775
- const packageJsonPath = projectPackage ? import_node_path.default.join(projectRoot, "package.json") : import_node_path.default.join(
7680
+ const packageJsonPath = projectPackage ? import_node_path2.default.join(projectRoot, "package.json") : import_node_path2.default.join(
6776
7681
  projectRoot,
6777
7682
  "node_modules",
6778
7683
  ...packageName.split("/"),
6779
7684
  "package.json"
6780
7685
  );
6781
7686
  try {
6782
- const packageJson = JSON.parse(await (0, import_promises.readFile)(packageJsonPath, "utf8"));
7687
+ const packageJson = JSON.parse(await (0, import_promises2.readFile)(packageJsonPath, "utf8"));
6783
7688
  if (typeof packageJson.version !== "string" || !packageJson.version.trim()) {
6784
7689
  throw new Error("version is invalid");
6785
7690
  }
@@ -6854,12 +7759,12 @@ var resolveStrategyPackage = async ({
6854
7759
  projectRoot
6855
7760
  }) => {
6856
7761
  if (!pluginSource) return null;
6857
- if (!pluginSource.startsWith(".") && !import_node_path.default.isAbsolute(pluginSource)) {
7762
+ if (!pluginSource.startsWith(".") && !import_node_path2.default.isAbsolute(pluginSource)) {
6858
7763
  return { name: pluginSource, projectPackage: false };
6859
7764
  }
6860
7765
  try {
6861
7766
  const packageJson = JSON.parse(
6862
- await (0, import_promises.readFile)(import_node_path.default.join(projectRoot, "package.json"), "utf8")
7767
+ await (0, import_promises2.readFile)(import_node_path2.default.join(projectRoot, "package.json"), "utf8")
6863
7768
  );
6864
7769
  return typeof packageJson.name === "string" && packageJson.name.trim() ? { name: packageJson.name, projectPackage: true } : null;
6865
7770
  } catch {
@@ -6963,7 +7868,15 @@ var resolveStrategyComposition = async ({
6963
7868
  `Strategy config parser returned a non-object: ${strategyName}`
6964
7869
  );
6965
7870
  }
6966
- const strategyConfig = parsedConfig;
7871
+ const jev = (0, import_jev8.parseJevConfig)(declaration.config.JEV);
7872
+ if (jev && jev.source === "provider" && !jev.provider)
7873
+ throw new Error(
7874
+ "Runtime JEV requires a frozen provider endpoint and model"
7875
+ );
7876
+ const strategyConfig = {
7877
+ ...parsedConfig,
7878
+ ...jev ? { JEV: jev } : {}
7879
+ };
6967
7880
  const selection = resolveStrategySelection({
6968
7881
  deployment,
6969
7882
  strategy: declaration