@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.
@@ -414,6 +414,7 @@ var resolveAiTrainRecentLimit = ({
414
414
  var DAY_MS2 = 24 * 60 * 60 * 1e3;
415
415
  var DAYS_PER_WEEK2 = 7;
416
416
  var DAYS_PER_MONTH2 = 30.4375;
417
+ var PRODUCTION_CANDIDATE_MIN_EVENTS = 25;
417
418
  var OUTCOME_SEGMENTS = /* @__PURE__ */ new Set([
418
419
  "actual",
419
420
  "aiapproved",
@@ -499,6 +500,146 @@ var METADATA_LEAVES = /* @__PURE__ */ new Set([
499
500
  "targetsymbol",
500
501
  "universe"
501
502
  ]);
503
+ var normalizeFeaturePath = (path2) => (Array.isArray(path2) ? path2 : path2.split(".")).filter(Boolean).map((segment) => segment.toLowerCase());
504
+ var classifyAiPocketCoverageFeaturePath = (path2) => {
505
+ const segments = normalizeFeaturePath(path2);
506
+ const normalizedPath = segments.join(".");
507
+ if (segments.some(
508
+ (segment) => segment.includes("cmc") || segment.includes("coinmarketcap")
509
+ )) {
510
+ return "cmc";
511
+ }
512
+ if (segments.some(
513
+ (segment) => segment.includes("derivative") || segment.includes("coinalyze")
514
+ ) || /(?:^|\.)(?:funding|openinterest|oi(?:change|acceleration|divergence)|priceoi|liq(?:long|short|total|imbalance|spike)|liquidation)/.test(
515
+ normalizedPath
516
+ )) {
517
+ return "coinalyze";
518
+ }
519
+ return null;
520
+ };
521
+ var hasUsableContextPrimitive = (value) => {
522
+ if (Array.isArray(value) || value == null) {
523
+ return false;
524
+ }
525
+ if (isFeaturePrimitive(value)) {
526
+ return typeof value !== "string" || value.toLowerCase() !== "unknown";
527
+ }
528
+ if (!isPlainRecord(value)) {
529
+ return false;
530
+ }
531
+ return Object.entries(value).some(([key, child]) => {
532
+ const normalizedKey = key.toLowerCase();
533
+ if (DATA_QUALITY_LEAVES.has(normalizedKey) || METADATA_LEAVES.has(normalizedKey) || normalizedKey === "riskflags" || normalizedKey.endsWith("timestamp") || normalizedKey.endsWith("agems")) {
534
+ return false;
535
+ }
536
+ return hasUsableContextPrimitive(child);
537
+ });
538
+ };
539
+ var isUsableCmcContext = (value) => isPlainRecord(value) && value.available !== false && value.stale !== true && hasUsableContextPrimitive(value);
540
+ var hasUsableCmcContext = (baseContext) => {
541
+ const relative = isPlainRecord(baseContext?.relative) ? baseContext.relative : null;
542
+ return Boolean(
543
+ relative && Object.entries(relative).some(
544
+ ([key, value]) => key.toLowerCase().startsWith("cmc") && isUsableCmcContext(value)
545
+ )
546
+ );
547
+ };
548
+ var isUsableDerivativesContext = (context) => {
549
+ if (!context) {
550
+ return false;
551
+ }
552
+ const summary = isPlainRecord(context.summary) ? context.summary : null;
553
+ const riskFlags = Array.isArray(summary?.riskFlags) ? summary.riskFlags.filter(
554
+ (flag) => typeof flag === "string"
555
+ ) : [];
556
+ if (riskFlags.includes("missing_derivatives") || riskFlags.includes("stale_derivatives")) {
557
+ return false;
558
+ }
559
+ const intervals = isPlainRecord(context.intervals) ? context.intervals : null;
560
+ return Boolean(
561
+ intervals && Object.values(intervals).some(
562
+ (interval) => isPlainRecord(interval) && interval.stale !== true && hasUsableContextPrimitive(interval)
563
+ )
564
+ );
565
+ };
566
+ var hasUsableCoinalyzeContext = (baseContext) => {
567
+ const derivatives = isPlainRecord(baseContext?.derivatives) ? baseContext.derivatives : null;
568
+ if (!derivatives) {
569
+ return false;
570
+ }
571
+ if (isUsableDerivativesContext(derivatives)) {
572
+ return true;
573
+ }
574
+ const targetContext = isPlainRecord(derivatives.targetContext) ? derivatives.targetContext : null;
575
+ if (isUsableDerivativesContext(targetContext)) {
576
+ return true;
577
+ }
578
+ const referenceContexts = isPlainRecord(derivatives.referenceContexts) ? derivatives.referenceContexts : null;
579
+ return Boolean(
580
+ referenceContexts && Object.values(referenceContexts).some(
581
+ (context) => isUsableDerivativesContext(isPlainRecord(context) ? context : null)
582
+ )
583
+ );
584
+ };
585
+ var resolveAiPocketFeatureCoverage = (payload) => {
586
+ const additionalIndicators = isPlainRecord(payload.additionalIndicators) ? payload.additionalIndicators : null;
587
+ const baseContext = isPlainRecord(additionalIndicators?.baseContext) ? additionalIndicators.baseContext : null;
588
+ return {
589
+ cmc: hasUsableCmcContext(baseContext),
590
+ coinalyze: hasUsableCoinalyzeContext(baseContext)
591
+ };
592
+ };
593
+ var isAiPocketCoverageFeatureUsable = ({
594
+ payload,
595
+ segments,
596
+ family,
597
+ familyCoverage
598
+ }) => {
599
+ const additionalIndicators = isPlainRecord(payload.additionalIndicators) ? payload.additionalIndicators : null;
600
+ const baseContext = isPlainRecord(additionalIndicators?.baseContext) ? additionalIndicators.baseContext : null;
601
+ const baseContextIndex = segments.findIndex(
602
+ (segment) => segment.toLowerCase() === "basecontext"
603
+ );
604
+ if (family === "cmc") {
605
+ const relative = isPlainRecord(baseContext?.relative) ? baseContext.relative : null;
606
+ if (relative && baseContextIndex >= 0 && segments[baseContextIndex + 1]?.toLowerCase() === "relative") {
607
+ const contextKey = segments[baseContextIndex + 2];
608
+ if (contextKey?.toLowerCase().startsWith("cmc")) {
609
+ return isUsableCmcContext(relative[contextKey]);
610
+ }
611
+ }
612
+ return familyCoverage.cmc;
613
+ }
614
+ const derivatives = isPlainRecord(baseContext?.derivatives) ? baseContext.derivatives : null;
615
+ if (!derivatives || baseContextIndex < 0 || segments[baseContextIndex + 1]?.toLowerCase() !== "derivatives") {
616
+ return isUsableDerivativesContext(derivatives);
617
+ }
618
+ const branch = segments[baseContextIndex + 2]?.toLowerCase();
619
+ if (branch == null) {
620
+ return familyCoverage.coinalyze;
621
+ }
622
+ if (branch === "referencecontexts") {
623
+ const referenceContexts = isPlainRecord(derivatives.referenceContexts) ? derivatives.referenceContexts : null;
624
+ const symbol = segments[baseContextIndex + 3];
625
+ if (!symbol) {
626
+ return Boolean(
627
+ referenceContexts && Object.values(referenceContexts).some(
628
+ (context) => isUsableDerivativesContext(isPlainRecord(context) ? context : null)
629
+ )
630
+ );
631
+ }
632
+ return isUsableDerivativesContext(
633
+ symbol && referenceContexts && isPlainRecord(referenceContexts[symbol]) ? referenceContexts[symbol] : null
634
+ );
635
+ }
636
+ if (branch === "targetcontext" || branch === "targetderived") {
637
+ return isUsableDerivativesContext(
638
+ isPlainRecord(derivatives.targetContext) ? derivatives.targetContext : null
639
+ );
640
+ }
641
+ return isUsableDerivativesContext(derivatives);
642
+ };
502
643
  var DERIVED_POLICY_FRAGMENTS = [
503
644
  "approval",
504
645
  "decisionhint",
@@ -837,7 +978,7 @@ var addSignalRiskDistanceFeatures = ({
837
978
  features["derived.takeProfitDistanceBps"] = Math.abs(takeProfitPrice - currentPrice) / Math.abs(currentPrice) * 1e4;
838
979
  }
839
980
  };
840
- var collectAiPocketFeatures = ({
981
+ var collectAiPocketFeatureSnapshot = ({
841
982
  payload,
842
983
  gateContext,
843
984
  includeSymbol = false,
@@ -847,6 +988,7 @@ var collectAiPocketFeatures = ({
847
988
  onFeatureExcluded
848
989
  }) => {
849
990
  const features = {};
991
+ const featureCoverage = resolveAiPocketFeatureCoverage(payload);
850
992
  const signal = payload.signal ?? {};
851
993
  const source = {
852
994
  signal: {
@@ -865,6 +1007,19 @@ var collectAiPocketFeatures = ({
865
1007
  segments: [],
866
1008
  maxDepth: 8,
867
1009
  shouldSkipPath: (segments) => {
1010
+ const coverageFamily = classifyAiPocketCoverageFeaturePath(segments);
1011
+ if (featurePolicy === "causal-stationary" && coverageFamily != null && !isAiPocketCoverageFeatureUsable({
1012
+ payload,
1013
+ segments,
1014
+ family: coverageFamily,
1015
+ familyCoverage: featureCoverage
1016
+ })) {
1017
+ onFeatureExcluded?.({
1018
+ path: segments.join("."),
1019
+ classification: "data-quality"
1020
+ });
1021
+ return true;
1022
+ }
868
1023
  if (featureProfile === "compact" && isCompactFeaturePathSkipped(segments)) {
869
1024
  return true;
870
1025
  }
@@ -895,7 +1050,7 @@ var collectAiPocketFeatures = ({
895
1050
  }
896
1051
  addSignalRiskDistanceFeatures({ features, signal });
897
1052
  addDirectionalDerivedFeatures(features);
898
- return features;
1053
+ return { features, featureCoverage };
899
1054
  };
900
1055
  var formatNumber = (value) => {
901
1056
  if (Number.isInteger(value)) {
@@ -1162,6 +1317,37 @@ var summarizeRowIndexes = (rows, rowIndexes) => {
1162
1317
  return finalizeAiPocketSummary({ rows, accumulator });
1163
1318
  };
1164
1319
  var summarizeAiPocketRows = (rows) => summarizeSelectedRows(rows, rows);
1320
+ var summarizeAiPocketFeatureCoverage = (rows, family) => {
1321
+ const coveredRows = rows.filter(
1322
+ (row) => row.featureCoverage?.[family] === true
1323
+ );
1324
+ const allEvents = /* @__PURE__ */ new Set();
1325
+ const coveredEvents = /* @__PURE__ */ new Set();
1326
+ let minTimestamp = null;
1327
+ let maxTimestamp = null;
1328
+ for (const row of rows) {
1329
+ if (isFiniteNumber(row.timestamp)) {
1330
+ allEvents.add(row.timestamp);
1331
+ }
1332
+ }
1333
+ for (const row of coveredRows) {
1334
+ if (!isFiniteNumber(row.timestamp)) {
1335
+ continue;
1336
+ }
1337
+ coveredEvents.add(row.timestamp);
1338
+ minTimestamp = minTimestamp == null ? row.timestamp : Math.min(minTimestamp, row.timestamp);
1339
+ maxTimestamp = maxTimestamp == null ? row.timestamp : Math.max(maxTimestamp, row.timestamp);
1340
+ }
1341
+ return {
1342
+ family,
1343
+ rows: coveredRows.length,
1344
+ rowRatio: rows.length > 0 ? coveredRows.length / rows.length : 0,
1345
+ events: coveredEvents.size,
1346
+ eventRatio: allEvents.size > 0 ? coveredEvents.size / allEvents.size : 0,
1347
+ minTimestamp,
1348
+ maxTimestamp
1349
+ };
1350
+ };
1165
1351
  var matchesPredicate = (value, predicate) => {
1166
1352
  if (predicate.kind === "numeric") {
1167
1353
  if (!isFiniteNumber(value)) {
@@ -1474,6 +1660,19 @@ var createPocketResult = (rows, predicates, mask, validationRows, testRows, obje
1474
1660
  const condition = publicPredicates.map((predicate) => predicate.label).join(" AND ");
1475
1661
  const validationScore = validationSummary == null || validationObjectiveSummary == null ? void 0 : validationSummary.support > 0 ? scorePositivePocket(validationObjectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(validationObjectiveBaseline)) : Number.NEGATIVE_INFINITY;
1476
1662
  const testScore = testSummary == null || testObjectiveSummary == null ? void 0 : testSummary.support > 0 ? scorePositivePocket(testObjectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(testObjectiveBaseline)) : Number.NEGATIVE_INFINITY;
1663
+ const readinessReasons = [];
1664
+ if (summary.events < PRODUCTION_CANDIDATE_MIN_EVENTS) {
1665
+ readinessReasons.push(
1666
+ `train events ${summary.events} < ${PRODUCTION_CANDIDATE_MIN_EVENTS}`
1667
+ );
1668
+ }
1669
+ if (testSummary == null) {
1670
+ readinessReasons.push("untouched test missing");
1671
+ } else if (testSummary.events < PRODUCTION_CANDIDATE_MIN_EVENTS) {
1672
+ readinessReasons.push(
1673
+ `test events ${testSummary.events} < ${PRODUCTION_CANDIDATE_MIN_EVENTS}`
1674
+ );
1675
+ }
1477
1676
  return {
1478
1677
  id: publicPredicates.map((predicate) => predicate.id).join("&&"),
1479
1678
  depth: publicPredicates.length,
@@ -1487,6 +1686,8 @@ var createPocketResult = (rows, predicates, mask, validationRows, testRows, obje
1487
1686
  ...objective !== "standalone" && testObjectiveSummary ? { testObjectiveSummary } : {},
1488
1687
  ...validationScore != null ? { validationScore } : {},
1489
1688
  ...testScore != null ? { testScore } : {},
1689
+ readiness: readinessReasons.length === 0 ? "production-candidate" : "research-only",
1690
+ readinessReasons,
1490
1691
  score: scorePositivePocket(objectiveSummary) - (objective === "standalone" ? 0 : scorePositivePocket(objectiveBaseline))
1491
1692
  };
1492
1693
  };
@@ -1581,6 +1782,43 @@ var diversifyPredicatePool = (predicates, maximum) => {
1581
1782
  }
1582
1783
  return selected;
1583
1784
  };
1785
+ var prioritizeRequiredFeatureFamilyPredicates = ({
1786
+ selected,
1787
+ predicates,
1788
+ requiredFeatureFamilies,
1789
+ maximum
1790
+ }) => {
1791
+ if (!requiredFeatureFamilies.length) {
1792
+ return selected;
1793
+ }
1794
+ const perFamilyLimit = Math.max(
1795
+ 6,
1796
+ Math.floor(maximum * 0.2 / requiredFeatureFamilies.length)
1797
+ );
1798
+ const prioritized = /* @__PURE__ */ new Map();
1799
+ for (const family of requiredFeatureFamilies) {
1800
+ const familyPredicates = predicates.filter(
1801
+ (predicate) => classifyAiPocketCoverageFeaturePath(predicate.featureKey) === family
1802
+ );
1803
+ const positive = [...familyPredicates].sort(
1804
+ (left, right) => scorePositivePocket(right.atomSummary) - scorePositivePocket(left.atomSummary)
1805
+ ).slice(0, Math.ceil(perFamilyLimit * 0.5));
1806
+ const negative = [...familyPredicates].sort(
1807
+ (left, right) => scoreNegativePocket(right.atomSummary) - scoreNegativePocket(left.atomSummary)
1808
+ ).slice(0, Math.ceil(perFamilyLimit * 0.3));
1809
+ const broad = [...familyPredicates].sort((left, right) => right.support - left.support).slice(0, Math.ceil(perFamilyLimit * 0.2));
1810
+ for (const predicate of [...positive, ...negative, ...broad]) {
1811
+ if (prioritized.size >= perFamilyLimit * requiredFeatureFamilies.length) {
1812
+ break;
1813
+ }
1814
+ prioritized.set(predicate.id, predicate);
1815
+ }
1816
+ }
1817
+ for (const predicate of selected) {
1818
+ prioritized.set(predicate.id, predicate);
1819
+ }
1820
+ return [...prioritized.values()].slice(0, maximum);
1821
+ };
1584
1822
  var canCombinePredicate = (chosen, candidate) => {
1585
1823
  const sameFeature = chosen.filter(
1586
1824
  (predicate) => predicate.featureKey === candidate.featureKey
@@ -1596,7 +1834,11 @@ var canCombinePredicate = (chosen, candidate) => {
1596
1834
  return lower <= upper;
1597
1835
  };
1598
1836
  var searchAiPockets = (rows, options = {}) => {
1599
- const minSupport = Math.max(1, Math.trunc(options.minSupport ?? 20));
1837
+ const cadenceProfileOption = options.cadenceProfile;
1838
+ const minSupport = Math.max(
1839
+ 1,
1840
+ Math.trunc(cadenceProfileOption?.minSupport ?? options.minSupport ?? 20)
1841
+ );
1600
1842
  const minProfitFactor = Math.max(0, options.minProfitFactor ?? 1.2);
1601
1843
  const minTotalProfit = options.minTotalProfit ?? 0;
1602
1844
  const minWinRate = Math.max(0, options.minWinRate ?? 0);
@@ -1614,17 +1856,27 @@ var searchAiPockets = (rows, options = {}) => {
1614
1856
  const testRows = options.testRows ?? [];
1615
1857
  const minValidationSupport = Math.max(
1616
1858
  0,
1617
- Math.trunc(options.minValidationSupport ?? 0)
1859
+ Math.trunc(
1860
+ cadenceProfileOption?.minValidationSupport ?? options.minValidationSupport ?? 0
1861
+ )
1862
+ );
1863
+ const minEvents = Math.max(
1864
+ 1,
1865
+ Math.trunc(cadenceProfileOption?.minEvents ?? options.minEvents ?? 1)
1618
1866
  );
1619
- const minEvents = Math.max(1, Math.trunc(options.minEvents ?? 1));
1620
1867
  const minValidationEvents = Math.max(
1621
1868
  0,
1622
- Math.trunc(options.minValidationEvents ?? 0)
1869
+ Math.trunc(
1870
+ cadenceProfileOption?.minValidationEvents ?? options.minValidationEvents ?? 0
1871
+ )
1623
1872
  );
1624
1873
  const maxBatch = Math.max(1, options.maxBatch ?? Number.POSITIVE_INFINITY);
1625
1874
  const maxEventCountShare = Math.max(
1626
1875
  0,
1627
- Math.min(1, options.maxEventCountShare ?? 1)
1876
+ Math.min(
1877
+ 1,
1878
+ cadenceProfileOption?.maxEventCountShare ?? options.maxEventCountShare ?? 1
1879
+ )
1628
1880
  );
1629
1881
  const maxSymbolCountShare = Math.max(
1630
1882
  0,
@@ -1642,18 +1894,59 @@ var searchAiPockets = (rows, options = {}) => {
1642
1894
  const allowRiskRegression = options.allowRiskRegression === true;
1643
1895
  const requireValidationEligibility = options.requireValidationEligibility === true;
1644
1896
  const dedupeEquivalentSelections = options.dedupeEquivalentSelections !== false;
1897
+ const requiredFeatureFamilies = [
1898
+ ...new Set(options.requiredFeatureFamilies ?? [])
1899
+ ];
1900
+ const excludedFeatureFamilies = [
1901
+ ...new Set(options.excludedFeatureFamilies ?? [])
1902
+ ];
1645
1903
  const progressInterval = Math.max(
1646
1904
  1,
1647
1905
  Math.trunc(options.progressInterval ?? 500)
1648
1906
  );
1649
1907
  const onProgress = options.onProgress;
1908
+ const summarizePartition = (partitionRows) => {
1909
+ const summary = summarizeAiPocketRows(partitionRows);
1910
+ const periodDays = getPeriodDays(partitionRows);
1911
+ return {
1912
+ rows: partitionRows.length,
1913
+ events: summary.events,
1914
+ periodDays,
1915
+ eventsPerDay: periodDays == null ? null : summary.events / periodDays
1916
+ };
1917
+ };
1918
+ const trainPartition = summarizePartition(rows);
1919
+ const validationPartition = summarizePartition(validationRows);
1920
+ const testPartition = summarizePartition(testRows);
1921
+ const cadenceProfile = cadenceProfileOption ?? {
1922
+ mode: "fixed",
1923
+ lowCadence: false,
1924
+ sparseSample: false,
1925
+ adaptiveThresholds: false,
1926
+ trainRows: trainPartition.rows,
1927
+ trainEvents: trainPartition.events,
1928
+ trainPeriodDays: trainPartition.periodDays,
1929
+ trainEventsPerDay: trainPartition.eventsPerDay,
1930
+ validationRows: validationPartition.rows,
1931
+ validationEvents: validationPartition.events,
1932
+ testRows: testPartition.rows,
1933
+ testEvents: testPartition.events,
1934
+ minSupport,
1935
+ minEvents,
1936
+ minValidationSupport,
1937
+ minValidationEvents,
1938
+ maxEventCountShare
1939
+ };
1650
1940
  const predicateResult = buildAiPocketPredicateResult(rows, {
1651
1941
  minSupport,
1652
1942
  maxCategories: options.maxCategories,
1653
1943
  progressInterval,
1654
1944
  onProgress
1655
1945
  });
1656
- const { predicates } = predicateResult;
1946
+ const predicates = predicateResult.predicates.filter((predicate) => {
1947
+ const family = classifyAiPocketCoverageFeaturePath(predicate.featureKey);
1948
+ return family == null || !excludedFeatureFamilies.includes(family);
1949
+ });
1657
1950
  let lastMaskProgress = 0;
1658
1951
  const emitMaskProgress = (current, done = false) => {
1659
1952
  if (!onProgress) {
@@ -1685,10 +1978,15 @@ var searchAiPockets = (rows, options = {}) => {
1685
1978
  [...predicatePool, ...negativePool, ...supportPool].forEach((predicate) => {
1686
1979
  predicatePoolById.set(predicate.id, predicate);
1687
1980
  });
1688
- const diversifiedPredicates = diversifyPredicatePool(
1689
- [...predicatePoolById.values()],
1690
- maxAtomicPredicates
1691
- );
1981
+ const diversifiedPredicates = prioritizeRequiredFeatureFamilyPredicates({
1982
+ selected: diversifyPredicatePool(
1983
+ [...predicatePoolById.values()],
1984
+ maxAtomicPredicates
1985
+ ),
1986
+ predicates: scoredPredicates,
1987
+ requiredFeatureFamilies,
1988
+ maximum: maxAtomicPredicates
1989
+ });
1692
1990
  const selectedPredicatePool = diversifiedPredicates.map(
1693
1991
  (predicate) => {
1694
1992
  const { mask, support } = buildMask(rows, predicate);
@@ -1729,6 +2027,13 @@ var searchAiPockets = (rows, options = {}) => {
1729
2027
  });
1730
2028
  };
1731
2029
  const addPocket = (pocketPredicates, mask) => {
2030
+ if (requiredFeatureFamilies.some(
2031
+ (family) => !pocketPredicates.some(
2032
+ (predicate) => classifyAiPocketCoverageFeaturePath(predicate.featureKey) === family
2033
+ )
2034
+ )) {
2035
+ return;
2036
+ }
1732
2037
  const pocket = createPocketResult(
1733
2038
  rows,
1734
2039
  pocketPredicates,
@@ -1846,7 +2151,7 @@ var searchAiPockets = (rows, options = {}) => {
1846
2151
  negativePockets: [...negativePockets.values()].sort(compareNegativePockets).slice(0, top),
1847
2152
  stats: {
1848
2153
  rows: rows.length,
1849
- featureKeys: predicateResult.featureKeys,
2154
+ featureKeys: new Set(predicates.map((predicate) => predicate.featureKey)).size,
1850
2155
  predicates: predicates.length,
1851
2156
  atomicPredicatesUsed: selectedPredicatePool.length,
1852
2157
  estimatedCombinations,
@@ -1861,6 +2166,9 @@ var searchAiPockets = (rows, options = {}) => {
1861
2166
  )
1862
2167
  )
1863
2168
  ],
2169
+ requiredFeatureFamilies,
2170
+ excludedFeatureFamilies,
2171
+ cadence: cadenceProfile,
1864
2172
  truncated
1865
2173
  }
1866
2174
  };
@@ -1922,16 +2230,20 @@ var pocketRows = (pockets) => pockets.map((pocket, index) => [
1922
2230
  pocket.objectiveSummary ? formatMdNumber(pocket.objectiveSummary.totalProfit) : "n/a",
1923
2231
  formatMdNumber(pocket.summary.maxDrawdown),
1924
2232
  pocket.validationSummary?.support ?? "n/a",
2233
+ pocket.validationSummary?.events ?? "n/a",
1925
2234
  pocket.validationSummary ? formatMdPercent(pocket.validationSummary.winRate) : "n/a",
1926
2235
  pocket.validationSummary ? formatMdNumber(pocket.validationSummary.profitFactor) : "n/a",
1927
2236
  pocket.validationSummary ? formatMdNumber(pocket.validationSummary.totalProfit) : "n/a",
1928
2237
  pocket.testSummary?.support ?? "n/a",
2238
+ pocket.testSummary?.events ?? "n/a",
1929
2239
  pocket.testSummary ? formatMdNumber(pocket.testSummary.profitFactor) : "n/a",
1930
2240
  pocket.testSummary ? formatMdNumber(pocket.testSummary.totalProfit) : "n/a",
1931
2241
  pocket.testObjectiveSummary ? formatMdNumber(pocket.testObjectiveSummary.totalProfit) : "n/a",
1932
2242
  formatMdNumber(pocket.summary.avgTradesPerDay),
1933
2243
  pocket.summary.losingMonths,
1934
2244
  formatMdNumber(pocket.score),
2245
+ pocket.readiness,
2246
+ pocket.readinessReasons.join("; ") || "none",
1935
2247
  pocket.condition
1936
2248
  ]);
1937
2249
  var buildAiPocketMarkdownReport = ({
@@ -1939,6 +2251,7 @@ var buildAiPocketMarkdownReport = ({
1939
2251
  run,
1940
2252
  currentGate,
1941
2253
  pocketSearch,
2254
+ coverageSearches = [],
1942
2255
  errors
1943
2256
  }) => {
1944
2257
  const generatedIso = new Date(generatedAt).toISOString();
@@ -1949,6 +2262,34 @@ var buildAiPocketMarkdownReport = ({
1949
2262
  audit?.paths ?? 0,
1950
2263
  audit?.samples.join(", ") ?? ""
1951
2264
  ]);
2265
+ const pocketTableHeaders = [
2266
+ "#",
2267
+ "N",
2268
+ "Events",
2269
+ "Max batch",
2270
+ "Support",
2271
+ "WR",
2272
+ "PF",
2273
+ "PNL",
2274
+ "Objective PNL",
2275
+ "Max DD",
2276
+ "Val N",
2277
+ "Val Events",
2278
+ "Val WR",
2279
+ "Val PF",
2280
+ "Val PNL",
2281
+ "Test N",
2282
+ "Test Events",
2283
+ "Test PF",
2284
+ "Test PNL",
2285
+ "Test objective PNL",
2286
+ "Trades/Day",
2287
+ "Losing Months",
2288
+ "Score",
2289
+ "Readiness",
2290
+ "Readiness reasons",
2291
+ "Pocket"
2292
+ ];
1952
2293
  const lines = [
1953
2294
  "# AI Pocket Search Report",
1954
2295
  "",
@@ -1972,6 +2313,17 @@ var buildAiPocketMarkdownReport = ({
1972
2313
  ["test_rows", run.testRows],
1973
2314
  ["validation_split", formatMdPercent(run.validationSplit)],
1974
2315
  ["test_split", formatMdPercent(run.testSplit)],
2316
+ ["test_sealed", run.sealedTest?.sealed ? "yes" : "no"],
2317
+ ["sealed_test_rows", run.sealedTest?.rows ?? run.testRows],
2318
+ ["sealed_test_events", run.sealedTest?.events ?? "n/a"],
2319
+ [
2320
+ "sealed_test_start",
2321
+ run.sealedTest?.startTimestamp == null ? "n/a" : new Date(run.sealedTest.startTimestamp).toISOString()
2322
+ ],
2323
+ [
2324
+ "sealed_test_end",
2325
+ run.sealedTest?.endTimestamp == null ? "n/a" : new Date(run.sealedTest.endTimestamp).toISOString()
2326
+ ],
1975
2327
  ["min_validation_support", run.minValidationSupport],
1976
2328
  ["failed", run.failed],
1977
2329
  ["recent", run.recent === 0 ? "all" : run.recent],
@@ -2013,6 +2365,22 @@ var buildAiPocketMarkdownReport = ({
2013
2365
  ["include_gate_context", run.includeGateContext ? "on" : "off"],
2014
2366
  ["feature_profile", run.featureProfile ?? "all"],
2015
2367
  ["feature_policy", run.featurePolicy ?? "all"],
2368
+ ["coverage_mode", run.coverageMode ?? "full"],
2369
+ ["cadence_mode", run.cadenceMode ?? "fixed"],
2370
+ ["low_cadence", pocketSearch.stats.cadence.lowCadence ? "yes" : "no"],
2371
+ [
2372
+ "sparse_sample",
2373
+ pocketSearch.stats.cadence.sparseSample ? "yes" : "no"
2374
+ ],
2375
+ [
2376
+ "adaptive_thresholds",
2377
+ pocketSearch.stats.cadence.adaptiveThresholds ? "on" : "off"
2378
+ ],
2379
+ ["train_events", pocketSearch.stats.cadence.trainEvents],
2380
+ [
2381
+ "train_events_per_day",
2382
+ formatMdNumber(pocketSearch.stats.cadence.trainEventsPerDay, 4)
2383
+ ],
2016
2384
  ["report_path", run.reportPath]
2017
2385
  ]
2018
2386
  ),
@@ -2098,10 +2466,32 @@ var buildAiPocketMarkdownReport = ({
2098
2466
  ["predicates", pocketSearch.stats.predicates],
2099
2467
  ["atomic_used", pocketSearch.stats.atomicPredicatesUsed],
2100
2468
  ["feature_families", pocketSearch.stats.featureFamiliesUsed.join(", ")],
2469
+ [
2470
+ "required_feature_families",
2471
+ pocketSearch.stats.requiredFeatureFamilies.join(", ") || "none"
2472
+ ],
2473
+ [
2474
+ "excluded_feature_families",
2475
+ pocketSearch.stats.excludedFeatureFamilies.join(", ") || "none"
2476
+ ],
2101
2477
  ["estimated_combinations", pocketSearch.stats.estimatedCombinations],
2102
2478
  ["combinations_evaluated", pocketSearch.stats.combinationsEvaluated],
2103
2479
  ["validation_rows", pocketSearch.stats.validationRows],
2104
2480
  ["test_rows", pocketSearch.stats.testRows],
2481
+ ["effective_min_support", pocketSearch.stats.cadence.minSupport],
2482
+ ["effective_min_events", pocketSearch.stats.cadence.minEvents],
2483
+ [
2484
+ "effective_min_validation_support",
2485
+ pocketSearch.stats.cadence.minValidationSupport
2486
+ ],
2487
+ [
2488
+ "effective_min_validation_events",
2489
+ pocketSearch.stats.cadence.minValidationEvents
2490
+ ],
2491
+ [
2492
+ "effective_max_event_count_share",
2493
+ formatMdPercent(pocketSearch.stats.cadence.maxEventCountShare)
2494
+ ],
2105
2495
  [
2106
2496
  "duplicate_pockets_skipped",
2107
2497
  pocketSearch.stats.duplicatePocketsSkipped
@@ -2112,65 +2502,98 @@ var buildAiPocketMarkdownReport = ({
2112
2502
  "",
2113
2503
  "## Top Positive Pockets",
2114
2504
  "",
2115
- markdownTable(
2116
- [
2117
- "#",
2118
- "N",
2119
- "Events",
2120
- "Max batch",
2121
- "Support",
2122
- "WR",
2123
- "PF",
2124
- "PNL",
2125
- "Objective PNL",
2126
- "Max DD",
2127
- "Val N",
2128
- "Val WR",
2129
- "Val PF",
2130
- "Val PNL",
2131
- "Test N",
2132
- "Test PF",
2133
- "Test PNL",
2134
- "Test objective PNL",
2135
- "Trades/Day",
2136
- "Losing Months",
2137
- "Score",
2138
- "Pocket"
2139
- ],
2140
- pocketRows(pocketSearch.positivePockets)
2141
- ),
2505
+ markdownTable(pocketTableHeaders, pocketRows(pocketSearch.positivePockets)),
2142
2506
  "",
2143
2507
  "## Top Loss Pockets",
2144
2508
  "",
2145
- markdownTable(
2146
- [
2147
- "#",
2148
- "N",
2149
- "Events",
2150
- "Max batch",
2151
- "Support",
2152
- "WR",
2153
- "PF",
2154
- "PNL",
2155
- "Objective PNL",
2156
- "Max DD",
2157
- "Val N",
2158
- "Val WR",
2159
- "Val PF",
2160
- "Val PNL",
2161
- "Test N",
2162
- "Test PF",
2163
- "Test PNL",
2164
- "Test objective PNL",
2165
- "Trades/Day",
2166
- "Losing Months",
2167
- "Score",
2168
- "Pocket"
2169
- ],
2170
- pocketRows(pocketSearch.negativePockets)
2171
- ),
2509
+ markdownTable(pocketTableHeaders, pocketRows(pocketSearch.negativePockets)),
2172
2510
  ""
2173
2511
  ];
2512
+ for (const cohort of coverageSearches) {
2513
+ const title = cohort.family === "cmc" ? "CMC" : "Coinalyze";
2514
+ lines.push(
2515
+ `## Coverage Cohort: ${title}`,
2516
+ "",
2517
+ markdownTable(
2518
+ ["Field", "Value"],
2519
+ [
2520
+ ["coverage_rows", cohort.coverage.rows],
2521
+ ["coverage_row_ratio", formatMdPercent(cohort.coverage.rowRatio)],
2522
+ ["coverage_events", cohort.coverage.events],
2523
+ ["coverage_event_ratio", formatMdPercent(cohort.coverage.eventRatio)],
2524
+ [
2525
+ "coverage_from",
2526
+ cohort.coverage.minTimestamp == null ? "n/a" : new Date(cohort.coverage.minTimestamp).toISOString()
2527
+ ],
2528
+ [
2529
+ "coverage_to",
2530
+ cohort.coverage.maxTimestamp == null ? "n/a" : new Date(cohort.coverage.maxTimestamp).toISOString()
2531
+ ],
2532
+ ["scope_rows", cohort.scopeRows],
2533
+ ["train_rows", cohort.trainRows],
2534
+ ["validation_rows", cohort.validationRows],
2535
+ ["test_rows", cohort.testRows],
2536
+ [
2537
+ "low_cadence",
2538
+ cohort.search.stats.cadence.lowCadence ? "yes" : "no"
2539
+ ],
2540
+ [
2541
+ "sparse_sample",
2542
+ cohort.search.stats.cadence.sparseSample ? "yes" : "no"
2543
+ ],
2544
+ [
2545
+ "adaptive_thresholds",
2546
+ cohort.search.stats.cadence.adaptiveThresholds ? "on" : "off"
2547
+ ],
2548
+ ["train_events", cohort.search.stats.cadence.trainEvents],
2549
+ [
2550
+ "train_events_per_day",
2551
+ formatMdNumber(cohort.search.stats.cadence.trainEventsPerDay, 4)
2552
+ ],
2553
+ ["effective_min_support", cohort.search.stats.cadence.minSupport],
2554
+ ["effective_min_events", cohort.search.stats.cadence.minEvents],
2555
+ [
2556
+ "effective_min_validation_support",
2557
+ cohort.search.stats.cadence.minValidationSupport
2558
+ ],
2559
+ [
2560
+ "effective_min_validation_events",
2561
+ cohort.search.stats.cadence.minValidationEvents
2562
+ ],
2563
+ [
2564
+ "effective_max_event_count_share",
2565
+ formatMdPercent(cohort.search.stats.cadence.maxEventCountShare)
2566
+ ],
2567
+ [
2568
+ "required_feature_families",
2569
+ cohort.search.stats.requiredFeatureFamilies.join(", ") || "none"
2570
+ ]
2571
+ ]
2572
+ ),
2573
+ "",
2574
+ `### ${title} Train Baseline`,
2575
+ "",
2576
+ markdownTable(
2577
+ ["Metric", "Value"],
2578
+ summaryMetricRows(cohort.search.baseline)
2579
+ ),
2580
+ "",
2581
+ `### ${title} Top Positive Pockets`,
2582
+ "",
2583
+ markdownTable(
2584
+ pocketTableHeaders,
2585
+ pocketRows(cohort.search.positivePockets)
2586
+ ),
2587
+ "",
2588
+ `### ${title} Top Loss Pockets`,
2589
+ "",
2590
+ markdownTable(
2591
+ pocketTableHeaders,
2592
+ pocketRows(cohort.search.negativePockets)
2593
+ ),
2594
+ ""
2595
+ );
2596
+ }
2174
2597
  if (errors.length) {
2175
2598
  lines.push("## Errors", "", ...errors.map((error) => `- ${error}`), "");
2176
2599
  }
@@ -2179,6 +2602,90 @@ var buildAiPocketMarkdownReport = ({
2179
2602
  };
2180
2603
 
2181
2604
  // src/lib/aiPocketSearchCli.ts
2605
+ var DAY_MS3 = 24 * 60 * 60 * 1e3;
2606
+ var DEFAULT_MIN_SUPPORT = 20;
2607
+ var LOW_CADENCE_EVENT_LIMIT = 200;
2608
+ var LOW_CADENCE_MAX_EVENT_SHARE = 1 / 3;
2609
+ var countIndependentEvents = (rows) => {
2610
+ const timestampEvents = /* @__PURE__ */ new Set();
2611
+ let rowsWithoutTimestamp = 0;
2612
+ for (const row of rows) {
2613
+ if (typeof row.timestamp === "number" && Number.isFinite(row.timestamp)) {
2614
+ timestampEvents.add(row.timestamp);
2615
+ } else {
2616
+ rowsWithoutTimestamp += 1;
2617
+ }
2618
+ }
2619
+ return timestampEvents.size + rowsWithoutTimestamp;
2620
+ };
2621
+ var getPeriodDays2 = (rows) => {
2622
+ let minTimestamp = null;
2623
+ let maxTimestamp = null;
2624
+ for (const row of rows) {
2625
+ const timestamp = row.timestamp;
2626
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
2627
+ continue;
2628
+ }
2629
+ minTimestamp = minTimestamp == null ? timestamp : Math.min(minTimestamp, timestamp);
2630
+ maxTimestamp = maxTimestamp == null ? timestamp : Math.max(maxTimestamp, timestamp);
2631
+ }
2632
+ if (minTimestamp == null || maxTimestamp == null) {
2633
+ return null;
2634
+ }
2635
+ return Math.max((maxTimestamp - minTimestamp) / DAY_MS3, 1);
2636
+ };
2637
+ var clampInt = (value, minimum, maximum) => Math.max(minimum, Math.min(maximum, Math.ceil(value)));
2638
+ var resolveAiPocketCadenceProfile = ({
2639
+ trainRows,
2640
+ validationRows = [],
2641
+ testRows = [],
2642
+ mode = "auto",
2643
+ validationSplit = 0.25,
2644
+ minSupport,
2645
+ minEvents,
2646
+ minValidationSupport,
2647
+ minValidationEvents,
2648
+ maxEventCountShare = 0.25,
2649
+ explicitMaxEventCountShare = false
2650
+ }) => {
2651
+ const trainEvents = countIndependentEvents(trainRows);
2652
+ const validationEvents = countIndependentEvents(validationRows);
2653
+ const testEvents = countIndependentEvents(testRows);
2654
+ const trainPeriodDays = getPeriodDays2(trainRows);
2655
+ const trainEventsPerDay = trainPeriodDays == null ? null : trainEvents / trainPeriodDays;
2656
+ const sparseSample = trainEvents < LOW_CADENCE_EVENT_LIMIT;
2657
+ const lowCadence = trainEventsPerDay != null && trainEventsPerDay < 0.25;
2658
+ const adaptiveSample = mode === "auto" && sparseSample;
2659
+ const adaptiveThresholds = adaptiveSample && (minSupport == null || minEvents == null || validationRows.length > 0 && (minValidationSupport == null || minValidationEvents == null) || !explicitMaxEventCountShare);
2660
+ const resolvedMinSupport = minSupport ?? (adaptiveSample ? clampInt(trainEvents * 0.1, 3, DEFAULT_MIN_SUPPORT) : DEFAULT_MIN_SUPPORT);
2661
+ const resolvedMinEvents = minEvents ?? (minSupport != null || !adaptiveSample ? Math.max(5, Math.ceil(resolvedMinSupport * 0.5)) : clampInt(resolvedMinSupport * 0.5, 3, 10));
2662
+ const hasValidation = validationRows.length > 0;
2663
+ const resolvedMinValidationSupport = minValidationSupport ?? (hasValidation ? Math.max(3, Math.ceil(resolvedMinSupport * validationSplit * 0.5)) : 0);
2664
+ const resolvedMinValidationEvents = minValidationEvents ?? (hasValidation ? Math.max(
2665
+ adaptiveSample ? 3 : 2,
2666
+ Math.ceil(resolvedMinEvents * validationSplit * 0.5)
2667
+ ) : 0);
2668
+ const resolvedMaxEventCountShare = adaptiveSample && !explicitMaxEventCountShare ? Math.max(maxEventCountShare, LOW_CADENCE_MAX_EVENT_SHARE) : maxEventCountShare;
2669
+ return {
2670
+ mode,
2671
+ lowCadence,
2672
+ sparseSample,
2673
+ adaptiveThresholds,
2674
+ trainRows: trainRows.length,
2675
+ trainEvents,
2676
+ trainPeriodDays,
2677
+ trainEventsPerDay,
2678
+ validationRows: validationRows.length,
2679
+ validationEvents,
2680
+ testRows: testRows.length,
2681
+ testEvents,
2682
+ minSupport: resolvedMinSupport,
2683
+ minEvents: resolvedMinEvents,
2684
+ minValidationSupport: resolvedMinValidationSupport,
2685
+ minValidationEvents: resolvedMinValidationEvents,
2686
+ maxEventCountShare: resolvedMaxEventCountShare
2687
+ };
2688
+ };
2182
2689
  var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
2183
2690
  minProfitFactor: "1.2",
2184
2691
  minWinRate: "0",
@@ -2225,6 +2732,29 @@ var splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0
2225
2732
  testRows: rows.filter((row) => isIn(testTimestamps, row.timestamp))
2226
2733
  };
2227
2734
  };
2735
+ var splitAiPocketCoverageRowsByTimestamp = (rows, family, validationSplit, testSplit = 0) => splitAiPocketResearchRowsByTimestamp(
2736
+ rows.filter((row) => row.featureCoverage?.[family] === true),
2737
+ validationSplit,
2738
+ testSplit
2739
+ );
2740
+ var sealAiPocketTestPartition = (split, sealed) => {
2741
+ const finiteTimestamps = split.testRows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp));
2742
+ return {
2743
+ discoveryRows: [
2744
+ ...split.trainRows,
2745
+ ...split.validationRows,
2746
+ ...sealed ? [] : split.testRows
2747
+ ],
2748
+ searchTestRows: sealed ? [] : split.testRows,
2749
+ evidence: {
2750
+ sealed,
2751
+ rows: split.testRows.length,
2752
+ events: countIndependentEvents(split.testRows),
2753
+ startTimestamp: finiteTimestamps.length ? Math.min(...finiteTimestamps) : null,
2754
+ endTimestamp: finiteTimestamps.length ? Math.max(...finiteTimestamps) : null
2755
+ }
2756
+ };
2757
+ };
2228
2758
  var readAiPocketSearchCliOption = ({
2229
2759
  argv,
2230
2760
  longName,
@@ -2298,7 +2828,11 @@ import_args.default.option(
2298
2828
  ""
2299
2829
  );
2300
2830
  import_args.default.option(["d", "maxDepth"], "Maximum predicate-combination depth", 2);
2301
- import_args.default.option(["m", "minSupport"], "Minimum rows required for a pocket", 20);
2831
+ import_args.default.option(
2832
+ ["m", "minSupport"],
2833
+ "Minimum rows override; cadence auto derives it when omitted",
2834
+ 20
2835
+ );
2302
2836
  import_args.default.option(
2303
2837
  ["F", "minProfitFactor"],
2304
2838
  "Minimum profit factor required for positive pockets",
@@ -2334,6 +2868,11 @@ import_args.default.option(
2334
2868
  "Trailing timestamp-grouped scope share withheld as untouched test",
2335
2869
  AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.testSplit
2336
2870
  );
2871
+ import_args.default.option(
2872
+ ["w", "sealTest"],
2873
+ "Reserve test bounds without exposing its rows or economics to discovery",
2874
+ false
2875
+ );
2337
2876
  import_args.default.option(
2338
2877
  ["N", "minValidationSupport"],
2339
2878
  "Minimum validation rows required for positive pockets (0 = auto)",
@@ -2401,6 +2940,16 @@ import_args.default.option(
2401
2940
  "Feature policy: causal-stationary or all",
2402
2941
  "causal-stationary"
2403
2942
  );
2943
+ import_args.default.option(
2944
+ ["Q", "coverageMode"],
2945
+ "Coverage-aware search mode: auto or full",
2946
+ "auto"
2947
+ );
2948
+ import_args.default.option(
2949
+ ["c", "cadenceMode"],
2950
+ "Cadence thresholds: auto adapts discovery support, fixed keeps legacy defaults",
2951
+ "auto"
2952
+ );
2404
2953
  import_args.default.option(
2405
2954
  ["r", "reportDir"],
2406
2955
  "Directory for generated Markdown reports",
@@ -2522,6 +3071,24 @@ var normalizeFeaturePolicy = (value) => {
2522
3071
  }
2523
3072
  return normalized;
2524
3073
  };
3074
+ var normalizeCoverageMode = (value) => {
3075
+ const normalized = String(value || "auto").trim().toLowerCase();
3076
+ if (!["auto", "full"].includes(normalized)) {
3077
+ throw new Error(
3078
+ `Unsupported --coverageMode "${normalized}". Use auto or full.`
3079
+ );
3080
+ }
3081
+ return normalized;
3082
+ };
3083
+ var normalizeCadenceMode = (value) => {
3084
+ const normalized = String(value || "auto").trim().toLowerCase();
3085
+ if (!["auto", "fixed"].includes(normalized)) {
3086
+ throw new Error(
3087
+ `Unsupported --cadenceMode "${normalized}". Use auto or fixed.`
3088
+ );
3089
+ }
3090
+ return normalized;
3091
+ };
2525
3092
  var normalizeObjective = (value, scope) => {
2526
3093
  const normalized = String(value || "auto").trim().toLowerCase();
2527
3094
  if (normalized === "auto") {
@@ -2749,16 +3316,20 @@ var buildPocketRows = (pockets) => pockets.map((pocket, index) => [
2749
3316
  pocket.objectiveSummary ? colorizeProfit(pocket.objectiveSummary.totalProfit) : import_chalk.default.gray("n/a"),
2750
3317
  colorizeProfit(-pocket.summary.maxDrawdown),
2751
3318
  pocket.validationSummary ? import_chalk.default.cyan(String(pocket.validationSummary.support)) : import_chalk.default.gray("n/a"),
3319
+ pocket.validationSummary ? import_chalk.default.cyan(String(pocket.validationSummary.events)) : import_chalk.default.gray("n/a"),
2752
3320
  pocket.validationSummary ? colorizeRatio(pocket.validationSummary.winRate) : import_chalk.default.gray("n/a"),
2753
3321
  pocket.validationSummary ? colorizeNumber(pocket.validationSummary.profitFactor) : import_chalk.default.gray("n/a"),
2754
3322
  pocket.validationSummary ? colorizeProfit(pocket.validationSummary.totalProfit) : import_chalk.default.gray("n/a"),
2755
3323
  pocket.testSummary ? import_chalk.default.cyan(String(pocket.testSummary.support)) : import_chalk.default.gray("n/a"),
3324
+ pocket.testSummary ? import_chalk.default.cyan(String(pocket.testSummary.events)) : import_chalk.default.gray("n/a"),
2756
3325
  pocket.testSummary ? colorizeNumber(pocket.testSummary.profitFactor) : import_chalk.default.gray("n/a"),
2757
3326
  pocket.testSummary ? colorizeProfit(pocket.testSummary.totalProfit) : import_chalk.default.gray("n/a"),
2758
3327
  pocket.testObjectiveSummary ? colorizeProfit(pocket.testObjectiveSummary.totalProfit) : import_chalk.default.gray("n/a"),
2759
3328
  colorizeNumber(pocket.summary.avgTradesPerDay),
2760
3329
  import_chalk.default.yellow(String(pocket.summary.losingMonths)),
2761
3330
  colorizeNumber(pocket.score),
3331
+ pocket.readiness === "production-candidate" ? import_chalk.default.green(pocket.readiness) : import_chalk.default.yellow(pocket.readiness),
3332
+ pocket.readinessReasons.length ? import_chalk.default.yellow(pocket.readinessReasons.join("; ")) : import_chalk.default.gray("none"),
2762
3333
  pocket.condition
2763
3334
  ]);
2764
3335
  var buildQualityRows = (qualityThresholds) => qualityThresholds.map(({ label, summary }) => [
@@ -2818,7 +3389,7 @@ var main = async () => {
2818
3389
  );
2819
3390
  }
2820
3391
  const maxDepth = normalizePositiveInt(flags.maxDepth, 2);
2821
- const minSupport = normalizePositiveInt(flags.minSupport, 20);
3392
+ const explicitMinSupport = hasCliOption2("minSupport", "m") ? normalizePositiveInt(flags.minSupport, 20) : void 0;
2822
3393
  const minProfitFactor = normalizeNonNegativeNumber(
2823
3394
  readAiPocketSearchCliOption({
2824
3395
  argv: process.argv,
@@ -2862,6 +3433,10 @@ var main = async () => {
2862
3433
  }) ?? flags.testSplit,
2863
3434
  0
2864
3435
  ) : 0;
3436
+ const sealTest = Boolean(flags.sealTest);
3437
+ if (sealTest && testSplit <= 0) {
3438
+ throw new Error("--sealTest requires a positive --testSplit");
3439
+ }
2865
3440
  const explicitMinValidationSupport = normalizeInt(
2866
3441
  flags.minValidationSupport,
2867
3442
  0
@@ -2899,6 +3474,8 @@ var main = async () => {
2899
3474
  const includeGateContext = Boolean(flags.includeGateContext);
2900
3475
  const featureProfile = normalizeFeatureProfile(flags.featureProfile);
2901
3476
  const featurePolicy = normalizeFeaturePolicy(flags.featurePolicy);
3477
+ const coverageMode = normalizeCoverageMode(flags.coverageMode);
3478
+ const cadenceMode = normalizeCadenceMode(flags.cadenceMode);
2902
3479
  const jsonOutput = Boolean(flags.json);
2903
3480
  const outputPath = String(flags.output || "").trim();
2904
3481
  const reportDir = String(flags.reportDir || "data/ai/output").trim();
@@ -2992,6 +3569,19 @@ var main = async () => {
2992
3569
  const modelDirection = typeof analysis.direction === "string" && analysis.direction.trim() ? analysis.direction : null;
2993
3570
  const modelDirectionMatches = modelDirection === row.direction;
2994
3571
  const profit = Number(row.profit);
3572
+ const featureSnapshot = collectAiPocketFeatureSnapshot({
3573
+ payload,
3574
+ gateContext,
3575
+ includeSymbol,
3576
+ includeGateContext,
3577
+ featureProfile,
3578
+ featurePolicy,
3579
+ onFeatureExcluded: ({ path: featurePath, classification }) => {
3580
+ const paths = excludedFeaturePaths.get(classification) ?? /* @__PURE__ */ new Set();
3581
+ paths.add(featurePath);
3582
+ excludedFeaturePaths.set(classification, paths);
3583
+ }
3584
+ });
2995
3585
  rows.push({
2996
3586
  signalId: row.signalId,
2997
3587
  strategy: row.strategyName,
@@ -3004,19 +3594,8 @@ var main = async () => {
3004
3594
  quality,
3005
3595
  modelDirectionMatches,
3006
3596
  modelCandidate: getDeterministicModelCandidate(signal),
3007
- features: collectAiPocketFeatures({
3008
- payload,
3009
- gateContext,
3010
- includeSymbol,
3011
- includeGateContext,
3012
- featureProfile,
3013
- featurePolicy,
3014
- onFeatureExcluded: ({ path: featurePath, classification }) => {
3015
- const paths = excludedFeaturePaths.get(classification) ?? /* @__PURE__ */ new Set();
3016
- paths.add(featurePath);
3017
- excludedFeaturePaths.set(classification, paths);
3018
- }
3019
- })
3597
+ features: featureSnapshot.features,
3598
+ featureCoverage: featureSnapshot.featureCoverage
3020
3599
  });
3021
3600
  bar?.tick(1, {
3022
3601
  symbol: import_chalk.default.gray(row.symbol),
@@ -3038,86 +3617,188 @@ var main = async () => {
3038
3617
  });
3039
3618
  const directionRows = direction ? rows.filter((row) => row.direction === direction) : rows;
3040
3619
  const scopeRows = resolveScopeRows(directionRows, scope);
3041
- const split = splitAiPocketResearchRowsByTimestamp(
3620
+ const fullSplit = splitAiPocketResearchRowsByTimestamp(
3042
3621
  directionRows,
3043
3622
  validationSplit,
3044
3623
  testSplit
3045
3624
  );
3046
- const trainRows = resolveScopeRows(split.trainRows, scope);
3047
- const validationRows = resolveScopeRows(split.validationRows, scope);
3048
- const testRows = resolveScopeRows(split.testRows, scope);
3049
- const baselineRows = split.trainRows.filter((row) => row.aiApproved);
3050
- const validationBaselineRows = split.validationRows.filter(
3625
+ const sealedFullSplit = sealAiPocketTestPartition(fullSplit, sealTest);
3626
+ const discoveryRows = sealedFullSplit.discoveryRows;
3627
+ const trainRows = resolveScopeRows(fullSplit.trainRows, scope);
3628
+ const validationRows = resolveScopeRows(fullSplit.validationRows, scope);
3629
+ const testRows = resolveScopeRows(sealedFullSplit.searchTestRows, scope);
3630
+ const baselineRows = fullSplit.trainRows.filter((row) => row.aiApproved);
3631
+ const validationBaselineRows = fullSplit.validationRows.filter(
3051
3632
  (row) => row.aiApproved
3052
3633
  );
3053
- const testBaselineRows = split.testRows.filter((row) => row.aiApproved);
3054
- const minValidationSupport = explicitMinValidationSupport > 0 ? explicitMinValidationSupport : validationRows.length ? Math.max(3, Math.ceil(minSupport * validationSplit * 0.5)) : 0;
3055
- const minEvents = explicitMinEvents > 0 ? explicitMinEvents : Math.max(5, Math.ceil(minSupport * 0.5));
3056
- const minValidationEvents = explicitMinValidationEvents > 0 ? explicitMinValidationEvents : validationRows.length ? Math.max(2, Math.ceil(minEvents * validationSplit * 0.5)) : 0;
3057
- const currentGateSummary = summarizeAiTrainEvaluations(rows);
3058
- const currentGateQualityThresholds = summarizeAiTrainEvaluationsByQualityThreshold(rows, qualityThresholds);
3059
- let searchBar = null;
3060
- let searchBarPhase = null;
3061
- const search = searchAiPockets(trainRows, {
3062
- minSupport,
3063
- minProfitFactor,
3064
- minTotalProfit,
3065
- minWinRate,
3066
- maxDepth,
3067
- maxAtomicPredicates,
3068
- maxCombinations,
3069
- top,
3070
- validationRows,
3071
- testRows,
3072
- minValidationSupport,
3073
- minEvents,
3074
- minValidationEvents,
3075
- ...maxBatch > 0 ? { maxBatch } : {},
3076
- maxEventCountShare,
3077
- maxSymbolCountShare,
3078
- objective,
3079
- baselineRows,
3080
- validationBaselineRows,
3081
- testBaselineRows,
3082
- allowRiskRegression,
3083
- requireValidationEligibility,
3084
- dedupeEquivalentSelections,
3085
- progressInterval: 250,
3086
- onProgress: (progress) => {
3087
- if (searchBar && searchBarPhase !== progress.phase) {
3088
- tickProgressBarTo(searchBar, searchBar.total, {
3089
- status: import_chalk.default.gray("done")
3634
+ const testBaselineRows = sealedFullSplit.searchTestRows.filter(
3635
+ (row) => row.aiApproved
3636
+ );
3637
+ const currentGateSummary = summarizeAiTrainEvaluations(discoveryRows);
3638
+ const currentGateQualityThresholds = summarizeAiTrainEvaluationsByQualityThreshold(
3639
+ discoveryRows,
3640
+ qualityThresholds
3641
+ );
3642
+ const runSearch = ({
3643
+ label,
3644
+ train,
3645
+ validation,
3646
+ test,
3647
+ trainBaseline,
3648
+ validationBaseline,
3649
+ testBaseline,
3650
+ requiredFeatureFamilies = [],
3651
+ excludedFeatureFamilies = []
3652
+ }) => {
3653
+ const cadenceProfile = resolveAiPocketCadenceProfile({
3654
+ trainRows: train,
3655
+ validationRows: validation,
3656
+ testRows: test,
3657
+ mode: cadenceMode,
3658
+ validationSplit,
3659
+ ...explicitMinSupport != null ? { minSupport: explicitMinSupport } : {},
3660
+ ...explicitMinEvents > 0 ? { minEvents: explicitMinEvents } : {},
3661
+ ...explicitMinValidationSupport > 0 ? { minValidationSupport: explicitMinValidationSupport } : {},
3662
+ ...explicitMinValidationEvents > 0 ? { minValidationEvents: explicitMinValidationEvents } : {},
3663
+ maxEventCountShare,
3664
+ explicitMaxEventCountShare: hasCliOption2("maxEventCountShare", "U")
3665
+ });
3666
+ let searchBar = null;
3667
+ let searchBarPhase = null;
3668
+ return searchAiPockets(train, {
3669
+ minSupport: cadenceProfile.minSupport,
3670
+ minProfitFactor,
3671
+ minTotalProfit,
3672
+ minWinRate,
3673
+ maxDepth,
3674
+ maxAtomicPredicates,
3675
+ maxCombinations,
3676
+ top,
3677
+ validationRows: validation,
3678
+ testRows: test,
3679
+ minValidationSupport: cadenceProfile.minValidationSupport,
3680
+ minEvents: cadenceProfile.minEvents,
3681
+ minValidationEvents: cadenceProfile.minValidationEvents,
3682
+ ...maxBatch > 0 ? { maxBatch } : {},
3683
+ maxEventCountShare: cadenceProfile.maxEventCountShare,
3684
+ maxSymbolCountShare,
3685
+ objective,
3686
+ baselineRows: trainBaseline,
3687
+ validationBaselineRows: validationBaseline,
3688
+ testBaselineRows: testBaseline,
3689
+ allowRiskRegression,
3690
+ requireValidationEligibility,
3691
+ dedupeEquivalentSelections,
3692
+ requiredFeatureFamilies,
3693
+ excludedFeatureFamilies,
3694
+ cadenceProfile,
3695
+ progressInterval: 250,
3696
+ onProgress: (progress) => {
3697
+ if (searchBar && searchBarPhase !== progress.phase) {
3698
+ tickProgressBarTo(searchBar, searchBar.total, {
3699
+ status: import_chalk.default.gray("done")
3700
+ });
3701
+ searchBar = null;
3702
+ }
3703
+ if (!searchBar) {
3704
+ searchBarPhase = progress.phase;
3705
+ const phaseLabel = searchPhaseLabels[progress.phase].padEnd(8, " ");
3706
+ console.error(
3707
+ import_chalk.default.gray(
3708
+ `stage: ${label}/${searchPhaseLabels[progress.phase]} (${progress.total})`
3709
+ )
3710
+ );
3711
+ searchBar = new import_progress.default(
3712
+ `${phaseLabel} :current/:total [:bar] :percent :status`,
3713
+ {
3714
+ total: Math.max(progress.total, 1),
3715
+ width: 20,
3716
+ stream: process.stderr
3717
+ }
3718
+ );
3719
+ }
3720
+ tickProgressBarTo(searchBar, progress.current, {
3721
+ status: progress.truncated ? import_chalk.default.yellow("truncated") : "running"
3090
3722
  });
3091
- searchBar = null;
3092
- }
3093
- if (!searchBar) {
3094
- searchBarPhase = progress.phase;
3095
- const label = searchPhaseLabels[progress.phase].padEnd(8, " ");
3096
- console.error(
3097
- import_chalk.default.gray(
3098
- `stage: ${searchPhaseLabels[progress.phase]} (${progress.total})`
3099
- )
3100
- );
3101
- searchBar = new import_progress.default(
3102
- `${label} :current/:total [:bar] :percent :status`,
3103
- {
3104
- total: Math.max(progress.total, 1),
3105
- width: 20,
3106
- stream: process.stderr
3107
- }
3108
- );
3723
+ if (progress.done) {
3724
+ tickProgressBarTo(searchBar, searchBar.total, {
3725
+ status: progress.truncated ? import_chalk.default.yellow("truncated") : "done"
3726
+ });
3727
+ searchBar = null;
3728
+ }
3109
3729
  }
3110
- tickProgressBarTo(searchBar, progress.current, {
3111
- status: progress.truncated ? import_chalk.default.yellow("truncated") : "running"
3730
+ });
3731
+ };
3732
+ const search = runSearch({
3733
+ label: "full",
3734
+ train: trainRows,
3735
+ validation: validationRows,
3736
+ test: testRows,
3737
+ trainBaseline: baselineRows,
3738
+ validationBaseline: validationBaselineRows,
3739
+ testBaseline: testBaselineRows,
3740
+ ...coverageMode === "auto" ? {
3741
+ excludedFeatureFamilies: [
3742
+ "cmc",
3743
+ "coinalyze"
3744
+ ]
3745
+ } : {}
3746
+ });
3747
+ const coverageSearches = [];
3748
+ if (coverageMode === "auto") {
3749
+ for (const family of ["cmc", "coinalyze"]) {
3750
+ const cohortSplit = splitAiPocketCoverageRowsByTimestamp(
3751
+ directionRows,
3752
+ family,
3753
+ validationSplit,
3754
+ testSplit
3755
+ );
3756
+ const sealedCohortSplit = sealAiPocketTestPartition(
3757
+ cohortSplit,
3758
+ sealTest
3759
+ );
3760
+ const cohortTrainRows = resolveScopeRows(cohortSplit.trainRows, scope);
3761
+ const cohortValidationRows = resolveScopeRows(
3762
+ cohortSplit.validationRows,
3763
+ scope
3764
+ );
3765
+ const cohortTestRows = resolveScopeRows(
3766
+ sealedCohortSplit.searchTestRows,
3767
+ scope
3768
+ );
3769
+ const cohortSearch = runSearch({
3770
+ label: family,
3771
+ train: cohortTrainRows,
3772
+ validation: cohortValidationRows,
3773
+ test: cohortTestRows,
3774
+ trainBaseline: cohortSplit.trainRows.filter((row) => row.aiApproved),
3775
+ validationBaseline: cohortSplit.validationRows.filter(
3776
+ (row) => row.aiApproved
3777
+ ),
3778
+ testBaseline: sealedCohortSplit.searchTestRows.filter(
3779
+ (row) => row.aiApproved
3780
+ ),
3781
+ requiredFeatureFamilies: [family]
3782
+ });
3783
+ coverageSearches.push({
3784
+ family,
3785
+ coverage: summarizeAiPocketFeatureCoverage(
3786
+ sealedCohortSplit.discoveryRows,
3787
+ family
3788
+ ),
3789
+ scopeRows: resolveScopeRows(
3790
+ sealedCohortSplit.discoveryRows.filter(
3791
+ (row) => row.featureCoverage?.[family] === true
3792
+ ),
3793
+ scope
3794
+ ).length,
3795
+ trainRows: cohortTrainRows.length,
3796
+ validationRows: cohortValidationRows.length,
3797
+ testRows: cohortTestRows.length,
3798
+ search: cohortSearch
3112
3799
  });
3113
- if (progress.done) {
3114
- tickProgressBarTo(searchBar, searchBar.total, {
3115
- status: progress.truncated ? import_chalk.default.yellow("truncated") : "done"
3116
- });
3117
- searchBar = null;
3118
- }
3119
3800
  }
3120
- });
3801
+ }
3121
3802
  const result = {
3122
3803
  generatedAt,
3123
3804
  run: {
@@ -3146,6 +3827,25 @@ var main = async () => {
3146
3827
  includeGateContext,
3147
3828
  featureProfile,
3148
3829
  featurePolicy,
3830
+ coverageMode,
3831
+ cadenceMode,
3832
+ coverageSearches: coverageSearches.map(
3833
+ ({
3834
+ family,
3835
+ coverage,
3836
+ scopeRows: scopeRows2,
3837
+ trainRows: trainRows2,
3838
+ validationRows: validationRows2,
3839
+ testRows: testRows2
3840
+ }) => ({
3841
+ family,
3842
+ coverage,
3843
+ scopeRows: scopeRows2,
3844
+ trainRows: trainRows2,
3845
+ validationRows: validationRows2,
3846
+ testRows: testRows2
3847
+ })
3848
+ ),
3149
3849
  featurePolicyAudit: Object.fromEntries(
3150
3850
  [...excludedFeaturePaths.entries()].map(([classification, paths]) => [
3151
3851
  classification,
@@ -3158,26 +3858,27 @@ var main = async () => {
3158
3858
  objective,
3159
3859
  validationSplit,
3160
3860
  testSplit,
3161
- minValidationSupport,
3861
+ sealedTest: sealedFullSplit.evidence,
3862
+ minValidationSupport: search.stats.cadence.minValidationSupport,
3162
3863
  reportPath,
3163
3864
  search: {
3164
3865
  maxDepth,
3165
- minSupport,
3866
+ minSupport: search.stats.cadence.minSupport,
3166
3867
  minProfitFactor,
3167
3868
  minWinRate,
3168
3869
  minTotalProfit,
3169
3870
  maxAtomicPredicates,
3170
3871
  maxCombinations,
3171
- minEvents,
3172
- minValidationEvents,
3872
+ minEvents: search.stats.cadence.minEvents,
3873
+ minValidationEvents: search.stats.cadence.minValidationEvents,
3173
3874
  ...maxBatch > 0 ? { maxBatch } : {},
3174
- maxEventCountShare,
3875
+ maxEventCountShare: search.stats.cadence.maxEventCountShare,
3175
3876
  maxSymbolCountShare,
3176
3877
  allowRiskRegression,
3177
3878
  requireValidationEligibility,
3879
+ cadence: search.stats.cadence,
3178
3880
  validationSplit,
3179
3881
  testSplit,
3180
- minValidationSupport,
3181
3882
  dedupeEquivalentSelections,
3182
3883
  top
3183
3884
  }
@@ -3187,6 +3888,7 @@ var main = async () => {
3187
3888
  qualityThresholds: currentGateQualityThresholds
3188
3889
  },
3189
3890
  pocketSearch: search,
3891
+ coverageSearches,
3190
3892
  errors
3191
3893
  };
3192
3894
  const markdownReport = buildAiPocketMarkdownReport(result);
@@ -3246,17 +3948,53 @@ var main = async () => {
3246
3948
  ],
3247
3949
  ["min_quality", import_chalk.default.magenta(String(minQuality))],
3248
3950
  ["max_depth", import_chalk.default.magenta(String(maxDepth))],
3249
- ["min_support", import_chalk.default.magenta(String(minSupport))],
3250
- ["min_events", import_chalk.default.magenta(String(minEvents))],
3251
- ["min_validation_events", import_chalk.default.magenta(String(minValidationEvents))],
3951
+ ["cadence_mode", import_chalk.default.magenta(cadenceMode)],
3952
+ [
3953
+ "low_cadence",
3954
+ search.stats.cadence.lowCadence ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
3955
+ ],
3956
+ [
3957
+ "sparse_sample",
3958
+ search.stats.cadence.sparseSample ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
3959
+ ],
3960
+ [
3961
+ "adaptive_thresholds",
3962
+ search.stats.cadence.adaptiveThresholds ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
3963
+ ],
3964
+ [
3965
+ "train_events",
3966
+ import_chalk.default.magenta(String(search.stats.cadence.trainEvents))
3967
+ ],
3968
+ [
3969
+ "train_events_per_day",
3970
+ import_chalk.default.magenta(formatNumber2(search.stats.cadence.trainEventsPerDay))
3971
+ ],
3972
+ ["min_support", import_chalk.default.magenta(String(search.stats.cadence.minSupport))],
3973
+ ["min_events", import_chalk.default.magenta(String(search.stats.cadence.minEvents))],
3974
+ [
3975
+ "min_validation_events",
3976
+ import_chalk.default.magenta(String(search.stats.cadence.minValidationEvents))
3977
+ ],
3252
3978
  ["max_batch", import_chalk.default.magenta(maxBatch > 0 ? String(maxBatch) : "off")],
3253
- ["max_event_share", import_chalk.default.magenta(formatRatio(maxEventCountShare))],
3979
+ [
3980
+ "max_event_share",
3981
+ import_chalk.default.magenta(formatRatio(search.stats.cadence.maxEventCountShare))
3982
+ ],
3254
3983
  ["max_symbol_share", import_chalk.default.magenta(formatRatio(maxSymbolCountShare))],
3255
3984
  ["max_atomic_predicates", import_chalk.default.magenta(String(maxAtomicPredicates))],
3256
3985
  ["max_combinations", import_chalk.default.magenta(String(maxCombinations))],
3257
3986
  ["validation_split", import_chalk.default.magenta(formatRatio(validationSplit))],
3258
3987
  ["test_split", import_chalk.default.magenta(formatRatio(testSplit))],
3259
- ["min_validation_support", import_chalk.default.magenta(String(minValidationSupport))],
3988
+ [
3989
+ "test_evidence",
3990
+ sealedFullSplit.evidence.sealed ? import_chalk.default.green(
3991
+ `sealed (${sealedFullSplit.evidence.rows} rows / ${sealedFullSplit.evidence.events} events)`
3992
+ ) : import_chalk.default.yellow("open")
3993
+ ],
3994
+ [
3995
+ "min_validation_support",
3996
+ import_chalk.default.magenta(String(search.stats.cadence.minValidationSupport))
3997
+ ],
3260
3998
  [
3261
3999
  "dedupe_equivalent",
3262
4000
  dedupeEquivalentSelections ? import_chalk.default.green("on") : import_chalk.default.gray("off")
@@ -3271,6 +4009,7 @@ var main = async () => {
3271
4009
  ],
3272
4010
  ["feature_profile", import_chalk.default.magenta(featureProfile)],
3273
4011
  ["feature_policy", import_chalk.default.magenta(featurePolicy)],
4012
+ ["coverage_mode", import_chalk.default.magenta(coverageMode)],
3274
4013
  [
3275
4014
  "allow_risk_regression",
3276
4015
  allowRiskRegression ? import_chalk.default.yellow("on") : import_chalk.default.green("off")
@@ -3346,6 +4085,14 @@ var main = async () => {
3346
4085
  "feature_families",
3347
4086
  import_chalk.default.cyan(search.stats.featureFamiliesUsed.join(", "))
3348
4087
  ],
4088
+ [
4089
+ "required_feature_families",
4090
+ import_chalk.default.cyan(search.stats.requiredFeatureFamilies.join(", ") || "none")
4091
+ ],
4092
+ [
4093
+ "excluded_feature_families",
4094
+ import_chalk.default.cyan(search.stats.excludedFeatureFamilies.join(", ") || "none")
4095
+ ],
3349
4096
  [
3350
4097
  "estimated_combinations",
3351
4098
  import_chalk.default.cyan(String(search.stats.estimatedCombinations))
@@ -3356,6 +4103,27 @@ var main = async () => {
3356
4103
  ],
3357
4104
  ["validation_rows", import_chalk.default.cyan(String(search.stats.validationRows))],
3358
4105
  ["test_rows", import_chalk.default.cyan(String(search.stats.testRows))],
4106
+ ["train_events", import_chalk.default.cyan(String(search.stats.cadence.trainEvents))],
4107
+ [
4108
+ "train_events_per_day",
4109
+ import_chalk.default.cyan(formatNumber2(search.stats.cadence.trainEventsPerDay))
4110
+ ],
4111
+ [
4112
+ "effective_min_support",
4113
+ import_chalk.default.cyan(String(search.stats.cadence.minSupport))
4114
+ ],
4115
+ [
4116
+ "effective_min_events",
4117
+ import_chalk.default.cyan(String(search.stats.cadence.minEvents))
4118
+ ],
4119
+ [
4120
+ "effective_min_validation_support",
4121
+ import_chalk.default.cyan(String(search.stats.cadence.minValidationSupport))
4122
+ ],
4123
+ [
4124
+ "effective_min_validation_events",
4125
+ import_chalk.default.cyan(String(search.stats.cadence.minValidationEvents))
4126
+ ],
3359
4127
  [
3360
4128
  "duplicate_pockets_skipped",
3361
4129
  import_chalk.default.cyan(String(search.stats.duplicatePocketsSkipped))
@@ -3382,16 +4150,20 @@ var main = async () => {
3382
4150
  import_chalk.default.gray("OBJ_PNL"),
3383
4151
  import_chalk.default.gray("MAX_DD"),
3384
4152
  import_chalk.default.gray("VAL_N"),
4153
+ import_chalk.default.gray("VAL_EVENTS"),
3385
4154
  import_chalk.default.gray("VAL_WR"),
3386
4155
  import_chalk.default.gray("VAL_PF"),
3387
4156
  import_chalk.default.gray("VAL_PNL"),
3388
4157
  import_chalk.default.gray("TEST_N"),
4158
+ import_chalk.default.gray("TEST_EVENTS"),
3389
4159
  import_chalk.default.gray("TEST_PF"),
3390
4160
  import_chalk.default.gray("TEST_PNL"),
3391
4161
  import_chalk.default.gray("TEST_OBJ"),
3392
4162
  import_chalk.default.gray("TR/D"),
3393
4163
  import_chalk.default.gray("LOSS_M"),
3394
4164
  import_chalk.default.gray("SCORE"),
4165
+ import_chalk.default.gray("READINESS"),
4166
+ import_chalk.default.gray("REASONS"),
3395
4167
  import_chalk.default.gray("POCKET")
3396
4168
  ],
3397
4169
  buildPocketRows(search.positivePockets)
@@ -3412,21 +4184,155 @@ var main = async () => {
3412
4184
  import_chalk.default.gray("OBJ_PNL"),
3413
4185
  import_chalk.default.gray("MAX_DD"),
3414
4186
  import_chalk.default.gray("VAL_N"),
4187
+ import_chalk.default.gray("VAL_EVENTS"),
3415
4188
  import_chalk.default.gray("VAL_WR"),
3416
4189
  import_chalk.default.gray("VAL_PF"),
3417
4190
  import_chalk.default.gray("VAL_PNL"),
3418
4191
  import_chalk.default.gray("TEST_N"),
4192
+ import_chalk.default.gray("TEST_EVENTS"),
3419
4193
  import_chalk.default.gray("TEST_PF"),
3420
4194
  import_chalk.default.gray("TEST_PNL"),
3421
4195
  import_chalk.default.gray("TEST_OBJ"),
3422
4196
  import_chalk.default.gray("TR/D"),
3423
4197
  import_chalk.default.gray("LOSS_M"),
3424
4198
  import_chalk.default.gray("SCORE"),
4199
+ import_chalk.default.gray("READINESS"),
4200
+ import_chalk.default.gray("REASONS"),
3425
4201
  import_chalk.default.gray("POCKET")
3426
4202
  ],
3427
4203
  buildPocketRows(search.negativePockets)
3428
4204
  )
3429
4205
  );
4206
+ for (const cohort of coverageSearches) {
4207
+ const title = cohort.family === "cmc" ? "CMC" : "COINALYZE";
4208
+ printSection(
4209
+ `${title} COVERAGE COHORT`,
4210
+ createTable(
4211
+ [import_chalk.default.gray("FIELD"), import_chalk.default.gray("VALUE")],
4212
+ [
4213
+ ["coverage_rows", import_chalk.default.cyan(String(cohort.coverage.rows))],
4214
+ ["coverage_ratio", import_chalk.default.cyan(formatRatio(cohort.coverage.rowRatio))],
4215
+ ["coverage_events", import_chalk.default.cyan(String(cohort.coverage.events))],
4216
+ ["event_ratio", import_chalk.default.cyan(formatRatio(cohort.coverage.eventRatio))],
4217
+ [
4218
+ "coverage_from",
4219
+ cohort.coverage.minTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(
4220
+ new Date(cohort.coverage.minTimestamp).toISOString()
4221
+ )
4222
+ ],
4223
+ [
4224
+ "coverage_to",
4225
+ cohort.coverage.maxTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(
4226
+ new Date(cohort.coverage.maxTimestamp).toISOString()
4227
+ )
4228
+ ],
4229
+ ["scope_rows", import_chalk.default.cyan(String(cohort.scopeRows))],
4230
+ ["train_rows", import_chalk.default.cyan(String(cohort.trainRows))],
4231
+ ["validation_rows", import_chalk.default.cyan(String(cohort.validationRows))],
4232
+ ["test_rows", import_chalk.default.cyan(String(cohort.testRows))],
4233
+ [
4234
+ "low_cadence",
4235
+ cohort.search.stats.cadence.lowCadence ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
4236
+ ],
4237
+ [
4238
+ "sparse_sample",
4239
+ cohort.search.stats.cadence.sparseSample ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
4240
+ ],
4241
+ [
4242
+ "adaptive_thresholds",
4243
+ cohort.search.stats.cadence.adaptiveThresholds ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
4244
+ ],
4245
+ [
4246
+ "train_events",
4247
+ import_chalk.default.cyan(String(cohort.search.stats.cadence.trainEvents))
4248
+ ],
4249
+ [
4250
+ "effective_min_support",
4251
+ import_chalk.default.cyan(String(cohort.search.stats.cadence.minSupport))
4252
+ ],
4253
+ [
4254
+ "effective_min_events",
4255
+ import_chalk.default.cyan(String(cohort.search.stats.cadence.minEvents))
4256
+ ]
4257
+ ]
4258
+ )
4259
+ );
4260
+ printSection(
4261
+ `${title} TRAIN BASELINE`,
4262
+ createTable(
4263
+ [import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
4264
+ buildSummaryRows(cohort.search.baseline)
4265
+ )
4266
+ );
4267
+ printSection(
4268
+ `${title} TOP POSITIVE POCKETS`,
4269
+ createTable(
4270
+ [
4271
+ import_chalk.default.gray("#"),
4272
+ import_chalk.default.gray("N"),
4273
+ import_chalk.default.gray("EVENTS"),
4274
+ import_chalk.default.gray("MAX_B"),
4275
+ import_chalk.default.gray("SUP"),
4276
+ import_chalk.default.gray("WR"),
4277
+ import_chalk.default.gray("PF"),
4278
+ import_chalk.default.gray("PNL"),
4279
+ import_chalk.default.gray("OBJ_PNL"),
4280
+ import_chalk.default.gray("MAX_DD"),
4281
+ import_chalk.default.gray("VAL_N"),
4282
+ import_chalk.default.gray("VAL_EVENTS"),
4283
+ import_chalk.default.gray("VAL_WR"),
4284
+ import_chalk.default.gray("VAL_PF"),
4285
+ import_chalk.default.gray("VAL_PNL"),
4286
+ import_chalk.default.gray("TEST_N"),
4287
+ import_chalk.default.gray("TEST_EVENTS"),
4288
+ import_chalk.default.gray("TEST_PF"),
4289
+ import_chalk.default.gray("TEST_PNL"),
4290
+ import_chalk.default.gray("TEST_OBJ"),
4291
+ import_chalk.default.gray("TR/D"),
4292
+ import_chalk.default.gray("LOSS_M"),
4293
+ import_chalk.default.gray("SCORE"),
4294
+ import_chalk.default.gray("READINESS"),
4295
+ import_chalk.default.gray("REASONS"),
4296
+ import_chalk.default.gray("POCKET")
4297
+ ],
4298
+ buildPocketRows(cohort.search.positivePockets)
4299
+ )
4300
+ );
4301
+ printSection(
4302
+ `${title} TOP LOSS POCKETS`,
4303
+ createTable(
4304
+ [
4305
+ import_chalk.default.gray("#"),
4306
+ import_chalk.default.gray("N"),
4307
+ import_chalk.default.gray("EVENTS"),
4308
+ import_chalk.default.gray("MAX_B"),
4309
+ import_chalk.default.gray("SUP"),
4310
+ import_chalk.default.gray("WR"),
4311
+ import_chalk.default.gray("PF"),
4312
+ import_chalk.default.gray("PNL"),
4313
+ import_chalk.default.gray("OBJ_PNL"),
4314
+ import_chalk.default.gray("MAX_DD"),
4315
+ import_chalk.default.gray("VAL_N"),
4316
+ import_chalk.default.gray("VAL_EVENTS"),
4317
+ import_chalk.default.gray("VAL_WR"),
4318
+ import_chalk.default.gray("VAL_PF"),
4319
+ import_chalk.default.gray("VAL_PNL"),
4320
+ import_chalk.default.gray("TEST_N"),
4321
+ import_chalk.default.gray("TEST_EVENTS"),
4322
+ import_chalk.default.gray("TEST_PF"),
4323
+ import_chalk.default.gray("TEST_PNL"),
4324
+ import_chalk.default.gray("TEST_OBJ"),
4325
+ import_chalk.default.gray("TR/D"),
4326
+ import_chalk.default.gray("LOSS_M"),
4327
+ import_chalk.default.gray("SCORE"),
4328
+ import_chalk.default.gray("READINESS"),
4329
+ import_chalk.default.gray("REASONS"),
4330
+ import_chalk.default.gray("POCKET")
4331
+ ],
4332
+ buildPocketRows(cohort.search.negativePockets)
4333
+ )
4334
+ );
4335
+ }
3430
4336
  if (errors.length) {
3431
4337
  console.log(import_chalk.default.yellow("Errors:"));
3432
4338
  errors.forEach((error) => console.log(import_chalk.default.yellow(`- ${error}`)));