@tradejs/cli 2.0.19 → 2.0.20

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,15 +22,20 @@ var aiPocketSearch_exports = {};
22
22
  __export(aiPocketSearch_exports, {
23
23
  buildAiPocketMarkdownReport: () => buildAiPocketMarkdownReport,
24
24
  buildAiPocketPredicates: () => buildAiPocketPredicates,
25
+ classifyAiPocketCoverageFeaturePath: () => classifyAiPocketCoverageFeaturePath,
25
26
  classifyAiPocketFeaturePath: () => classifyAiPocketFeaturePath,
27
+ collectAiPocketFeatureSnapshot: () => collectAiPocketFeatureSnapshot,
26
28
  collectAiPocketFeatures: () => collectAiPocketFeatures,
29
+ resolveAiPocketFeatureCoverage: () => resolveAiPocketFeatureCoverage,
27
30
  searchAiPockets: () => searchAiPockets,
31
+ summarizeAiPocketFeatureCoverage: () => summarizeAiPocketFeatureCoverage,
28
32
  summarizeAiPocketRows: () => summarizeAiPocketRows
29
33
  });
30
34
  module.exports = __toCommonJS(aiPocketSearch_exports);
31
35
  var DAY_MS = 24 * 60 * 60 * 1e3;
32
36
  var DAYS_PER_WEEK = 7;
33
37
  var DAYS_PER_MONTH = 30.4375;
