@tradejs/cli 1.0.9 → 1.0.10

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.
Files changed (76) hide show
  1. package/dist/cli.js +29497 -13537
  2. package/dist/lib/aiPocketSearch.js +1316 -0
  3. package/dist/lib/aiTrainCharts.js +711 -0
  4. package/dist/lib/aiTrainDataset.js +40 -0
  5. package/dist/lib/aiTrainEvaluationDump.js +76 -0
  6. package/dist/lib/aiTrainMetrics.js +148 -8
  7. package/dist/lib/aiTrainOptions.js +130 -0
  8. package/dist/lib/aiTrainQuarantine.js +199 -0
  9. package/dist/lib/aiTrainResearch.js +467 -0
  10. package/dist/lib/binanceMarketContextBackfill.js +841 -0
  11. package/dist/lib/binanceMarketData.js +392 -0
  12. package/dist/lib/coinMarketCapContextBackfill.js +1088 -0
  13. package/dist/lib/continuity.js +60 -0
  14. package/dist/lib/derivativesContextBackfill.js +490 -77
  15. package/dist/lib/executionCalibration.js +705 -0
  16. package/dist/lib/marketContextPrepare.js +4403 -0
  17. package/dist/lib/paritySummary.js +101 -0
  18. package/dist/lib/runBot.js +1 -0
  19. package/dist/lib/runEnvironment.js +2254 -0
  20. package/dist/lib/runFormatting.js +1657 -0
  21. package/dist/lib/runtimeDebugEvidence.js +526 -0
  22. package/dist/lib/runtimeModeConfig.js +53 -0
  23. package/dist/lib/runtimeParity.js +88 -11
  24. package/dist/lib/runtimeParityDetails.js +751 -0
  25. package/dist/lib/runtimeRedis.js +157 -0
  26. package/dist/lib/runtimeSignalsLoader.js +40 -6
  27. package/dist/lib/runtimeSignalsStorage.js +45 -22
  28. package/dist/lib/runtimeStrategyBacktest.js +134 -0
  29. package/dist/lib/runtimeTradeSync.js +263 -0
  30. package/dist/lib/telegramReports.js +9 -0
  31. package/dist/lib/tickerUniverseCache.js +116 -0
  32. package/dist/lib/timeWindow.js +3 -3
  33. package/dist/scripts/agentRun.js +14 -8
  34. package/dist/scripts/aiExport.js +1721 -7
  35. package/dist/scripts/aiExportSelect.js +16 -4
  36. package/dist/scripts/aiPocketSearch.js +4190 -0
  37. package/dist/scripts/aiTrain.js +1866 -87
  38. package/dist/scripts/backtest.js +4509 -818
  39. package/dist/scripts/binanceMarketIngest.js +565 -0
  40. package/dist/scripts/bot.js +16 -2
  41. package/dist/scripts/candlesMigrateProvider.js +32 -22
  42. package/dist/scripts/cleanDir.js +15 -2
  43. package/dist/scripts/cleanRedis.js +15 -2
  44. package/dist/scripts/cleanTests.js +38 -2
  45. package/dist/scripts/cleanupMarketContext.js +69 -0
  46. package/dist/scripts/continuity.js +148 -67
  47. package/dist/scripts/derivativesIngest.js +14 -4
  48. package/dist/scripts/derivativesIngestCoinalyzeAll.js +14 -4
  49. package/dist/scripts/doctor.js +14 -4
  50. package/dist/scripts/executionCalibration.js +792 -0
  51. package/dist/scripts/infraCommon.js +10 -0
  52. package/dist/scripts/infraDown.js +14 -4
  53. package/dist/scripts/infraInit.js +24 -4
  54. package/dist/scripts/infraUp.js +14 -4
  55. package/dist/scripts/marketWs.js +222 -0
  56. package/dist/scripts/migration.js +15 -2
  57. package/dist/scripts/mlExport.js +1698 -10
  58. package/dist/scripts/mlExportSelect.js +16 -4
  59. package/dist/scripts/mlInspect.js +13 -3
  60. package/dist/scripts/mlTrainLatestSelect.js +14 -4
  61. package/dist/scripts/replay.js +8824 -0
  62. package/dist/scripts/replayRunner.js +8827 -0
  63. package/dist/scripts/replayRuntimeEvidence.js +868 -0
  64. package/dist/scripts/researchAuto.js +286 -59
  65. package/dist/scripts/results.js +3 -3
  66. package/dist/scripts/runtimeEvidence.js +605 -0
  67. package/dist/scripts/runtimeParity.js +6362 -1043
  68. package/dist/scripts/serverHealth.js +588 -0
  69. package/dist/scripts/signals.js +5590 -511
  70. package/dist/scripts/signalsDaemon.js +6167 -0
  71. package/dist/scripts/signalsSummary.js +833 -248
  72. package/dist/scripts/test-ml.js +25 -3
  73. package/dist/scripts/test.js +26 -1
  74. package/dist/scripts/user-add.js +15 -2
  75. package/dist/workers/testerWorker.js +130 -21
  76. package/package.json +18 -12
