@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.
- package/dist/cli.js +29497 -13537
- package/dist/lib/aiPocketSearch.js +1316 -0
- package/dist/lib/aiTrainCharts.js +711 -0
- package/dist/lib/aiTrainDataset.js +40 -0
- package/dist/lib/aiTrainEvaluationDump.js +76 -0
- package/dist/lib/aiTrainMetrics.js +148 -8
- package/dist/lib/aiTrainOptions.js +130 -0
- package/dist/lib/aiTrainQuarantine.js +199 -0
- package/dist/lib/aiTrainResearch.js +467 -0
- package/dist/lib/binanceMarketContextBackfill.js +841 -0
- package/dist/lib/binanceMarketData.js +392 -0
- package/dist/lib/coinMarketCapContextBackfill.js +1088 -0
- package/dist/lib/continuity.js +60 -0
- package/dist/lib/derivativesContextBackfill.js +490 -77
- package/dist/lib/executionCalibration.js +705 -0
- package/dist/lib/marketContextPrepare.js +4403 -0
- package/dist/lib/paritySummary.js +101 -0
- package/dist/lib/runBot.js +1 -0
- package/dist/lib/runEnvironment.js +2254 -0
- package/dist/lib/runFormatting.js +1657 -0
- package/dist/lib/runtimeDebugEvidence.js +526 -0
- package/dist/lib/runtimeModeConfig.js +53 -0
- package/dist/lib/runtimeParity.js +88 -11
- package/dist/lib/runtimeParityDetails.js +751 -0
- package/dist/lib/runtimeRedis.js +157 -0
- package/dist/lib/runtimeSignalsLoader.js +40 -6
- package/dist/lib/runtimeSignalsStorage.js +45 -22
- package/dist/lib/runtimeStrategyBacktest.js +134 -0
- package/dist/lib/runtimeTradeSync.js +263 -0
- package/dist/lib/telegramReports.js +9 -0
- package/dist/lib/tickerUniverseCache.js +116 -0
- package/dist/lib/timeWindow.js +3 -3
- package/dist/scripts/agentRun.js +14 -8
- package/dist/scripts/aiExport.js +1721 -7
- package/dist/scripts/aiExportSelect.js +16 -4
- package/dist/scripts/aiPocketSearch.js +4190 -0
- package/dist/scripts/aiTrain.js +1866 -87
- package/dist/scripts/backtest.js +4509 -818
- package/dist/scripts/binanceMarketIngest.js +565 -0
- package/dist/scripts/bot.js +16 -2
- package/dist/scripts/candlesMigrateProvider.js +32 -22
- package/dist/scripts/cleanDir.js +15 -2
- package/dist/scripts/cleanRedis.js +15 -2
- package/dist/scripts/cleanTests.js +38 -2
- package/dist/scripts/cleanupMarketContext.js +69 -0
- package/dist/scripts/continuity.js +148 -67
- package/dist/scripts/derivativesIngest.js +14 -4
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +14 -4
- package/dist/scripts/doctor.js +14 -4
- package/dist/scripts/executionCalibration.js +792 -0
- package/dist/scripts/infraCommon.js +10 -0
- package/dist/scripts/infraDown.js +14 -4
- package/dist/scripts/infraInit.js +24 -4
- package/dist/scripts/infraUp.js +14 -4
- package/dist/scripts/marketWs.js +222 -0
- package/dist/scripts/migration.js +15 -2
- package/dist/scripts/mlExport.js +1698 -10
- package/dist/scripts/mlExportSelect.js +16 -4
- package/dist/scripts/mlInspect.js +13 -3
- package/dist/scripts/mlTrainLatestSelect.js +14 -4
- package/dist/scripts/replay.js +8824 -0
- package/dist/scripts/replayRunner.js +8827 -0
- package/dist/scripts/replayRuntimeEvidence.js +868 -0
- package/dist/scripts/researchAuto.js +286 -59
- package/dist/scripts/results.js +3 -3
- package/dist/scripts/runtimeEvidence.js +605 -0
- package/dist/scripts/runtimeParity.js +6362 -1043
- package/dist/scripts/serverHealth.js +588 -0
- package/dist/scripts/signals.js +5590 -511
- package/dist/scripts/signalsDaemon.js +6167 -0
- package/dist/scripts/signalsSummary.js +833 -248
- package/dist/scripts/test-ml.js +25 -3
- package/dist/scripts/test.js +26 -1
- package/dist/scripts/user-add.js +15 -2
- package/dist/workers/testerWorker.js +130 -21
- 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
|
+
});
|