@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.
package/dist/backtest.js CHANGED
@@ -37,7 +37,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
37
37
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
38
 
39
39
  // src/jevInput.ts
40
- var import_data, import_jev, object, at, finite, fact, buildJevInput, criteria, JEV_QUESTIONS, questionsForJevInput, stateForJevInput;
40
+ var import_data, import_jev, object, at, finite, fact, buildJevInput, JEV_COMPONENT_QUESTIONS, JEV_SIGNAL_STRENGTH_QUESTION, JEV_QUESTIONS, questionsForJevInput, groupJevFields, stateForJevInput;
41
41
  var init_jevInput = __esm({
42
42
  "src/jevInput.ts"() {
43
43
  "use strict";
@@ -121,6 +121,35 @@ var init_jevInput = __esm({
121
121
  const stop = finite(signal.prices.stopLossPrice);
122
122
  const target = finite(signal.prices.takeProfitPrice);
123
123
  const atr = baseProblem ? null : finite(at(base, "raw", "volatility", "atr"));
124
+ if (!baseProblem) {
125
+ const open = finite(at(base, "candle", "open"));
126
+ const close = finite(at(base, "candle", "close"));
127
+ const high = finite(at(base, "candle", "high"));
128
+ const low = finite(at(base, "candle", "low"));
129
+ const sign = signal.direction === "LONG" ? 1 : -1;
130
+ const candleFact = {
131
+ knownAt: baseKnownAt,
132
+ scope: "target",
133
+ source: "baseContext.candle"
134
+ };
135
+ if (open != null && close != null && atr != null && atr > 0)
136
+ put("entry.directionalBodyAtr", sign * (close - open) / atr, {
137
+ ...candleFact,
138
+ unit: "ATR"
139
+ });
140
+ if (high != null && low != null && close != null && high > low && close >= low && close <= high) {
141
+ put(
142
+ "entry.directionalCloseLocation",
143
+ signal.direction === "LONG" ? (close - low) / (high - low) : (high - close) / (high - low),
144
+ { ...candleFact, unit: "ratio" }
145
+ );
146
+ if (atr != null && atr > 0)
147
+ put("entry.rangeAtr", (high - low) / atr, {
148
+ ...candleFact,
149
+ unit: "ATR"
150
+ });
151
+ }
152
+ }
124
153
  if (price == null || stop == null || target == null)
125
154
  throw new Error("Invalid Jev signal prices");
126
155
  put(
@@ -207,33 +236,30 @@ var init_jevInput = __esm({
207
236
  );
208
237
  const questions = import_jev.JEV_DIMENSIONS.filter((dimension) => {
209
238
  switch (dimension) {
239
+ case "signalStrength":
240
+ return has("signal.validLevels");
210
241
  case "trend":
211
242
  return has("market.trendBias");
212
243
  case "swing":
213
- return has("market.swingBias");
244
+ return has("setup.retracementRatio");
214
245
  case "participation":
215
- return has("market.volumeRel20") || has("market.deltaPct");
246
+ return has("market.volumeRel20");
216
247
  case "setupParticipation":
217
248
  return setupKeys.some(
218
249
  (key) => /volume|delta|flow|liquidity|turnover/i.test(key)
219
250
  );
220
251
  case "extension":
221
- return has("market.fastMaDistanceAtr") || has("setup.breakoutDistanceAtr") || has("signal.stopDistanceAtr");
252
+ return has("setup.breakoutDistanceAtr");
222
253
  case "confirmation":
223
- return setupKeys.some(
224
- (key) => /stage|confirm|retest|accept/i.test(key)
225
- );
254
+ return has("entry.directionalBodyAtr") && has("entry.directionalCloseLocation");
226
255
  case "setupStrength":
227
- return setupKeys.some(
228
- (key) => key !== "setup.version" && !/volume|delta|flow|liquidity|turnover|stage|confirm|retest|accept|breakoutDistance/i.test(
229
- key
230
- )
231
- );
256
+ return setupKeys.some((key) => /efficiencyRatio$/i.test(key));
232
257
  case "geometry":
233
258
  return geometryStatus !== "invalid" && Object.keys(features).some((key) => key.startsWith("geometry."));
234
259
  }
235
260
  });
236
261
  return {
262
+ questionSet: import_jev.JEV_QUESTION_SET,
237
263
  schema: "tradejs-jev-input/v4",
238
264
  strategy: signal.strategy,
239
265
  symbol: signal.symbol,
@@ -247,68 +273,155 @@ var init_jevInput = __esm({
247
273
  geometryStatus
248
274
  };
249
275
  };
250
- criteria = (subject) => [
251
- `${subject} is clearly contradicted by the supplied facts.`,
252
- `${subject} has a material conflict.`,
253
- `${subject} is uncertain from the supplied facts.`,
254
- `${subject} has adequate support.`,
255
- `${subject} has strong support without a material conflict.`
256
- ];
257
- JEV_QUESTIONS = {
276
+ JEV_COMPONENT_QUESTIONS = {
258
277
  trend: {
259
278
  type: "score",
260
- instructions: "Does market.trendBias support signal.direction? Judge only the target-asset trend, not swings or setup quality.",
261
- criteria: criteria("Target-asset trend alignment")
279
+ 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.",
280
+ criteria: [
281
+ "Both oppose.",
282
+ "One opposes; other inconclusive.",
283
+ "Biases conflict, or both inconclusive.",
284
+ "One supports; other inconclusive.",
285
+ "Both support."
286
+ ]
262
287
  },
263
288
  swing: {
264
289
  type: "score",
265
- instructions: "Does market.swingBias support signal.direction? Judge only the target-asset swing structure, not the broader trend.",
266
- criteria: criteria("Target-asset swing alignment")
290
+ instructions: "Rate shallow correction using only setup.retracementRatio (correction / preceding impulse). Missing is inconclusive, not favorable.",
291
+ criteria: [
292
+ "Exceeds impulse (>1).",
293
+ "Deep (>0.65 to 1).",
294
+ "Moderate (>0.4 to 0.65), or missing.",
295
+ "Contained (>0.2 to 0.4).",
296
+ "Shallow (0 to 0.2)."
297
+ ]
267
298
  },
268
299
  participation: {
269
300
  type: "score",
270
- 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.",
271
- criteria: criteria("Current target-asset participation")
301
+ instructions: "Rate activity using only market.volumeRel20 (candle volume / trailing 20-candle mean). Not directional flow; ignore setup volume.",
302
+ criteria: [
303
+ "Very low (<0.5).",
304
+ "Below ordinary (0.5 to <0.8).",
305
+ "Ordinary (0.8 to <1.2), or missing.",
306
+ "Elevated (1.2 to <2).",
307
+ "Strong expansion (>=2)."
308
+ ]
272
309
  },
273
310
  setupParticipation: {
274
311
  type: "score",
275
- 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.",
276
- criteria: criteria("Participation within the setup")
312
+ 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.",
313
+ criteria: [
314
+ "Exceeds impulse (>1.2).",
315
+ "No contraction (>0.9 to 1.2).",
316
+ "Mild (>0.7 to 0.9), or inconclusive.",
317
+ "Clear (>0.4 to 0.7).",
318
+ "Strong (0 to 0.4)."
319
+ ]
277
320
  },
278
321
  extension: {
279
322
  type: "score",
280
- 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.",
281
- criteria: criteria("Entry location without excessive extension")
323
+ instructions: "Rate absence of entry extension using only setup.breakoutDistanceAtr (distance past setup boundary in ATR). Stop/target distances are not extension.",
324
+ criteria: [
325
+ "Extreme (>2 ATR).",
326
+ "Large (>1 to 2 ATR).",
327
+ "Moderate (>0.5 to 1 ATR), or missing.",
328
+ "Small (>0.2 to 0.5 ATR).",
329
+ "Near boundary (0 to 0.2 ATR)."
330
+ ]
282
331
  },
283
332
  confirmation: {
284
333
  type: "score",
285
- instructions: "Do the supplied setup stage and confirmation facts indicate a confirmed rather than premature entry? Do not judge distance or predict the outcome.",
286
- criteria: criteria("Setup confirmation at entry")
334
+ 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.",
335
+ criteria: [
336
+ "Opposing body AND unfavorable-quarter close.",
337
+ "Opposing body OR unfavorable-half close.",
338
+ "Small body (<0.2 ATR), mixed or missing evidence.",
339
+ "Supporting body (>=0.2 ATR) AND favorable-half close.",
340
+ "Strong supporting body (>=0.5 ATR) AND favorable-quarter close."
341
+ ]
287
342
  },
288
343
  setupStrength: {
289
344
  type: "score",
290
- 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.",
291
- criteria: criteria("Non-geometric setup strength")
345
+ 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.",
346
+ criteria: [
347
+ "Highly choppy (<0.2).",
348
+ "Weak (0.2 to <0.4).",
349
+ "Mixed (0.4 to <0.6), or missing.",
350
+ "Clear (0.6 to <0.8).",
351
+ "Highly persistent (0.8 to 1)."
352
+ ]
292
353
  },
293
354
  geometry: {
294
355
  type: "score",
295
- instructions: "Do the supplied geometry.* facts support the named setup? Do not infer unseen figure points or certify mathematical validity.",
296
- criteria: criteria("Setup geometry")
356
+ 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.",
357
+ criteria: [
358
+ "Fit <0.5 OR divergence >0.5.",
359
+ "Fit <0.7 OR divergence >0.3.",
360
+ "Fit <0.85 OR divergence >0.15, or missing.",
361
+ "Both fits >=0.85 AND divergence <=0.15.",
362
+ "Both fits >=0.95 AND divergence <=0.05."
363
+ ]
364
+ }
365
+ };
366
+ JEV_SIGNAL_STRENGTH_QUESTION = {
367
+ type: "score",
368
+ 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.",
369
+ criteria: [
370
+ "1: Invalid levels or decisive contrary evidence.",
371
+ "2: Severe directional/setup conflicts; poor support.",
372
+ "3: Substantial weaknesses outweigh support.",
373
+ "4: Some support; material weakness makes entry unattractive.",
374
+ "5: Balanced conflicts or essential evidence missing.",
375
+ "6: Modest coherent support; reasonable but limited evidence.",
376
+ "7: Convincing support from several facts; remaining weakness.",
377
+ "8: Strong coherent support, suitable placement/plan; no material conflict.",
378
+ "9: Very strong independent support; well-defined entry/stop/target plan.",
379
+ "10: Exceptional coherent support and plan; no visible material weakness, profit uncertain."
380
+ ]
381
+ };
382
+ JEV_QUESTIONS = {
383
+ ...JEV_COMPONENT_QUESTIONS,
384
+ signalStrength: JEV_SIGNAL_STRENGTH_QUESTION
385
+ };
386
+ questionsForJevInput = (input) => {
387
+ if (input.questionSet !== import_jev.JEV_QUESTION_SET)
388
+ throw new Error(
389
+ "Unsupported Jev recording question set; rerun the backtest with --jev"
390
+ );
391
+ return Object.fromEntries(
392
+ input.questions.map((key) => {
393
+ const question = JEV_QUESTIONS[key];
394
+ if (!question) throw new Error("Unknown Jev question");
395
+ return [key, question];
396
+ })
397
+ );
398
+ };
399
+ groupJevFields = (entries) => {
400
+ const groups = /* @__PURE__ */ new Map();
401
+ const flat = [];
402
+ for (const [key, value] of entries) {
403
+ const separator = key.indexOf(".");
404
+ if (separator < 0) {
405
+ flat.push([key, value]);
406
+ continue;
407
+ }
408
+ const namespace = key.slice(0, separator);
409
+ const group = groups.get(namespace) ?? [];
410
+ group.push([key.slice(separator + 1), value]);
411
+ groups.set(namespace, group);
297
412
  }
413
+ return Object.fromEntries([
414
+ ...flat,
415
+ ...Array.from(groups, ([key, fields]) => [key, Object.fromEntries(fields)])
416
+ ]);
298
417
  };
299
- questionsForJevInput = (input) => Object.fromEntries(input.questions.map((key) => [key, JEV_QUESTIONS[key]]));
300
418
  stateForJevInput = (input) => ({
301
- schema: input.schema,
302
419
  strategy: input.strategy,
303
- direction: input.direction,
304
- facts: input.features,
305
- missing: input.missing,
420
+ facts: groupJevFields(Object.entries(input.features)),
421
+ missing: groupJevFields(Object.entries(input.missing)),
306
422
  geometryStatus: input.geometryStatus,
307
- units: Object.fromEntries(
423
+ units: groupJevFields(
308
424
  Object.entries(input.provenance).filter(([, detail]) => detail.unit != null).map(([key, detail]) => [key, detail.unit])
309
- ),
310
- sources: Object.fromEntries(
311
- Object.entries(input.provenance).filter(([, detail]) => detail.source != null).map(([key, detail]) => [key, detail.source])
312
425
  )
313
426
  });
314
427
  }
@@ -342,7 +455,7 @@ var init_jevTraining = __esm({
342
455
  );
343
456
  for (const row of rows) {
344
457
  const record = row?.record;
345
- 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)({
458
+ 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 !== import_jev2.JEV_QUESTION_SET || !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)({
346
459
  inputHash: record.inputHash,
347
460
  questionsHash: record.questionsHash,
348
461
  provider: record.provider
@@ -350,7 +463,7 @@ var init_jevTraining = __esm({
350
463
  throw new Error("Invalid Jev study provenance");
351
464
  (0, import_jev3.validateJevResponse)(record.response, record.input.questions);
352
465
  for (const dimension of import_jev2.JEV_DIMENSIONS) {
353
- const expected = record.input.questions.includes(dimension) ? record.response.answers[dimension].score / 4 : null;
466
+ const expected = record.input.questions.includes(dimension) ? record.response.answers[dimension].score / (0, import_jev3.jevScoreMaximum)(dimension) : null;
354
467
  if (record.scores[dimension] !== expected)
355
468
  throw new Error("Jev study scores do not match the teacher response");
356
469
  }
@@ -433,10 +546,12 @@ var init_jevTraining = __esm({
433
546
  };
434
547
  validateJevGateModel = (value) => {
435
548
  const model = value;
436
- 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))
549
+ 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 !== import_jev2.JEV_QUESTION_SET || import_jev2.JEV_DIMENSIONS.some((dimension) => !(dimension in model.trees)) || Object.keys(model.trees).some(
550
+ (dimension) => !import_jev2.JEV_DIMENSIONS.includes(dimension)
551
+ ) || model.questionsHash !== (0, import_jev3.jevHash)(JEV_QUESTIONS) || !model.training || !/^[a-f0-9]{64}$/.test(model.training.datasetHash))
437
552
  throw new Error("Invalid local Jev gate");
438
553
  const { trainEnd, validationEnd, testEnd } = model.training;
439
- if (![trainEnd, validationEnd, testEnd].every(Number.isFinite) || !(trainEnd < validationEnd && validationEnd < testEnd))
554
+ if (![trainEnd, validationEnd, testEnd].every(Number.isFinite) || model.training.split !== "outer-60-40" || !(trainEnd === validationEnd && trainEnd < testEnd))
440
555
  throw new Error("Invalid local Jev gate partitions");
441
556
  const check = (tree, depth) => {
442
557
  if (!tree || depth > 8) throw new Error("Invalid local Jev tree");
@@ -572,13 +687,10 @@ var init_jevTraining = __esm({
572
687
  ];
573
688
  if (rows.length < 30 || timestamps.length < 10)
574
689
  throw new Error("Need at least 30 distinct samples across 10 signal times");
575
- const trainEnd = timestamps[Math.floor(timestamps.length * 0.6) - 1];
576
- const validationEnd = timestamps[Math.floor(timestamps.length * 0.8) - 1];
690
+ const trainEnd = timestamps[timestamps.length - Math.max(1, Math.floor(timestamps.length * 0.4)) - 1];
691
+ const validationEnd = trainEnd;
577
692
  const train = rows.filter((row) => row.record.input.timestamp <= trainEnd);
578
- const validation = rows.filter(
579
- (row) => row.record.input.timestamp > trainEnd && row.record.input.timestamp <= validationEnd
580
- );
581
- const test = rows.filter((row) => row.record.input.timestamp > validationEnd);
693
+ const test = rows.filter((row) => row.record.input.timestamp > trainEnd);
582
694
  const fit = (samples) => Object.fromEntries(
583
695
  import_jev2.JEV_DIMENSIONS.map((key) => {
584
696
  const eligible = samples.filter(
@@ -595,10 +707,12 @@ var init_jevTraining = __esm({
595
707
  schema: "tradejs-jev-gate/v2",
596
708
  inputSchema: "tradejs-jev-input/v4",
597
709
  strategy: first.input.strategy,
710
+ questionSet: import_jev2.JEV_QUESTION_SET,
598
711
  questionsHash: (0, import_jev3.jevHash)(JEV_QUESTIONS),
599
712
  teacherModel: first.response.model,
600
713
  trees: fit(train),
601
714
  training: {
715
+ split: "outer-60-40",
602
716
  datasetHash: (0, import_jev3.jevHash)(
603
717
  rows.map((row) => ({
604
718
  id: row.record.id,
@@ -613,18 +727,20 @@ var init_jevTraining = __esm({
613
727
  }
614
728
  };
615
729
  validateJevGateModel(model);
616
- const walkForward = [0.4, 0.6, 0.8].map((fraction) => {
617
- const trainThrough = timestamps[Math.max(0, Math.floor(timestamps.length * (fraction - 0.1)) - 1)];
618
- const validationThrough = timestamps[Math.floor(timestamps.length * fraction) - 1];
619
- const testThrough = timestamps[Math.min(
620
- timestamps.length - 1,
621
- Math.round(timestamps.length * (fraction + 0.2)) - 1
730
+ const developmentTimes = timestamps.filter(
731
+ (timestamp) => timestamp <= trainEnd
732
+ );
733
+ const walkForward = [0.25, 0.5, 0.75].map((fraction) => {
734
+ const trainThrough = developmentTimes[Math.max(0, Math.floor(developmentTimes.length * fraction) - 1)];
735
+ const testThrough = developmentTimes[Math.min(
736
+ developmentTimes.length - 1,
737
+ Math.floor(developmentTimes.length * (fraction + 0.25)) - 1
622
738
  )];
623
- const fitting = rows.filter(
739
+ const fitting = train.filter(
624
740
  (row) => row.record.input.timestamp <= trainThrough
625
741
  );
626
- const evaluation = rows.filter(
627
- (row) => row.record.input.timestamp > validationThrough && row.record.input.timestamp <= testThrough
742
+ const evaluation = train.filter(
743
+ (row) => row.record.input.timestamp > trainThrough && row.record.input.timestamp <= testThrough
628
744
  );
629
745
  const fold = {
630
746
  ...model,
@@ -632,13 +748,13 @@ var init_jevTraining = __esm({
632
748
  training: {
633
749
  ...model.training,
634
750
  trainEnd: trainThrough,
635
- validationEnd: validationThrough,
751
+ validationEnd: trainThrough,
636
752
  testEnd: testThrough
637
753
  }
638
754
  };
639
755
  return {
640
756
  trainThrough,
641
- validationThrough,
757
+ validationThrough: trainThrough,
642
758
  testThrough,
643
759
  trainRows: fitting.length,
644
760
  test: compareJevGate(fold, evaluation)
@@ -648,7 +764,8 @@ var init_jevTraining = __esm({
648
764
  model,
649
765
  report: {
650
766
  train: compareJevGate(model, train),
651
- validation: compareJevGate(model, validation),
767
+ partitionScheme: "outer-60-40",
768
+ stabilityRole: "independently fitted folds inside development only",
652
769
  test: compareJevGate(model, test),
653
770
  walkForward,
654
771
  promotion: "research-only; validate a sequential backtest and prospective signals before runtime admission"
@@ -716,6 +833,7 @@ var evaluateJevInput = async ({
716
833
  const questions = questionsForJevInput(input);
717
834
  const questionsHash = (0, import_jev5.jevHash)(questions);
718
835
  const base = {
836
+ questionSet: import_jev4.JEV_QUESTION_SET,
719
837
  schema: "tradejs-signal-assessment/v5",
720
838
  source: config.source,
721
839
  mode: config.mode,
@@ -772,7 +890,7 @@ var evaluateJevInput = async ({
772
890
  throw new Error("Jev recording identity mismatch");
773
891
  (0, import_jev5.validateJevResponse)(existing.response, input.questions);
774
892
  for (const key of import_jev4.JEV_DIMENSIONS) {
775
- const expected = input.questions.includes(key) ? existing.response.answers[key].score / 4 : null;
893
+ const expected = input.questions.includes(key) ? existing.response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null;
776
894
  if (existing.scores[key] !== expected)
777
895
  throw new Error("Jev recording scores do not match the response");
778
896
  }
@@ -820,7 +938,7 @@ var evaluateJevInput = async ({
820
938
  const scores = Object.fromEntries(
821
939
  import_jev4.JEV_DIMENSIONS.map((key) => [
822
940
  key,
823
- input.questions.includes(key) ? response.answers[key].score / 4 : null
941
+ input.questions.includes(key) ? response.answers[key].score / (0, import_jev5.jevScoreMaximum)(key) : null
824
942
  ])
825
943
  );
826
944
  const value = {
@@ -887,6 +1005,7 @@ var assessSignalWithJev = async ({
887
1005
  } catch (error) {
888
1006
  if (strict) throw error;
889
1007
  assessment = {
1008
+ questionSet: import_jev4.JEV_QUESTION_SET,
890
1009
  schema: "tradejs-signal-assessment/v5",
891
1010
  source: config.source,
892
1011
  mode: config.mode,
@@ -903,12 +1022,7 @@ var assessSignalWithJev = async ({
903
1022
  signal.assessment = assessment;
904
1023
  signal.additionalIndicators = {
905
1024
  ...signal.additionalIndicators,
906
- jev: {
907
- schema: "tradejs-jev-features/v2",
908
- status: assessment.status,
909
- scores: assessment.scores,
910
- confidence: assessment.confidence
911
- }
1025
+ jev: (0, import_jev4.jevAssessmentFeatures)(assessment)
912
1026
  };
913
1027
  const identity = {
914
1028
  signalId: signal.signalId,
package/dist/backtest.mjs CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  buildMlPayload,
7
7
  enrichSignalWithMarketContextStages
8
- } from "./chunk-WGL7XJ3W.mjs";
8
+ } from "./chunk-SKY2HNZU.mjs";
9
9
  import {
10
10
  buildAiPayload,
11
11
  getStrategyCreator
@@ -13,8 +13,8 @@ import {
13
13
  import {
14
14
  getTradejsProjectCwd
15
15
  } from "./chunk-LZDXRXIU.mjs";
16
- import "./chunk-3BBXGZ7M.mjs";
17
- import "./chunk-ZZQLTUTP.mjs";
16
+ import "./chunk-YR55Q355.mjs";
17
+ import "./chunk-GHSLOJLE.mjs";
18
18
  import "./chunk-Y6FXYEAI.mjs";
19
19
 
20
20
  // src/backtest.ts