@@ -0,0 +1,1316 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lib/aiPocketSearch.ts
21
+ var aiPocketSearch_exports = {};
22
+ __export(aiPocketSearch_exports, {
23
+ buildAiPocketMarkdownReport: () => buildAiPocketMarkdownReport,
24
+ buildAiPocketPredicates: () => buildAiPocketPredicates,
25
+ collectAiPocketFeatures: () => collectAiPocketFeatures,
26
+ searchAiPockets: () => searchAiPockets,
27
+ summarizeAiPocketRows: () => summarizeAiPocketRows
28
+ });
29
+ module.exports = __toCommonJS(aiPocketSearch_exports);
30
+ var DAY_MS = 24 * 60 * 60 * 1e3;
31
+ var DAYS_PER_WEEK = 7;
32
+ var DAYS_PER_MONTH = 30.4375;
33
+ var OUTCOME_SEGMENTS = /* @__PURE__ */ new Set([
34
+ "actual",
35
+ "aiapproved",
36
+ "approvalallowednow",
37
+ "backtestexecution",
38
+ "closedat",
39
+ "closedpnl",
40
+ "deterministicquality",
41
+ "entrydelaybars",
42
+ "entrydelaymovebps",
43
+ "executionprice",
44
+ "exitprice",
45
+ "exitreason",
46
+ "exittimestamp",
47
+ "fillprice",
48
+ "future",
49
+ "futuremove",
50
+ "futureprofit",
51
+ "hardblockreasons",
52
+ "label",
53
+ "maxallowedquality",
54
+ "maxquality",
55
+ "modeldirection",
56
+ "modeldirectionmatches",
57
+ "outcome",
58
+ "pnl",
59
+ "profit",
60
+ "profitabletrade",
61
+ "q4continuationrecoveryallowed",
62
+ "q4continuationrecoverycandidate",
63
+ "q4trendshiftgatefeaturesrecoverycandidate",
64
+ "q4usclosingoiconfirmationrecoverycandidate",
65
+ "quality",
66
+ "rawaiapproved",
67
+ "recoveryallowed",
68
+ "recoverycandidate",
69
+ "realized",
70
+ "realizedpnl",
71
+ "rejectreason",
72
+ "result",
73
+ "structuralhardblockreasons",
74
+ "target",
75
+ "traderesult"
76
+ ]);
77
+ var isPlainRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
78
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
79
+ var isFeaturePrimitive = (value) => value == null || typeof value === "string" || typeof value === "boolean" || isFiniteNumber(value);
80
+ var normalizeFeaturePrimitive = (value) => {
81
+ if (typeof value === "string") {
82
+ const trimmed = value.trim();
83
+ return trimmed ? trimmed.slice(0, 120) : void 0;
84
+ }
85
+ if (value == null) {
86
+ return null;
87
+ }
88
+ return value;
89
+ };
90
+ var isOutcomePath = (segments) => segments.some((segment) => OUTCOME_SEGMENTS.has(segment.toLowerCase()));
91
+ var isFeaturePathPrefix = (segments, prefix) => prefix.every((segment, index) => segments[index] === segment);
92
+ var isCompactFeaturePathSkipped = (segments) => {
93
+ if (!segments.length) {
94
+ return false;
95
+ }
96
+ const skippedPrefixes = [
97
+ ["indicators"],
98
+ ["additionalIndicators", "marketContext"],
99
+ ["additionalIndicators", "baseContext", "candle"],
100
+ ["additionalIndicators", "baseContext", "prevCandle"],
101
+ ["additionalIndicators", "baseContext", "regime", "memory"],
102
+ ["additionalIndicators", "baseContext", "structure", "acceptance"],
103
+ ["additionalIndicators", "baseContext", "structure", "liquidity"],
104
+ ["additionalIndicators", "baseContext", "structure", "liquidityTails"],
105
+ ["additionalIndicators", "baseContext", "structure", "liquidityZones"],
106
+ ["additionalIndicators", "baseContext", "structure", "pivots"],
107
+ ["additionalIndicators", "baseContext", "structure", "srZones"],
108
+ ["additionalIndicators", "baseContext", "structure", "structureZones"],
109
+ ["additionalIndicators", "baseContext", "structure", "swing"],
110
+ ["additionalIndicators", "baseContext", "structure", "zones"],
111
+ ["additionalIndicators", "baseContext", "derivatives", "referenceContexts"],
112
+ ["additionalIndicators", "baseContext", "derivatives", "referenceSymbols"],
113
+ ["additionalIndicators", "baseContext", "relative", "cmcReferenceAssets"],
114
+ ["additionalIndicators", "baseContext", "relative", "cmcExchangeLiquidity"],
115
+ ["additionalIndicators", "baseContext", "relative", "referenceTradeFlow"]
116
+ ];
117
+ if (skippedPrefixes.some((prefix) => isFeaturePathPrefix(segments, prefix))) {
118
+ return true;
119
+ }
120
+ const leaf = segments.at(-1)?.toLowerCase() ?? "";
121
+ if (leaf === "asofts" || leaf === "timestamp" || leaf.endsWith("timestamp")) {
122
+ return true;
123
+ }
124
+ if (isFeaturePathPrefix(segments, [
125
+ "additionalIndicators",
126
+ "baseContext",
127
+ "derivatives"
128
+ ]) && [
129
+ "source",
130
+ "symbol",
131
+ "timestamp",
132
+ "targetSymbol",
133
+ "primaryReferenceSymbol",
134
+ "secondaryReferenceSymbol"
135
+ ].includes(segments[3] ?? "")) {
136
+ return true;
137
+ }
138
+ return false;
139
+ };
140
+ var addFlattenedFeatures = ({
141
+ output,
142
+ value,
143
+ segments,
144
+ maxDepth,
145
+ shouldSkipPath = () => false
146
+ }) => {
147
+ if (!segments.length && !isPlainRecord(value)) {
148
+ return;
149
+ }
150
+ if (segments.length > maxDepth || isOutcomePath(segments) || shouldSkipPath(segments)) {
151
+ return;
152
+ }
153
+ if (Array.isArray(value)) {
154
+ return;
155
+ }
156
+ if (isFeaturePrimitive(value)) {
157
+ const normalized = normalizeFeaturePrimitive(value);
158
+ if (normalized !== void 0 && segments.length) {
159
+ output[segments.join(".")] = normalized;
160
+ }
161
+ return;
162
+ }
163
+ if (!isPlainRecord(value)) {
164
+ return;
165
+ }
166
+ for (const [key, child] of Object.entries(value)) {
167
+ if (!key || key.startsWith("_")) {
168
+ continue;
169
+ }
170
+ addFlattenedFeatures({
171
+ output,
172
+ value: child,
173
+ segments: [...segments, key],
174
+ maxDepth,
175
+ shouldSkipPath
176
+ });
177
+ }
178
+ };
179
+ var findFeatureNumber = (features, aliases, requiredPathFragments = []) => {
180
+ const normalizedAliases = new Set(
181
+ aliases.map((alias) => alias.toLowerCase())
182
+ );
183
+ const normalizedFragments = requiredPathFragments.map(
184
+ (fragment) => fragment.toLowerCase()
185
+ );
186
+ for (const [key, value] of Object.entries(features)) {
187
+ if (!isFiniteNumber(value)) {
188
+ continue;
189
+ }
190
+ const keyLower = key.toLowerCase();
191
+ if (normalizedFragments.some((fragment) => !keyLower.includes(fragment))) {
192
+ continue;
193
+ }
194
+ const lastSegment = keyLower.split(".").at(-1) ?? keyLower;
195
+ if (normalizedAliases.has(lastSegment)) {
196
+ return value;
197
+ }
198
+ }
199
+ return null;
200
+ };
201
+ var findFeatureString = (features, aliases) => {
202
+ const normalizedAliases = new Set(
203
+ aliases.map((alias) => alias.toLowerCase())
204
+ );
205
+ for (const [key, value] of Object.entries(features)) {
206
+ if (typeof value !== "string") {
207
+ continue;
208
+ }
209
+ const lastSegment = key.toLowerCase().split(".").at(-1) ?? key;
210
+ if (normalizedAliases.has(lastSegment)) {
211
+ return value;
212
+ }
213
+ }
214
+ return null;
215
+ };
216
+ var addDirectionalDerivedFeatures = (features) => {
217
+ const direction = String(features["signal.direction"] ?? "").toUpperCase();
218
+ const directionSign = direction === "LONG" ? 1 : direction === "SHORT" ? -1 : null;
219
+ if (directionSign == null) {
220
+ return;
221
+ }
222
+ const currentPrice = findFeatureNumber(features, [
223
+ "current",
224
+ "currentprice",
225
+ "close",
226
+ "last",
227
+ "lastprice",
228
+ "price"
229
+ ]);
230
+ const maFast = findFeatureNumber(features, [
231
+ "fastma",
232
+ "mafast",
233
+ "ma_fast",
234
+ "emafast",
235
+ "emafastvalue",
236
+ "smafast"
237
+ ]);
238
+ const maSlow = findFeatureNumber(features, [
239
+ "slowma",
240
+ "maslow",
241
+ "ma_slow",
242
+ "emaslow",
243
+ "emaslowvalue",
244
+ "smaslow"
245
+ ]);
246
+ const macdHistogram = findFeatureNumber(
247
+ features,
248
+ ["histogram", "hist", "macdhistogram", "macdhist"],
249
+ ["macd"]
250
+ );
251
+ const macdHistogramSlope = findFeatureNumber(
252
+ features,
253
+ ["histogramslope", "histogramdelta", "slope", "delta"],
254
+ ["macd"]
255
+ );
256
+ const obvSlope = findFeatureNumber(
257
+ features,
258
+ ["slope", "delta", "change", "obvslope", "obvdelta"],
259
+ ["obv"]
260
+ );
261
+ const obvTrend = findFeatureString(features, [
262
+ "obvtrend",
263
+ "trend",
264
+ "obvdirection"
265
+ ]);
266
+ let supportCount = 0;
267
+ if (currentPrice != null && maFast != null && currentPrice !== 0) {
268
+ const signedDistanceBps = (currentPrice - maFast) / Math.abs(currentPrice) * 1e4 * directionSign;
269
+ const aligned = signedDistanceBps >= 0;
270
+ features["derived.maFastAligned"] = aligned;
271
+ features["derived.priceMaFastDistanceBps"] = signedDistanceBps;
272
+ if (aligned) {
273
+ supportCount += 1;
274
+ }
275
+ }
276
+ if (currentPrice != null && maSlow != null && currentPrice !== 0) {
277
+ const signedDistanceBps = (currentPrice - maSlow) / Math.abs(currentPrice) * 1e4 * directionSign;
278
+ const aligned = signedDistanceBps >= 0;
279
+ features["derived.maSlowAligned"] = aligned;
280
+ features["derived.priceMaSlowDistanceBps"] = signedDistanceBps;
281
+ if (aligned) {
282
+ supportCount += 1;
283
+ }
284
+ }
285
+ if (maFast != null && maSlow != null) {
286
+ const signedDistance = (maFast - maSlow) * directionSign;
287
+ const aligned = signedDistance >= 0;
288
+ features["derived.maStackAligned"] = aligned;
289
+ features["derived.maFastSlowDistanceSigned"] = signedDistance;
290
+ if (aligned) {
291
+ supportCount += 1;
292
+ }
293
+ }
294
+ if (macdHistogram != null) {
295
+ const signedHistogram = macdHistogram * directionSign;
296
+ const aligned = signedHistogram >= 0;
297
+ features["derived.macdHistogramAligned"] = aligned;
298
+ features["derived.macdHistogramSigned"] = signedHistogram;
299
+ if (aligned) {
300
+ supportCount += 1;
301
+ }
302
+ }
303
+ if (macdHistogramSlope != null) {
304
+ const signedSlope = macdHistogramSlope * directionSign;
305
+ const aligned = signedSlope >= 0;
306
+ features["derived.macdHistogramSlopeAligned"] = aligned;
307
+ features["derived.macdHistogramSlopeSigned"] = signedSlope;
308
+ if (aligned) {
309
+ supportCount += 1;
310
+ }
311
+ }
312
+ if (obvSlope != null) {
313
+ const signedSlope = obvSlope * directionSign;
314
+ const aligned = signedSlope >= 0;
315
+ features["derived.obvSlopeAligned"] = aligned;
316
+ features["derived.obvSlopeSigned"] = signedSlope;
317
+ if (aligned) {
318
+ supportCount += 1;
319
+ }
320
+ } else if (obvTrend) {
321
+ const normalizedTrend = obvTrend.toLowerCase();
322
+ const aligned = direction === "LONG" && ["up", "rising", "bullish", "positive"].includes(normalizedTrend) || direction === "SHORT" && ["down", "falling", "bearish", "negative"].includes(normalizedTrend);
323
+ features["derived.obvTrendAligned"] = aligned;
324
+ if (aligned) {
325
+ supportCount += 1;
326
+ }
327
+ }
328
+ features["derived.directIndicatorSupportCount"] = supportCount;
329
+ };
330
+ var collectAiPocketFeatures = ({
331
+ payload,
332
+ gateContext,
333
+ includeSymbol = false,
334
+ includeGateContext = false,
335
+ featureProfile = "all"
336
+ }) => {
337
+ const features = {};
338
+ const signal = payload.signal ?? {};
339
+ const source = {
340
+ signal: {
341
+ direction: signal.direction,
342
+ interval: signal.interval,
343
+ strategy: signal.strategy,
344
+ ...includeSymbol ? { symbol: signal.symbol } : {}
345
+ },
346
+ indicators: payload.indicators,
347
+ additionalIndicators: payload.additionalIndicators,
348
+ ...includeGateContext ? { gate: gateContext } : {}
349
+ };
350
+ addFlattenedFeatures({
351
+ output: features,
352
+ value: source,
353
+ segments: [],
354
+ maxDepth: 8,
355
+ shouldSkipPath: featureProfile === "compact" ? isCompactFeaturePathSkipped : void 0
356
+ });
357
+ addDirectionalDerivedFeatures(features);
358
+ return features;
359
+ };
360
+ var formatNumber = (value) => {
361
+ if (Number.isInteger(value)) {
362
+ return String(value);
363
+ }
364
+ const abs = Math.abs(value);
365
+ if (abs > 0 && abs < 1e-3) {
366
+ return value.toExponential(3);
367
+ }
368
+ return Number(value.toPrecision(6)).toString();
369
+ };
370
+ var formatPredicateValue = (value) => {
371
+ if (typeof value === "number") {
372
+ return formatNumber(value);
373
+ }
374
+ if (typeof value === "string") {
375
+ return JSON.stringify(value);
376
+ }
377
+ return String(value);
378
+ };
379
+ var roundThreshold = (value) => Number(value.toPrecision(8));
380
+ var quantileAt = (values, quantile) => {
381
+ const index = Math.floor((values.length - 1) * quantile);
382
+ return values[Math.max(0, Math.min(values.length - 1, index))];
383
+ };
384
+ var getPeriodDays = (rows) => {
385
+ let minTimestamp = null;
386
+ let maxTimestamp = null;
387
+ for (const row of rows) {
388
+ const timestamp = typeof row.timestamp === "number" && Number.isFinite(row.timestamp) ? row.timestamp : null;
389
+ if (timestamp == null) {
390
+ continue;
391
+ }
392
+ minTimestamp = minTimestamp == null ? timestamp : Math.min(minTimestamp, timestamp);
393
+ maxTimestamp = maxTimestamp == null ? timestamp : Math.max(maxTimestamp, timestamp);
394
+ }
395
+ if (minTimestamp == null || maxTimestamp == null) {
396
+ return null;
397
+ }
398
+ return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
399
+ };
400
+ var createSummaryAccumulator = () => ({
401
+ support: 0,
402
+ grossProfit: 0,
403
+ grossLoss: 0,
404
+ wins: 0,
405
+ directionCounts: /* @__PURE__ */ new Map(),
406
+ symbolCounts: /* @__PURE__ */ new Map(),
407
+ monthProfits: /* @__PURE__ */ new Map(),
408
+ equityPoints: []
409
+ });
410
+ var addSummaryRow = (accumulator, row) => {
411
+ const profit = Number(row.profit);
412
+ accumulator.support += 1;
413
+ if (profit > 0) {
414
+ accumulator.grossProfit += profit;
415
+ accumulator.wins += 1;
416
+ } else if (profit < 0) {
417
+ accumulator.grossLoss += Math.abs(profit);
418
+ }
419
+ const direction = typeof row.direction === "string" && row.direction.trim() ? row.direction : "UNKNOWN";
420
+ accumulator.directionCounts.set(
421
+ direction,
422
+ (accumulator.directionCounts.get(direction) ?? 0) + 1
423
+ );
424
+ const symbol = typeof row.symbol === "string" && row.symbol.trim() ? row.symbol : "UNKNOWN";
425
+ const symbolBucket = accumulator.symbolCounts.get(symbol) ?? {
426
+ count: 0,
427
+ totalProfit: 0
428
+ };
429
+ symbolBucket.count += 1;
430
+ symbolBucket.totalProfit += profit;
431
+ accumulator.symbolCounts.set(symbol, symbolBucket);
432
+ const timestamp = typeof row.timestamp === "number" && Number.isFinite(row.timestamp) ? row.timestamp : Number.POSITIVE_INFINITY;
433
+ const month = Number.isFinite(timestamp) && timestamp !== Number.POSITIVE_INFINITY ? new Date(timestamp).toISOString().slice(0, 7) : "UNKNOWN";
434
+ accumulator.monthProfits.set(
435
+ month,
436
+ (accumulator.monthProfits.get(month) ?? 0) + profit
437
+ );
438
+ accumulator.equityPoints.push({ timestamp, profit });
439
+ };
440
+ var emptyAiPocketSummary = ({
441
+ fullPeriodDays,
442
+ supportRatio
443
+ }) => ({
444
+ support: 0,
445
+ supportRatio,
446
+ totalProfit: 0,
447
+ grossProfit: 0,
448
+ grossLoss: 0,
449
+ profitFactor: null,
450
+ winRate: null,
451
+ avgProfit: null,
452
+ maxDrawdown: 0,
453
+ maxDrawdownPctOfGrossProfit: null,
454
+ maxDrawdownPctOfTotalProfit: null,
455
+ recoveryFactor: null,
456
+ maxConsecutiveWins: 0,
457
+ maxConsecutiveLosses: 0,
458
+ avgTradesPerDay: fullPeriodDays == null ? null : 0,
459
+ avgTradesPerWeek: fullPeriodDays == null ? null : 0,
460
+ avgProfitPerDay: fullPeriodDays == null ? null : 0,
461
+ avgProfitPerMonth: fullPeriodDays == null ? null : 0,
462
+ losingMonths: 0,
463
+ worstMonth: null,
464
+ directionCounts: {},
465
+ topSymbols: []
466
+ });
467
+ var finalizeAiPocketSummary = ({
468
+ rows,
469
+ accumulator
470
+ }) => {
471
+ const fullPeriodDays = getPeriodDays(rows);
472
+ const support = accumulator.support;
473
+ const supportRatio = rows.length > 0 ? support / rows.length : 0;
474
+ if (support === 0) {
475
+ return {
476
+ ...emptyAiPocketSummary({ fullPeriodDays, supportRatio })
477
+ };
478
+ }
479
+ let equity = 0;
480
+ let peakEquity = 0;
481
+ let maxDrawdown = 0;
482
+ let currentWinStreak = 0;
483
+ let currentLossStreak = 0;
484
+ let maxConsecutiveWins = 0;
485
+ let maxConsecutiveLosses = 0;
486
+ accumulator.equityPoints.sort(
487
+ (left, right) => left.timestamp - right.timestamp
488
+ );
489
+ for (const point of accumulator.equityPoints) {
490
+ equity += point.profit;
491
+ peakEquity = Math.max(peakEquity, equity);
492
+ maxDrawdown = Math.max(maxDrawdown, Math.max(0, peakEquity - equity));
493
+ if (point.profit > 0) {
494
+ currentWinStreak += 1;
495
+ currentLossStreak = 0;
496
+ } else if (point.profit < 0) {
497
+ currentLossStreak += 1;
498
+ currentWinStreak = 0;
499
+ } else {
500
+ currentWinStreak = 0;
501
+ currentLossStreak = 0;
502
+ }
503
+ maxConsecutiveWins = Math.max(maxConsecutiveWins, currentWinStreak);
504
+ maxConsecutiveLosses = Math.max(maxConsecutiveLosses, currentLossStreak);
505
+ }
506
+ const grossProfit = accumulator.grossProfit;
507
+ const grossLoss = accumulator.grossLoss;
508
+ const totalProfit = grossProfit - grossLoss;
509
+ const avgTradesPerDay = fullPeriodDays == null ? null : support / fullPeriodDays;
510
+ const avgProfitPerDay = fullPeriodDays == null ? null : totalProfit / fullPeriodDays;
511
+ const losingMonthEntries = [...accumulator.monthProfits.entries()].filter(
512
+ ([, profit]) => profit < 0
513
+ );
514
+ const worstMonth = [...accumulator.monthProfits.entries()].sort(
515
+ (left, right) => left[1] - right[1]
516
+ )[0] ?? null;
517
+ return {
518
+ support,
519
+ supportRatio,
520
+ totalProfit,
521
+ grossProfit,
522
+ grossLoss,
523
+ profitFactor: grossLoss > 0 ? grossProfit / grossLoss : null,
524
+ winRate: support > 0 ? accumulator.wins / support : null,
525
+ avgProfit: support > 0 ? totalProfit / support : null,
526
+ maxDrawdown,
527
+ maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
528
+ maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
529
+ recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
530
+ maxConsecutiveWins,
531
+ maxConsecutiveLosses,
532
+ avgTradesPerDay,
533
+ avgTradesPerWeek: avgTradesPerDay == null ? null : avgTradesPerDay * DAYS_PER_WEEK,
534
+ avgProfitPerDay,
535
+ avgProfitPerMonth: avgProfitPerDay == null ? null : avgProfitPerDay * DAYS_PER_MONTH,
536
+ losingMonths: losingMonthEntries.length,
537
+ worstMonth: worstMonth ? {
538
+ month: worstMonth[0],
539
+ totalProfit: worstMonth[1]
540
+ } : null,
541
+ directionCounts: Object.fromEntries(accumulator.directionCounts.entries()),
542
+ topSymbols: [...accumulator.symbolCounts.entries()].map(([symbol, value]) => ({
543
+ symbol,
544
+ ...value
545
+ })).sort(
546
+ (left, right) => right.count - left.count || Math.abs(right.totalProfit) - Math.abs(left.totalProfit)
547
+ ).slice(0, 5)
548
+ };
549
+ };
550
+ var summarizeSelectedRows = (rows, selected) => {
551
+ const accumulator = createSummaryAccumulator();
552
+ for (const row of selected) {
553
+ addSummaryRow(accumulator, row);
554
+ }
555
+ return finalizeAiPocketSummary({ rows, accumulator });
556
+ };
557
+ var summarizeMask = (rows, mask) => {
558
+ const accumulator = createSummaryAccumulator();
559
+ for (let index = 0; index < rows.length; index += 1) {
560
+ if (mask[index] === 1) {
561
+ addSummaryRow(accumulator, rows[index]);
562
+ }
563
+ }
564
+ return finalizeAiPocketSummary({ rows, accumulator });
565
+ };
566
+ var summarizeRowIndexes = (rows, rowIndexes) => {
567
+ const accumulator = createSummaryAccumulator();
568
+ for (const rowIndex of rowIndexes) {
569
+ addSummaryRow(accumulator, rows[rowIndex]);
570
+ }
571
+ return finalizeAiPocketSummary({ rows, accumulator });
572
+ };
573
+ var summarizeAiPocketRows = (rows) => summarizeSelectedRows(rows, rows);
574
+ var matchesPredicate = (value, predicate) => {
575
+ if (predicate.kind === "numeric") {
576
+ if (!isFiniteNumber(value)) {
577
+ return false;
578
+ }
579
+ return predicate.op === "<=" ? value <= predicate.threshold : value >= predicate.threshold;
580
+ }
581
+ return value === predicate.value;
582
+ };
583
+ var buildMask = (rows, predicate) => {
584
+ const mask = new Uint8Array(rows.length);
585
+ let support = 0;
586
+ rows.forEach((row, index) => {
587
+ if (matchesPredicate(row.features[predicate.featureKey], predicate)) {
588
+ mask[index] = 1;
589
+ support += 1;
590
+ }
591
+ });
592
+ return { mask, support };
593
+ };
594
+ var intersectMasks = (left, right) => {
595
+ const mask = new Uint8Array(left.length);
596
+ let support = 0;
597
+ for (let index = 0; index < left.length; index += 1) {
598
+ if (left[index] === 1 && right[index] === 1) {
599
+ mask[index] = 1;
600
+ support += 1;
601
+ }
602
+ }
603
+ return { mask, support };
604
+ };
605
+ var buildPredicateListMask = (rows, predicates) => {
606
+ const mask = new Uint8Array(rows.length);
607
+ let support = 0;
608
+ rows.forEach((row, index) => {
609
+ if (predicates.every(
610
+ (predicate) => matchesPredicate(row.features[predicate.featureKey], predicate)
611
+ )) {
612
+ mask[index] = 1;
613
+ support += 1;
614
+ }
615
+ });
616
+ return { mask, support };
617
+ };
618
+ var toPublicPredicate = (predicate) => {
619
+ if (predicate.kind === "numeric") {
620
+ return {
621
+ id: predicate.id,
622
+ featureKey: predicate.featureKey,
623
+ label: predicate.label,
624
+ kind: predicate.kind,
625
+ op: predicate.op,
626
+ threshold: predicate.threshold
627
+ };
628
+ }
629
+ return {
630
+ id: predicate.id,
631
+ featureKey: predicate.featureKey,
632
+ label: predicate.label,
633
+ kind: predicate.kind,
634
+ op: predicate.op,
635
+ value: predicate.value
636
+ };
637
+ };
638
+ var buildAiPocketPredicateResult = (rows, options = {}) => {
639
+ const minSupport = Math.max(1, Math.trunc(options.minSupport ?? 20));
640
+ const maxCategories = Math.max(2, Math.trunc(options.maxCategories ?? 24));
641
+ const progressInterval = Math.max(
642
+ 1,
643
+ Math.trunc(options.progressInterval ?? 500)
644
+ );
645
+ const onProgress = options.onProgress;
646
+ const buckets = /* @__PURE__ */ new Map();
647
+ let lastFeatureProgress = 0;
648
+ let lastPredicateProgress = 0;
649
+ const emitProgress = (phase, current, total, done = false) => {
650
+ if (!onProgress) {
651
+ return;
652
+ }
653
+ const lastProgress = phase === "features" ? lastFeatureProgress : lastPredicateProgress;
654
+ if (!done && current - lastProgress < progressInterval) {
655
+ return;
656
+ }
657
+ if (phase === "features") {
658
+ lastFeatureProgress = current;
659
+ } else if (phase === "predicates") {
660
+ lastPredicateProgress = current;
661
+ }
662
+ onProgress({
663
+ phase,
664
+ current,
665
+ total,
666
+ done,
667
+ truncated: false
668
+ });
669
+ };
670
+ rows.forEach((row, rowIndex) => {
671
+ for (const [key, value] of Object.entries(row.features)) {
672
+ if (value === void 0) {
673
+ continue;
674
+ }
675
+ const bucket = buckets.get(key) ?? {
676
+ key,
677
+ count: 0,
678
+ numericValues: [],
679
+ numericRowIndexes: [],
680
+ categoryCounts: /* @__PURE__ */ new Map()
681
+ };
682
+ bucket.count += 1;
683
+ if (isFiniteNumber(value)) {
684
+ bucket.numericValues.push(value);
685
+ bucket.numericRowIndexes.push(rowIndex);
686
+ }
687
+ if (typeof value === "string" || typeof value === "boolean" || value === null) {
688
+ const serialized = JSON.stringify(value);
689
+ const categoryBucket = bucket.categoryCounts.get(serialized) ?? {
690
+ value,
691
+ count: 0,
692
+ rowIndexes: []
693
+ };
694
+ categoryBucket.count += 1;
695
+ categoryBucket.rowIndexes.push(rowIndex);
696
+ bucket.categoryCounts.set(serialized, categoryBucket);
697
+ }
698
+ buckets.set(key, bucket);
699
+ }
700
+ emitProgress("features", rowIndex + 1, rows.length);
701
+ });
702
+ emitProgress("features", rows.length, rows.length, true);
703
+ const bucketList = [...buckets.values()].sort(
704
+ (left, right) => left.key.localeCompare(right.key)
705
+ );
706
+ const predicates = [];
707
+ for (let bucketIndex = 0; bucketIndex < bucketList.length; bucketIndex += 1) {
708
+ const bucket = bucketList[bucketIndex];
709
+ const key = bucket.key;
710
+ if (bucket.count < minSupport) {
711
+ emitProgress("predicates", bucketIndex + 1, bucketList.length);
712
+ continue;
713
+ }
714
+ const numericEntries = bucket.numericValues.map((value, index) => ({
715
+ value,
716
+ rowIndex: bucket.numericRowIndexes[index]
717
+ })).sort((left, right) => left.value - right.value);
718
+ const numericValues = numericEntries.map((entry) => entry.value);
719
+ if (numericValues.length >= minSupport) {
720
+ const uniqueValues = [...new Set(numericValues.map(roundThreshold))];
721
+ if (uniqueValues.length > 1) {
722
+ const thresholdSet = /* @__PURE__ */ new Set();
723
+ [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9].forEach(
724
+ (quantile) => thresholdSet.add(roundThreshold(quantileAt(numericValues, quantile)))
725
+ );
726
+ if (numericValues[0] < 0 && numericValues.at(-1) > 0) {
727
+ thresholdSet.add(0);
728
+ }
729
+ const thresholds = [...thresholdSet].sort((left, right) => {
730
+ return left - right;
731
+ });
732
+ const summaryByPredicate = /* @__PURE__ */ new Map();
733
+ const lessOrEqualAccumulator = createSummaryAccumulator();
734
+ let lessOrEqualIndex = 0;
735
+ for (const threshold of thresholds) {
736
+ while (lessOrEqualIndex < numericEntries.length && numericEntries[lessOrEqualIndex].value <= threshold) {
737
+ addSummaryRow(
738
+ lessOrEqualAccumulator,
739
+ rows[numericEntries[lessOrEqualIndex].rowIndex]
740
+ );
741
+ lessOrEqualIndex += 1;
742
+ }
743
+ const support = lessOrEqualAccumulator.support;
744
+ if (support >= minSupport && support < rows.length) {
745
+ summaryByPredicate.set(`<=:${threshold}`, {
746
+ support,
747
+ summary: finalizeAiPocketSummary({
748
+ rows,
749
+ accumulator: lessOrEqualAccumulator
750
+ })
751
+ });
752
+ }
753
+ }
754
+ const greaterOrEqualAccumulator = createSummaryAccumulator();
755
+ let greaterOrEqualIndex = numericEntries.length - 1;
756
+ for (const threshold of [...thresholds].reverse()) {
757
+ while (greaterOrEqualIndex >= 0 && numericEntries[greaterOrEqualIndex].value >= threshold) {
758
+ addSummaryRow(
759
+ greaterOrEqualAccumulator,
760
+ rows[numericEntries[greaterOrEqualIndex].rowIndex]
761
+ );
762
+ greaterOrEqualIndex -= 1;
763
+ }
764
+ const support = greaterOrEqualAccumulator.support;
765
+ if (support >= minSupport && support < rows.length) {
766
+ summaryByPredicate.set(`>=:${threshold}`, {
767
+ support,
768
+ summary: finalizeAiPocketSummary({
769
+ rows,
770
+ accumulator: greaterOrEqualAccumulator
771
+ })
772
+ });
773
+ }
774
+ }
775
+ for (const threshold of thresholds) {
776
+ for (const op of ["<=", ">="]) {
777
+ const scored = summaryByPredicate.get(`${op}:${threshold}`);
778
+ if (!scored) {
779
+ continue;
780
+ }
781
+ predicates.push({
782
+ id: `${key}${op}${formatNumber(threshold)}`,
783
+ featureKey: key,
784
+ label: `${key} ${op} ${formatNumber(threshold)}`,
785
+ kind: "numeric",
786
+ op,
787
+ threshold,
788
+ support: scored.support,
789
+ atomSummary: scored.summary
790
+ });
791
+ }
792
+ }
793
+ }
794
+ emitProgress("predicates", bucketIndex + 1, bucketList.length);
795
+ continue;
796
+ }
797
+ if (!bucket.categoryCounts.size || bucket.categoryCounts.size > maxCategories) {
798
+ emitProgress("predicates", bucketIndex + 1, bucketList.length);
799
+ continue;
800
+ }
801
+ for (const { value, count, rowIndexes } of bucket.categoryCounts.values()) {
802
+ if (count < minSupport || count >= rows.length) {
803
+ continue;
804
+ }
805
+ predicates.push({
806
+ id: `${key}==${formatPredicateValue(value)}`,
807
+ featureKey: key,
808
+ label: `${key} == ${formatPredicateValue(value)}`,
809
+ kind: "category",
810
+ op: "==",
811
+ value,
812
+ support: count,
813
+ atomSummary: summarizeRowIndexes(rows, rowIndexes)
814
+ });
815
+ }
816
+ emitProgress("predicates", bucketIndex + 1, bucketList.length);
817
+ }
818
+ emitProgress("predicates", bucketList.length, bucketList.length, true);
819
+ return { featureKeys: buckets.size, predicates };
820
+ };
821
+ var buildAiPocketPredicates = (rows, options = {}) => buildAiPocketPredicateResult(rows, options).predicates.map(toPublicPredicate);
822
+ var scorePositivePocket = (summary) => {
823
+ const profitFactor = summary.profitFactor ?? (summary.grossLoss === 0 ? 8 : 0);
824
+ const winRate = summary.winRate ?? 0;
825
+ return summary.totalProfit - summary.maxDrawdown * 0.6 + Math.min(profitFactor, 8) * 12 + winRate * 30 + Math.log10(summary.support + 1) * 6;
826
+ };
827
+ var scoreNegativePocket = (summary) => -summary.totalProfit + summary.maxDrawdown * 0.4 + summary.grossLoss * 0.2 + summary.maxConsecutiveLosses * 3;
828
+ var comparePositivePockets = (left, right) => (right.validationScore ?? right.score) - (left.validationScore ?? left.score) || right.score - left.score || (right.validationSummary?.totalProfit ?? right.summary.totalProfit) - (left.validationSummary?.totalProfit ?? left.summary.totalProfit) || right.summary.totalProfit - left.summary.totalProfit || (right.summary.profitFactor ?? 999) - (left.summary.profitFactor ?? 999) || right.summary.support - left.summary.support;
829
+ var compareNegativePockets = (left, right) => (right.validationScore ?? right.score) - (left.validationScore ?? left.score) || right.score - left.score || (left.validationSummary?.totalProfit ?? left.summary.totalProfit) - (right.validationSummary?.totalProfit ?? right.summary.totalProfit) || left.summary.totalProfit - right.summary.totalProfit || right.summary.grossLoss - left.summary.grossLoss || right.summary.support - left.summary.support;
830
+ var createPocketResult = (rows, predicates, mask, validationRows) => {
831
+ const publicPredicates = predicates.map(toPublicPredicate);
832
+ const summary = summarizeMask(rows, mask);
833
+ const validationMask = validationRows.length ? buildPredicateListMask(validationRows, publicPredicates).mask : null;
834
+ const validationSummary = validationMask == null ? void 0 : summarizeMask(validationRows, validationMask);
835
+ const condition = publicPredicates.map((predicate) => predicate.label).join(" AND ");
836
+ const validationScore = validationSummary == null ? void 0 : validationSummary.support > 0 ? scorePositivePocket(validationSummary) : Number.NEGATIVE_INFINITY;
837
+ return {
838
+ id: publicPredicates.map((predicate) => predicate.id).join("&&"),
839
+ depth: publicPredicates.length,
840
+ predicates: publicPredicates,
841
+ condition,
842
+ summary,
843
+ ...validationSummary ? { validationSummary } : {},
844
+ ...validationScore != null ? { validationScore } : {},
845
+ score: scorePositivePocket(summary)
846
+ };
847
+ };
848
+ var hashMask = (mask) => {
849
+ let hash = 2166136261;
850
+ for (let index = 0; index < mask.length; index += 1) {
851
+ if (mask[index] !== 1) {
852
+ continue;
853
+ }
854
+ hash ^= index + 1;
855
+ hash = Math.imul(hash, 16777619);
856
+ }
857
+ return (hash >>> 0).toString(36);
858
+ };
859
+ var isBetterRepresentativePocket = (candidate, existing, compare) => candidate.depth < existing.depth || candidate.depth === existing.depth && candidate.condition.length < existing.condition.length || candidate.depth === existing.depth && candidate.condition.length === existing.condition.length && compare(candidate, existing) < 0;
860
+ var estimateCombinationCount = (poolSize, maxDepth, maxCombinations) => {
861
+ if (poolSize <= 0 || maxCombinations <= 0) {
862
+ return 0;
863
+ }
864
+ let total = 0;
865
+ let combinationsAtDepth = 1;
866
+ const depthLimit = Math.min(poolSize, maxDepth);
867
+ for (let depth = 1; depth <= depthLimit; depth += 1) {
868
+ combinationsAtDepth = combinationsAtDepth * (poolSize - depth + 1) / depth;
869
+ total += combinationsAtDepth;
870
+ if (total >= maxCombinations) {
871
+ return maxCombinations;
872
+ }
873
+ }
874
+ return Math.min(Math.trunc(total), maxCombinations);
875
+ };
876
+ var searchAiPockets = (rows, options = {}) => {
877
+ const minSupport = Math.max(1, Math.trunc(options.minSupport ?? 20));
878
+ const minProfitFactor = Math.max(0, options.minProfitFactor ?? 1.2);
879
+ const minTotalProfit = options.minTotalProfit ?? 0;
880
+ const minWinRate = Math.max(0, options.minWinRate ?? 0);
881
+ const maxDepth = Math.max(1, Math.trunc(options.maxDepth ?? 2));
882
+ const maxAtomicPredicates = Math.max(
883
+ 1,
884
+ Math.trunc(options.maxAtomicPredicates ?? 180)
885
+ );
886
+ const maxCombinations = Math.max(
887
+ 0,
888
+ Math.trunc(options.maxCombinations ?? 6e4)
889
+ );
890
+ const top = Math.max(1, Math.trunc(options.top ?? 50));
891
+ const validationRows = options.validationRows ?? [];
892
+ const minValidationSupport = Math.max(
893
+ 0,
894
+ Math.trunc(options.minValidationSupport ?? 0)
895
+ );
896
+ const dedupeEquivalentSelections = options.dedupeEquivalentSelections !== false;
897
+ const progressInterval = Math.max(
898
+ 1,
899
+ Math.trunc(options.progressInterval ?? 500)
900
+ );
901
+ const onProgress = options.onProgress;
902
+ const predicateResult = buildAiPocketPredicateResult(rows, {
903
+ minSupport,
904
+ maxCategories: options.maxCategories,
905
+ progressInterval,
906
+ onProgress
907
+ });
908
+ const { predicates } = predicateResult;
909
+ let lastMaskProgress = 0;
910
+ const emitMaskProgress = (current, done = false) => {
911
+ if (!onProgress) {
912
+ return;
913
+ }
914
+ if (!done && current - lastMaskProgress < progressInterval) {
915
+ return;
916
+ }
917
+ lastMaskProgress = current;
918
+ onProgress({
919
+ phase: "masks",
920
+ current,
921
+ total: predicates.length,
922
+ done,
923
+ truncated: false
924
+ });
925
+ };
926
+ predicates.forEach((_, index) => emitMaskProgress(index + 1));
927
+ const scoredPredicates = predicates;
928
+ emitMaskProgress(predicates.length, true);
929
+ const predicatePool = [...scoredPredicates].sort(
930
+ (left, right) => scorePositivePocket(right.atomSummary) - scorePositivePocket(left.atomSummary)
931
+ ).slice(0, Math.ceil(maxAtomicPredicates * 0.55));
932
+ const negativePool = [...scoredPredicates].sort(
933
+ (left, right) => scoreNegativePocket(right.atomSummary) - scoreNegativePocket(left.atomSummary)
934
+ ).slice(0, Math.ceil(maxAtomicPredicates * 0.3));
935
+ const supportPool = [...scoredPredicates].sort((left, right) => right.support - left.support).slice(0, Math.ceil(maxAtomicPredicates * 0.2));
936
+ const predicatePoolById = /* @__PURE__ */ new Map();
937
+ [...predicatePool, ...negativePool, ...supportPool].forEach((predicate) => {
938
+ if (predicatePoolById.size < maxAtomicPredicates) {
939
+ predicatePoolById.set(predicate.id, predicate);
940
+ }
941
+ });
942
+ const selectedPredicatePool = [...predicatePoolById.values()].map(
943
+ (predicate) => {
944
+ const { mask, support } = buildMask(rows, predicate);
945
+ return {
946
+ ...predicate,
947
+ mask,
948
+ support
949
+ };
950
+ }
951
+ );
952
+ const estimatedCombinations = estimateCombinationCount(
953
+ selectedPredicatePool.length,
954
+ maxDepth,
955
+ maxCombinations
956
+ );
957
+ const positivePockets = /* @__PURE__ */ new Map();
958
+ const negativePockets = /* @__PURE__ */ new Map();
959
+ const positiveSelectionKeys = /* @__PURE__ */ new Map();
960
+ const negativeSelectionKeys = /* @__PURE__ */ new Map();
961
+ let combinationsEvaluated = 0;
962
+ let duplicatePocketsSkipped = 0;
963
+ let truncated = false;
964
+ let lastProgressCombinations = 0;
965
+ const emitProgress = (done = false) => {
966
+ if (!onProgress) {
967
+ return;
968
+ }
969
+ if (!done && combinationsEvaluated - lastProgressCombinations < progressInterval) {
970
+ return;
971
+ }
972
+ lastProgressCombinations = combinationsEvaluated;
973
+ onProgress({
974
+ phase: "combinations",
975
+ current: Math.min(combinationsEvaluated, estimatedCombinations),
976
+ total: estimatedCombinations,
977
+ done,
978
+ truncated
979
+ });
980
+ };
981
+ const addPocket = (pocketPredicates, mask) => {
982
+ const pocket = createPocketResult(
983
+ rows,
984
+ pocketPredicates,
985
+ mask,
986
+ validationRows
987
+ );
988
+ const { summary } = pocket;
989
+ const profitFactor = summary.profitFactor ?? (summary.grossLoss === 0 && summary.totalProfit > 0 ? Number.POSITIVE_INFINITY : 0);
990
+ const winRate = summary.winRate ?? 0;
991
+ const validationSupport = pocket.validationSummary?.support ?? 0;
992
+ const validationEligible = !validationRows.length || validationSupport >= minValidationSupport;
993
+ if (summary.support >= minSupport && summary.totalProfit >= minTotalProfit && profitFactor >= minProfitFactor && winRate >= minWinRate && validationEligible) {
994
+ const selectionKey = `${summary.support}:${hashMask(mask)}`;
995
+ const mapKey = dedupeEquivalentSelections ? selectionKey : pocket.id;
996
+ const existing = dedupeEquivalentSelections ? positiveSelectionKeys.get(mapKey) : positivePockets.get(mapKey);
997
+ if (!existing || isBetterRepresentativePocket(pocket, existing, comparePositivePockets)) {
998
+ positivePockets.delete(existing?.id ?? "");
999
+ positivePockets.set(pocket.id, pocket);
1000
+ positiveSelectionKeys.set(mapKey, pocket);
1001
+ } else {
1002
+ duplicatePocketsSkipped += 1;
1003
+ }
1004
+ }
1005
+ if (summary.support >= minSupport && summary.totalProfit < 0) {
1006
+ const negativePocket = {
1007
+ ...pocket,
1008
+ score: scoreNegativePocket(summary),
1009
+ ...pocket.validationSummary ? { validationScore: scoreNegativePocket(pocket.validationSummary) } : {}
1010
+ };
1011
+ const selectionKey = `${summary.support}:${hashMask(mask)}`;
1012
+ const mapKey = dedupeEquivalentSelections ? selectionKey : pocket.id;
1013
+ const existing = dedupeEquivalentSelections ? negativeSelectionKeys.get(mapKey) : negativePockets.get(mapKey);
1014
+ if (!existing || isBetterRepresentativePocket(
1015
+ negativePocket,
1016
+ existing,
1017
+ compareNegativePockets
1018
+ )) {
1019
+ negativePockets.delete(existing?.id ?? "");
1020
+ negativePockets.set(negativePocket.id, negativePocket);
1021
+ negativeSelectionKeys.set(mapKey, negativePocket);
1022
+ } else {
1023
+ duplicatePocketsSkipped += 1;
1024
+ }
1025
+ }
1026
+ };
1027
+ const visit = ({
1028
+ startIndex,
1029
+ chosen,
1030
+ mask,
1031
+ usedFeatureKeys
1032
+ }) => {
1033
+ if (truncated) {
1034
+ return;
1035
+ }
1036
+ for (let index = startIndex; index < selectedPredicatePool.length; index += 1) {
1037
+ if (combinationsEvaluated >= maxCombinations) {
1038
+ truncated = true;
1039
+ return;
1040
+ }
1041
+ const predicate = selectedPredicatePool[index];
1042
+ if (usedFeatureKeys.has(predicate.featureKey)) {
1043
+ continue;
1044
+ }
1045
+ const intersection = mask == null ? { mask: predicate.mask, support: predicate.support } : intersectMasks(mask, predicate.mask);
1046
+ const nextMask = intersection.mask;
1047
+ combinationsEvaluated += 1;
1048
+ emitProgress();
1049
+ const support = intersection.support;
1050
+ if (support < minSupport) {
1051
+ continue;
1052
+ }
1053
+ const nextChosen = [...chosen, predicate];
1054
+ addPocket(nextChosen, nextMask);
1055
+ if (nextChosen.length >= maxDepth) {
1056
+ continue;
1057
+ }
1058
+ const nextUsedFeatureKeys = new Set(usedFeatureKeys);
1059
+ nextUsedFeatureKeys.add(predicate.featureKey);
1060
+ visit({
1061
+ startIndex: index + 1,
1062
+ chosen: nextChosen,
1063
+ mask: nextMask,
1064
+ usedFeatureKeys: nextUsedFeatureKeys
1065
+ });
1066
+ }
1067
+ };
1068
+ visit({
1069
+ startIndex: 0,
1070
+ chosen: [],
1071
+ mask: null,
1072
+ usedFeatureKeys: /* @__PURE__ */ new Set()
1073
+ });
1074
+ emitProgress(true);
1075
+ return {
1076
+ baseline: summarizeAiPocketRows(rows),
1077
+ ...validationRows.length ? { validationBaseline: summarizeAiPocketRows(validationRows) } : {},
1078
+ predicates: predicates.map(toPublicPredicate),
1079
+ positivePockets: [...positivePockets.values()].sort(comparePositivePockets).slice(0, top),
1080
+ negativePockets: [...negativePockets.values()].sort(compareNegativePockets).slice(0, top),
1081
+ stats: {
1082
+ rows: rows.length,
1083
+ featureKeys: predicateResult.featureKeys,
1084
+ predicates: predicates.length,
1085
+ atomicPredicatesUsed: selectedPredicatePool.length,
1086
+ estimatedCombinations,
1087
+ combinationsEvaluated,
1088
+ validationRows: validationRows.length,
1089
+ duplicatePocketsSkipped,
1090
+ truncated
1091
+ }
1092
+ };
1093
+ };
1094
+ var formatMdNumber = (value, digits = 2) => value == null || !Number.isFinite(value) ? "n/a" : value.toFixed(digits);
1095
+ var formatMdPercent = (value) => value == null || !Number.isFinite(value) ? "n/a" : `${(value * 100).toFixed(1)}%`;
1096
+ var escapeMarkdownCell = (value) => String(value ?? "n/a").replace(/\|/g, "\\|").replace(/\n/g, "<br>");
1097
+ var markdownTable = (headers, rows) => {
1098
+ const header = `| ${headers.map(escapeMarkdownCell).join(" | ")} |`;
1099
+ const divider = `| ${headers.map(() => "---").join(" | ")} |`;
1100
+ const body = rows.map(
1101
+ (row) => `| ${row.map(escapeMarkdownCell).join(" | ")} |`
1102
+ );
1103
+ return [header, divider, ...body].join("\n");
1104
+ };
1105
+ var summaryMetricRows = (summary) => [
1106
+ ["rows", summary.support],
1107
+ ["win_rate", formatMdPercent(summary.winRate)],
1108
+ ["total_profit", formatMdNumber(summary.totalProfit)],
1109
+ ["gross_profit", formatMdNumber(summary.grossProfit)],
1110
+ ["gross_loss", formatMdNumber(-summary.grossLoss)],
1111
+ ["profit_factor", formatMdNumber(summary.profitFactor)],
1112
+ ["max_drawdown", formatMdNumber(summary.maxDrawdown)],
1113
+ [
1114
+ "max_drawdown_pct_of_gross_profit",
1115
+ formatMdPercent(summary.maxDrawdownPctOfGrossProfit)
1116
+ ],
1117
+ [
1118
+ "max_drawdown_pct_of_total_profit",
1119
+ formatMdPercent(summary.maxDrawdownPctOfTotalProfit)
1120
+ ],
1121
+ ["max_consecutive_losses", summary.maxConsecutiveLosses],
1122
+ ["avg_trades_per_day", formatMdNumber(summary.avgTradesPerDay)],
1123
+ ["avg_trades_per_week", formatMdNumber(summary.avgTradesPerWeek)],
1124
+ ["avg_profit_per_day", formatMdNumber(summary.avgProfitPerDay)],
1125
+ ["avg_profit_per_month", formatMdNumber(summary.avgProfitPerMonth)],
1126
+ ["losing_months", summary.losingMonths],
1127
+ [
1128
+ "worst_month",
1129
+ summary.worstMonth ? `${summary.worstMonth.month} ${formatMdNumber(summary.worstMonth.totalProfit)}` : "n/a"
1130
+ ]
1131
+ ];
1132
+ var pocketRows = (pockets) => pockets.map((pocket, index) => [
1133
+ index + 1,
1134
+ pocket.summary.support,
1135
+ formatMdPercent(pocket.summary.supportRatio),
1136
+ formatMdPercent(pocket.summary.winRate),
1137
+ formatMdNumber(pocket.summary.profitFactor),
1138
+ formatMdNumber(pocket.summary.totalProfit),
1139
+ formatMdNumber(pocket.summary.maxDrawdown),
1140
+ pocket.validationSummary?.support ?? "n/a",
1141
+ pocket.validationSummary ? formatMdPercent(pocket.validationSummary.winRate) : "n/a",
1142
+ pocket.validationSummary ? formatMdNumber(pocket.validationSummary.profitFactor) : "n/a",
1143
+ pocket.validationSummary ? formatMdNumber(pocket.validationSummary.totalProfit) : "n/a",
1144
+ formatMdNumber(pocket.summary.avgTradesPerDay),
1145
+ pocket.summary.losingMonths,
1146
+ formatMdNumber(pocket.score),
1147
+ pocket.condition
1148
+ ]);
1149
+ var buildAiPocketMarkdownReport = ({
1150
+ generatedAt,
1151
+ run,
1152
+ currentGate,
1153
+ pocketSearch,
1154
+ errors
1155
+ }) => {
1156
+ const generatedIso = new Date(generatedAt).toISOString();
1157
+ const lines = [
1158
+ "# AI Pocket Search Report",
1159
+ "",
1160
+ `Generated: ${generatedIso}`,
1161
+ "",
1162
+ "## Run",
1163
+ "",
1164
+ markdownTable(
1165
+ ["Field", "Value"],
1166
+ [
1167
+ ["strategy", run.strategy],
1168
+ ["source_rows", run.sourceRows],
1169
+ ["selected_rows", run.selectedRows],
1170
+ ["evaluated_rows", run.evaluatedRows],
1171
+ ["scope", run.scope],
1172
+ ["scope_rows", run.scopeRows],
1173
+ ["train_rows", run.trainRows],
1174
+ ["validation_rows", run.validationRows],
1175
+ ["validation_split", formatMdPercent(run.validationSplit)],
1176
+ ["min_validation_support", run.minValidationSupport],
1177
+ ["failed", run.failed],
1178
+ ["recent", run.recent === 0 ? "all" : run.recent],
1179
+ ["skip", run.skip],
1180
+ [
1181
+ "since",
1182
+ run.since == null ? "n/a" : new Date(run.since).toISOString()
1183
+ ],
1184
+ [
1185
+ "until",
1186
+ run.until == null ? "n/a" : new Date(run.until).toISOString()
1187
+ ],
1188
+ ["period", run.period ?? "n/a"],
1189
+ ["min_quality", run.minQuality],
1190
+ ["max_depth", run.search.maxDepth],
1191
+ ["min_support", run.search.minSupport],
1192
+ ["max_atomic_predicates", run.search.maxAtomicPredicates],
1193
+ ["max_combinations", run.search.maxCombinations],
1194
+ ["include_symbol", run.includeSymbol ? "on" : "off"],
1195
+ ["include_gate_context", run.includeGateContext ? "on" : "off"],
1196
+ ["feature_profile", run.featureProfile ?? "all"],
1197
+ ["report_path", run.reportPath]
1198
+ ]
1199
+ ),
1200
+ "",
1201
+ "## Dataset Files",
1202
+ "",
1203
+ ...run.filePaths.map((filePath) => `- \`${filePath}\``),
1204
+ "",
1205
+ "## Current Gate qN+ Baseline",
1206
+ "",
1207
+ markdownTable(
1208
+ ["Q", "Approved", "WR", "PF", "PNL", "Max DD", "Trades/Day", "PNL/Day"],
1209
+ currentGate.qualityThresholds.map(({ label, summary }) => [
1210
+ label,
1211
+ summary.approved,
1212
+ formatMdPercent(summary.approvedRisk.winRate),
1213
+ formatMdNumber(summary.approvedRisk.profitFactor),
1214
+ formatMdNumber(summary.approvedRisk.totalProfit),
1215
+ formatMdNumber(summary.approvedRisk.maxDrawdown),
1216
+ formatMdNumber(summary.avgApprovedTradesPerDay),
1217
+ formatMdNumber(summary.avgProfitApprovedPerDay)
1218
+ ])
1219
+ ),
1220
+ "",
1221
+ "## Train Baseline",
1222
+ "",
1223
+ markdownTable(
1224
+ ["Metric", "Value"],
1225
+ summaryMetricRows(pocketSearch.baseline)
1226
+ ),
1227
+ "",
1228
+ ...pocketSearch.validationBaseline ? [
1229
+ "## Validation Baseline",
1230
+ "",
1231
+ markdownTable(
1232
+ ["Metric", "Value"],
1233
+ summaryMetricRows(pocketSearch.validationBaseline)
1234
+ ),
1235
+ ""
1236
+ ] : [],
1237
+ "## Search Stats",
1238
+ "",
1239
+ markdownTable(
1240
+ ["Field", "Value"],
1241
+ [
1242
+ ["feature_keys", pocketSearch.stats.featureKeys],
1243
+ ["predicates", pocketSearch.stats.predicates],
1244
+ ["atomic_used", pocketSearch.stats.atomicPredicatesUsed],
1245
+ ["estimated_combinations", pocketSearch.stats.estimatedCombinations],
1246
+ ["combinations_evaluated", pocketSearch.stats.combinationsEvaluated],
1247
+ ["validation_rows", pocketSearch.stats.validationRows],
1248
+ [
1249
+ "duplicate_pockets_skipped",
1250
+ pocketSearch.stats.duplicatePocketsSkipped
1251
+ ],
1252
+ ["truncated", pocketSearch.stats.truncated ? "yes" : "no"]
1253
+ ]
1254
+ ),
1255
+ "",
1256
+ "## Top Positive Pockets",
1257
+ "",
1258
+ markdownTable(
1259
+ [
1260
+ "#",
1261
+ "N",
1262
+ "Support",
1263
+ "WR",
1264
+ "PF",
1265
+ "PNL",
1266
+ "Max DD",
1267
+ "Val N",
1268
+ "Val WR",
1269
+ "Val PF",
1270
+ "Val PNL",
1271
+ "Trades/Day",
1272
+ "Losing Months",
1273
+ "Score",
1274
+ "Pocket"
1275
+ ],
1276
+ pocketRows(pocketSearch.positivePockets)
1277
+ ),
1278
+ "",
1279
+ "## Top Loss Pockets",
1280
+ "",
1281
+ markdownTable(
1282
+ [
1283
+ "#",
1284
+ "N",
1285
+ "Support",
1286
+ "WR",
1287
+ "PF",
1288
+ "PNL",
1289
+ "Max DD",
1290
+ "Val N",
1291
+ "Val WR",
1292
+ "Val PF",
1293
+ "Val PNL",
1294
+ "Trades/Day",
1295
+ "Losing Months",
1296
+ "Score",
1297
+ "Pocket"
1298
+ ],
1299
+ pocketRows(pocketSearch.negativePockets)
1300
+ ),
1301
+ ""
1302
+ ];
1303
+ if (errors.length) {
1304
+ lines.push("## Errors", "", ...errors.map((error) => `- ${error}`), "");
1305
+ }
1306
+ return `${lines.join("\n")}
1307
+ `;
1308
+ };
1309
+ // Annotate the CommonJS export names for ESM import in node:
1310
+ 0 && (module.exports = {
1311
+ buildAiPocketMarkdownReport,
1312
+ buildAiPocketPredicates,
1313
+ collectAiPocketFeatures,
1314
+ searchAiPockets,
1315
+ summarizeAiPocketRows
1316
+ });