38
+ var PRODUCTION_CANDIDATE_MIN_EVENTS = 25;
34
39
  var OUTCOME_SEGMENTS = /* @__PURE__ */ new Set([
35
40
  "actual",
36
41
  "aiapproved",
@@ -116,6 +121,146 @@ var METADATA_LEAVES = /* @__PURE__ */ new Set([
116
121
  "targetsymbol",
117
122
  "universe"
118
123
  ]);
124
+ var normalizeFeaturePath = (path) => (Array.isArray(path) ? path : path.split(".")).filter(Boolean).map((segment) => segment.toLowerCase());
125
+ var classifyAiPocketCoverageFeaturePath = (path) => {
126
+ const segments = normalizeFeaturePath(path);
127
+ const normalizedPath = segments.join(".");
128
+ if (segments.some(
129
+ (segment) => segment.includes("cmc") || segment.includes("coinmarketcap")
130
+ )) {
131
+ return "cmc";
132
+ }
133
+ if (segments.some(
134
+ (segment) => segment.includes("derivative") || segment.includes("coinalyze")
135
+ ) || /(?:^|\.)(?:funding|openinterest|oi(?:change|acceleration|divergence)|priceoi|liq(?:long|short|total|imbalance|spike)|liquidation)/.test(
136
+ normalizedPath
137
+ )) {
138
+ return "coinalyze";
139
+ }
140
+ return null;
141
+ };
142
+ var hasUsableContextPrimitive = (value) => {
143
+ if (Array.isArray(value) || value == null) {
144
+ return false;
145
+ }
146
+ if (isFeaturePrimitive(value)) {
147
+ return typeof value !== "string" || value.toLowerCase() !== "unknown";
148
+ }
149
+ if (!isPlainRecord(value)) {
150
+ return false;
151
+ }
152
+ return Object.entries(value).some(([key, child]) => {
153
+ const normalizedKey = key.toLowerCase();
154
+ if (DATA_QUALITY_LEAVES.has(normalizedKey) || METADATA_LEAVES.has(normalizedKey) || normalizedKey === "riskflags" || normalizedKey.endsWith("timestamp") || normalizedKey.endsWith("agems")) {
155
+ return false;
156
+ }
157
+ return hasUsableContextPrimitive(child);
158
+ });
159
+ };
160
+ var isUsableCmcContext = (value) => isPlainRecord(value) && value.available !== false && value.stale !== true && hasUsableContextPrimitive(value);
161
+ var hasUsableCmcContext = (baseContext) => {
162
+ const relative = isPlainRecord(baseContext?.relative) ? baseContext.relative : null;
163
+ return Boolean(
164
+ relative && Object.entries(relative).some(
165
+ ([key, value]) => key.toLowerCase().startsWith("cmc") && isUsableCmcContext(value)
166
+ )
167
+ );
168
+ };
169
+ var isUsableDerivativesContext = (context) => {
170
+ if (!context) {
171
+ return false;
172
+ }
173
+ const summary = isPlainRecord(context.summary) ? context.summary : null;
174
+ const riskFlags = Array.isArray(summary?.riskFlags) ? summary.riskFlags.filter(
175
+ (flag) => typeof flag === "string"
176
+ ) : [];
177
+ if (riskFlags.includes("missing_derivatives") || riskFlags.includes("stale_derivatives")) {
178
+ return false;
179
+ }
180
+ const intervals = isPlainRecord(context.intervals) ? context.intervals : null;
181
+ return Boolean(
182
+ intervals && Object.values(intervals).some(
183
+ (interval) => isPlainRecord(interval) && interval.stale !== true && hasUsableContextPrimitive(interval)
184
+ )
185
+ );
186
+ };
187
+ var hasUsableCoinalyzeContext = (baseContext) => {
188
+ const derivatives = isPlainRecord(baseContext?.derivatives) ? baseContext.derivatives : null;
189
+ if (!derivatives) {
190
+ return false;
191
+ }
192
+ if (isUsableDerivativesContext(derivatives)) {
193
+ return true;
194
+ }
195
+ const targetContext = isPlainRecord(derivatives.targetContext) ? derivatives.targetContext : null;
196
+ if (isUsableDerivativesContext(targetContext)) {
197
+ return true;
198
+ }
199
+ const referenceContexts = isPlainRecord(derivatives.referenceContexts) ? derivatives.referenceContexts : null;
200
+ return Boolean(
201
+ referenceContexts && Object.values(referenceContexts).some(
202
+ (context) => isUsableDerivativesContext(isPlainRecord(context) ? context : null)
203
+ )
204
+ );
205
+ };
206
+ var resolveAiPocketFeatureCoverage = (payload) => {
207
+ const additionalIndicators = isPlainRecord(payload.additionalIndicators) ? payload.additionalIndicators : null;
208
+ const baseContext = isPlainRecord(additionalIndicators?.baseContext) ? additionalIndicators.baseContext : null;
209
+ return {
210
+ cmc: hasUsableCmcContext(baseContext),
211
+ coinalyze: hasUsableCoinalyzeContext(baseContext)
212
+ };
213
+ };
214
+ var isAiPocketCoverageFeatureUsable = ({
215
+ payload,
216
+ segments,
217
+ family,
218
+ familyCoverage
219
+ }) => {
220
+ const additionalIndicators = isPlainRecord(payload.additionalIndicators) ? payload.additionalIndicators : null;
221
+ const baseContext = isPlainRecord(additionalIndicators?.baseContext) ? additionalIndicators.baseContext : null;
222
+ const baseContextIndex = segments.findIndex(
223
+ (segment) => segment.toLowerCase() === "basecontext"
224
+ );
225
+ if (family === "cmc") {
226
+ const relative = isPlainRecord(baseContext?.relative) ? baseContext.relative : null;
227
+ if (relative && baseContextIndex >= 0 && segments[baseContextIndex + 1]?.toLowerCase() === "relative") {
228
+ const contextKey = segments[baseContextIndex + 2];
229
+ if (contextKey?.toLowerCase().startsWith("cmc")) {
230
+ return isUsableCmcContext(relative[contextKey]);
231
+ }
232
+ }
233
+ return familyCoverage.cmc;
234
+ }
235
+ const derivatives = isPlainRecord(baseContext?.derivatives) ? baseContext.derivatives : null;
236
+ if (!derivatives || baseContextIndex < 0 || segments[baseContextIndex + 1]?.toLowerCase() !== "derivatives") {
237
+ return isUsableDerivativesContext(derivatives);
238
+ }
239
+ const branch = segments[baseContextIndex + 2]?.toLowerCase();
240
+ if (branch == null) {
241
+ return familyCoverage.coinalyze;
242
+ }
243
+ if (branch === "referencecontexts") {
244
+ const referenceContexts = isPlainRecord(derivatives.referenceContexts) ? derivatives.referenceContexts : null;
245
+ const symbol = segments[baseContextIndex + 3];
246
+ if (!symbol) {
247
+ return Boolean(
248
+ referenceContexts && Object.values(referenceContexts).some(
249
+ (context) => isUsableDerivativesContext(isPlainRecord(context) ? context : null)
250
+ )
251
+ );
252
+ }
253
+ return isUsableDerivativesContext(
254
+ symbol && referenceContexts && isPlainRecord(referenceContexts[symbol]) ? referenceContexts[symbol] : null
255
+ );
256
+ }
257
+ if (branch === "targetcontext" || branch === "targetderived") {
258
+ return isUsableDerivativesContext(
259
+ isPlainRecord(derivatives.targetContext) ? derivatives.targetContext : null
260
+ );
261
+ }
262
+ return isUsableDerivativesContext(derivatives);
263
+ };
119
264
  var DERIVED_POLICY_FRAGMENTS = [
120
265
  "approval",
121
266
  "decisionhint",
@@ -454,7 +599,7 @@ var addSignalRiskDistanceFeatures = ({
454
599
  features["derived.takeProfitDistanceBps"] = Math.abs(takeProfitPrice - currentPrice) / Math.abs(currentPrice) * 1e4;
455
600
  }
456
601
  };
457
- var collectAiPocketFeatures = ({
602
+ var collectAiPocketFeatureSnapshot = ({
458
603
  payload,
459
604
  gateContext,
460
605
  includeSymbol = false,
@@ -464,6 +609,7 @@ var collectAiPocketFeatures = ({
464
609
  onFeatureExcluded
465
610
  }) => {
466
611
  const features = {};
612
+ const featureCoverage = resolveAiPocketFeatureCoverage(payload);
467
613
  const signal = payload.signal ?? {};
468
614
  const source = {
469
615
  signal: {
@@ -482,6 +628,19 @@ var collectAiPocketFeatures = ({
482
628
  segments: [],
483
629
  maxDepth: 8,
484
630
  shouldSkipPath: (segments) => {
631
+ const coverageFamily = classifyAiPocketCoverageFeaturePath(segments);
632
+ if (featurePolicy === "causal-stationary" && coverageFamily != null && !isAiPocketCoverageFeatureUsable({
633
+ payload,
634
+ segments,
635
+ family: coverageFamily,
636
+ familyCoverage: featureCoverage
637
+ })) {
638
+ onFeatureExcluded?.({
639
+ path: segments.join("."),
640
+ classification: "data-quality"
641
+ });
642
+ return true;
643
+ }
485
644
  if (featureProfile === "compact" && isCompactFeaturePathSkipped(segments)) {
486
645
  return true;
487
646
  }
@@ -512,8 +671,9 @@ var collectAiPocketFeatures = ({
512
671
  }
513
672
  addSignalRiskDistanceFeatures({ features, signal });
514
673
  addDirectionalDerivedFeatures(features);
515
- return features;
674
+ return { features, featureCoverage };
516
675
  };
676
+ var collectAiPocketFeatures = (options) => collectAiPocketFeatureSnapshot(options).features;
517
677
  var formatNumber = (value) => {
518
678
  if (Number.isInteger(value)) {
519
679
  return String(value);
@@ -779,6 +939,37 @@ var summarizeRowIndexes = (rows, rowIndexes) => {
779
939
  return finalizeAiPocketSummary({ rows, accumulator });
780
940
  };
781
941
  var summarizeAiPocketRows = (rows) => summarizeSelectedRows(rows, rows);
942
+ var summarizeAiPocketFeatureCoverage = (rows, family) => {
943
+ const coveredRows = rows.filter(
944
+ (row) => row.featureCoverage?.[family] === true
945
+ );
946
+ const allEvents = /* @__PURE__ */ new Set();
947
+ const coveredEvents = /* @__PURE__ */ new Set();
948
+ let minTimestamp = null;
949
+ let maxTimestamp = null;
950
+ for (const row of rows) {
951
+ if (isFiniteNumber(row.timestamp)) {
952
+ allEvents.add(row.timestamp);
953
+ }
954
+ }
955
+ for (const row of coveredRows) {
956
+ if (!isFiniteNumber(row.timestamp)) {
957
+ continue;
958
+ }
959
+ coveredEvents.add(row.timestamp);
960
+ minTimestamp = minTimestamp == null ? row.timestamp : Math.min(minTimestamp, row.timestamp);
961
+ maxTimestamp = maxTimestamp == null ? row.timestamp : Math.max(maxTimestamp, row.timestamp);
962
+ }
963
+ return {
964
+ family,
965
+ rows: coveredRows.length,
966
+ rowRatio: rows.length > 0 ? coveredRows.length / rows.length : 0,
967
+ events: coveredEvents.size,
968
+ eventRatio: allEvents.size > 0 ? coveredEvents.size / allEvents.size : 0,
969
+ minTimestamp,
970
+ maxTimestamp
971
+ };
972
+ };
782
973
  var matchesPredicate = (value, predicate) => {
783
974
  if (predicate.kind === "numeric") {
784
975
  if (!isFiniteNumber(value)) {
@@ -1092,6 +1283,19 @@ var createPocketResult = (rows, predicates, mask, validationRows, testRows, obje
1092
1283
  const condition = publicPredicates.map((predicate) => predicate.label).join(" AND ");
1093
1284
  const validationScore = validationSummary == null || validationObjectiveSummary == null ? void 0 : validationSummary.support > 0 ? scorePositivePocket(validationObjectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(validationObjectiveBaseline)) : Number.NEGATIVE_INFINITY;
1094
1285
  const testScore = testSummary == null || testObjectiveSummary == null ? void 0 : testSummary.support > 0 ? scorePositivePocket(testObjectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(testObjectiveBaseline)) : Number.NEGATIVE_INFINITY;
1286
+ const readinessReasons = [];
1287
+ if (summary.events < PRODUCTION_CANDIDATE_MIN_EVENTS) {
1288
+ readinessReasons.push(
1289
+ `train events ${summary.events} < ${PRODUCTION_CANDIDATE_MIN_EVENTS}`
1290
+ );
1291
+ }
1292
+ if (testSummary == null) {
1293
+ readinessReasons.push("untouched test missing");
1294
+ } else if (testSummary.events < PRODUCTION_CANDIDATE_MIN_EVENTS) {
1295
+ readinessReasons.push(
1296
+ `test events ${testSummary.events} < ${PRODUCTION_CANDIDATE_MIN_EVENTS}`
1297
+ );
1298
+ }
1095
1299
  return {
1096
1300
  id: publicPredicates.map((predicate) => predicate.id).join("&&"),
1097
1301
  depth: publicPredicates.length,
@@ -1105,6 +1309,8 @@ var createPocketResult = (rows, predicates, mask, validationRows, testRows, obje
1105
1309
  ...objective !== "standalone" && testObjectiveSummary ? { testObjectiveSummary } : {},
1106
1310
  ...validationScore != null ? { validationScore } : {},
1107
1311
  ...testScore != null ? { testScore } : {},
1312
+ readiness: readinessReasons.length === 0 ? "production-candidate" : "research-only",
1313
+ readinessReasons,
1108
1314
  score: scorePositivePocket(objectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(objectiveBaseline))
1109
1315
  };
1110
1316
  };
@@ -1199,6 +1405,43 @@ var diversifyPredicatePool = (predicates, maximum) => {
1199
1405
  }
1200
1406
  return selected;
1201
1407
  };
1408
+ var prioritizeRequiredFeatureFamilyPredicates = ({
1409
+ selected,
1410
+ predicates,
1411
+ requiredFeatureFamilies,
1412
+ maximum
1413
+ }) => {
1414
+ if (!requiredFeatureFamilies.length) {
1415
+ return selected;
1416
+ }
1417
+ const perFamilyLimit = Math.max(
1418
+ 6,
1419
+ Math.floor(maximum * 0.2 / requiredFeatureFamilies.length)
1420
+ );
1421
+ const prioritized = /* @__PURE__ */ new Map();
1422
+ for (const family of requiredFeatureFamilies) {
1423
+ const familyPredicates = predicates.filter(
1424
+ (predicate) => classifyAiPocketCoverageFeaturePath(predicate.featureKey) === family
1425
+ );
1426
+ const positive = [...familyPredicates].sort(
1427
+ (left, right) => scorePositivePocket(right.atomSummary) - scorePositivePocket(left.atomSummary)
1428
+ ).slice(0, Math.ceil(perFamilyLimit * 0.5));
1429
+ const negative = [...familyPredicates].sort(
1430
+ (left, right) => scoreNegativePocket(right.atomSummary) - scoreNegativePocket(left.atomSummary)
1431
+ ).slice(0, Math.ceil(perFamilyLimit * 0.3));
1432
+ const broad = [...familyPredicates].sort((left, right) => right.support - left.support).slice(0, Math.ceil(perFamilyLimit * 0.2));
1433
+ for (const predicate of [...positive, ...negative, ...broad]) {
1434
+ if (prioritized.size >= perFamilyLimit * requiredFeatureFamilies.length) {
1435
+ break;
1436
+ }
1437
+ prioritized.set(predicate.id, predicate);
1438
+ }
1439
+ }
1440
+ for (const predicate of selected) {
1441
+ prioritized.set(predicate.id, predicate);
1442
+ }
1443
+ return [...prioritized.values()].slice(0, maximum);
1444
+ };
1202
1445
  var canCombinePredicate = (chosen, candidate) => {
1203
1446
  const sameFeature = chosen.filter(
1204
1447
  (predicate) => predicate.featureKey === candidate.featureKey
@@ -1214,7 +1457,11 @@ var canCombinePredicate = (chosen, candidate) => {
1214
1457
  return lower <= upper;
1215
1458
  };
1216
1459
  var searchAiPockets = (rows, options = {}) => {
1217
- const minSupport = Math.max(1, Math.trunc(options.minSupport ?? 20));
1460
+ const cadenceProfileOption = options.cadenceProfile;
1461
+ const minSupport = Math.max(
1462
+ 1,
1463
+ Math.trunc(cadenceProfileOption?.minSupport ?? options.minSupport ?? 20)
1464
+ );
1218
1465
  const minProfitFactor = Math.max(0, options.minProfitFactor ?? 1.2);
1219
1466
  const minTotalProfit = options.minTotalProfit ?? 0;
1220
1467
  const minWinRate = Math.max(0, options.minWinRate ?? 0);
@@ -1232,17 +1479,27 @@ var searchAiPockets = (rows, options = {}) => {
1232
1479
  const testRows = options.testRows ?? [];
1233
1480
  const minValidationSupport = Math.max(
1234
1481
  0,
1235
- Math.trunc(options.minValidationSupport ?? 0)
1482
+ Math.trunc(
1483
+ cadenceProfileOption?.minValidationSupport ?? options.minValidationSupport ?? 0
1484
+ )
1485
+ );
1486
+ const minEvents = Math.max(
1487
+ 1,
1488
+ Math.trunc(cadenceProfileOption?.minEvents ?? options.minEvents ?? 1)
1236
1489
  );
1237
- const minEvents = Math.max(1, Math.trunc(options.minEvents ?? 1));
1238
1490
  const minValidationEvents = Math.max(
1239
1491
  0,
1240
- Math.trunc(options.minValidationEvents ?? 0)
1492
+ Math.trunc(
1493
+ cadenceProfileOption?.minValidationEvents ?? options.minValidationEvents ?? 0
1494
+ )
1241
1495
  );
1242
1496
  const maxBatch = Math.max(1, options.maxBatch ?? Number.POSITIVE_INFINITY);
1243
1497
  const maxEventCountShare = Math.max(
1244
1498
  0,
1245
- Math.min(1, options.maxEventCountShare ?? 1)
1499
+ Math.min(
1500
+ 1,
1501
+ cadenceProfileOption?.maxEventCountShare ?? options.maxEventCountShare ?? 1
1502
+ )
1246
1503
  );
1247
1504
  const maxSymbolCountShare = Math.max(
1248
1505
  0,
@@ -1260,18 +1517,59 @@ var searchAiPockets = (rows, options = {}) => {
1260
1517
  const allowRiskRegression = options.allowRiskRegression === true;
1261
1518
  const requireValidationEligibility = options.requireValidationEligibility === true;
1262
1519
  const dedupeEquivalentSelections = options.dedupeEquivalentSelections !== false;
1520
+ const requiredFeatureFamilies = [
1521
+ ...new Set(options.requiredFeatureFamilies ?? [])
1522
+ ];
1523
+ const excludedFeatureFamilies = [
1524
+ ...new Set(options.excludedFeatureFamilies ?? [])
1525
+ ];
1263
1526
  const progressInterval = Math.max(
1264
1527
  1,
1265
1528
  Math.trunc(options.progressInterval ?? 500)
1266
1529
  );
1267
1530
  const onProgress = options.onProgress;
1531
+ const summarizePartition = (partitionRows) => {
1532
+ const summary = summarizeAiPocketRows(partitionRows);
1533
+ const periodDays = getPeriodDays(partitionRows);
1534
+ return {
1535
+ rows: partitionRows.length,
1536
+ events: summary.events,
1537
+ periodDays,
1538
+ eventsPerDay: periodDays == null ? null : summary.events / periodDays
1539
+ };
1540
+ };
1541
+ const trainPartition = summarizePartition(rows);
1542
+ const validationPartition = summarizePartition(validationRows);
1543
+ const testPartition = summarizePartition(testRows);
1544
+ const cadenceProfile = cadenceProfileOption ?? {
1545
+ mode: "fixed",
1546
+ lowCadence: false,
1547
+ sparseSample: false,
1548
+ adaptiveThresholds: false,
1549
+ trainRows: trainPartition.rows,
1550
+ trainEvents: trainPartition.events,
1551
+ trainPeriodDays: trainPartition.periodDays,
1552
+ trainEventsPerDay: trainPartition.eventsPerDay,
1553
+ validationRows: validationPartition.rows,
1554
+ validationEvents: validationPartition.events,
1555
+ testRows: testPartition.rows,
1556
+ testEvents: testPartition.events,
1557
+ minSupport,
1558
+ minEvents,
1559
+ minValidationSupport,
1560
+ minValidationEvents,
1561
+ maxEventCountShare
1562
+ };
1268
1563
  const predicateResult = buildAiPocketPredicateResult(rows, {
1269
1564
  minSupport,
1270
1565
  maxCategories: options.maxCategories,
1271
1566
  progressInterval,
1272
1567
  onProgress
1273
1568
  });
1274
- const { predicates } = predicateResult;
1569
+ const predicates = predicateResult.predicates.filter((predicate) => {
1570
+ const family = classifyAiPocketCoverageFeaturePath(predicate.featureKey);
1571
+ return family == null || !excludedFeatureFamilies.includes(family);
1572
+ });
1275
1573
  let lastMaskProgress = 0;
1276
1574
  const emitMaskProgress = (current, done = false) => {
1277
1575
  if (!onProgress) {
@@ -1303,10 +1601,15 @@ var searchAiPockets = (rows, options = {}) => {
1303
1601
  [...predicatePool, ...negativePool, ...supportPool].forEach((predicate) => {
1304
1602
  predicatePoolById.set(predicate.id, predicate);
1305
1603
  });
1306
- const diversifiedPredicates = diversifyPredicatePool(
1307
- [...predicatePoolById.values()],
1308
- maxAtomicPredicates
1309
- );
1604
+ const diversifiedPredicates = prioritizeRequiredFeatureFamilyPredicates({
1605
+ selected: diversifyPredicatePool(
1606
+ [...predicatePoolById.values()],
1607
+ maxAtomicPredicates
1608
+ ),
1609
+ predicates: scoredPredicates,
1610
+ requiredFeatureFamilies,
1611
+ maximum: maxAtomicPredicates
1612
+ });
1310
1613
  const selectedPredicatePool = diversifiedPredicates.map(
1311
1614
  (predicate) => {
1312
1615
  const { mask, support } = buildMask(rows, predicate);
@@ -1347,6 +1650,13 @@ var searchAiPockets = (rows, options = {}) => {
1347
1650
  });
1348
1651
  };
1349
1652
  const addPocket = (pocketPredicates, mask) => {
1653
+ if (requiredFeatureFamilies.some(
1654
+ (family) => !pocketPredicates.some(
1655
+ (predicate) => classifyAiPocketCoverageFeaturePath(predicate.featureKey) === family
1656
+ )
1657
+ )) {
1658
+ return;
1659
+ }
1350
1660
  const pocket = createPocketResult(
1351
1661
  rows,
1352
1662
  pocketPredicates,
@@ -1464,7 +1774,7 @@ var searchAiPockets = (rows, options = {}) => {
1464
1774
  negativePockets: [...negativePockets.values()].sort(compareNegativePockets).slice(0, top),
1465
1775
  stats: {
1466
1776
  rows: rows.length,
1467
- featureKeys: predicateResult.featureKeys,
1777
+ featureKeys: new Set(predicates.map((predicate) => predicate.featureKey)).size,
1468
1778
  predicates: predicates.length,
1469
1779
  atomicPredicatesUsed: selectedPredicatePool.length,
1470
1780
  estimatedCombinations,
@@ -1479,6 +1789,9 @@ var searchAiPockets = (rows, options = {}) => {
1479
1789
  )
1480
1790
  )
1481
1791
  ],
1792
+ requiredFeatureFamilies,
1793
+ excludedFeatureFamilies,
1794
+ cadence: cadenceProfile,
1482
1795
  truncated
1483
1796
  }
1484
1797
  };
@@ -1540,16 +1853,20 @@ var pocketRows = (pockets) => pockets.map((pocket, index) => [
1540
1853
  pocket.objectiveSummary ? formatMdNumber(pocket.objectiveSummary.totalProfit) : "n/a",
1541
1854
  formatMdNumber(pocket.summary.maxDrawdown),
1542
1855
  pocket.validationSummary?.support ?? "n/a",
1856
+ pocket.validationSummary?.events ?? "n/a",
1543
1857
  pocket.validationSummary ? formatMdPercent(pocket.validationSummary.winRate) : "n/a",
1544
1858
  pocket.validationSummary ? formatMdNumber(pocket.validationSummary.profitFactor) : "n/a",
1545
1859
  pocket.validationSummary ? formatMdNumber(pocket.validationSummary.totalProfit) : "n/a",
1546
1860
  pocket.testSummary?.support ?? "n/a",
1861
+ pocket.testSummary?.events ?? "n/a",
1547
1862
  pocket.testSummary ? formatMdNumber(pocket.testSummary.profitFactor) : "n/a",
1548
1863
  pocket.testSummary ? formatMdNumber(pocket.testSummary.totalProfit) : "n/a",
1549
1864
  pocket.testObjectiveSummary ? formatMdNumber(pocket.testObjectiveSummary.totalProfit) : "n/a",
1550
1865
  formatMdNumber(pocket.summary.avgTradesPerDay),
1551
1866
  pocket.summary.losingMonths,
1552
1867
  formatMdNumber(pocket.score),
1868
+ pocket.readiness,
1869
+ pocket.readinessReasons.join("; ") || "none",
1553
1870
  pocket.condition
1554
1871
  ]);
1555
1872
  var buildAiPocketMarkdownReport = ({
@@ -1557,6 +1874,7 @@ var buildAiPocketMarkdownReport = ({
1557
1874
  run,
1558
1875
  currentGate,
1559
1876
  pocketSearch,
1877
+ coverageSearches = [],
1560
1878
  errors
1561
1879
  }) => {
1562
1880
  const generatedIso = new Date(generatedAt).toISOString();
@@ -1567,6 +1885,34 @@ var buildAiPocketMarkdownReport = ({
1567
1885
  audit?.paths ?? 0,
1568
1886
  audit?.samples.join(", ") ?? ""
1569
1887
  ]);
1888
+ const pocketTableHeaders = [
1889
+ "#",
1890
+ "N",
1891
+ "Events",
1892
+ "Max batch",
1893
+ "Support",
1894
+ "WR",
1895
+ "PF",
1896
+ "PNL",
1897
+ "Objective PNL",
1898
+ "Max DD",
1899
+ "Val N",
1900
+ "Val Events",
1901
+ "Val WR",
1902
+ "Val PF",
1903
+ "Val PNL",
1904
+ "Test N",
1905
+ "Test Events",
1906
+ "Test PF",
1907
+ "Test PNL",
1908
+ "Test objective PNL",
1909
+ "Trades/Day",
1910
+ "Losing Months",
1911
+ "Score",
1912
+ "Readiness",
1913
+ "Readiness reasons",
1914
+ "Pocket"
1915
+ ];
1570
1916
  const lines = [
1571
1917
  "# AI Pocket Search Report",
1572
1918
  "",
@@ -1590,6 +1936,17 @@ var buildAiPocketMarkdownReport = ({
1590
1936
  ["test_rows", run.testRows],
1591
1937
  ["validation_split", formatMdPercent(run.validationSplit)],
1592
1938
  ["test_split", formatMdPercent(run.testSplit)],
1939
+ ["test_sealed", run.sealedTest?.sealed ? "yes" : "no"],
1940
+ ["sealed_test_rows", run.sealedTest?.rows ?? run.testRows],
1941
+ ["sealed_test_events", run.sealedTest?.events ?? "n/a"],
1942
+ [
1943
+ "sealed_test_start",
1944
+ run.sealedTest?.startTimestamp == null ? "n/a" : new Date(run.sealedTest.startTimestamp).toISOString()
1945
+ ],
1946
+ [
1947
+ "sealed_test_end",
1948
+ run.sealedTest?.endTimestamp == null ? "n/a" : new Date(run.sealedTest.endTimestamp).toISOString()
1949
+ ],
1593
1950
  ["min_validation_support", run.minValidationSupport],
1594
1951
  ["failed", run.failed],
1595
1952
  ["recent", run.recent === 0 ? "all" : run.recent],
@@ -1631,6 +1988,22 @@ var buildAiPocketMarkdownReport = ({
1631
1988
  ["include_gate_context", run.includeGateContext ? "on" : "off"],
1632
1989
  ["feature_profile", run.featureProfile ?? "all"],
1633
1990
  ["feature_policy", run.featurePolicy ?? "all"],
1991
+ ["coverage_mode", run.coverageMode ?? "full"],
1992
+ ["cadence_mode", run.cadenceMode ?? "fixed"],
1993
+ ["low_cadence", pocketSearch.stats.cadence.lowCadence ? "yes" : "no"],
1994
+ [
1995
+ "sparse_sample",
1996
+ pocketSearch.stats.cadence.sparseSample ? "yes" : "no"
1997
+ ],
1998
+ [
1999
+ "adaptive_thresholds",
2000
+ pocketSearch.stats.cadence.adaptiveThresholds ? "on" : "off"
2001
+ ],
2002
+ ["train_events", pocketSearch.stats.cadence.trainEvents],
2003
+ [
2004
+ "train_events_per_day",
2005
+ formatMdNumber(pocketSearch.stats.cadence.trainEventsPerDay, 4)
2006
+ ],
1634
2007
  ["report_path", run.reportPath]
1635
2008
  ]
1636
2009
  ),
@@ -1716,10 +2089,32 @@ var buildAiPocketMarkdownReport = ({
1716
2089
  ["predicates", pocketSearch.stats.predicates],
1717
2090
  ["atomic_used", pocketSearch.stats.atomicPredicatesUsed],
1718
2091
  ["feature_families", pocketSearch.stats.featureFamiliesUsed.join(", ")],
2092
+ [
2093
+ "required_feature_families",
2094
+ pocketSearch.stats.requiredFeatureFamilies.join(", ") || "none"
2095
+ ],
2096
+ [
2097
+ "excluded_feature_families",
2098
+ pocketSearch.stats.excludedFeatureFamilies.join(", ") || "none"
2099
+ ],
1719
2100
  ["estimated_combinations", pocketSearch.stats.estimatedCombinations],
1720
2101
  ["combinations_evaluated", pocketSearch.stats.combinationsEvaluated],
1721
2102
  ["validation_rows", pocketSearch.stats.validationRows],
1722
2103
  ["test_rows", pocketSearch.stats.testRows],
2104
+ ["effective_min_support", pocketSearch.stats.cadence.minSupport],
2105
+ ["effective_min_events", pocketSearch.stats.cadence.minEvents],
2106
+ [
2107
+ "effective_min_validation_support",
2108
+ pocketSearch.stats.cadence.minValidationSupport
2109
+ ],
2110
+ [
2111
+ "effective_min_validation_events",
2112
+ pocketSearch.stats.cadence.minValidationEvents
2113
+ ],
2114
+ [
2115
+ "effective_max_event_count_share",
2116
+ formatMdPercent(pocketSearch.stats.cadence.maxEventCountShare)
2117
+ ],
1723
2118
  [
1724
2119
  "duplicate_pockets_skipped",
1725
2120
  pocketSearch.stats.duplicatePocketsSkipped
@@ -1730,65 +2125,98 @@ var buildAiPocketMarkdownReport = ({
1730
2125
  "",
1731
2126
  "## Top Positive Pockets",
1732
2127
  "",
1733
- markdownTable(
1734
- [
1735
- "#",
1736
- "N",
1737
- "Events",
1738
- "Max batch",
1739
- "Support",
1740
- "WR",
1741
- "PF",
1742
- "PNL",
1743
- "Objective PNL",
1744
- "Max DD",
1745
- "Val N",
1746
- "Val WR",
1747
- "Val PF",
1748
- "Val PNL",
1749
- "Test N",
1750
- "Test PF",
1751
- "Test PNL",
1752
- "Test objective PNL",
1753
- "Trades/Day",
1754
- "Losing Months",
1755
- "Score",
1756
- "Pocket"
1757
- ],
1758
- pocketRows(pocketSearch.positivePockets)
1759
- ),
2128
+ markdownTable(pocketTableHeaders, pocketRows(pocketSearch.positivePockets)),
1760
2129
  "",
1761
2130
  "## Top Loss Pockets",
1762
2131
  "",
1763
- markdownTable(
1764
- [
1765
- "#",
1766
- "N",
1767
- "Events",
1768
- "Max batch",
1769
- "Support",
1770
- "WR",
1771
- "PF",
1772
- "PNL",
1773
- "Objective PNL",
1774
- "Max DD",
1775
- "Val N",
1776
- "Val WR",
1777
- "Val PF",
1778
- "Val PNL",
1779
- "Test N",
1780
- "Test PF",
1781
- "Test PNL",
1782
- "Test objective PNL",
1783
- "Trades/Day",
1784
- "Losing Months",
1785
- "Score",
1786
- "Pocket"
1787
- ],
1788
- pocketRows(pocketSearch.negativePockets)
1789
- ),
2132
+ markdownTable(pocketTableHeaders, pocketRows(pocketSearch.negativePockets)),
1790
2133
  ""
1791
2134
  ];
2135
+ for (const cohort of coverageSearches) {
2136
+ const title = cohort.family === "cmc" ? "CMC" : "Coinalyze";
2137
+ lines.push(
2138
+ `## Coverage Cohort: ${title}`,
2139
+ "",
2140
+ markdownTable(
2141
+ ["Field", "Value"],
2142
+ [
2143
+ ["coverage_rows", cohort.coverage.rows],
2144
+ ["coverage_row_ratio", formatMdPercent(cohort.coverage.rowRatio)],
2145
+ ["coverage_events", cohort.coverage.events],
2146
+ ["coverage_event_ratio", formatMdPercent(cohort.coverage.eventRatio)],
2147
+ [
2148
+ "coverage_from",
2149
+ cohort.coverage.minTimestamp == null ? "n/a" : new Date(cohort.coverage.minTimestamp).toISOString()
2150
+ ],
2151
+ [
2152
+ "coverage_to",
2153
+ cohort.coverage.maxTimestamp == null ? "n/a" : new Date(cohort.coverage.maxTimestamp).toISOString()
2154
+ ],
2155
+ ["scope_rows", cohort.scopeRows],
2156
+ ["train_rows", cohort.trainRows],
2157
+ ["validation_rows", cohort.validationRows],
2158
+ ["test_rows", cohort.testRows],
2159
+ [
2160
+ "low_cadence",
2161
+ cohort.search.stats.cadence.lowCadence ? "yes" : "no"
2162
+ ],
2163
+ [
2164
+ "sparse_sample",
2165
+ cohort.search.stats.cadence.sparseSample ? "yes" : "no"
2166
+ ],
2167
+ [
2168
+ "adaptive_thresholds",
2169
+ cohort.search.stats.cadence.adaptiveThresholds ? "on" : "off"
2170
+ ],
2171
+ ["train_events", cohort.search.stats.cadence.trainEvents],
2172
+ [
2173
+ "train_events_per_day",
2174
+ formatMdNumber(cohort.search.stats.cadence.trainEventsPerDay, 4)
2175
+ ],
2176
+ ["effective_min_support", cohort.search.stats.cadence.minSupport],
2177
+ ["effective_min_events", cohort.search.stats.cadence.minEvents],
2178
+ [
2179
+ "effective_min_validation_support",
2180
+ cohort.search.stats.cadence.minValidationSupport
2181
+ ],
2182
+ [
2183
+ "effective_min_validation_events",
2184
+ cohort.search.stats.cadence.minValidationEvents
2185
+ ],
2186
+ [
2187
+ "effective_max_event_count_share",
2188
+ formatMdPercent(cohort.search.stats.cadence.maxEventCountShare)
2189
+ ],
2190
+ [
2191
+ "required_feature_families",
2192
+ cohort.search.stats.requiredFeatureFamilies.join(", ") || "none"
2193
+ ]
2194
+ ]
2195
+ ),
2196
+ "",
2197
+ `### ${title} Train Baseline`,
2198
+ "",
2199
+ markdownTable(
2200
+ ["Metric", "Value"],
2201
+ summaryMetricRows(cohort.search.baseline)
2202
+ ),
2203
+ "",
2204
+ `### ${title} Top Positive Pockets`,
2205
+ "",
2206
+ markdownTable(
2207
+ pocketTableHeaders,
2208
+ pocketRows(cohort.search.positivePockets)
2209
+ ),
2210
+ "",
2211
+ `### ${title} Top Loss Pockets`,
2212
+ "",
2213
+ markdownTable(
2214
+ pocketTableHeaders,
2215
+ pocketRows(cohort.search.negativePockets)
2216
+ ),
2217
+ ""
2218
+ );
2219
+ }
1792
2220
  if (errors.length) {
1793
2221
  lines.push("## Errors", "", ...errors.map((error) => `- ${error}`), "");
1794
2222
  }
@@ -1799,8 +2227,12 @@ var buildAiPocketMarkdownReport = ({
1799
2227
  0 && (module.exports = {
1800
2228
  buildAiPocketMarkdownReport,
1801
2229
  buildAiPocketPredicates,
2230
+ classifyAiPocketCoverageFeaturePath,
1802
2231
  classifyAiPocketFeaturePath,
2232
+ collectAiPocketFeatureSnapshot,
1803
2233
  collectAiPocketFeatures,
2234
+ resolveAiPocketFeatureCoverage,
1804
2235
  searchAiPockets,
2236
+ summarizeAiPocketFeatureCoverage,
1805
2237
  summarizeAiPocketRows
1806
2238
  });