@tradejs/node 3.1.29-beta.261 → 3.1.29-beta.262

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.
@@ -1,10 +1,19 @@
1
1
  // src/jevTraining.ts
2
- import { JEV_DIMENSIONS as JEV_DIMENSIONS2, decideJev, predictJevTree } from "@tradejs/core/jev";
3
- import { jevHash, validateJevResponse } from "@tradejs/infra/jev";
2
+ import {
3
+ JEV_DIMENSIONS as JEV_DIMENSIONS2,
4
+ JEV_QUESTION_SET as JEV_QUESTION_SET2,
5
+ decideJev,
6
+ predictJevTree
7
+ } from "@tradejs/core/jev";
8
+ import {
9
+ jevScoreMaximum,
10
+ jevHash,
11
+ validateJevResponse
12
+ } from "@tradejs/infra/jev";
4
13
 
5
14
  // src/jevInput.ts
6
15
  import { intervalToMs } from "@tradejs/core/data";
7
- import { JEV_DIMENSIONS } from "@tradejs/core/jev";
16
+ import { JEV_DIMENSIONS, JEV_QUESTION_SET } from "@tradejs/core/jev";
8
17
  var object = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
9
18
  var at = (value, ...path) => path.reduce((current, key) => object(current)[key], value);
10
19
  var finite = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
