@tradejs/cli 2.0.14 → 2.0.16

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.
@@ -22,6 +22,7 @@ var aiPocketSearch_exports = {};
22
22
  __export(aiPocketSearch_exports, {
23
23
  buildAiPocketMarkdownReport: () => buildAiPocketMarkdownReport,
24
24
  buildAiPocketPredicates: () => buildAiPocketPredicates,
25
+ classifyAiPocketFeaturePath: () => classifyAiPocketFeaturePath,
25
26
  collectAiPocketFeatures: () => collectAiPocketFeatures,
26
27
  searchAiPockets: () => searchAiPockets,
27
28
  summarizeAiPocketRows: () => summarizeAiPocketRows
@@ -88,6 +89,115 @@ var normalizeFeaturePrimitive = (value) => {
88
89
  return value;
89
90
  };
90
91
  var isOutcomePath = (segments) => segments.some((segment) => OUTCOME_SEGMENTS.has(segment.toLowerCase()));
92
+ var DATA_QUALITY_LEAVES = /* @__PURE__ */ new Set([
93
+ "age",
94
+ "agems",
95
+ "available",
96
+ "asofts",
97
+ "calcbars",
98
+ "coverage",
99
+ "latestindex",
100
+ "points",
101
+ "rowcount",
102
+ "rows",
103
+ "stale",
104
+ "timestamp",
105
+ "windowendts"
106
+ ]);
107
+ var METADATA_LEAVES = /* @__PURE__ */ new Set([
108
+ "connector",
109
+ "exchange",
110
+ "fingerprint",
111
+ "interval",
112
+ "provider",
113
+ "source",
114
+ "strategy",
115
+ "symbol",
116
+ "targetsymbol",
117
+ "universe"
118
+ ]);
119
+ var DERIVED_POLICY_FRAGMENTS = [
120
+ "approval",
121
+ "decisionhint",
122
+ "gatefeature",
123
+ "gatescore",
124
+ "hardblock",
125
+ "qualitycap",
126
+ "recoverycandidate",
127
+ "rejectreason",
128
+ "riskflag"
129
+ ];
130
+ var RAW_NONSTATIONARY_LEAVES = /* @__PURE__ */ new Set([
131
+ "activeassets",
132
+ "activemarkets",
133
+ "close",
134
+ "current",
135
+ "currentprice",
136
+ "high",
137
+ "indexprice",
138
+ "liquidations",
139
+ "liquidationsusd",
140
+ "low",
141
+ "marketcap",
142
+ "marketcapusd",
143
+ "notional",
144
+ "open",
145
+ "openinterest",
146
+ "openinterestusd",
147
+ "price",
148
+ "quotevolume",
149
+ "turnover",
150
+ "turnoverusd",
151
+ "volume",
152
+ "volumeusd",
153
+ "vwap"
154
+ ]);
155
+ var classifyAiPocketFeaturePath = (path) => {
156
+ const segments = (Array.isArray(path) ? path : path.split(".")).filter(
157
+ Boolean
158
+ );
159
+ const normalized = segments.map((segment) => segment.toLowerCase());
160
+ const leaf = normalized.at(-1) ?? "";
161
+ const normalizedPath = normalized.join(".");
162
+ const strategyGateFeaturesIndex = normalized.findIndex(
163
+ (segment) => segment !== "gatefeatures" && segment.endsWith("gatefeatures")
164
+ );
165
+ const strategyGateFeaturePath = normalized.slice(
166
+ strategyGateFeaturesIndex + 1
167
+ );
168
+ const isStrategySetupEvidence = normalized.includes("basecontext") && strategyGateFeaturesIndex >= 0 && (strategyGateFeaturePath.length === 0 || strategyGateFeaturePath.some(
169
+ (segment) => segment === "geometry" || segment === "path"
170
+ ));
171
+ if (DATA_QUALITY_LEAVES.has(leaf) || leaf.endsWith("timestamp") || leaf.endsWith("agems") || normalized.some(
172
+ (segment) => ["availability", "dataquality", "freshness"].includes(segment)
173
+ )) {
174
+ return "data-quality";
175
+ }
176
+ if (METADATA_LEAVES.has(leaf) || leaf.endsWith("fingerprint") || normalized.some((segment) => segment === "metadata")) {
177
+ return "metadata";
178
+ }
179
+ if (DERIVED_POLICY_FRAGMENTS.some(
180
+ (fragment) => !(fragment === "gatefeature" && isStrategySetupEvidence) && normalizedPath.includes(fragment)
181
+ )) {
182
+ return "derived-policy";
183
+ }
184
+ const isDerivedFeature = normalized[0] === "derived";
185
+ const isNormalizedLeaf = /(alpha|bps|change|delta|direction|distance|pct|percentile|ratio|regime|return|share|slope|trend|zscore)/.test(
186
+ leaf
187
+ );
188
+ const isRawMovingAverage = /^(?:ema|ma|sma|wma)(?:fast|slow|\d+)$/.test(leaf);
189
+ const isRawAbsolutePositioning = /^(?:liq(?:long|net|short|total)|oi|openinterest(?:long|short)?)$/.test(
190
+ leaf
191
+ );
192
+ const isRawAbsoluteMeasure = /(?:liquidations?|marketcap|notional|openinterest|price|turnover|volume)$/.test(
193
+ leaf
194
+ );
195
+ const isRawStructureLevel = leaf === "level" && normalized.includes("structure");
196
+ if (!isDerivedFeature && !isNormalizedLeaf && (RAW_NONSTATIONARY_LEAVES.has(leaf) || isRawMovingAverage || isRawAbsolutePositioning || isRawAbsoluteMeasure || isRawStructureLevel)) {
197
+ return "raw-nonstationary";
198
+ }
199
+ return "eligible";
200
+ };
91
201
  var isFeaturePathPrefix = (segments, prefix) => prefix.every((segment, index) => segments[index] === segment);
92
202
  var isCompactFeaturePathSkipped = (segments) => {
93
203
  if (!segments.length) {
@@ -349,7 +459,9 @@ var collectAiPocketFeatures = ({
349
459
  gateContext,
350
460
  includeSymbol = false,
351
461
  includeGateContext = false,
352
- featureProfile = "all"
462
+ featureProfile = "all",
463
+ featurePolicy = "all",
464
+ onFeatureExcluded
353
465
  }) => {
354
466
  const features = {};
355
467
  const signal = payload.signal ?? {};
@@ -369,8 +481,35 @@ var collectAiPocketFeatures = ({
369
481
  value: source,
370
482
  segments: [],
371
483
  maxDepth: 8,
372
- shouldSkipPath: featureProfile === "compact" ? isCompactFeaturePathSkipped : void 0
484
+ shouldSkipPath: (segments) => {
485
+ if (featureProfile === "compact" && isCompactFeaturePathSkipped(segments)) {
486
+ return true;
487
+ }
488
+ if (featurePolicy !== "causal-stationary") {
489
+ return false;
490
+ }
491
+ const classification = classifyAiPocketFeaturePath(segments);
492
+ if (classification === "eligible") {
493
+ return false;
494
+ }
495
+ onFeatureExcluded?.({
496
+ path: segments.join("."),
497
+ classification
498
+ });
499
+ return true;
500
+ }
373
501
  });
502
+ if (featurePolicy === "causal-stationary") {
503
+ for (const [key, value] of Object.entries(features)) {
504
+ if (value === null) {
505
+ onFeatureExcluded?.({
506
+ path: key,
507
+ classification: "data-quality"
508
+ });
509
+ delete features[key];
510
+ }
511
+ }
512
+ }
374
513
  addSignalRiskDistanceFeatures({ features, signal });
375
514
  addDirectionalDerivedFeatures(features);
376
515
  return features;
@@ -423,6 +562,7 @@ var createSummaryAccumulator = () => ({
423
562
  directionCounts: /* @__PURE__ */ new Map(),
424
563
  symbolCounts: /* @__PURE__ */ new Map(),
425
564
  monthProfits: /* @__PURE__ */ new Map(),
565
+ eventStats: /* @__PURE__ */ new Map(),
426
566
  equityPoints: []
427
567
  });
428
568
  var addSummaryRow = (accumulator, row) => {
@@ -448,6 +588,14 @@ var addSummaryRow = (accumulator, row) => {
448
588
  symbolBucket.totalProfit += profit;
449
589
  accumulator.symbolCounts.set(symbol, symbolBucket);
450
590
  const timestamp = typeof row.timestamp === "number" && Number.isFinite(row.timestamp) ? row.timestamp : Number.POSITIVE_INFINITY;
591
+ const eventKey = Number.isFinite(timestamp) ? String(timestamp) : `UNKNOWN:${accumulator.support}`;
592
+ const event = accumulator.eventStats.get(eventKey) ?? {
593
+ count: 0,
594
+ totalProfit: 0
595
+ };
596
+ event.count += 1;
597
+ event.totalProfit += profit;
598
+ accumulator.eventStats.set(eventKey, event);
451
599
  const month = Number.isFinite(timestamp) && timestamp !== Number.POSITIVE_INFINITY ? new Date(timestamp).toISOString().slice(0, 7) : "UNKNOWN";
452
600
  accumulator.monthProfits.set(
453
601
  month,
@@ -461,6 +609,15 @@ var emptyAiPocketSummary = ({
461
609
  }) => ({
462
610
  support: 0,
463
611
  supportRatio,
612
+ events: 0,
613
+ eventBalancedProfit: 0,
614
+ tradesPerEvent: null,
615
+ p95Batch: 0,
616
+ maxBatch: 0,
617
+ topEventCountShare: null,
618
+ topEventProfitShare: null,
619
+ topSymbolCountShare: null,
620
+ topSymbolProfitShare: null,
464
621
  totalProfit: 0,
465
622
  grossProfit: 0,
466
623
  grossLoss: 0,
@@ -532,9 +689,42 @@ var finalizeAiPocketSummary = ({
532
689
  const worstMonth = [...accumulator.monthProfits.entries()].sort(
533
690
  (left, right) => left[1] - right[1]
534
691
  )[0] ?? null;
692
+ const eventStats = [...accumulator.eventStats.values()];
693
+ const events = eventStats.length;
694
+ const batchSizes = eventStats.map((event) => event.count).sort((left, right) => left - right);
695
+ const p95Batch = batchSizes[Math.max(0, Math.ceil(batchSizes.length * 0.95) - 1)] ?? 0;
696
+ const maxBatch = batchSizes.at(-1) ?? 0;
697
+ const absoluteEventProfit = eventStats.reduce(
698
+ (sum, event) => sum + Math.abs(event.totalProfit),
699
+ 0
700
+ );
701
+ const absoluteSymbolProfit = [...accumulator.symbolCounts.values()].reduce(
702
+ (sum, symbol) => sum + Math.abs(symbol.totalProfit),
703
+ 0
704
+ );
535
705
  return {
536
706
  support,
537
707
  supportRatio,
708
+ events,
709
+ eventBalancedProfit: eventStats.reduce(
710
+ (sum, event) => sum + event.totalProfit / event.count,
711
+ 0
712
+ ),
713
+ tradesPerEvent: events > 0 ? support / events : null,
714
+ p95Batch,
715
+ maxBatch,
716
+ topEventCountShare: support > 0 ? maxBatch / support : null,
717
+ topEventProfitShare: absoluteEventProfit > 0 ? Math.max(...eventStats.map((event) => Math.abs(event.totalProfit))) / absoluteEventProfit : null,
718
+ topSymbolCountShare: support > 0 ? Math.max(
719
+ ...[...accumulator.symbolCounts.values()].map(
720
+ (symbol) => symbol.count
721
+ )
722
+ ) / support : null,
723
+ topSymbolProfitShare: absoluteSymbolProfit > 0 ? Math.max(
724
+ ...[...accumulator.symbolCounts.values()].map(
725
+ (symbol) => Math.abs(symbol.totalProfit)
726
+ )
727
+ ) / absoluteSymbolProfit : null,
538
728
  totalProfit,
539
729
  grossProfit,
540
730
  grossLoss,
@@ -840,27 +1030,82 @@ var buildAiPocketPredicates = (rows, options = {}) => buildAiPocketPredicateResu
840
1030
  var scorePositivePocket = (summary) => {
841
1031
  const profitFactor = summary.profitFactor ?? (summary.grossLoss === 0 ? 8 : 0);
842
1032
  const winRate = summary.winRate ?? 0;
843
- return summary.totalProfit - summary.maxDrawdown * 0.6 + Math.min(profitFactor, 8) * 12 + winRate * 30 + Math.log10(summary.support + 1) * 6;
1033
+ return summary.eventBalancedProfit - summary.maxDrawdown * 0.6 + Math.min(profitFactor, 8) * 12 + winRate * 30 + Math.log10(summary.events + 1) * 6;
844
1034
  };
845
1035
  var scoreNegativePocket = (summary) => -summary.totalProfit + summary.maxDrawdown * 0.4 + summary.grossLoss * 0.2 + summary.maxConsecutiveLosses * 3;
846
1036
  var comparePositivePockets = (left, right) => (right.validationScore ?? right.score) - (left.validationScore ?? left.score) || right.score - left.score || (right.validationSummary?.totalProfit ?? right.summary.totalProfit) - (left.validationSummary?.totalProfit ?? left.summary.totalProfit) || right.summary.totalProfit - left.summary.totalProfit || (right.summary.profitFactor ?? 999) - (left.summary.profitFactor ?? 999) || right.summary.support - left.summary.support;
847
1037
  var compareNegativePockets = (left, right) => (right.validationScore ?? right.score) - (left.validationScore ?? left.score) || right.score - left.score || (left.validationSummary?.totalProfit ?? left.summary.totalProfit) - (right.validationSummary?.totalProfit ?? right.summary.totalProfit) || left.summary.totalProfit - right.summary.totalProfit || right.summary.grossLoss - left.summary.grossLoss || right.summary.support - left.summary.support;
848
- var createPocketResult = (rows, predicates, mask, validationRows) => {
1038
+ var mergeDistinctRows = (left, right) => {
1039
+ const seenRows = /* @__PURE__ */ new Set();
1040
+ const seenSignalIds = /* @__PURE__ */ new Set();
1041
+ return [...left, ...right].filter((row) => {
1042
+ if (seenRows.has(row)) {
1043
+ return false;
1044
+ }
1045
+ seenRows.add(row);
1046
+ const signalId = typeof row.signalId === "string" && row.signalId.trim() ? row.signalId : null;
1047
+ if (signalId == null) {
1048
+ return true;
1049
+ }
1050
+ if (seenSignalIds.has(signalId)) {
1051
+ return false;
1052
+ }
1053
+ seenSignalIds.add(signalId);
1054
+ return true;
1055
+ });
1056
+ };
1057
+ var resolveObjectiveRows = ({
1058
+ objective,
1059
+ selectedRows,
1060
+ baselineRows
1061
+ }) => objective === "add-to-gate" ? mergeDistinctRows(baselineRows, selectedRows) : selectedRows;
1062
+ var effectiveProfitFactor = (summary) => summary.profitFactor ?? (summary.grossLoss === 0 && summary.totalProfit >= 0 ? Number.POSITIVE_INFINITY : 0);
1063
+ var doesNotRegressRisk = (candidate, baseline) => candidate.totalProfit >= baseline.totalProfit - 1e-9 && effectiveProfitFactor(candidate) >= effectiveProfitFactor(baseline) && candidate.maxDrawdown <= baseline.maxDrawdown + 1e-9 && candidate.maxConsecutiveLosses <= baseline.maxConsecutiveLosses && candidate.losingMonths <= baseline.losingMonths;
1064
+ var createPocketResult = (rows, predicates, mask, validationRows, testRows, objective, baselineRows, validationBaselineRows, testBaselineRows, objectiveBaseline, validationObjectiveBaseline, testObjectiveBaseline) => {
849
1065
  const publicPredicates = predicates.map(toPublicPredicate);
850
1066
  const summary = summarizeMask(rows, mask);
1067
+ const selectedRows = rows.filter((_, index) => mask[index] === 1);
1068
+ const objectiveRows = resolveObjectiveRows({
1069
+ objective,
1070
+ selectedRows,
1071
+ baselineRows
1072
+ });
1073
+ const objectiveSummary = objective === "add-to-gate" ? summarizeAiPocketRows(objectiveRows) : summary;
851
1074
  const validationMask = validationRows.length ? buildPredicateListMask(validationRows, publicPredicates).mask : null;
852
1075
  const validationSummary = validationMask == null ? void 0 : summarizeMask(validationRows, validationMask);
1076
+ const validationSelectedRows = validationMask == null ? [] : validationRows.filter((_, index) => validationMask[index] === 1);
1077
+ const validationObjectiveRows = resolveObjectiveRows({
1078
+ objective,
1079
+ selectedRows: validationSelectedRows,
1080
+ baselineRows: validationBaselineRows
1081
+ });
1082
+ const validationObjectiveSummary = validationRows.length ? objective === "add-to-gate" ? summarizeAiPocketRows(validationObjectiveRows) : validationSummary : void 0;
1083
+ const testMask = testRows.length ? buildPredicateListMask(testRows, publicPredicates).mask : null;
1084
+ const testSummary = testMask == null ? void 0 : summarizeMask(testRows, testMask);
1085
+ const testSelectedRows = testMask == null ? [] : testRows.filter((_, index) => testMask[index] === 1);
1086
+ const testObjectiveRows = resolveObjectiveRows({
1087
+ objective,
1088
+ selectedRows: testSelectedRows,
1089
+ baselineRows: testBaselineRows
1090
+ });
1091
+ const testObjectiveSummary = testRows.length ? objective === "add-to-gate" ? summarizeAiPocketRows(testObjectiveRows) : testSummary : void 0;
853
1092
  const condition = publicPredicates.map((predicate) => predicate.label).join(" AND ");
854
- const validationScore = validationSummary == null ? void 0 : validationSummary.support > 0 ? scorePositivePocket(validationSummary) : Number.NEGATIVE_INFINITY;
1093
+ const validationScore = validationSummary == null || validationObjectiveSummary == null ? void 0 : validationSummary.support > 0 ? scorePositivePocket(validationObjectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(validationObjectiveBaseline)) : Number.NEGATIVE_INFINITY;
1094
+ const testScore = testSummary == null || testObjectiveSummary == null ? void 0 : testSummary.support > 0 ? scorePositivePocket(testObjectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(testObjectiveBaseline)) : Number.NEGATIVE_INFINITY;
855
1095
  return {
856
1096
  id: publicPredicates.map((predicate) => predicate.id).join("&&"),
857
1097
  depth: publicPredicates.length,
858
1098
  predicates: publicPredicates,
859
1099
  condition,
860
1100
  summary,
1101
+ ...objective === "standalone" ? {} : { objectiveSummary },
861
1102
  ...validationSummary ? { validationSummary } : {},
1103
+ ...testSummary ? { testSummary } : {},
1104
+ ...objective !== "standalone" && validationObjectiveSummary ? { validationObjectiveSummary } : {},
1105
+ ...objective !== "standalone" && testObjectiveSummary ? { testObjectiveSummary } : {},
862
1106
  ...validationScore != null ? { validationScore } : {},
863
- score: scorePositivePocket(summary)
1107
+ ...testScore != null ? { testScore } : {},
1108
+ score: scorePositivePocket(objectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(objectiveBaseline))
864
1109
  };
865
1110
  };
866
1111
  var hashMask = (mask) => {
@@ -891,6 +1136,83 @@ var estimateCombinationCount = (poolSize, maxDepth, maxCombinations) => {
891
1136
  }
892
1137
  return Math.min(Math.trunc(total), maxCombinations);
893
1138
  };
1139
+ var classifyFeatureFamily = (featureKey) => {
1140
+ const key = featureKey.toLowerCase();
1141
+ if (/(setup|liquidity|doubletap|trendline|pivot|swing|zone)/.test(key)) {
1142
+ return "strategy-structure";
1143
+ }
1144
+ if (/(structure|breakout|rejection|range|level)/.test(key)) {
1145
+ return "market-structure";
1146
+ }
1147
+ if (/(participation|volume|turnover|obv|effort)/.test(key)) {
1148
+ return "participation";
1149
+ }
1150
+ if (/(relative|benchmark|cmc|breadth|targetvs|btc|eth)/.test(key)) {
1151
+ return "relative-market";
1152
+ }
1153
+ if (/(derivative|funding|openinterest|liquidation|longshort)/.test(key)) {
1154
+ return "derivatives";
1155
+ }
1156
+ if (/(risk|stop|takeprofit|execution|spread|slippage)/.test(key)) {
1157
+ return "execution-risk";
1158
+ }
1159
+ if (/(regime|momentum|trend|volatility|atr|macd|rsi|ma)/.test(key)) {
1160
+ return "regime-indicators";
1161
+ }
1162
+ return "other";
1163
+ };
1164
+ var diversifyPredicatePool = (predicates, maximum) => {
1165
+ const byFamily = /* @__PURE__ */ new Map();
1166
+ const featureCounts = /* @__PURE__ */ new Map();
1167
+ for (const predicate of predicates) {
1168
+ const family = classifyFeatureFamily(predicate.featureKey);
1169
+ const familyPredicates = byFamily.get(family) ?? [];
1170
+ familyPredicates.push(predicate);
1171
+ byFamily.set(family, familyPredicates);
1172
+ }
1173
+ const selected = [];
1174
+ const selectedIds = /* @__PURE__ */ new Set();
1175
+ while (selected.length < maximum) {
1176
+ let added = false;
1177
+ for (const familyPredicates of byFamily.values()) {
1178
+ while (familyPredicates.length) {
1179
+ const predicate = familyPredicates.shift();
1180
+ if (selectedIds.has(predicate.id) || (featureCounts.get(predicate.featureKey) ?? 0) >= 4) {
1181
+ continue;
1182
+ }
1183
+ selected.push(predicate);
1184
+ selectedIds.add(predicate.id);
1185
+ featureCounts.set(
1186
+ predicate.featureKey,
1187
+ (featureCounts.get(predicate.featureKey) ?? 0) + 1
1188
+ );
1189
+ added = true;
1190
+ break;
1191
+ }
1192
+ if (selected.length >= maximum) {
1193
+ break;
1194
+ }
1195
+ }
1196
+ if (!added) {
1197
+ break;
1198
+ }
1199
+ }
1200
+ return selected;
1201
+ };
1202
+ var canCombinePredicate = (chosen, candidate) => {
1203
+ const sameFeature = chosen.filter(
1204
+ (predicate) => predicate.featureKey === candidate.featureKey
1205
+ );
1206
+ if (!sameFeature.length) {
1207
+ return true;
1208
+ }
1209
+ if (sameFeature.length !== 1 || sameFeature[0].kind !== "numeric" || candidate.kind !== "numeric" || sameFeature[0].op === candidate.op) {
1210
+ return false;
1211
+ }
1212
+ const lower = candidate.op === ">=" ? candidate.threshold : sameFeature[0].threshold;
1213
+ const upper = candidate.op === "<=" ? candidate.threshold : sameFeature[0].threshold;
1214
+ return lower <= upper;
1215
+ };
894
1216
  var searchAiPockets = (rows, options = {}) => {
895
1217
  const minSupport = Math.max(1, Math.trunc(options.minSupport ?? 20));
896
1218
  const minProfitFactor = Math.max(0, options.minProfitFactor ?? 1.2);
@@ -907,10 +1229,36 @@ var searchAiPockets = (rows, options = {}) => {
907
1229
  );
908
1230
  const top = Math.max(1, Math.trunc(options.top ?? 50));
909
1231
  const validationRows = options.validationRows ?? [];
1232
+ const testRows = options.testRows ?? [];
910
1233
  const minValidationSupport = Math.max(
911
1234
  0,
912
1235
  Math.trunc(options.minValidationSupport ?? 0)
913
1236
  );
1237
+ const minEvents = Math.max(1, Math.trunc(options.minEvents ?? 1));
1238
+ const minValidationEvents = Math.max(
1239
+ 0,
1240
+ Math.trunc(options.minValidationEvents ?? 0)
1241
+ );
1242
+ const maxBatch = Math.max(1, options.maxBatch ?? Number.POSITIVE_INFINITY);
1243
+ const maxEventCountShare = Math.max(
1244
+ 0,
1245
+ Math.min(1, options.maxEventCountShare ?? 1)
1246
+ );
1247
+ const maxSymbolCountShare = Math.max(
1248
+ 0,
1249
+ Math.min(1, options.maxSymbolCountShare ?? 1)
1250
+ );
1251
+ const objective = options.objective ?? "standalone";
1252
+ const baselineRows = options.baselineRows ?? (objective === "filter-gate" ? rows : []);
1253
+ const validationBaselineRows = options.validationBaselineRows ?? (objective === "filter-gate" ? validationRows : []);
1254
+ const testBaselineRows = options.testBaselineRows ?? (objective === "filter-gate" ? testRows : []);
1255
+ const objectiveBaseline = summarizeAiPocketRows(baselineRows);
1256
+ const validationObjectiveBaseline = summarizeAiPocketRows(
1257
+ validationBaselineRows
1258
+ );
1259
+ const testObjectiveBaseline = summarizeAiPocketRows(testBaselineRows);
1260
+ const allowRiskRegression = options.allowRiskRegression === true;
1261
+ const requireValidationEligibility = options.requireValidationEligibility === true;
914
1262
  const dedupeEquivalentSelections = options.dedupeEquivalentSelections !== false;
915
1263
  const progressInterval = Math.max(
916
1264
  1,
@@ -953,11 +1301,13 @@ var searchAiPockets = (rows, options = {}) => {
953
1301
  const supportPool = [...scoredPredicates].sort((left, right) => right.support - left.support).slice(0, Math.ceil(maxAtomicPredicates * 0.2));
954
1302
  const predicatePoolById = /* @__PURE__ */ new Map();
955
1303
  [...predicatePool, ...negativePool, ...supportPool].forEach((predicate) => {
956
- if (predicatePoolById.size < maxAtomicPredicates) {
957
- predicatePoolById.set(predicate.id, predicate);
958
- }
1304
+ predicatePoolById.set(predicate.id, predicate);
959
1305
  });
960
- const selectedPredicatePool = [...predicatePoolById.values()].map(
1306
+ const diversifiedPredicates = diversifyPredicatePool(
1307
+ [...predicatePoolById.values()],
1308
+ maxAtomicPredicates
1309
+ );
1310
+ const selectedPredicatePool = diversifiedPredicates.map(
961
1311
  (predicate) => {
962
1312
  const { mask, support } = buildMask(rows, predicate);
963
1313
  return {
@@ -1001,14 +1351,30 @@ var searchAiPockets = (rows, options = {}) => {
1001
1351
  rows,
1002
1352
  pocketPredicates,
1003
1353
  mask,
1004
- validationRows
1354
+ validationRows,
1355
+ testRows,
1356
+ objective,
1357
+ baselineRows,
1358
+ validationBaselineRows,
1359
+ testBaselineRows,
1360
+ objectiveBaseline,
1361
+ validationObjectiveBaseline,
1362
+ testObjectiveBaseline
1005
1363
  );
1006
1364
  const { summary } = pocket;
1007
- const profitFactor = summary.profitFactor ?? (summary.grossLoss === 0 && summary.totalProfit > 0 ? Number.POSITIVE_INFINITY : 0);
1008
- const winRate = summary.winRate ?? 0;
1365
+ const scoredSummary = pocket.objectiveSummary ?? summary;
1366
+ const profitFactor = scoredSummary.profitFactor ?? (scoredSummary.grossLoss === 0 && scoredSummary.totalProfit > 0 ? Number.POSITIVE_INFINITY : 0);
1367
+ const winRate = scoredSummary.winRate ?? 0;
1009
1368
  const validationSupport = pocket.validationSummary?.support ?? 0;
1010
- const validationEligible = !validationRows.length || validationSupport >= minValidationSupport;
1011
- if (summary.support >= minSupport && summary.totalProfit >= minTotalProfit && profitFactor >= minProfitFactor && winRate >= minWinRate && validationEligible) {
1369
+ const validationEligible = !validationRows.length || validationSupport >= minValidationSupport && (pocket.validationSummary?.events ?? 0) >= minValidationEvents && (pocket.validationSummary?.maxBatch ?? 0) <= maxBatch && (pocket.validationSummary?.topEventCountShare ?? 0) <= maxEventCountShare && (pocket.validationSummary?.topSymbolCountShare ?? 0) <= maxSymbolCountShare && (!requireValidationEligibility || (pocket.validationObjectiveSummary ?? pocket.validationSummary).totalProfit >= minTotalProfit && effectiveProfitFactor(
1370
+ pocket.validationObjectiveSummary ?? pocket.validationSummary
1371
+ ) >= minProfitFactor && ((pocket.validationObjectiveSummary ?? pocket.validationSummary).winRate ?? 0) >= minWinRate);
1372
+ const concentrationEligible = summary.events >= minEvents && summary.maxBatch <= maxBatch && (summary.topEventCountShare ?? 0) <= maxEventCountShare && (summary.topSymbolCountShare ?? 0) <= maxSymbolCountShare;
1373
+ const riskEligible = objective === "standalone" || allowRiskRegression || (!objectiveBaseline.support || doesNotRegressRisk(scoredSummary, objectiveBaseline)) && (!validationRows.length || !validationObjectiveBaseline.support || doesNotRegressRisk(
1374
+ pocket.validationObjectiveSummary ?? pocket.validationSummary,
1375
+ validationObjectiveBaseline
1376
+ ));
1377
+ if (summary.support >= minSupport && scoredSummary.totalProfit >= minTotalProfit && profitFactor >= minProfitFactor && winRate >= minWinRate && validationEligible && concentrationEligible && riskEligible) {
1012
1378
  const selectionKey = `${summary.support}:${hashMask(mask)}`;
1013
1379
  const mapKey = dedupeEquivalentSelections ? selectionKey : pocket.id;
1014
1380
  const existing = dedupeEquivalentSelections ? positiveSelectionKeys.get(mapKey) : positivePockets.get(mapKey);
@@ -1045,8 +1411,7 @@ var searchAiPockets = (rows, options = {}) => {
1045
1411
  const visit = ({
1046
1412
  startIndex,
1047
1413
  chosen,
1048
- mask,
1049
- usedFeatureKeys
1414
+ mask
1050
1415
  }) => {
1051
1416
  if (truncated) {
1052
1417
  return;
@@ -1057,7 +1422,7 @@ var searchAiPockets = (rows, options = {}) => {
1057
1422
  return;
1058
1423
  }
1059
1424
  const predicate = selectedPredicatePool[index];
1060
- if (usedFeatureKeys.has(predicate.featureKey)) {
1425
+ if (!canCombinePredicate(chosen, predicate)) {
1061
1426
  continue;
1062
1427
  }
1063
1428
  const intersection = mask == null ? { mask: predicate.mask, support: predicate.support } : intersectMasks(mask, predicate.mask);
@@ -1073,26 +1438,27 @@ var searchAiPockets = (rows, options = {}) => {
1073
1438
  if (nextChosen.length >= maxDepth) {
1074
1439
  continue;
1075
1440
  }
1076
- const nextUsedFeatureKeys = new Set(usedFeatureKeys);
1077
- nextUsedFeatureKeys.add(predicate.featureKey);
1078
1441
  visit({
1079
1442
  startIndex: index + 1,
1080
1443
  chosen: nextChosen,
1081
- mask: nextMask,
1082
- usedFeatureKeys: nextUsedFeatureKeys
1444
+ mask: nextMask
1083
1445
  });
1084
1446
  }
1085
1447
  };
1086
1448
  visit({
1087
1449
  startIndex: 0,
1088
1450
  chosen: [],
1089
- mask: null,
1090
- usedFeatureKeys: /* @__PURE__ */ new Set()
1451
+ mask: null
1091
1452
  });
1092
1453
  emitProgress(true);
1093
1454
  return {
1455
+ objective,
1094
1456
  baseline: summarizeAiPocketRows(rows),
1095
1457
  ...validationRows.length ? { validationBaseline: summarizeAiPocketRows(validationRows) } : {},
1458
+ ...testRows.length ? { testBaseline: summarizeAiPocketRows(testRows) } : {},
1459
+ ...objective === "standalone" ? {} : { objectiveBaseline },
1460
+ ...objective !== "standalone" && validationRows.length ? { validationObjectiveBaseline } : {},
1461
+ ...objective !== "standalone" && testRows.length ? { testObjectiveBaseline } : {},
1096
1462
  predicates: predicates.map(toPublicPredicate),
1097
1463
  positivePockets: [...positivePockets.values()].sort(comparePositivePockets).slice(0, top),
1098
1464
  negativePockets: [...negativePockets.values()].sort(compareNegativePockets).slice(0, top),
@@ -1104,7 +1470,15 @@ var searchAiPockets = (rows, options = {}) => {
1104
1470
  estimatedCombinations,
1105
1471
  combinationsEvaluated,
1106
1472
  validationRows: validationRows.length,
1473
+ testRows: testRows.length,
1107
1474
  duplicatePocketsSkipped,
1475
+ featureFamiliesUsed: [
1476
+ ...new Set(
1477
+ selectedPredicatePool.map(
1478
+ (predicate) => classifyFeatureFamily(predicate.featureKey)
1479
+ )
1480
+ )
1481
+ ],
1108
1482
  truncated
1109
1483
  }
1110
1484
  };
@@ -1122,6 +1496,13 @@ var markdownTable = (headers, rows) => {
1122
1496
  };
1123
1497
  var summaryMetricRows = (summary) => [
1124
1498
  ["rows", summary.support],
1499
+ ["events", summary.events],
1500
+ ["event_balanced_profit", formatMdNumber(summary.eventBalancedProfit)],
1501
+ ["trades_per_event", formatMdNumber(summary.tradesPerEvent)],
1502
+ ["p95_batch", summary.p95Batch],
1503
+ ["max_batch", summary.maxBatch],
1504
+ ["top_event_count_share", formatMdPercent(summary.topEventCountShare)],
1505
+ ["top_symbol_count_share", formatMdPercent(summary.topSymbolCountShare)],
1125
1506
  ["win_rate", formatMdPercent(summary.winRate)],
1126
1507
  ["total_profit", formatMdNumber(summary.totalProfit)],
1127
1508
  ["gross_profit", formatMdNumber(summary.grossProfit)],
@@ -1150,15 +1531,22 @@ var summaryMetricRows = (summary) => [
1150
1531
  var pocketRows = (pockets) => pockets.map((pocket, index) => [
1151
1532
  index + 1,
1152
1533
  pocket.summary.support,
1534
+ pocket.summary.events,
1535
+ pocket.summary.maxBatch,
1153
1536
  formatMdPercent(pocket.summary.supportRatio),
1154
1537
  formatMdPercent(pocket.summary.winRate),
1155
1538
  formatMdNumber(pocket.summary.profitFactor),
1156
1539
  formatMdNumber(pocket.summary.totalProfit),
1540
+ pocket.objectiveSummary ? formatMdNumber(pocket.objectiveSummary.totalProfit) : "n/a",
1157
1541
  formatMdNumber(pocket.summary.maxDrawdown),
1158
1542
  pocket.validationSummary?.support ?? "n/a",
1159
1543
  pocket.validationSummary ? formatMdPercent(pocket.validationSummary.winRate) : "n/a",
1160
1544
  pocket.validationSummary ? formatMdNumber(pocket.validationSummary.profitFactor) : "n/a",
1161
1545
  pocket.validationSummary ? formatMdNumber(pocket.validationSummary.totalProfit) : "n/a",
1546
+ pocket.testSummary?.support ?? "n/a",
1547
+ pocket.testSummary ? formatMdNumber(pocket.testSummary.profitFactor) : "n/a",
1548
+ pocket.testSummary ? formatMdNumber(pocket.testSummary.totalProfit) : "n/a",
1549
+ pocket.testObjectiveSummary ? formatMdNumber(pocket.testObjectiveSummary.totalProfit) : "n/a",
1162
1550
  formatMdNumber(pocket.summary.avgTradesPerDay),
1163
1551
  pocket.summary.losingMonths,
1164
1552
  formatMdNumber(pocket.score),
@@ -1172,6 +1560,13 @@ var buildAiPocketMarkdownReport = ({
1172
1560
  errors
1173
1561
  }) => {
1174
1562
  const generatedIso = new Date(generatedAt).toISOString();
1563
+ const featurePolicyAuditRows = Object.entries(
1564
+ run.featurePolicyAudit ?? {}
1565
+ ).map(([classification, audit]) => [
1566
+ classification,
1567
+ audit?.paths ?? 0,
1568
+ audit?.samples.join(", ") ?? ""
1569
+ ]);
1175
1570
  const lines = [
1176
1571
  "# AI Pocket Search Report",
1177
1572
  "",
@@ -1187,6 +1582,7 @@ var buildAiPocketMarkdownReport = ({
1187
1582
  ["selected_rows", run.selectedRows],
1188
1583
  ["evaluated_rows", run.evaluatedRows],
1189
1584
  ["scope", run.scope],
1585
+ ["objective", run.objective ?? pocketSearch.objective],
1190
1586
  ["direction", run.direction ?? "all"],
1191
1587
  ["scope_rows", run.scopeRows],
1192
1588
  ["train_rows", run.trainRows],
@@ -1210,15 +1606,44 @@ var buildAiPocketMarkdownReport = ({
1210
1606
  ["min_quality", run.minQuality],
1211
1607
  ["max_depth", run.search.maxDepth],
1212
1608
  ["min_support", run.search.minSupport],
1609
+ ["min_events", run.search.minEvents ?? 1],
1610
+ ["min_validation_events", run.search.minValidationEvents ?? 0],
1611
+ ["max_batch", run.search.maxBatch ?? "off"],
1612
+ [
1613
+ "max_event_count_share",
1614
+ run.search.maxEventCountShare == null ? "off" : formatMdPercent(run.search.maxEventCountShare)
1615
+ ],
1616
+ [
1617
+ "max_symbol_count_share",
1618
+ run.search.maxSymbolCountShare == null ? "off" : formatMdPercent(run.search.maxSymbolCountShare)
1619
+ ],
1620
+ [
1621
+ "allow_risk_regression",
1622
+ run.search.allowRiskRegression ? "yes" : "no"
1623
+ ],
1624
+ [
1625
+ "require_validation_eligibility",
1626
+ run.search.requireValidationEligibility ? "yes" : "no"
1627
+ ],
1213
1628
  ["max_atomic_predicates", run.search.maxAtomicPredicates],
1214
1629
  ["max_combinations", run.search.maxCombinations],
1215
1630
  ["include_symbol", run.includeSymbol ? "on" : "off"],
1216
1631
  ["include_gate_context", run.includeGateContext ? "on" : "off"],
1217
1632
  ["feature_profile", run.featureProfile ?? "all"],
1633
+ ["feature_policy", run.featurePolicy ?? "all"],
1218
1634
  ["report_path", run.reportPath]
1219
1635
  ]
1220
1636
  ),
1221
1637
  "",
1638
+ ...featurePolicyAuditRows.length ? [
1639
+ "## Feature Policy Audit",
1640
+ "",
1641
+ markdownTable(
1642
+ ["Classification", "Excluded paths", "Samples"],
1643
+ featurePolicyAuditRows
1644
+ ),
1645
+ ""
1646
+ ] : [],
1222
1647
  "## Dataset Files",
1223
1648
  "",
1224
1649
  ...run.filePaths.map((filePath) => `- \`${filePath}\``),
@@ -1255,6 +1680,33 @@ var buildAiPocketMarkdownReport = ({
1255
1680
  ),
1256
1681
  ""
1257
1682
  ] : [],
1683
+ ...pocketSearch.testBaseline ? [
1684
+ "## Untouched Test Baseline",
1685
+ "",
1686
+ markdownTable(
1687
+ ["Metric", "Value"],
1688
+ summaryMetricRows(pocketSearch.testBaseline)
1689
+ ),
1690
+ ""
1691
+ ] : [],
1692
+ ...pocketSearch.objectiveBaseline ? [
1693
+ "## Current Gate Train Baseline",
1694
+ "",
1695
+ markdownTable(
1696
+ ["Metric", "Value"],
1697
+ summaryMetricRows(pocketSearch.objectiveBaseline)
1698
+ ),
1699
+ ""
1700
+ ] : [],
1701
+ ...pocketSearch.testObjectiveBaseline ? [
1702
+ "## Current Gate Untouched Test Baseline",
1703
+ "",
1704
+ markdownTable(
1705
+ ["Metric", "Value"],
1706
+ summaryMetricRows(pocketSearch.testObjectiveBaseline)
1707
+ ),
1708
+ ""
1709
+ ] : [],
1258
1710
  "## Search Stats",
1259
1711
  "",
1260
1712
  markdownTable(
@@ -1263,9 +1715,11 @@ var buildAiPocketMarkdownReport = ({
1263
1715
  ["feature_keys", pocketSearch.stats.featureKeys],
1264
1716
  ["predicates", pocketSearch.stats.predicates],
1265
1717
  ["atomic_used", pocketSearch.stats.atomicPredicatesUsed],
1718
+ ["feature_families", pocketSearch.stats.featureFamiliesUsed.join(", ")],
1266
1719
  ["estimated_combinations", pocketSearch.stats.estimatedCombinations],
1267
1720
  ["combinations_evaluated", pocketSearch.stats.combinationsEvaluated],
1268
1721
  ["validation_rows", pocketSearch.stats.validationRows],
1722
+ ["test_rows", pocketSearch.stats.testRows],
1269
1723
  [
1270
1724
  "duplicate_pockets_skipped",
1271
1725
  pocketSearch.stats.duplicatePocketsSkipped
@@ -1280,15 +1734,22 @@ var buildAiPocketMarkdownReport = ({
1280
1734
  [
1281
1735
  "#",
1282
1736
  "N",
1737
+ "Events",
1738
+ "Max batch",
1283
1739
  "Support",
1284
1740
  "WR",
1285
1741
  "PF",
1286
1742
  "PNL",
1743
+ "Objective PNL",
1287
1744
  "Max DD",
1288
1745
  "Val N",
1289
1746
  "Val WR",
1290
1747
  "Val PF",
1291
1748
  "Val PNL",
1749
+ "Test N",
1750
+ "Test PF",
1751
+ "Test PNL",
1752
+ "Test objective PNL",
1292
1753
  "Trades/Day",
1293
1754
  "Losing Months",
1294
1755
  "Score",
@@ -1303,15 +1764,22 @@ var buildAiPocketMarkdownReport = ({
1303
1764
  [
1304
1765
  "#",
1305
1766
  "N",
1767
+ "Events",
1768
+ "Max batch",
1306
1769
  "Support",
1307
1770
  "WR",
1308
1771
  "PF",
1309
1772
  "PNL",
1773
+ "Objective PNL",
1310
1774
  "Max DD",
1311
1775
  "Val N",
1312
1776
  "Val WR",
1313
1777
  "Val PF",
1314
1778
  "Val PNL",
1779
+ "Test N",
1780
+ "Test PF",
1781
+ "Test PNL",
1782
+ "Test objective PNL",
1315
1783
  "Trades/Day",
1316
1784
  "Losing Months",
1317
1785
  "Score",
@@ -1331,6 +1799,7 @@ var buildAiPocketMarkdownReport = ({
1331
1799
  0 && (module.exports = {
1332
1800
  buildAiPocketMarkdownReport,
1333
1801
  buildAiPocketPredicates,
1802
+ classifyAiPocketFeaturePath,
1334
1803
  collectAiPocketFeatures,
1335
1804
  searchAiPockets,
1336
1805
  summarizeAiPocketRows