@@ -83,6 +92,35 @@ var buildJevInput = (signal) => {
83
92
  const stop = finite(signal.prices.stopLossPrice);
84
93
  const target = finite(signal.prices.takeProfitPrice);
85
94
  const atr = baseProblem ? null : finite(at(base, "raw", "volatility", "atr"));
95
+ if (!baseProblem) {
96
+ const open = finite(at(base, "candle", "open"));
97
+ const close = finite(at(base, "candle", "close"));
98
+ const high = finite(at(base, "candle", "high"));
99
+ const low = finite(at(base, "candle", "low"));
100
+ const sign = signal.direction === "LONG" ? 1 : -1;
101
+ const candleFact = {
102
+ knownAt: baseKnownAt,
103
+ scope: "target",
104
+ source: "baseContext.candle"
105
+ };
106
+ if (open != null && close != null && atr != null && atr > 0)
107
+ put("entry.directionalBodyAtr", sign * (close - open) / atr, {
108
+ ...candleFact,
109
+ unit: "ATR"
110
+ });
111
+ if (high != null && low != null && close != null && high > low && close >= low && close <= high) {
112
+ put(
113
+ "entry.directionalCloseLocation",
114
+ signal.direction === "LONG" ? (close - low) / (high - low) : (high - close) / (high - low),
115
+ { ...candleFact, unit: "ratio" }
116
+ );
117
+ if (atr != null && atr > 0)
118
+ put("entry.rangeAtr", (high - low) / atr, {
119
+ ...candleFact,
120
+ unit: "ATR"
121
+ });
122
+ }
123
+ }
86
124
  if (price == null || stop == null || target == null)
87
125
  throw new Error("Invalid Jev signal prices");
88
126
  put(
@@ -169,33 +207,30 @@ var buildJevInput = (signal) => {
169
207
  );
170
208
  const questions = JEV_DIMENSIONS.filter((dimension) => {
171
209
  switch (dimension) {
210
+ case "signalStrength":
211
+ return has("signal.validLevels");
172
212
  case "trend":
173
213
  return has("market.trendBias");
174
214
  case "swing":
175
- return has("market.swingBias");
215
+ return has("setup.retracementRatio");
176
216
  case "participation":
177
- return has("market.volumeRel20") || has("market.deltaPct");
217
+ return has("market.volumeRel20");
178
218
  case "setupParticipation":
179
219
  return setupKeys.some(
180
220
  (key) => /volume|delta|flow|liquidity|turnover/i.test(key)
181
221
  );
182
222
  case "extension":
183
- return has("market.fastMaDistanceAtr") || has("setup.breakoutDistanceAtr") || has("signal.stopDistanceAtr");
223
+ return has("setup.breakoutDistanceAtr");
184
224
  case "confirmation":
185
- return setupKeys.some(
186
- (key) => /stage|confirm|retest|accept/i.test(key)
187
- );
225
+ return has("entry.directionalBodyAtr") && has("entry.directionalCloseLocation");
188
226
  case "setupStrength":
189
- return setupKeys.some(
190
- (key) => key !== "setup.version" && !/volume|delta|flow|liquidity|turnover|stage|confirm|retest|accept|breakoutDistance/i.test(
191
- key
192
- )
193
- );
227
+ return setupKeys.some((key) => /efficiencyRatio$/i.test(key));
194
228
  case "geometry":
195
229
  return geometryStatus !== "invalid" && Object.keys(features).some((key) => key.startsWith("geometry."));
196
230
  }
197
231
  });
198
232
  return {
233
+ questionSet: JEV_QUESTION_SET,
199
234
  schema: "tradejs-jev-input/v4",
200
235
  strategy: signal.strategy,
201
236
  symbol: signal.symbol,
@@ -209,68 +244,155 @@ var buildJevInput = (signal) => {
209
244
  geometryStatus
210
245
  };
211
246
  };
212
- var criteria = (subject) => [
213
- `${subject} is clearly contradicted by the supplied facts.`,
214
- `${subject} has a material conflict.`,
215
- `${subject} is uncertain from the supplied facts.`,
216
- `${subject} has adequate support.`,
217
- `${subject} has strong support without a material conflict.`
218
- ];
219
- var JEV_QUESTIONS = {
247
+ var JEV_COMPONENT_QUESTIONS = {
220
248
  trend: {
221
249
  type: "score",
222
- instructions: "Does market.trendBias support signal.direction? Judge only the target-asset trend, not swings or setup quality.",
223
- criteria: criteria("Target-asset trend alignment")
250
+ instructions: "Rate agreement of market.trendBias and market.swingBias with signal.direction: bull supports LONG, bear supports SHORT; neutral/unknown is inconclusive. No profit prediction.",
251
+ criteria: [
252
+ "Both oppose.",
253
+ "One opposes; other inconclusive.",
254
+ "Biases conflict, or both inconclusive.",
255
+ "One supports; other inconclusive.",
256
+ "Both support."
257
+ ]
224
258
  },
225
259
  swing: {
226
260
  type: "score",
227
- instructions: "Does market.swingBias support signal.direction? Judge only the target-asset swing structure, not the broader trend.",
228
- criteria: criteria("Target-asset swing alignment")
261
+ instructions: "Rate shallow correction using only setup.retracementRatio (correction / preceding impulse). Missing is inconclusive, not favorable.",
262
+ criteria: [
263
+ "Exceeds impulse (>1).",
264
+ "Deep (>0.65 to 1).",
265
+ "Moderate (>0.4 to 0.65), or missing.",
266
+ "Contained (>0.2 to 0.4).",
267
+ "Shallow (0 to 0.2)."
268
+ ]
229
269
  },
230
270
  participation: {
231
271
  type: "score",
232
- 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.",
233
- criteria: criteria("Current target-asset participation")
272
+ instructions: "Rate activity using only market.volumeRel20 (candle volume / trailing 20-candle mean). Not directional flow; ignore setup volume.",
273
+ criteria: [
274
+ "Very low (<0.5).",
275
+ "Below ordinary (0.5 to <0.8).",
276
+ "Ordinary (0.8 to <1.2), or missing.",
277
+ "Elevated (1.2 to <2).",
278
+ "Strong expansion (>=2)."
279
+ ]
234
280
  },
235
281
  setupParticipation: {
236
282
  type: "score",
237
- 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.",
238
- criteria: criteria("Participation within the setup")
283
+ instructions: "Rate consolidation volume contraction using a supplied setup consolidation / preceding impulse volume ratio, e.g. setup.flagToPoleVolumeRatio. Smaller means stronger contraction. Ignore current volume and directional flow; unrelated setup semantics are inconclusive.",
284
+ criteria: [
285
+ "Exceeds impulse (>1.2).",
286
+ "No contraction (>0.9 to 1.2).",
287
+ "Mild (>0.7 to 0.9), or inconclusive.",
288
+ "Clear (>0.4 to 0.7).",
289
+ "Strong (0 to 0.4)."
290
+ ]
239
291
  },
240
292
  extension: {
241
293
  type: "score",
242
- 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.",
243
- criteria: criteria("Entry location without excessive extension")
294
+ instructions: "Rate absence of entry extension using only setup.breakoutDistanceAtr (distance past setup boundary in ATR). Stop/target distances are not extension.",
295
+ criteria: [
296
+ "Extreme (>2 ATR).",
297
+ "Large (>1 to 2 ATR).",
298
+ "Moderate (>0.5 to 1 ATR), or missing.",
299
+ "Small (>0.2 to 0.5 ATR).",
300
+ "Near boundary (0 to 0.2 ATR)."
301
+ ]
244
302
  },
245
303
  confirmation: {
246
304
  type: "score",
247
- instructions: "Do the supplied setup stage and confirmation facts indicate a confirmed rather than premature entry? Do not judge distance or predict the outcome.",
248
- criteria: criteria("Setup confirmation at entry")
305
+ instructions: "Rate closed entry candle conviction using entry.directionalBodyAtr (signed toward signal.direction) and entry.directionalCloseLocation (1=favorable extreme). Stage names alone prove neither conviction nor breakout retention.",
306
+ criteria: [
307
+ "Opposing body AND unfavorable-quarter close.",
308
+ "Opposing body OR unfavorable-half close.",
309
+ "Small body (<0.2 ATR), mixed or missing evidence.",
310
+ "Supporting body (>=0.2 ATR) AND favorable-half close.",
311
+ "Strong supporting body (>=0.5 ATR) AND favorable-quarter close."
312
+ ]
249
313
  },
250
314
  setupStrength: {
251
315
  type: "score",
252
- 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.",
253
- criteria: criteria("Non-geometric setup strength")
316
+ instructions: "Rate impulse persistence using only supplied setup efficiency (absolute net move / sum of absolute bar moves), e.g. setup.poleEfficiencyRatio. Exclude size, volume, correction and geometry.",
317
+ criteria: [
318
+ "Highly choppy (<0.2).",
319
+ "Weak (0.2 to <0.4).",
320
+ "Mixed (0.4 to <0.6), or missing.",
321
+ "Clear (0.6 to <0.8).",
322
+ "Highly persistent (0.8 to 1)."
323
+ ]
254
324
  },
255
325
  geometry: {
256
326
  type: "score",
257
- instructions: "Do the supplied geometry.* facts support the named setup? Do not infer unseen figure points or certify mathematical validity.",
258
- criteria: criteria("Setup geometry")
327
+ instructions: "Rate fitted consolidation boundaries jointly: geometry.upperR2 and lowerR2 (1=best fit), slopeDivergenceRatio (0=parallel). Use the weaker fit; exclude depth, width, volume and profit. Missing equivalent geometry is inconclusive.",
328
+ criteria: [
329
+ "Fit <0.5 OR divergence >0.5.",
330
+ "Fit <0.7 OR divergence >0.3.",
331
+ "Fit <0.85 OR divergence >0.15, or missing.",
332
+ "Both fits >=0.85 AND divergence <=0.15.",
333
+ "Both fits >=0.95 AND divergence <=0.05."
334
+ ]
259
335
  }
260
336
  };
261
- var questionsForJevInput = (input) => Object.fromEntries(input.questions.map((key) => [key, JEV_QUESTIONS[key]]));
337
+ var JEV_SIGNAL_STRENGTH_QUESTION = {
338
+ type: "score",
339
+ instructions: "Rate overall support for entry now from signal-time facts: direction, market, setup, candle, placement, relevant geometry and stop/target plan. No other Jev answers, future prices or outcomes. Essential missing facts and conflicts weaken support; optional missing geometry alone does not disqualify. Invalid signal.validLevels cannot rate strong. Not win probability or execution permission. Criteria 1\u201310 map to API indices 0\u20139.",
340
+ criteria: [
341
+ "1: Invalid levels or decisive contrary evidence.",
342
+ "2: Severe directional/setup conflicts; poor support.",
343
+ "3: Substantial weaknesses outweigh support.",
344
+ "4: Some support; material weakness makes entry unattractive.",
345
+ "5: Balanced conflicts or essential evidence missing.",
346
+ "6: Modest coherent support; reasonable but limited evidence.",
347
+ "7: Convincing support from several facts; remaining weakness.",
348
+ "8: Strong coherent support, suitable placement/plan; no material conflict.",
349
+ "9: Very strong independent support; well-defined entry/stop/target plan.",
350
+ "10: Exceptional coherent support and plan; no visible material weakness, profit uncertain."
351
+ ]
352
+ };
353
+ var JEV_QUESTIONS = {
354
+ ...JEV_COMPONENT_QUESTIONS,
355
+ signalStrength: JEV_SIGNAL_STRENGTH_QUESTION
356
+ };
357
+ var questionsForJevInput = (input) => {
358
+ if (input.questionSet !== JEV_QUESTION_SET)
359
+ throw new Error(
360
+ "Unsupported Jev recording question set; rerun the backtest with --jev"
361
+ );
362
+ return Object.fromEntries(
363
+ input.questions.map((key) => {
364
+ const question = JEV_QUESTIONS[key];
365
+ if (!question) throw new Error("Unknown Jev question");
366
+ return [key, question];
367
+ })
368
+ );
369
+ };
370
+ var groupJevFields = (entries) => {
371
+ const groups = /* @__PURE__ */ new Map();
372
+ const flat = [];
373
+ for (const [key, value] of entries) {
374
+ const separator = key.indexOf(".");
375
+ if (separator < 0) {
376
+ flat.push([key, value]);
377
+ continue;
378
+ }
379
+ const namespace = key.slice(0, separator);
380
+ const group = groups.get(namespace) ?? [];
381
+ group.push([key.slice(separator + 1), value]);
382
+ groups.set(namespace, group);
383
+ }
384
+ return Object.fromEntries([
385
+ ...flat,
386
+ ...Array.from(groups, ([key, fields]) => [key, Object.fromEntries(fields)])
387
+ ]);
388
+ };
262
389
  var stateForJevInput = (input) => ({
263
- schema: input.schema,
264
390
  strategy: input.strategy,
265
- direction: input.direction,
266
- facts: input.features,
267
- missing: input.missing,
391
+ facts: groupJevFields(Object.entries(input.features)),
392
+ missing: groupJevFields(Object.entries(input.missing)),
268
393
  geometryStatus: input.geometryStatus,
269
- units: Object.fromEntries(
394
+ units: groupJevFields(
270
395
  Object.entries(input.provenance).filter(([, detail]) => detail.unit != null).map(([key, detail]) => [key, detail.unit])
271
- ),
272
- sources: Object.fromEntries(
273
- Object.entries(input.provenance).filter(([, detail]) => detail.source != null).map(([key, detail]) => [key, detail.source])
274
396
  )
275
397
  });
276
398
 
@@ -288,7 +410,7 @@ var validateJevStudy = (rows) => {
288
410
  );
289
411
  for (const row of rows) {
290
412
  const record = row?.record;
291
- 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 !== jevHash(record.input) || record.questionsHash !== jevHash(questionsForJevInput(record.input)) || record.id !== jevHash({
413
+ if (row?.schema !== "tradejs-jev-study/v4" || !row.signalId || record?.schema !== "tradejs-jev-record/v4" || record.input?.schema !== "tradejs-jev-input/v4" || record.input.questionSet !== JEV_QUESTION_SET2 || !Number.isFinite(record.input.timestamp) || record.inputHash !== jevHash(record.input) || record.questionsHash !== jevHash(questionsForJevInput(record.input)) || record.id !== jevHash({
292
414
  inputHash: record.inputHash,
293
415
  questionsHash: record.questionsHash,
294
416
  provider: record.provider
@@ -296,7 +418,7 @@ var validateJevStudy = (rows) => {
296
418
  throw new Error("Invalid Jev study provenance");
297
419
  validateJevResponse(record.response, record.input.questions);
298
420
  for (const dimension of JEV_DIMENSIONS2) {
299
- const expected = record.input.questions.includes(dimension) ? record.response.answers[dimension].score / 4 : null;
421
+ const expected = record.input.questions.includes(dimension) ? record.response.answers[dimension].score / jevScoreMaximum(dimension) : null;
300
422
  if (record.scores[dimension] !== expected)
301
423
  throw new Error("Jev study scores do not match the teacher response");
302
424
  }
@@ -379,10 +501,12 @@ var trainTree = (rows, dimension, depth, minLeaf) => {
379
501
  };
380
502
  var validateJevGateModel = (value) => {
381
503
  const model = value;
382
- 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 !== jevHash(JEV_QUESTIONS) || !model.training || !/^[a-f0-9]{64}$/.test(model.training.datasetHash))
504
+ if (model?.schema !== "tradejs-jev-gate/v2" || model.inputSchema !== "tradejs-jev-input/v4" || typeof model.strategy !== "string" || !model.strategy || !model.teacherModel || !model.trees || model.questionSet !== JEV_QUESTION_SET2 || JEV_DIMENSIONS2.some((dimension) => !(dimension in model.trees)) || Object.keys(model.trees).some(
505
+ (dimension) => !JEV_DIMENSIONS2.includes(dimension)
506
+ ) || model.questionsHash !== jevHash(JEV_QUESTIONS) || !model.training || !/^[a-f0-9]{64}$/.test(model.training.datasetHash))
383
507
  throw new Error("Invalid local Jev gate");
384
508
  const { trainEnd, validationEnd, testEnd } = model.training;
385
- if (![trainEnd, validationEnd, testEnd].every(Number.isFinite) || !(trainEnd < validationEnd && validationEnd < testEnd))
509
+ if (![trainEnd, validationEnd, testEnd].every(Number.isFinite) || model.training.split !== "outer-60-40" || !(trainEnd === validationEnd && trainEnd < testEnd))
386
510
  throw new Error("Invalid local Jev gate partitions");
387
511
  const check = (tree, depth) => {
388
512
  if (!tree || depth > 8) throw new Error("Invalid local Jev tree");
@@ -518,13 +642,10 @@ var trainJevGate = (input, options = {}) => {
518
642
  ];
519
643
  if (rows.length < 30 || timestamps.length < 10)
520
644
  throw new Error("Need at least 30 distinct samples across 10 signal times");
521
- const trainEnd = timestamps[Math.floor(timestamps.length * 0.6) - 1];
522
- const validationEnd = timestamps[Math.floor(timestamps.length * 0.8) - 1];
645
+ const trainEnd = timestamps[timestamps.length - Math.max(1, Math.floor(timestamps.length * 0.4)) - 1];
646
+ const validationEnd = trainEnd;
523
647
  const train = rows.filter((row) => row.record.input.timestamp <= trainEnd);
524
- const validation = rows.filter(
525
- (row) => row.record.input.timestamp > trainEnd && row.record.input.timestamp <= validationEnd
526
- );
527
- const test = rows.filter((row) => row.record.input.timestamp > validationEnd);
648
+ const test = rows.filter((row) => row.record.input.timestamp > trainEnd);
528
649
  const fit = (samples) => Object.fromEntries(
529
650
  JEV_DIMENSIONS2.map((key) => {
530
651
  const eligible = samples.filter(
@@ -541,10 +662,12 @@ var trainJevGate = (input, options = {}) => {
541
662
  schema: "tradejs-jev-gate/v2",
542
663
  inputSchema: "tradejs-jev-input/v4",
543
664
  strategy: first.input.strategy,
665
+ questionSet: JEV_QUESTION_SET2,
544
666
  questionsHash: jevHash(JEV_QUESTIONS),
545
667
  teacherModel: first.response.model,
546
668
  trees: fit(train),
547
669
  training: {
670
+ split: "outer-60-40",
548
671
  datasetHash: jevHash(
549
672
  rows.map((row) => ({
550
673
  id: row.record.id,
@@ -559,18 +682,20 @@ var trainJevGate = (input, options = {}) => {
559
682
  }
560
683
  };
561
684
  validateJevGateModel(model);
562
- const walkForward = [0.4, 0.6, 0.8].map((fraction) => {
563
- const trainThrough = timestamps[Math.max(0, Math.floor(timestamps.length * (fraction - 0.1)) - 1)];
564
- const validationThrough = timestamps[Math.floor(timestamps.length * fraction) - 1];
565
- const testThrough = timestamps[Math.min(
566
- timestamps.length - 1,
567
- Math.round(timestamps.length * (fraction + 0.2)) - 1
685
+ const developmentTimes = timestamps.filter(
686
+ (timestamp) => timestamp <= trainEnd
687
+ );
688
+ const walkForward = [0.25, 0.5, 0.75].map((fraction) => {
689
+ const trainThrough = developmentTimes[Math.max(0, Math.floor(developmentTimes.length * fraction) - 1)];
690
+ const testThrough = developmentTimes[Math.min(
691
+ developmentTimes.length - 1,
692
+ Math.floor(developmentTimes.length * (fraction + 0.25)) - 1
568
693
  )];
569
- const fitting = rows.filter(
694
+ const fitting = train.filter(
570
695
  (row) => row.record.input.timestamp <= trainThrough
571
696
  );
572
- const evaluation = rows.filter(
573
- (row) => row.record.input.timestamp > validationThrough && row.record.input.timestamp <= testThrough
697
+ const evaluation = train.filter(
698
+ (row) => row.record.input.timestamp > trainThrough && row.record.input.timestamp <= testThrough
574
699
  );
575
700
  const fold = {
576
701
  ...model,
@@ -578,13 +703,13 @@ var trainJevGate = (input, options = {}) => {
578
703
  training: {
579
704
  ...model.training,
580
705
  trainEnd: trainThrough,
581
- validationEnd: validationThrough,
706
+ validationEnd: trainThrough,
582
707
  testEnd: testThrough
583
708
  }
584
709
  };
585
710
  return {
586
711
  trainThrough,
587
- validationThrough,
712
+ validationThrough: trainThrough,
588
713
  testThrough,
589
714
  trainRows: fitting.length,
590
715
  test: compareJevGate(fold, evaluation)
@@ -594,7 +719,8 @@ var trainJevGate = (input, options = {}) => {
594
719
  model,
595
720
  report: {
596
721
  train: compareJevGate(model, train),
597
- validation: compareJevGate(model, validation),
722
+ partitionScheme: "outer-60-40",
723
+ stabilityRole: "independently fitted folds inside development only",
598
724
  test: compareJevGate(model, test),
599
725
  walkForward,
600
726
  promotion: "research-only; validate a sequential backtest and prospective signals before runtime admission"
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-LZDXRXIU.mjs";
13
13
  import {
14
14
  assessSignalWithJev
15
- } from "./chunk-3BBXGZ7M.mjs";
15
+ } from "./chunk-YR55Q355.mjs";
16
16
 
17
17
  // src/binanceBreadthUniverses.ts
18
18
  import { createHash } from "crypto";
@@ -3,18 +3,21 @@ import {
3
3
  buildJevInput,
4
4
  questionsForJevInput,
5
5
  stateForJevInput
6
- } from "./chunk-ZZQLTUTP.mjs";
6
+ } from "./chunk-GHSLOJLE.mjs";
7
7
 
8
8
  // src/jev.ts
9
9
  import fs from "fs/promises";
10
10
  import path from "path";
11
11
  import {
12
12
  JEV_DIMENSIONS,
13
+ JEV_QUESTION_SET,
13
14
  JEV_PROVIDERS,
14
15
  parseJevConfig,
15
- predictJevTree
16
+ predictJevTree,
17
+ jevAssessmentFeatures
16
18
  } from "@tradejs/core/jev";
17
19
  import {
20
+ jevScoreMaximum,
18
21
  jevHash,
19
22
  jevFileHash,
20
23
  readJevArtifact,
@@ -48,6 +51,7 @@ var evaluateJevInput = async ({
48
51
  const questions = questionsForJevInput(input);
49
52
  const questionsHash = jevHash(questions);
50
53
  const base = {
54
+ questionSet: JEV_QUESTION_SET,
51
55
  schema: "tradejs-signal-assessment/v5",
52
56
  source: config.source,
53
57
  mode: config.mode,
@@ -73,7 +77,7 @@ var evaluateJevInput = async ({
73
77
  );
74
78
  if (jevFileHash(contents) !== config.modelSha256)
75
79
  throw new Error("Local Jev gate checksum mismatch");
76
- const { validateJevGateModel: validateJevGateModel2 } = await import("./jevTraining-JYXUVAZG.mjs");
80
+ const { validateJevGateModel: validateJevGateModel2 } = await import("./jevTraining-J4Y4VCM4.mjs");
77
81
  const model = validateJevGateModel2(JSON.parse(contents));
78
82
  if (model.strategy !== input.strategy || model.questionsHash !== jevHash(JEV_QUESTIONS))
79
83
  throw new Error("Local Jev gate strategy or question version mismatch");
@@ -104,7 +108,7 @@ var evaluateJevInput = async ({
104
108
  throw new Error("Jev recording identity mismatch");
105
109
  validateJevResponse(existing.response, input.questions);
106
110
  for (const key of JEV_DIMENSIONS) {
107
- const expected = input.questions.includes(key) ? existing.response.answers[key].score / 4 : null;
111
+ const expected = input.questions.includes(key) ? existing.response.answers[key].score / jevScoreMaximum(key) : null;
108
112
  if (existing.scores[key] !== expected)
109
113
  throw new Error("Jev recording scores do not match the response");
110
114
  }
@@ -152,7 +156,7 @@ var evaluateJevInput = async ({
152
156
  const scores = Object.fromEntries(
153
157
  JEV_DIMENSIONS.map((key) => [
154
158
  key,
155
- input.questions.includes(key) ? response.answers[key].score / 4 : null
159
+ input.questions.includes(key) ? response.answers[key].score / jevScoreMaximum(key) : null
156
160
  ])
157
161
  );
158
162
  const value = {
@@ -219,6 +223,7 @@ var assessSignalWithJev = async ({
219
223
  } catch (error) {
220
224
  if (strict) throw error;
221
225
  assessment = {
226
+ questionSet: JEV_QUESTION_SET,
222
227
  schema: "tradejs-signal-assessment/v5",
223
228
  source: config.source,
224
229
  mode: config.mode,
@@ -235,12 +240,7 @@ var assessSignalWithJev = async ({
235
240
  signal.assessment = assessment;
236
241
  signal.additionalIndicators = {
237
242
  ...signal.additionalIndicators,
238
- jev: {
239
- schema: "tradejs-jev-features/v2",
240
- status: assessment.status,
241
- scores: assessment.scores,
242
- confidence: assessment.confidence
243
- }
243
+ jev: jevAssessmentFeatures(assessment)
244
244
  };
245
245
  const identity = {
246
246
  signalId: signal.signalId,