@tradejs/cli 3.0.0 → 3.1.0
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 +2606 -2171
- package/dist/lib/aiPocketSearch.js +10 -0
- package/dist/lib/aiTrainResearch.js +24 -15
- package/dist/lib/researchAgent.js +16 -2
- package/dist/lib/runtimeLineage.js +24 -15
- package/dist/lib/strategyGateFingerprint.js +26 -15
- package/dist/lib/strategyRelease.js +266 -253
- package/dist/scripts/agentRun.js +50 -45
- package/dist/scripts/aiPocketSearch.js +2269 -1972
- package/dist/scripts/aiTrain.js +24 -15
- package/dist/scripts/backtest.js +2 -2
- package/dist/scripts/replay.js +24 -15
- package/dist/scripts/replayRunner.js +24 -15
- package/dist/scripts/researchAuto.js +2 -2
- package/dist/scripts/runtimeEvidence.js +1 -0
- package/dist/scripts/runtimeScorecard.js +108 -103
- package/dist/scripts/signals.js +35 -17
- package/dist/scripts/signalsDaemon.js +35 -17
- package/dist/scripts/strategyRelease.js +222 -209
- package/dist/scripts/test-ml.js +8 -2
- package/package.json +8 -9
|
@@ -34,310 +34,178 @@ __export(aiPocketSearch_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(aiPocketSearch_exports);
|
|
36
36
|
var import_args = __toESM(require("args"));
|
|
37
|
-
var import_chalk = __toESM(require("chalk"));
|
|
38
|
-
var import_promises = __toESM(require("fs/promises"));
|
|
39
|
-
var import_path = __toESM(require("path"));
|
|
40
|
-
var import_progress = __toESM(require("progress"));
|
|
41
|
-
var import_ai = require("@tradejs/infra/ai");
|
|
42
|
-
var import_ai2 = require("@tradejs/node/ai");
|
|
43
|
-
var import_registry = require("@tradejs/node/registry");
|
|
44
|
-
|
|
45
|
-
// src/lib/aiTrainDataset.ts
|
|
46
|
-
var extractSignalFromAiDatasetRow = (row) => {
|
|
47
|
-
const { payload } = row;
|
|
48
|
-
return {
|
|
49
|
-
...payload.signal,
|
|
50
|
-
strategy: payload.signal.strategy,
|
|
51
|
-
figures: payload.figures ?? {},
|
|
52
|
-
indicators: payload.indicators ?? {},
|
|
53
|
-
additionalIndicators: payload.additionalIndicators ?? {},
|
|
54
|
-
prices: payload.signal.prices
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
37
|
|
|
58
|
-
// src/lib/
|
|
59
|
-
var
|
|
60
|
-
|
|
61
|
-
|
|
38
|
+
// src/lib/aiPocketSearchCli.ts
|
|
39
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
40
|
+
var DEFAULT_MIN_SUPPORT = 20;
|
|
41
|
+
var LOW_CADENCE_EVENT_LIMIT = 200;
|
|
42
|
+
var LOW_CADENCE_MAX_EVENT_SHARE = 1 / 3;
|
|
43
|
+
var countIndependentEvents = (rows) => {
|
|
44
|
+
const timestampEvents = /* @__PURE__ */ new Set();
|
|
45
|
+
let rowsWithoutTimestamp = 0;
|
|
46
|
+
for (const row of rows) {
|
|
47
|
+
if (typeof row.timestamp === "number" && Number.isFinite(row.timestamp)) {
|
|
48
|
+
timestampEvents.add(row.timestamp);
|
|
49
|
+
} else {
|
|
50
|
+
rowsWithoutTimestamp += 1;
|
|
51
|
+
}
|
|
62
52
|
}
|
|
63
|
-
return
|
|
53
|
+
return timestampEvents.size + rowsWithoutTimestamp;
|
|
64
54
|
};
|
|
65
|
-
var
|
|
66
|
-
var DAYS_PER_WEEK = 7;
|
|
67
|
-
var DAYS_PER_MONTH = 30.4375;
|
|
68
|
-
var DAYS_PER_YEAR = 365;
|
|
69
|
-
var getEvaluationPeriodDays = (evaluations) => {
|
|
55
|
+
var getPeriodDays = (rows) => {
|
|
70
56
|
let minTimestamp = null;
|
|
71
57
|
let maxTimestamp = null;
|
|
72
|
-
for (const
|
|
73
|
-
const timestamp =
|
|
74
|
-
if (timestamp
|
|
58
|
+
for (const row of rows) {
|
|
59
|
+
const timestamp = row.timestamp;
|
|
60
|
+
if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
|
|
75
61
|
continue;
|
|
76
62
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
if (maxTimestamp == null || timestamp > maxTimestamp) {
|
|
81
|
-
maxTimestamp = timestamp;
|
|
82
|
-
}
|
|
63
|
+
minTimestamp = minTimestamp == null ? timestamp : Math.min(minTimestamp, timestamp);
|
|
64
|
+
maxTimestamp = maxTimestamp == null ? timestamp : Math.max(maxTimestamp, timestamp);
|
|
83
65
|
}
|
|
84
66
|
if (minTimestamp == null || maxTimestamp == null) {
|
|
85
67
|
return null;
|
|
86
68
|
}
|
|
87
69
|
return Math.max((maxTimestamp - minTimestamp) / DAY_MS, 1);
|
|
88
70
|
};
|
|
89
|
-
var
|
|
90
|
-
var
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
71
|
+
var clampInt = (value, minimum, maximum) => Math.max(minimum, Math.min(maximum, Math.ceil(value)));
|
|
72
|
+
var resolveAiPocketCadenceProfile = ({
|
|
73
|
+
trainRows,
|
|
74
|
+
validationRows = [],
|
|
75
|
+
testRows = [],
|
|
76
|
+
mode = "auto",
|
|
77
|
+
validationSplit = 0.25,
|
|
78
|
+
minSupport,
|
|
79
|
+
minEvents,
|
|
80
|
+
minValidationSupport,
|
|
81
|
+
minValidationEvents,
|
|
82
|
+
maxEventCountShare = 0.25,
|
|
83
|
+
explicitMaxEventCountShare = false
|
|
95
84
|
}) => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
);
|
|
115
|
-
const annualizationScale = Math.sqrt(
|
|
116
|
-
profits.length / periodDays * DAYS_PER_YEAR
|
|
117
|
-
);
|
|
85
|
+
const trainEvents = countIndependentEvents(trainRows);
|
|
86
|
+
const validationEvents = countIndependentEvents(validationRows);
|
|
87
|
+
const testEvents = countIndependentEvents(testRows);
|
|
88
|
+
const trainPeriodDays = getPeriodDays(trainRows);
|
|
89
|
+
const trainEventsPerDay = trainPeriodDays == null ? null : trainEvents / trainPeriodDays;
|
|
90
|
+
const sparseSample = trainEvents < LOW_CADENCE_EVENT_LIMIT;
|
|
91
|
+
const lowCadence = trainEventsPerDay != null && trainEventsPerDay < 0.25;
|
|
92
|
+
const adaptiveSample = mode === "auto" && sparseSample;
|
|
93
|
+
const adaptiveThresholds = adaptiveSample && (minSupport == null || minEvents == null || validationRows.length > 0 && (minValidationSupport == null || minValidationEvents == null) || !explicitMaxEventCountShare);
|
|
94
|
+
const resolvedMinSupport = minSupport ?? (adaptiveSample ? clampInt(trainEvents * 0.1, 3, DEFAULT_MIN_SUPPORT) : DEFAULT_MIN_SUPPORT);
|
|
95
|
+
const resolvedMinEvents = minEvents ?? (minSupport != null || !adaptiveSample ? Math.max(5, Math.ceil(resolvedMinSupport * 0.5)) : clampInt(resolvedMinSupport * 0.5, 3, 10));
|
|
96
|
+
const hasValidation = validationRows.length > 0;
|
|
97
|
+
const resolvedMinValidationSupport = minValidationSupport ?? (hasValidation ? Math.max(3, Math.ceil(resolvedMinSupport * validationSplit * 0.5)) : 0);
|
|
98
|
+
const resolvedMinValidationEvents = minValidationEvents ?? (hasValidation ? Math.max(
|
|
99
|
+
adaptiveSample ? 3 : 2,
|
|
100
|
+
Math.ceil(resolvedMinEvents * validationSplit * 0.5)
|
|
101
|
+
) : 0);
|
|
102
|
+
const resolvedMaxEventCountShare = adaptiveSample && !explicitMaxEventCountShare ? Math.max(maxEventCountShare, LOW_CADENCE_MAX_EVENT_SHARE) : maxEventCountShare;
|
|
118
103
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
104
|
+
mode,
|
|
105
|
+
lowCadence,
|
|
106
|
+
sparseSample,
|
|
107
|
+
adaptiveThresholds,
|
|
108
|
+
trainRows: trainRows.length,
|
|
109
|
+
trainEvents,
|
|
110
|
+
trainPeriodDays,
|
|
111
|
+
trainEventsPerDay,
|
|
112
|
+
validationRows: validationRows.length,
|
|
113
|
+
validationEvents,
|
|
114
|
+
testRows: testRows.length,
|
|
115
|
+
testEvents,
|
|
116
|
+
minSupport: resolvedMinSupport,
|
|
117
|
+
minEvents: resolvedMinEvents,
|
|
118
|
+
minValidationSupport: resolvedMinValidationSupport,
|
|
119
|
+
minValidationEvents: resolvedMinValidationEvents,
|
|
120
|
+
maxEventCountShare: resolvedMaxEventCountShare
|
|
122
121
|
};
|
|
123
122
|
};
|
|
124
|
-
var
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
sharpeRatio: null,
|
|
140
|
-
sortinoRatio: null,
|
|
141
|
-
calmarRatio: null,
|
|
142
|
-
recoveryFactor: null,
|
|
143
|
-
ulcerIndex: null,
|
|
144
|
-
maxConsecutiveWins: 0,
|
|
145
|
-
maxConsecutiveLosses: 0
|
|
146
|
-
});
|
|
147
|
-
var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
|
|
148
|
-
const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
|
|
149
|
-
const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
|
|
150
|
-
const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
|
|
151
|
-
return leftTimestamp - rightTimestamp;
|
|
152
|
-
});
|
|
153
|
-
if (!approvedEvaluations.length) {
|
|
154
|
-
return emptyRiskSummary();
|
|
123
|
+
var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
|
|
124
|
+
minProfitFactor: "1.2",
|
|
125
|
+
minWinRate: "0",
|
|
126
|
+
minTotalProfit: "0",
|
|
127
|
+
validationSplit: "0.25",
|
|
128
|
+
testSplit: "0"
|
|
129
|
+
};
|
|
130
|
+
var splitAiPocketResearchRowsByTimestamp = (rows, validationSplit, testSplit = 0) => {
|
|
131
|
+
const timestamps = [
|
|
132
|
+
...new Set(
|
|
133
|
+
rows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp))
|
|
134
|
+
)
|
|
135
|
+
].sort((left, right) => left - right);
|
|
136
|
+
if (timestamps.length < 2) {
|
|
137
|
+
return { trainRows: rows, validationRows: [], testRows: [] };
|
|
155
138
|
}
|
|
156
|
-
|
|
157
|
-
let
|
|
158
|
-
let
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
let drawdownSquares = 0;
|
|
166
|
-
let currentWinStreak = 0;
|
|
167
|
-
let currentLossStreak = 0;
|
|
168
|
-
let maxConsecutiveWins = 0;
|
|
169
|
-
let maxConsecutiveLosses = 0;
|
|
170
|
-
const approvedProfits = [];
|
|
171
|
-
for (const evaluation of approvedEvaluations) {
|
|
172
|
-
const profit = evaluation.profit;
|
|
173
|
-
approvedProfits.push(profit);
|
|
174
|
-
if (profit > 0) {
|
|
175
|
-
grossProfit += profit;
|
|
176
|
-
wins += 1;
|
|
177
|
-
largestWin = largestWin == null ? profit : Math.max(largestWin, profit);
|
|
178
|
-
currentWinStreak += 1;
|
|
179
|
-
currentLossStreak = 0;
|
|
180
|
-
} else if (profit < 0) {
|
|
181
|
-
grossLoss += Math.abs(profit);
|
|
182
|
-
losses += 1;
|
|
183
|
-
largestLoss = largestLoss == null ? profit : Math.min(largestLoss, profit);
|
|
184
|
-
currentLossStreak += 1;
|
|
185
|
-
currentWinStreak = 0;
|
|
186
|
-
} else {
|
|
187
|
-
currentWinStreak = 0;
|
|
188
|
-
currentLossStreak = 0;
|
|
189
|
-
}
|
|
190
|
-
maxConsecutiveWins = Math.max(maxConsecutiveWins, currentWinStreak);
|
|
191
|
-
maxConsecutiveLosses = Math.max(maxConsecutiveLosses, currentLossStreak);
|
|
192
|
-
equity += profit;
|
|
193
|
-
peakEquity = Math.max(peakEquity, equity);
|
|
194
|
-
const drawdown = Math.max(0, peakEquity - equity);
|
|
195
|
-
maxDrawdown = Math.max(maxDrawdown, drawdown);
|
|
196
|
-
drawdownSquares += drawdown * drawdown;
|
|
139
|
+
const getSplitCount = (ratio) => ratio > 0 ? Math.max(1, Math.floor(timestamps.length * ratio)) : 0;
|
|
140
|
+
let testEvents = getSplitCount(testSplit);
|
|
141
|
+
let validationEvents = getSplitCount(validationSplit);
|
|
142
|
+
const maximumHeldOut = timestamps.length - 1;
|
|
143
|
+
if (testEvents + validationEvents > maximumHeldOut) {
|
|
144
|
+
validationEvents = Math.max(
|
|
145
|
+
0,
|
|
146
|
+
validationEvents - (testEvents + validationEvents - maximumHeldOut)
|
|
147
|
+
);
|
|
197
148
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
149
|
+
if (testEvents + validationEvents > maximumHeldOut) {
|
|
150
|
+
testEvents = Math.max(0, maximumHeldOut - validationEvents);
|
|
151
|
+
}
|
|
152
|
+
const testStart = timestamps.length - testEvents;
|
|
153
|
+
const validationStart = testStart - validationEvents;
|
|
154
|
+
const validationTimestamps = new Set(
|
|
155
|
+
timestamps.slice(validationStart, testStart)
|
|
156
|
+
);
|
|
157
|
+
const testTimestamps = new Set(timestamps.slice(testStart));
|
|
158
|
+
const isIn = (timestampsSet, timestamp) => typeof timestamp === "number" && timestampsSet.has(timestamp);
|
|
207
159
|
return {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
avgLoss,
|
|
216
|
-
largestWin,
|
|
217
|
-
largestLoss,
|
|
218
|
-
winRate: divideOrNull(wins, approvedEvaluations.length),
|
|
219
|
-
maxDrawdown,
|
|
220
|
-
maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
|
|
221
|
-
maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
|
|
222
|
-
...riskRatios,
|
|
223
|
-
recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
|
|
224
|
-
ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
|
|
225
|
-
maxConsecutiveWins,
|
|
226
|
-
maxConsecutiveLosses
|
|
160
|
+
trainRows: rows.filter(
|
|
161
|
+
(row) => !isIn(validationTimestamps, row.timestamp) && !isIn(testTimestamps, row.timestamp)
|
|
162
|
+
),
|
|
163
|
+
validationRows: rows.filter(
|
|
164
|
+
(row) => isIn(validationTimestamps, row.timestamp)
|
|
165
|
+
),
|
|
166
|
+
testRows: rows.filter((row) => isIn(testTimestamps, row.timestamp))
|
|
227
167
|
};
|
|
228
168
|
};
|
|
229
|
-
var
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
} else if (profit < 0) {
|
|
250
|
-
unprofitable += 1;
|
|
251
|
-
} else {
|
|
252
|
-
flat += 1;
|
|
253
|
-
}
|
|
254
|
-
if (aiApproved) {
|
|
255
|
-
approved += 1;
|
|
256
|
-
totalProfitApproved += profit;
|
|
257
|
-
} else {
|
|
258
|
-
rejected += 1;
|
|
259
|
-
}
|
|
260
|
-
const isCorrect = aiApproved === profitableTrade;
|
|
261
|
-
if (isCorrect) {
|
|
262
|
-
correct += 1;
|
|
263
|
-
} else {
|
|
264
|
-
incorrect += 1;
|
|
265
|
-
}
|
|
266
|
-
if (aiApproved && profitableTrade) {
|
|
267
|
-
truePositive += 1;
|
|
268
|
-
} else if (aiApproved && !profitableTrade) {
|
|
269
|
-
falsePositive += 1;
|
|
270
|
-
} else if (!aiApproved && profitableTrade) {
|
|
271
|
-
falseNegative += 1;
|
|
272
|
-
} else {
|
|
273
|
-
trueNegative += 1;
|
|
169
|
+
var splitAiPocketCoverageRowsByTimestamp = (rows, family, validationSplit, testSplit = 0) => splitAiPocketResearchRowsByTimestamp(
|
|
170
|
+
rows.filter((row) => row.featureCoverage?.[family] === true),
|
|
171
|
+
validationSplit,
|
|
172
|
+
testSplit
|
|
173
|
+
);
|
|
174
|
+
var sealAiPocketTestPartition = (split, sealed) => {
|
|
175
|
+
const finiteTimestamps = split.testRows.map((row) => row.timestamp).filter((timestamp) => Number.isFinite(timestamp));
|
|
176
|
+
return {
|
|
177
|
+
discoveryRows: [
|
|
178
|
+
...split.trainRows,
|
|
179
|
+
...split.validationRows,
|
|
180
|
+
...sealed ? [] : split.testRows
|
|
181
|
+
],
|
|
182
|
+
searchTestRows: sealed ? [] : split.testRows,
|
|
183
|
+
evidence: {
|
|
184
|
+
sealed,
|
|
185
|
+
rows: split.testRows.length,
|
|
186
|
+
events: countIndependentEvents(split.testRows),
|
|
187
|
+
startTimestamp: finiteTimestamps.length ? Math.min(...finiteTimestamps) : null,
|
|
188
|
+
endTimestamp: finiteTimestamps.length ? Math.max(...finiteTimestamps) : null
|
|
274
189
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
var readAiPocketSearchCliOption = ({
|
|
193
|
+
argv,
|
|
194
|
+
longName,
|
|
195
|
+
shortName
|
|
196
|
+
}) => {
|
|
197
|
+
const longPrefix = `--${longName}=`;
|
|
198
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
199
|
+
const arg = argv[index];
|
|
200
|
+
if (arg.startsWith(longPrefix)) {
|
|
201
|
+
return arg.slice(longPrefix.length);
|
|
286
202
|
}
|
|
287
|
-
if (
|
|
288
|
-
|
|
203
|
+
if (arg === `--${longName}` || shortName != null && arg === `-${shortName}`) {
|
|
204
|
+
return argv[index + 1];
|
|
289
205
|
}
|
|
290
|
-
bucketMap.set(quality, bucket);
|
|
291
206
|
}
|
|
292
|
-
|
|
293
|
-
const avgProfitApproved = divideOrNull(totalProfitApproved, approved);
|
|
294
|
-
const periodDays = getEvaluationPeriodDays(evaluations);
|
|
295
|
-
const avgProfitApprovedPerDay = periodDays == null ? null : divideOrNull(totalProfitApproved, periodDays);
|
|
296
|
-
const avgApprovedTradesPerDay = periodDays == null ? null : divideOrNull(approved, periodDays);
|
|
297
|
-
const avgProfitApprovedPerMonth = avgProfitApprovedPerDay == null ? null : avgProfitApprovedPerDay * DAYS_PER_MONTH;
|
|
298
|
-
const avgApprovedTradesPerWeek = avgApprovedTradesPerDay == null ? null : avgApprovedTradesPerDay * DAYS_PER_WEEK;
|
|
299
|
-
const expectancyDelta = avgProfitAll == null || avgProfitApproved == null ? null : avgProfitApproved - avgProfitAll;
|
|
300
|
-
return {
|
|
301
|
-
approved,
|
|
302
|
-
rejected,
|
|
303
|
-
correct,
|
|
304
|
-
incorrect,
|
|
305
|
-
truePositive,
|
|
306
|
-
falsePositive,
|
|
307
|
-
trueNegative,
|
|
308
|
-
falseNegative,
|
|
309
|
-
profitable,
|
|
310
|
-
unprofitable,
|
|
311
|
-
flat,
|
|
312
|
-
precisionApproved: divideOrNull(truePositive, approved),
|
|
313
|
-
recallWinners: divideOrNull(truePositive, profitable),
|
|
314
|
-
avgProfitAll,
|
|
315
|
-
avgProfitApproved,
|
|
316
|
-
avgProfitApprovedPerDay,
|
|
317
|
-
avgProfitApprovedPerMonth,
|
|
318
|
-
avgApprovedTradesPerDay,
|
|
319
|
-
avgApprovedTradesPerWeek,
|
|
320
|
-
expectancyDelta,
|
|
321
|
-
approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
|
|
322
|
-
qualityBuckets: [...bucketMap.values()].sort(
|
|
323
|
-
(a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
|
|
324
|
-
)
|
|
325
|
-
};
|
|
326
|
-
};
|
|
327
|
-
var isDirectionMatchAtThreshold = (evaluation, threshold) => {
|
|
328
|
-
const directionMatches = typeof evaluation.modelDirectionMatches === "boolean" ? evaluation.modelDirectionMatches : evaluation.aiApproved;
|
|
329
|
-
return directionMatches === true && evaluation.quality != null && evaluation.quality >= threshold;
|
|
207
|
+
return void 0;
|
|
330
208
|
};
|
|
331
|
-
var summarizeAiTrainEvaluationsByQualityThreshold = (evaluations, thresholds = [3, 4, 5]) => thresholds.map((threshold) => ({
|
|
332
|
-
threshold,
|
|
333
|
-
label: `q${threshold}+`,
|
|
334
|
-
summary: summarizeAiTrainEvaluations(
|
|
335
|
-
evaluations.map((evaluation) => ({
|
|
336
|
-
...evaluation,
|
|
337
|
-
aiApproved: isDirectionMatchAtThreshold(evaluation, threshold)
|
|
338
|
-
}))
|
|
339
|
-
)
|
|
340
|
-
}));
|
|
341
209
|
|
|
342
210
|
// src/lib/aiTrainOptions.ts
|
|
343
211
|
var parseTimestampFilter = (value) => {
|
|
@@ -411,85 +279,318 @@ var resolveAiTrainRecentLimit = ({
|
|
|
411
279
|
return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : defaultRecent;
|
|
412
280
|
};
|
|
413
281
|
|
|
414
|
-
// src/lib/aiPocketSearch.ts
|
|
415
|
-
var
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
var
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
"
|
|
429
|
-
|
|
430
|
-
"exitprice",
|
|
431
|
-
"exitreason",
|
|
432
|
-
"exittimestamp",
|
|
433
|
-
"fillprice",
|
|
434
|
-
"future",
|
|
435
|
-
"futuremove",
|
|
436
|
-
"futureprofit",
|
|
437
|
-
"hardblockreasons",
|
|
438
|
-
"label",
|
|
439
|
-
"maxallowedquality",
|
|
440
|
-
"maxquality",
|
|
441
|
-
"modeldirection",
|
|
442
|
-
"modeldirectionmatches",
|
|
443
|
-
"outcome",
|
|
444
|
-
"pnl",
|
|
445
|
-
"profit",
|
|
446
|
-
"profitabletrade",
|
|
447
|
-
"q4continuationrecoveryallowed",
|
|
448
|
-
"q4continuationrecoverycandidate",
|
|
449
|
-
"q4trendshiftgatefeaturesrecoverycandidate",
|
|
450
|
-
"q4usclosingoiconfirmationrecoverycandidate",
|
|
451
|
-
"quality",
|
|
452
|
-
"rawaiapproved",
|
|
453
|
-
"recoveryallowed",
|
|
454
|
-
"recoverycandidate",
|
|
455
|
-
"realized",
|
|
456
|
-
"realizedpnl",
|
|
457
|
-
"rejectreason",
|
|
458
|
-
"result",
|
|
459
|
-
"structuralhardblockreasons",
|
|
460
|
-
"target",
|
|
461
|
-
"traderesult"
|
|
462
|
-
]);
|
|
463
|
-
var isPlainRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
464
|
-
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
465
|
-
var isFeaturePrimitive = (value) => value == null || typeof value === "string" || typeof value === "boolean" || isFiniteNumber(value);
|
|
466
|
-
var normalizeFeaturePrimitive = (value) => {
|
|
467
|
-
if (typeof value === "string") {
|
|
468
|
-
const trimmed = value.trim();
|
|
469
|
-
return trimmed ? trimmed.slice(0, 120) : void 0;
|
|
282
|
+
// src/lib/aiPocketSearch/commandOptions.ts
|
|
283
|
+
var normalizeInt = (value, fallback) => {
|
|
284
|
+
const parsed = Number(value);
|
|
285
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : fallback;
|
|
286
|
+
};
|
|
287
|
+
var normalizePositiveInt = (value, fallback) => {
|
|
288
|
+
const parsed = Number(value);
|
|
289
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : fallback;
|
|
290
|
+
};
|
|
291
|
+
var normalizeNonNegativeNumber = (value, fallback) => {
|
|
292
|
+
const parsed = Number(value);
|
|
293
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
294
|
+
};
|
|
295
|
+
var normalizeRatio = (value, fallback) => {
|
|
296
|
+
if (typeof value === "boolean") {
|
|
297
|
+
return fallback;
|
|
470
298
|
}
|
|
471
|
-
|
|
472
|
-
|
|
299
|
+
const parsed = Number(value);
|
|
300
|
+
if (!Number.isFinite(parsed)) {
|
|
301
|
+
return fallback;
|
|
473
302
|
}
|
|
474
|
-
return
|
|
303
|
+
return Math.max(0, Math.min(0.9, parsed));
|
|
475
304
|
};
|
|
476
|
-
var
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
"
|
|
482
|
-
"
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
"
|
|
486
|
-
"
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
305
|
+
var normalizeUnitRatio = (value, fallback) => {
|
|
306
|
+
const parsed = Number(value);
|
|
307
|
+
return Number.isFinite(parsed) ? Math.max(0, Math.min(1, parsed)) : fallback;
|
|
308
|
+
};
|
|
309
|
+
var normalizeFeatureProfile = (value) => {
|
|
310
|
+
const normalized = String(value || "all").trim().toLowerCase();
|
|
311
|
+
return normalized === "compact" ? "compact" : "all";
|
|
312
|
+
};
|
|
313
|
+
var normalizeFeaturePolicy = (value) => {
|
|
314
|
+
const normalized = String(value || "causal-stationary").trim().toLowerCase();
|
|
315
|
+
if (!["causal-stationary", "all"].includes(normalized)) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Unsupported --featurePolicy "${normalized}". Use causal-stationary or all.`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
return normalized;
|
|
321
|
+
};
|
|
322
|
+
var normalizeCoverageMode = (value) => {
|
|
323
|
+
const normalized = String(value || "auto").trim().toLowerCase();
|
|
324
|
+
if (!["auto", "full"].includes(normalized)) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Unsupported --coverageMode "${normalized}". Use auto or full.`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
return normalized;
|
|
330
|
+
};
|
|
331
|
+
var normalizeCadenceMode = (value) => {
|
|
332
|
+
const normalized = String(value || "auto").trim().toLowerCase();
|
|
333
|
+
if (!["auto", "fixed"].includes(normalized)) {
|
|
334
|
+
throw new Error(
|
|
335
|
+
`Unsupported --cadenceMode "${normalized}". Use auto or fixed.`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return normalized;
|
|
339
|
+
};
|
|
340
|
+
var normalizeObjective = (value, scope) => {
|
|
341
|
+
const normalized = String(value || "auto").trim().toLowerCase();
|
|
342
|
+
if (normalized === "auto") {
|
|
343
|
+
if (scope === "approved") {
|
|
344
|
+
return "filter-gate";
|
|
345
|
+
}
|
|
346
|
+
if (scope === "rejected" || scope === "candidates") {
|
|
347
|
+
return "add-to-gate";
|
|
348
|
+
}
|
|
349
|
+
return "standalone";
|
|
350
|
+
}
|
|
351
|
+
if (!["standalone", "add-to-gate", "filter-gate"].includes(normalized)) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`Unsupported --objective "${normalized}". Use auto, standalone, add-to-gate, or filter-gate.`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
return normalized;
|
|
357
|
+
};
|
|
358
|
+
var hasCliOption2 = (argv, longName, shortName) => argv.some(
|
|
359
|
+
(arg) => arg === `--${longName}` || arg.startsWith(`--${longName}=`) || (shortName ? arg === `-${shortName}` : false)
|
|
360
|
+
);
|
|
361
|
+
var resolveAiPocketSearchCommandOptions = ({
|
|
362
|
+
flags: flags2,
|
|
363
|
+
argv
|
|
364
|
+
}) => {
|
|
365
|
+
const outDir = String(flags2.outDir || "data/ai/export");
|
|
366
|
+
const strategyName = String(flags2.strategy || "").trim() || void 0;
|
|
367
|
+
const explicitFile = String(flags2.file || "").trim();
|
|
368
|
+
const skip = normalizeInt(flags2.skip, 0);
|
|
369
|
+
const minQuality = normalizeInt(flags2.minQuality, 4);
|
|
370
|
+
const sinceInput = parseTimestampFilter(flags2.since);
|
|
371
|
+
const untilTimestamp = parseTimestampFilter(flags2.until);
|
|
372
|
+
const trailingPeriodMs = parseTrailingPeriodMs(flags2.period);
|
|
373
|
+
const periodLabel = String(flags2.period || "").trim() || null;
|
|
374
|
+
const hasDateFilter = trailingPeriodMs != null || sinceInput != null || untilTimestamp != null;
|
|
375
|
+
const recent = resolveAiTrainRecentLimit({
|
|
376
|
+
argv,
|
|
377
|
+
recentValue: flags2.recent,
|
|
378
|
+
hasDateFilter
|
|
379
|
+
});
|
|
380
|
+
const qualityThresholds = parseQualityThresholds(flags2.qualityThresholds);
|
|
381
|
+
const scope = String(flags2.scope || "all").trim().toLowerCase();
|
|
382
|
+
if (!["all", "approved", "rejected", "candidates"].includes(scope)) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Unsupported --scope "${scope}". Use all, approved, rejected, or candidates.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
const direction = String(flags2.direction || "").trim().toUpperCase();
|
|
388
|
+
if (direction && !["LONG", "SHORT"].includes(direction)) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Unsupported --direction "${direction}". Use LONG or SHORT.`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const explicitMaxEventCountShare = hasCliOption2(
|
|
394
|
+
argv,
|
|
395
|
+
"maxEventCountShare",
|
|
396
|
+
"U"
|
|
397
|
+
);
|
|
398
|
+
const maxDepth = normalizePositiveInt(flags2.maxDepth, 2);
|
|
399
|
+
const explicitMinSupport = hasCliOption2(argv, "minSupport", "m") ? normalizePositiveInt(flags2.minSupport, 20) : void 0;
|
|
400
|
+
const minProfitFactor = normalizeNonNegativeNumber(
|
|
401
|
+
readAiPocketSearchCliOption({
|
|
402
|
+
argv,
|
|
403
|
+
longName: "minProfitFactor",
|
|
404
|
+
shortName: "F"
|
|
405
|
+
}) ?? flags2.minProfitFactor,
|
|
406
|
+
1.2
|
|
407
|
+
);
|
|
408
|
+
const minWinRate = normalizeNonNegativeNumber(
|
|
409
|
+
readAiPocketSearchCliOption({
|
|
410
|
+
argv,
|
|
411
|
+
longName: "minWinRate",
|
|
412
|
+
shortName: "W"
|
|
413
|
+
}) ?? flags2.minWinRate,
|
|
414
|
+
0
|
|
415
|
+
);
|
|
416
|
+
const minTotalProfitValue = readAiPocketSearchCliOption({
|
|
417
|
+
argv,
|
|
418
|
+
longName: "minTotalProfit",
|
|
419
|
+
shortName: "R"
|
|
420
|
+
}) ?? flags2.minTotalProfit;
|
|
421
|
+
const minTotalProfit = Number.isFinite(Number(minTotalProfitValue)) ? Number(minTotalProfitValue) : 0;
|
|
422
|
+
const validationSplit = hasCliOption2(argv, "validationSplit", "V") ? normalizeRatio(
|
|
423
|
+
readAiPocketSearchCliOption({
|
|
424
|
+
argv,
|
|
425
|
+
longName: "validationSplit",
|
|
426
|
+
shortName: "V"
|
|
427
|
+
}) ?? flags2.validationSplit,
|
|
428
|
+
0.25
|
|
429
|
+
) : 0.25;
|
|
430
|
+
const testSplit = hasCliOption2(argv, "testSplit", "T") ? normalizeRatio(
|
|
431
|
+
readAiPocketSearchCliOption({
|
|
432
|
+
argv,
|
|
433
|
+
longName: "testSplit",
|
|
434
|
+
shortName: "T"
|
|
435
|
+
}) ?? flags2.testSplit,
|
|
436
|
+
0
|
|
437
|
+
) : 0;
|
|
438
|
+
const sealTest = Boolean(flags2.sealTest);
|
|
439
|
+
if (sealTest && testSplit <= 0) {
|
|
440
|
+
throw new Error("--sealTest requires a positive --testSplit");
|
|
441
|
+
}
|
|
442
|
+
const maxEventCountShare = normalizeUnitRatio(
|
|
443
|
+
readAiPocketSearchCliOption({
|
|
444
|
+
argv,
|
|
445
|
+
longName: "maxEventCountShare",
|
|
446
|
+
shortName: "U"
|
|
447
|
+
}) ?? flags2.maxEventCountShare,
|
|
448
|
+
0.25
|
|
449
|
+
);
|
|
450
|
+
const maxSymbolCountShare = normalizeUnitRatio(
|
|
451
|
+
readAiPocketSearchCliOption({
|
|
452
|
+
argv,
|
|
453
|
+
longName: "maxSymbolCountShare",
|
|
454
|
+
shortName: "Z"
|
|
455
|
+
}) ?? flags2.maxSymbolCountShare,
|
|
456
|
+
0.5
|
|
457
|
+
);
|
|
458
|
+
const objective = normalizeObjective(flags2.objective, scope);
|
|
459
|
+
return {
|
|
460
|
+
outDir,
|
|
461
|
+
strategyName,
|
|
462
|
+
explicitFile,
|
|
463
|
+
skip,
|
|
464
|
+
minQuality,
|
|
465
|
+
sinceInput,
|
|
466
|
+
untilTimestamp,
|
|
467
|
+
trailingPeriodMs,
|
|
468
|
+
periodLabel,
|
|
469
|
+
recent,
|
|
470
|
+
qualityThresholds,
|
|
471
|
+
scope,
|
|
472
|
+
direction,
|
|
473
|
+
maxDepth,
|
|
474
|
+
...explicitMinSupport != null ? { explicitMinSupport } : {},
|
|
475
|
+
minProfitFactor,
|
|
476
|
+
minWinRate,
|
|
477
|
+
minTotalProfit,
|
|
478
|
+
maxAtomicPredicates: normalizePositiveInt(flags2.maxAtomicPredicates, 180),
|
|
479
|
+
maxCombinations: normalizePositiveInt(flags2.maxCombinations, 6e4),
|
|
480
|
+
validationSplit,
|
|
481
|
+
testSplit,
|
|
482
|
+
sealTest,
|
|
483
|
+
explicitMinValidationSupport: normalizeInt(flags2.minValidationSupport, 0),
|
|
484
|
+
explicitMinEvents: normalizeInt(flags2.minEvents, 0),
|
|
485
|
+
explicitMinValidationEvents: normalizeInt(flags2.minValidationEvents, 0),
|
|
486
|
+
maxBatch: normalizeInt(flags2.maxBatch, 5),
|
|
487
|
+
maxEventCountShare,
|
|
488
|
+
explicitMaxEventCountShare,
|
|
489
|
+
maxSymbolCountShare,
|
|
490
|
+
objective,
|
|
491
|
+
allowRiskRegression: Boolean(flags2.allowRiskRegression),
|
|
492
|
+
requireValidationEligibility: !Boolean(flags2.allowValidationRegression),
|
|
493
|
+
dedupeEquivalentSelections: Boolean(flags2.dedupeEquivalentSelections),
|
|
494
|
+
top: normalizePositiveInt(flags2.top, 30),
|
|
495
|
+
includeSymbol: Boolean(flags2.includeSymbol),
|
|
496
|
+
includeGateContext: Boolean(flags2.includeGateContext),
|
|
497
|
+
featureProfile: normalizeFeatureProfile(flags2.featureProfile),
|
|
498
|
+
featurePolicy: normalizeFeaturePolicy(flags2.featurePolicy),
|
|
499
|
+
coverageMode: normalizeCoverageMode(flags2.coverageMode),
|
|
500
|
+
cadenceMode: normalizeCadenceMode(flags2.cadenceMode),
|
|
501
|
+
jsonOutput: Boolean(flags2.json),
|
|
502
|
+
outputPath: String(flags2.output || "").trim(),
|
|
503
|
+
reportDir: String(flags2.reportDir || "data/ai/output").trim(),
|
|
504
|
+
explicitReportFile: String(flags2.reportFile || "").trim()
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// src/lib/aiPocketSearch/presenter.ts
|
|
509
|
+
var import_chalk = __toESM(require("chalk"));
|
|
510
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
511
|
+
var import_path = __toESM(require("path"));
|
|
512
|
+
var import_progress = __toESM(require("progress"));
|
|
513
|
+
var import_ai = require("@tradejs/infra/ai");
|
|
514
|
+
|
|
515
|
+
// src/lib/aiPocketSearch/features.ts
|
|
516
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
517
|
+
var DAYS_PER_WEEK = 7;
|
|
518
|
+
var DAYS_PER_MONTH = 30.4375;
|
|
519
|
+
var PRODUCTION_CANDIDATE_MIN_EVENTS = 25;
|
|
520
|
+
var OUTCOME_SEGMENTS = /* @__PURE__ */ new Set([
|
|
521
|
+
"actual",
|
|
522
|
+
"aiapproved",
|
|
523
|
+
"approvalallowednow",
|
|
524
|
+
"backtestexecution",
|
|
525
|
+
"closedat",
|
|
526
|
+
"closedpnl",
|
|
527
|
+
"deterministicquality",
|
|
528
|
+
"entrydelaybars",
|
|
529
|
+
"entrydelaymovebps",
|
|
530
|
+
"executionprice",
|
|
531
|
+
"exitprice",
|
|
532
|
+
"exitreason",
|
|
533
|
+
"exittimestamp",
|
|
534
|
+
"fillprice",
|
|
535
|
+
"future",
|
|
536
|
+
"futuremove",
|
|
537
|
+
"futureprofit",
|
|
538
|
+
"hardblockreasons",
|
|
539
|
+
"label",
|
|
540
|
+
"maxallowedquality",
|
|
541
|
+
"maxquality",
|
|
542
|
+
"modeldirection",
|
|
543
|
+
"modeldirectionmatches",
|
|
544
|
+
"outcome",
|
|
545
|
+
"pnl",
|
|
546
|
+
"profit",
|
|
547
|
+
"profitabletrade",
|
|
548
|
+
"q4continuationrecoveryallowed",
|
|
549
|
+
"q4continuationrecoverycandidate",
|
|
550
|
+
"q4trendshiftgatefeaturesrecoverycandidate",
|
|
551
|
+
"q4usclosingoiconfirmationrecoverycandidate",
|
|
552
|
+
"quality",
|
|
553
|
+
"rawaiapproved",
|
|
554
|
+
"recoveryallowed",
|
|
555
|
+
"recoverycandidate",
|
|
556
|
+
"realized",
|
|
557
|
+
"realizedpnl",
|
|
558
|
+
"rejectreason",
|
|
559
|
+
"result",
|
|
560
|
+
"structuralhardblockreasons",
|
|
561
|
+
"target",
|
|
562
|
+
"traderesult"
|
|
563
|
+
]);
|
|
564
|
+
var isPlainRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
565
|
+
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
566
|
+
var isFeaturePrimitive = (value) => value == null || typeof value === "string" || typeof value === "boolean" || isFiniteNumber(value);
|
|
567
|
+
var normalizeFeaturePrimitive = (value) => {
|
|
568
|
+
if (typeof value === "string") {
|
|
569
|
+
const trimmed = value.trim();
|
|
570
|
+
return trimmed ? trimmed.slice(0, 120) : void 0;
|
|
571
|
+
}
|
|
572
|
+
if (value == null) {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
return value;
|
|
576
|
+
};
|
|
577
|
+
var isOutcomePath = (segments) => segments.some((segment) => OUTCOME_SEGMENTS.has(segment.toLowerCase()));
|
|
578
|
+
var DATA_QUALITY_LEAVES = /* @__PURE__ */ new Set([
|
|
579
|
+
"age",
|
|
580
|
+
"agems",
|
|
581
|
+
"available",
|
|
582
|
+
"asofts",
|
|
583
|
+
"calcbars",
|
|
584
|
+
"coverage",
|
|
585
|
+
"latestindex",
|
|
586
|
+
"points",
|
|
587
|
+
"rowcount",
|
|
588
|
+
"rows",
|
|
589
|
+
"stale",
|
|
590
|
+
"timestamp",
|
|
591
|
+
"windowendts"
|
|
592
|
+
]);
|
|
593
|
+
var METADATA_LEAVES = /* @__PURE__ */ new Set([
|
|
493
594
|
"connector",
|
|
494
595
|
"exchange",
|
|
495
596
|
"fingerprint",
|
|
@@ -501,9 +602,9 @@ var METADATA_LEAVES = /* @__PURE__ */ new Set([
|
|
|
501
602
|
"targetsymbol",
|
|
502
603
|
"universe"
|
|
503
604
|
]);
|
|
504
|
-
var normalizeFeaturePath = (
|
|
505
|
-
var classifyAiPocketCoverageFeaturePath = (
|
|
506
|
-
const segments = normalizeFeaturePath(
|
|
605
|
+
var normalizeFeaturePath = (path3) => (Array.isArray(path3) ? path3 : path3.split(".")).filter(Boolean).map((segment) => segment.toLowerCase());
|
|
606
|
+
var classifyAiPocketCoverageFeaturePath = (path3) => {
|
|
607
|
+
const segments = normalizeFeaturePath(path3);
|
|
507
608
|
const normalizedPath = segments.join(".");
|
|
508
609
|
if (segments.some(
|
|
509
610
|
(segment) => segment.includes("cmc") || segment.includes("coinmarketcap")
|
|
@@ -677,8 +778,8 @@ var RAW_NONSTATIONARY_LEAVES = /* @__PURE__ */ new Set([
|
|
|
677
778
|
"volumeusd",
|
|
678
779
|
"vwap"
|
|
679
780
|
]);
|
|
680
|
-
var classifyAiPocketFeaturePath = (
|
|
681
|
-
const segments = (Array.isArray(
|
|
781
|
+
var classifyAiPocketFeaturePath = (path3) => {
|
|
782
|
+
const segments = (Array.isArray(path3) ? path3 : path3.split(".")).filter(
|
|
682
783
|
Boolean
|
|
683
784
|
);
|
|
684
785
|
const normalized = segments.map((segment) => segment.toLowerCase());
|
|
@@ -1053,6 +1154,8 @@ var collectAiPocketFeatureSnapshot = ({
|
|
|
1053
1154
|
addDirectionalDerivedFeatures(features);
|
|
1054
1155
|
return { features, featureCoverage };
|
|
1055
1156
|
};
|
|
1157
|
+
|
|
1158
|
+
// src/lib/aiPocketSearch/summary.ts
|
|
1056
1159
|
var formatNumber = (value) => {
|
|
1057
1160
|
if (Number.isInteger(value)) {
|
|
1058
1161
|
return String(value);
|
|
@@ -1077,7 +1180,7 @@ var quantileAt = (values, quantile) => {
|
|
|
1077
1180
|
const index = Math.floor((values.length - 1) * quantile);
|
|
1078
1181
|
return values[Math.max(0, Math.min(values.length - 1, index))];
|
|
1079
1182
|
};
|
|
1080
|
-
var
|
|
1183
|
+
var getPeriodDays2 = (rows) => {
|
|
1081
1184
|
let minTimestamp = null;
|
|
1082
1185
|
let maxTimestamp = null;
|
|
1083
1186
|
for (const row of rows) {
|
|
@@ -1182,7 +1285,7 @@ var finalizeAiPocketSummary = ({
|
|
|
1182
1285
|
rows,
|
|
1183
1286
|
accumulator
|
|
1184
1287
|
}) => {
|
|
1185
|
-
const fullPeriodDays =
|
|
1288
|
+
const fullPeriodDays = getPeriodDays2(rows);
|
|
1186
1289
|
const support = accumulator.support;
|
|
1187
1290
|
const supportRatio = rows.length > 0 ? support / rows.length : 0;
|
|
1188
1291
|
if (support === 0) {
|
|
@@ -1277,9 +1380,9 @@ var finalizeAiPocketSummary = ({
|
|
|
1277
1380
|
maxConsecutiveWins,
|
|
1278
1381
|
maxConsecutiveLosses,
|
|
1279
1382
|
avgTradesPerDay,
|
|
1280
|
-
avgTradesPerWeek: avgTradesPerDay == null ? null : avgTradesPerDay *
|
|
1383
|
+
avgTradesPerWeek: avgTradesPerDay == null ? null : avgTradesPerDay * DAYS_PER_WEEK,
|
|
1281
1384
|
avgProfitPerDay,
|
|
1282
|
-
avgProfitPerMonth: avgProfitPerDay == null ? null : avgProfitPerDay *
|
|
1385
|
+
avgProfitPerMonth: avgProfitPerDay == null ? null : avgProfitPerDay * DAYS_PER_MONTH,
|
|
1283
1386
|
losingMonths: losingMonthEntries.length,
|
|
1284
1387
|
worstMonth: worstMonth ? {
|
|
1285
1388
|
month: worstMonth[0],
|
|
@@ -1349,6 +1452,8 @@ var summarizeAiPocketFeatureCoverage = (rows, family) => {
|
|
|
1349
1452
|
maxTimestamp
|
|
1350
1453
|
};
|
|
1351
1454
|
};
|
|
1455
|
+
|
|
1456
|
+
// src/lib/aiPocketSearch/predicates.ts
|
|
1352
1457
|
var matchesPredicate = (value, predicate) => {
|
|
1353
1458
|
if (predicate.kind === "numeric") {
|
|
1354
1459
|
if (!isFiniteNumber(value)) {
|
|
@@ -1596,6 +1701,8 @@ var buildAiPocketPredicateResult = (rows, options = {}) => {
|
|
|
1596
1701
|
emitProgress("predicates", bucketList.length, bucketList.length, true);
|
|
1597
1702
|
return { featureKeys: buckets.size, predicates };
|
|
1598
1703
|
};
|
|
1704
|
+
|
|
1705
|
+
// src/lib/aiPocketSearch/searchEngine.ts
|
|
1599
1706
|
var scorePositivePocket = (summary) => {
|
|
1600
1707
|
const profitFactor = summary.profitFactor ?? (summary.grossLoss === 0 ? 8 : 0);
|
|
1601
1708
|
const winRate = summary.winRate ?? 0;
|
|
@@ -1908,7 +2015,7 @@ var searchAiPockets = (rows, options = {}) => {
|
|
|
1908
2015
|
const onProgress = options.onProgress;
|
|
1909
2016
|
const summarizePartition = (partitionRows) => {
|
|
1910
2017
|
const summary = summarizeAiPocketRows(partitionRows);
|
|
1911
|
-
const periodDays =
|
|
2018
|
+
const periodDays = getPeriodDays2(partitionRows);
|
|
1912
2019
|
return {
|
|
1913
2020
|
rows: partitionRows.length,
|
|
1914
2021
|
events: summary.events,
|
|
@@ -2174,6 +2281,8 @@ var searchAiPockets = (rows, options = {}) => {
|
|
|
2174
2281
|
}
|
|
2175
2282
|
};
|
|
2176
2283
|
};
|
|
2284
|
+
|
|
2285
|
+
// src/lib/aiPocketSearch/markdownReport.ts
|
|
2177
2286
|
var formatMdNumber = (value, digits = 2) => value == null || !Number.isFinite(value) ? "n/a" : value.toFixed(digits);
|
|
2178
2287
|
var formatMdPercent = (value) => value == null || !Number.isFinite(value) ? "n/a" : `${(value * 100).toFixed(1)}%`;
|
|
2179
2288
|
var escapeMarkdownCell = (value) => String(value ?? "n/a").replace(/\|/g, "\\|").replace(/\n/g, "<br>");
|
|
@@ -2602,669 +2711,221 @@ var buildAiPocketMarkdownReport = ({
|
|
|
2602
2711
|
`;
|
|
2603
2712
|
};
|
|
2604
2713
|
|
|
2605
|
-
// src/lib/
|
|
2606
|
-
var
|
|
2607
|
-
var
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
for (const row of rows) {
|
|
2614
|
-
if (typeof row.timestamp === "number" && Number.isFinite(row.timestamp)) {
|
|
2615
|
-
timestampEvents.add(row.timestamp);
|
|
2616
|
-
} else {
|
|
2617
|
-
rowsWithoutTimestamp += 1;
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
return timestampEvents.size + rowsWithoutTimestamp;
|
|
2621
|
-
};
|
|
2622
|
-
var getPeriodDays2 = (rows) => {
|
|
2623
|
-
let minTimestamp = null;
|
|
2624
|
-
let maxTimestamp = null;
|
|
2625
|
-
for (const row of rows) {
|
|
2626
|
-
const timestamp = row.timestamp;
|
|
2627
|
-
if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
|
|
2628
|
-
continue;
|
|
2629
|
-
}
|
|
2630
|
-
minTimestamp = minTimestamp == null ? timestamp : Math.min(minTimestamp, timestamp);
|
|
2631
|
-
maxTimestamp = maxTimestamp == null ? timestamp : Math.max(maxTimestamp, timestamp);
|
|
2632
|
-
}
|
|
2633
|
-
if (minTimestamp == null || maxTimestamp == null) {
|
|
2634
|
-
return null;
|
|
2714
|
+
// src/lib/aiPocketSearch/presenter.ts
|
|
2715
|
+
var ListIt = require("list-it");
|
|
2716
|
+
var tickProgressBarTo = (bar, target, tokens) => {
|
|
2717
|
+
const current = Number(bar.curr ?? 0);
|
|
2718
|
+
const next = Math.max(current, Math.min(target, bar.total));
|
|
2719
|
+
const delta = next - current;
|
|
2720
|
+
if (delta > 0) {
|
|
2721
|
+
bar.tick(delta, tokens);
|
|
2635
2722
|
}
|
|
2636
|
-
return Math.max((maxTimestamp - minTimestamp) / DAY_MS3, 1);
|
|
2637
2723
|
};
|
|
2638
|
-
var
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
mode = "auto",
|
|
2644
|
-
validationSplit = 0.25,
|
|
2645
|
-
minSupport,
|
|
2646
|
-
minEvents,
|
|
2647
|
-
minValidationSupport,
|
|
2648
|
-
minValidationEvents,
|
|
2649
|
-
maxEventCountShare = 0.25,
|
|
2650
|
-
explicitMaxEventCountShare = false
|
|
2651
|
-
}) => {
|
|
2652
|
-
const trainEvents = countIndependentEvents(trainRows);
|
|
2653
|
-
const validationEvents = countIndependentEvents(validationRows);
|
|
2654
|
-
const testEvents = countIndependentEvents(testRows);
|
|
2655
|
-
const trainPeriodDays = getPeriodDays2(trainRows);
|
|
2656
|
-
const trainEventsPerDay = trainPeriodDays == null ? null : trainEvents / trainPeriodDays;
|
|
2657
|
-
const sparseSample = trainEvents < LOW_CADENCE_EVENT_LIMIT;
|
|
2658
|
-
const lowCadence = trainEventsPerDay != null && trainEventsPerDay < 0.25;
|
|
2659
|
-
const adaptiveSample = mode === "auto" && sparseSample;
|
|
2660
|
-
const adaptiveThresholds = adaptiveSample && (minSupport == null || minEvents == null || validationRows.length > 0 && (minValidationSupport == null || minValidationEvents == null) || !explicitMaxEventCountShare);
|
|
2661
|
-
const resolvedMinSupport = minSupport ?? (adaptiveSample ? clampInt(trainEvents * 0.1, 3, DEFAULT_MIN_SUPPORT) : DEFAULT_MIN_SUPPORT);
|
|
2662
|
-
const resolvedMinEvents = minEvents ?? (minSupport != null || !adaptiveSample ? Math.max(5, Math.ceil(resolvedMinSupport * 0.5)) : clampInt(resolvedMinSupport * 0.5, 3, 10));
|
|
2663
|
-
const hasValidation = validationRows.length > 0;
|
|
2664
|
-
const resolvedMinValidationSupport = minValidationSupport ?? (hasValidation ? Math.max(3, Math.ceil(resolvedMinSupport * validationSplit * 0.5)) : 0);
|
|
2665
|
-
const resolvedMinValidationEvents = minValidationEvents ?? (hasValidation ? Math.max(
|
|
2666
|
-
adaptiveSample ? 3 : 2,
|
|
2667
|
-
Math.ceil(resolvedMinEvents * validationSplit * 0.5)
|
|
2668
|
-
) : 0);
|
|
2669
|
-
const resolvedMaxEventCountShare = adaptiveSample && !explicitMaxEventCountShare ? Math.max(maxEventCountShare, LOW_CADENCE_MAX_EVENT_SHARE) : maxEventCountShare;
|
|
2670
|
-
return {
|
|
2671
|
-
mode,
|
|
2672
|
-
lowCadence,
|
|
2673
|
-
sparseSample,
|
|
2674
|
-
adaptiveThresholds,
|
|
2675
|
-
trainRows: trainRows.length,
|
|
2676
|
-
trainEvents,
|
|
2677
|
-
trainPeriodDays,
|
|
2678
|
-
trainEventsPerDay,
|
|
2679
|
-
validationRows: validationRows.length,
|
|
2680
|
-
validationEvents,
|
|
2681
|
-
testRows: testRows.length,
|
|
2682
|
-
testEvents,
|
|
2683
|
-
minSupport: resolvedMinSupport,
|
|
2684
|
-
minEvents: resolvedMinEvents,
|
|
2685
|
-
minValidationSupport: resolvedMinValidationSupport,
|
|
2686
|
-
minValidationEvents: resolvedMinValidationEvents,
|
|
2687
|
-
maxEventCountShare: resolvedMaxEventCountShare
|
|
2688
|
-
};
|
|
2689
|
-
};
|
|
2690
|
-
var AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS = {
|
|
2691
|
-
minProfitFactor: "1.2",
|
|
2692
|
-
minWinRate: "0",
|
|
2693
|
-
minTotalProfit: "0",
|
|
2694
|
-
validationSplit: "0.25",
|
|
2695
|
-
testSplit: "0"
|
|
2724
|
+
var searchPhaseLabels = {
|
|
2725
|
+
features: "features",
|
|
2726
|
+
predicates: "preds",
|
|
2727
|
+
masks: "masks",
|
|
2728
|
+
combinations: "search"
|
|
2696
2729
|
};
|
|
2697
|
-
var
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2730
|
+
var presentEmptyAiPocketSearchDataset = ({
|
|
2731
|
+
filePaths,
|
|
2732
|
+
recent,
|
|
2733
|
+
skip
|
|
2734
|
+
}) => {
|
|
2735
|
+
console.log(
|
|
2736
|
+
import_chalk.default.yellow(
|
|
2737
|
+
`No AI prompt rows selected in ${filePaths.join(", ")} (recent=${recent || "all"}, skip=${skip})`
|
|
2701
2738
|
)
|
|
2702
|
-
].sort((left, right) => left - right);
|
|
2703
|
-
if (timestamps.length < 2) {
|
|
2704
|
-
return { trainRows: rows, validationRows: [], testRows: [] };
|
|
2705
|
-
}
|
|
2706
|
-
const getSplitCount = (ratio) => ratio > 0 ? Math.max(1, Math.floor(timestamps.length * ratio)) : 0;
|
|
2707
|
-
let testEvents = getSplitCount(testSplit);
|
|
2708
|
-
let validationEvents = getSplitCount(validationSplit);
|
|
2709
|
-
const maximumHeldOut = timestamps.length - 1;
|
|
2710
|
-
if (testEvents + validationEvents > maximumHeldOut) {
|
|
2711
|
-
validationEvents = Math.max(
|
|
2712
|
-
0,
|
|
2713
|
-
validationEvents - (testEvents + validationEvents - maximumHeldOut)
|
|
2714
|
-
);
|
|
2715
|
-
}
|
|
2716
|
-
if (testEvents + validationEvents > maximumHeldOut) {
|
|
2717
|
-
testEvents = Math.max(0, maximumHeldOut - validationEvents);
|
|
2718
|
-
}
|
|
2719
|
-
const testStart = timestamps.length - testEvents;
|
|
2720
|
-
const validationStart = testStart - validationEvents;
|
|
2721
|
-
const validationTimestamps = new Set(
|
|
2722
|
-
timestamps.slice(validationStart, testStart)
|
|
2723
2739
|
);
|
|
2724
|
-
const testTimestamps = new Set(timestamps.slice(testStart));
|
|
2725
|
-
const isIn = (timestampsSet, timestamp) => typeof timestamp === "number" && timestampsSet.has(timestamp);
|
|
2726
|
-
return {
|
|
2727
|
-
trainRows: rows.filter(
|
|
2728
|
-
(row) => !isIn(validationTimestamps, row.timestamp) && !isIn(testTimestamps, row.timestamp)
|
|
2729
|
-
),
|
|
2730
|
-
validationRows: rows.filter(
|
|
2731
|
-
(row) => isIn(validationTimestamps, row.timestamp)
|
|
2732
|
-
),
|
|
2733
|
-
testRows: rows.filter((row) => isIn(testTimestamps, row.timestamp))
|
|
2734
|
-
};
|
|
2735
2740
|
};
|
|
2736
|
-
var
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
var
|
|
2742
|
-
const
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
],
|
|
2749
|
-
searchTestRows: sealed ? [] : split.testRows,
|
|
2750
|
-
evidence: {
|
|
2751
|
-
sealed,
|
|
2752
|
-
rows: split.testRows.length,
|
|
2753
|
-
events: countIndependentEvents(split.testRows),
|
|
2754
|
-
startTimestamp: finiteTimestamps.length ? Math.min(...finiteTimestamps) : null,
|
|
2755
|
-
endTimestamp: finiteTimestamps.length ? Math.max(...finiteTimestamps) : null
|
|
2741
|
+
var presentPendingAiPocketSearchReport = (reportPath) => {
|
|
2742
|
+
console.error(
|
|
2743
|
+
import_chalk.default.gray(`report: ${reportPath} (pending; final report after search)`)
|
|
2744
|
+
);
|
|
2745
|
+
};
|
|
2746
|
+
var createAiPocketSearchDatasetProgressPresenter = (selectedRows) => {
|
|
2747
|
+
const bar = new import_progress.default(
|
|
2748
|
+
"eval :current/:total [:bar] :percent :symbol :status",
|
|
2749
|
+
{
|
|
2750
|
+
total: selectedRows,
|
|
2751
|
+
width: 20,
|
|
2752
|
+
stream: process.stderr
|
|
2756
2753
|
}
|
|
2754
|
+
);
|
|
2755
|
+
return ({ symbol, status }) => {
|
|
2756
|
+
bar.tick(1, {
|
|
2757
|
+
symbol: import_chalk.default.gray(symbol),
|
|
2758
|
+
status: status === "ok" ? import_chalk.default.green(status) : status === "error" ? import_chalk.default.yellow(status) : import_chalk.default.gray(status)
|
|
2759
|
+
});
|
|
2757
2760
|
};
|
|
2758
2761
|
};
|
|
2759
|
-
var
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
return arg.slice(longPrefix.length);
|
|
2762
|
+
var createAiPocketSearchResearchProgressPresenter = () => {
|
|
2763
|
+
let searchBar = null;
|
|
2764
|
+
let searchBarPhase = null;
|
|
2765
|
+
return ({ label, progress }) => {
|
|
2766
|
+
if (searchBar && searchBarPhase !== progress.phase) {
|
|
2767
|
+
tickProgressBarTo(searchBar, searchBar.total, {
|
|
2768
|
+
status: import_chalk.default.gray("done")
|
|
2769
|
+
});
|
|
2770
|
+
searchBar = null;
|
|
2769
2771
|
}
|
|
2770
|
-
if (
|
|
2771
|
-
|
|
2772
|
+
if (!searchBar) {
|
|
2773
|
+
searchBarPhase = progress.phase;
|
|
2774
|
+
const phaseLabel = searchPhaseLabels[progress.phase].padEnd(8, " ");
|
|
2775
|
+
console.error(
|
|
2776
|
+
import_chalk.default.gray(
|
|
2777
|
+
`stage: ${label}/${searchPhaseLabels[progress.phase]} (${progress.total})`
|
|
2778
|
+
)
|
|
2779
|
+
);
|
|
2780
|
+
searchBar = new import_progress.default(
|
|
2781
|
+
`${phaseLabel} :current/:total [:bar] :percent :status`,
|
|
2782
|
+
{
|
|
2783
|
+
total: Math.max(progress.total, 1),
|
|
2784
|
+
width: 20,
|
|
2785
|
+
stream: process.stderr
|
|
2786
|
+
}
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2789
|
+
tickProgressBarTo(searchBar, progress.current, {
|
|
2790
|
+
status: progress.truncated ? import_chalk.default.yellow("truncated") : "running"
|
|
2791
|
+
});
|
|
2792
|
+
if (progress.done) {
|
|
2793
|
+
tickProgressBarTo(searchBar, searchBar.total, {
|
|
2794
|
+
status: progress.truncated ? import_chalk.default.yellow("truncated") : "done"
|
|
2795
|
+
});
|
|
2796
|
+
searchBar = null;
|
|
2772
2797
|
}
|
|
2798
|
+
};
|
|
2799
|
+
};
|
|
2800
|
+
var createListIt = () => new ListIt({
|
|
2801
|
+
autoAlign: true,
|
|
2802
|
+
headerUnderline: true
|
|
2803
|
+
});
|
|
2804
|
+
var createTable = (headers, rows) => createListIt().setHeaderRow(headers).d(rows).toString();
|
|
2805
|
+
var formatRatio = (value) => value == null ? "n/a" : `${(value * 100).toFixed(1)}%`;
|
|
2806
|
+
var formatProfit = (value) => value == null ? "n/a" : value.toFixed(2);
|
|
2807
|
+
var formatNumber2 = (value) => value == null ? "n/a" : value.toFixed(2);
|
|
2808
|
+
var colorizeRatio = (value) => {
|
|
2809
|
+
const text = formatRatio(value);
|
|
2810
|
+
if (value == null) {
|
|
2811
|
+
return import_chalk.default.gray(text);
|
|
2773
2812
|
}
|
|
2774
|
-
|
|
2813
|
+
if (value >= 0.55) {
|
|
2814
|
+
return import_chalk.default.green(text);
|
|
2815
|
+
}
|
|
2816
|
+
if (value >= 0.45) {
|
|
2817
|
+
return import_chalk.default.yellow(text);
|
|
2818
|
+
}
|
|
2819
|
+
return import_chalk.default.red(text);
|
|
2775
2820
|
};
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
);
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
);
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
)
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2821
|
+
var colorizeProfit = (value) => {
|
|
2822
|
+
const text = formatProfit(value);
|
|
2823
|
+
if (value == null) {
|
|
2824
|
+
return import_chalk.default.gray(text);
|
|
2825
|
+
}
|
|
2826
|
+
if (value > 0) {
|
|
2827
|
+
return import_chalk.default.green(text);
|
|
2828
|
+
}
|
|
2829
|
+
if (value < 0) {
|
|
2830
|
+
return import_chalk.default.red(text);
|
|
2831
|
+
}
|
|
2832
|
+
return import_chalk.default.yellow(text);
|
|
2833
|
+
};
|
|
2834
|
+
var colorizeNumber = (value) => value == null ? import_chalk.default.gray("n/a") : import_chalk.default.cyan(formatNumber2(value));
|
|
2835
|
+
var toReportTimestamp = (timestamp) => new Date(timestamp).toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:]/g, "-");
|
|
2836
|
+
var markdownPlainTable = (headers, rows) => {
|
|
2837
|
+
const escapeCell = (value) => value.replace(/\|/g, "\\|");
|
|
2838
|
+
return [
|
|
2839
|
+
`| ${headers.map(escapeCell).join(" | ")} |`,
|
|
2840
|
+
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
2841
|
+
...rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`)
|
|
2842
|
+
].join("\n");
|
|
2843
|
+
};
|
|
2844
|
+
var buildPendingMarkdownReport = ({
|
|
2845
|
+
generatedAt,
|
|
2846
|
+
strategy,
|
|
2847
|
+
filePaths,
|
|
2848
|
+
selectedRows,
|
|
2849
|
+
reportPath
|
|
2850
|
+
}) => [
|
|
2851
|
+
"# AI Pocket Search Report",
|
|
2852
|
+
"",
|
|
2853
|
+
"Status: running",
|
|
2854
|
+
"",
|
|
2855
|
+
`Generated at: ${new Date(generatedAt).toISOString()}`,
|
|
2856
|
+
"",
|
|
2857
|
+
markdownPlainTable(
|
|
2858
|
+
["Field", "Value"],
|
|
2859
|
+
[
|
|
2860
|
+
["strategy", strategy],
|
|
2861
|
+
["selected_rows", String(selectedRows)],
|
|
2862
|
+
["report", reportPath]
|
|
2863
|
+
]
|
|
2864
|
+
),
|
|
2865
|
+
"",
|
|
2866
|
+
"## Source Files",
|
|
2867
|
+
"",
|
|
2868
|
+
...filePaths.map((filePath) => `- \`${filePath}\``),
|
|
2869
|
+
"",
|
|
2870
|
+
"Final metrics and pockets will be written when the search finishes.",
|
|
2804
2871
|
""
|
|
2805
|
-
);
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
);
|
|
2811
|
-
import_args.default.option(
|
|
2812
|
-
["P", "period"],
|
|
2813
|
-
"Evaluate a trailing selected-row period such as last365d, last90d, or last30d",
|
|
2814
|
-
""
|
|
2815
|
-
);
|
|
2816
|
-
import_args.default.option(
|
|
2817
|
-
["q", "qualityThresholds"],
|
|
2818
|
-
"Comma-separated qN+ thresholds for current-gate baseline",
|
|
2819
|
-
"3,4,5"
|
|
2820
|
-
);
|
|
2821
|
-
import_args.default.option(
|
|
2822
|
-
["g", "scope"],
|
|
2823
|
-
"Search scope: all, approved, rejected, or candidates",
|
|
2824
|
-
"all"
|
|
2825
|
-
);
|
|
2826
|
-
import_args.default.option(
|
|
2827
|
-
["I", "direction"],
|
|
2828
|
-
"Only search rows with this signal direction: LONG or SHORT",
|
|
2829
|
-
""
|
|
2830
|
-
);
|
|
2831
|
-
import_args.default.option(["d", "maxDepth"], "Maximum predicate-combination depth", 2);
|
|
2832
|
-
import_args.default.option(
|
|
2833
|
-
["m", "minSupport"],
|
|
2834
|
-
"Minimum rows override; cadence auto derives it when omitted",
|
|
2835
|
-
20
|
|
2836
|
-
);
|
|
2837
|
-
import_args.default.option(
|
|
2838
|
-
["F", "minProfitFactor"],
|
|
2839
|
-
"Minimum profit factor required for positive pockets",
|
|
2840
|
-
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minProfitFactor
|
|
2841
|
-
);
|
|
2842
|
-
import_args.default.option(
|
|
2843
|
-
["W", "minWinRate"],
|
|
2844
|
-
"Minimum win rate required for positive pockets",
|
|
2845
|
-
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minWinRate
|
|
2846
|
-
);
|
|
2847
|
-
import_args.default.option(
|
|
2848
|
-
["R", "minTotalProfit"],
|
|
2849
|
-
"Minimum total PnL required for positive pockets",
|
|
2850
|
-
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minTotalProfit
|
|
2851
|
-
);
|
|
2852
|
-
import_args.default.option(
|
|
2853
|
-
["a", "maxAtomicPredicates"],
|
|
2854
|
-
"Maximum strongest atomic predicates used for combinations",
|
|
2855
|
-
180
|
|
2856
|
-
);
|
|
2857
|
-
import_args.default.option(
|
|
2858
|
-
["C", "maxCombinations"],
|
|
2859
|
-
"Maximum predicate combinations to evaluate",
|
|
2860
|
-
6e4
|
|
2861
|
-
);
|
|
2862
|
-
import_args.default.option(
|
|
2863
|
-
["V", "validationSplit"],
|
|
2864
|
-
"Trailing time-ordered scope share reserved for validation (0 disables)",
|
|
2865
|
-
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.validationSplit
|
|
2866
|
-
);
|
|
2867
|
-
import_args.default.option(
|
|
2868
|
-
["T", "testSplit"],
|
|
2869
|
-
"Trailing timestamp-grouped scope share withheld as untouched test",
|
|
2870
|
-
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.testSplit
|
|
2871
|
-
);
|
|
2872
|
-
import_args.default.option(
|
|
2873
|
-
["w", "sealTest"],
|
|
2874
|
-
"Reserve test bounds without exposing its rows or economics to discovery",
|
|
2875
|
-
false
|
|
2876
|
-
);
|
|
2877
|
-
import_args.default.option(
|
|
2878
|
-
["N", "minValidationSupport"],
|
|
2879
|
-
"Minimum validation rows required for positive pockets (0 = auto)",
|
|
2880
|
-
0
|
|
2881
|
-
);
|
|
2882
|
-
import_args.default.option(
|
|
2883
|
-
["H", "minEvents"],
|
|
2884
|
-
"Minimum independent timestamp events required (0 = auto)",
|
|
2885
|
-
0
|
|
2886
|
-
);
|
|
2887
|
-
import_args.default.option(
|
|
2888
|
-
["J", "minValidationEvents"],
|
|
2889
|
-
"Minimum validation timestamp events required (0 = auto)",
|
|
2890
|
-
0
|
|
2891
|
-
);
|
|
2892
|
-
import_args.default.option(
|
|
2893
|
-
["X", "maxBatch"],
|
|
2894
|
-
"Maximum selected rows at one timestamp (0 disables)",
|
|
2895
|
-
5
|
|
2896
|
-
);
|
|
2897
|
-
import_args.default.option(
|
|
2898
|
-
["U", "maxEventCountShare"],
|
|
2899
|
-
"Maximum row share contributed by one timestamp event",
|
|
2900
|
-
"0.25"
|
|
2901
|
-
);
|
|
2902
|
-
import_args.default.option(
|
|
2903
|
-
["Z", "maxSymbolCountShare"],
|
|
2904
|
-
"Maximum row share contributed by one symbol",
|
|
2905
|
-
"0.5"
|
|
2906
|
-
);
|
|
2907
|
-
import_args.default.option(
|
|
2908
|
-
["A", "objective"],
|
|
2909
|
-
"Search objective: auto, standalone, add-to-gate, or filter-gate",
|
|
2910
|
-
"auto"
|
|
2911
|
-
);
|
|
2912
|
-
import_args.default.option(
|
|
2913
|
-
["G", "allowRiskRegression"],
|
|
2914
|
-
"Allow incremental pockets to worsen q4+ PF, PnL, drawdown, or loss streak",
|
|
2915
|
-
false
|
|
2916
|
-
);
|
|
2917
|
-
import_args.default.option(
|
|
2918
|
-
["L", "allowValidationRegression"],
|
|
2919
|
-
"Keep train-positive pockets that fail validation PnL/PF thresholds",
|
|
2920
|
-
false
|
|
2921
|
-
);
|
|
2922
|
-
import_args.default.option(
|
|
2923
|
-
["D", "dedupeEquivalentSelections"],
|
|
2924
|
-
"Collapse pockets that select the same train rows",
|
|
2925
|
-
true
|
|
2926
|
-
);
|
|
2927
|
-
import_args.default.option(["t", "top"], "Top pockets to print in each section", 30);
|
|
2928
|
-
import_args.default.option(["Y", "includeSymbol"], "Allow symbol as a pocket feature", false);
|
|
2929
|
-
import_args.default.option(
|
|
2930
|
-
["E", "includeGateContext"],
|
|
2931
|
-
"Allow current deterministic gate output fields as pocket features",
|
|
2932
|
-
false
|
|
2933
|
-
);
|
|
2934
|
-
import_args.default.option(
|
|
2935
|
-
["p", "featureProfile"],
|
|
2936
|
-
"Feature extraction profile: compact or all",
|
|
2937
|
-
"all"
|
|
2938
|
-
);
|
|
2939
|
-
import_args.default.option(
|
|
2940
|
-
["K", "featurePolicy"],
|
|
2941
|
-
"Feature policy: causal-stationary or all",
|
|
2942
|
-
"causal-stationary"
|
|
2943
|
-
);
|
|
2944
|
-
import_args.default.option(
|
|
2945
|
-
["Q", "coverageMode"],
|
|
2946
|
-
"Coverage-aware search mode: auto or full",
|
|
2947
|
-
"auto"
|
|
2948
|
-
);
|
|
2949
|
-
import_args.default.option(
|
|
2950
|
-
["c", "cadenceMode"],
|
|
2951
|
-
"Cadence thresholds: auto adapts discovery support, fixed keeps legacy defaults",
|
|
2952
|
-
"auto"
|
|
2953
|
-
);
|
|
2954
|
-
import_args.default.option(
|
|
2955
|
-
["r", "reportDir"],
|
|
2956
|
-
"Directory for generated Markdown reports",
|
|
2957
|
-
"data/ai/output"
|
|
2958
|
-
);
|
|
2959
|
-
import_args.default.option(["B", "reportFile"], "Explicit Markdown report file path", "");
|
|
2960
|
-
import_args.default.option(["j", "json"], "Print structured JSON summary", false);
|
|
2961
|
-
import_args.default.option(["O", "output"], "Write structured JSON summary to file", "");
|
|
2962
|
-
var flags = import_args.default.parse(process.argv);
|
|
2963
|
-
var createListIt = () => new ListIt({
|
|
2964
|
-
autoAlign: true,
|
|
2965
|
-
headerUnderline: true
|
|
2966
|
-
});
|
|
2967
|
-
var createTable = (headers, rows) => createListIt().setHeaderRow(headers).d(rows).toString();
|
|
2968
|
-
var formatRatio = (value) => value == null ? "n/a" : `${(value * 100).toFixed(1)}%`;
|
|
2969
|
-
var formatProfit = (value) => value == null ? "n/a" : value.toFixed(2);
|
|
2970
|
-
var formatNumber2 = (value) => value == null ? "n/a" : value.toFixed(2);
|
|
2971
|
-
var colorizeRatio = (value) => {
|
|
2972
|
-
const text = formatRatio(value);
|
|
2973
|
-
if (value == null) {
|
|
2974
|
-
return import_chalk.default.gray(text);
|
|
2975
|
-
}
|
|
2976
|
-
if (value >= 0.55) {
|
|
2977
|
-
return import_chalk.default.green(text);
|
|
2978
|
-
}
|
|
2979
|
-
if (value >= 0.45) {
|
|
2980
|
-
return import_chalk.default.yellow(text);
|
|
2872
|
+
].join("\n");
|
|
2873
|
+
var getMergedGroupId = (filePath) => {
|
|
2874
|
+
const match = import_path.default.basename(filePath).match(/^ai-dataset-(.+)-merged-(\d+)(?:-part(\d+))?\.jsonl$/);
|
|
2875
|
+
if (!match) {
|
|
2876
|
+
return null;
|
|
2981
2877
|
}
|
|
2982
|
-
return
|
|
2878
|
+
return { strategyToken: match[1], mergeId: match[2] };
|
|
2983
2879
|
};
|
|
2984
|
-
var
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
if (
|
|
2993
|
-
return
|
|
2880
|
+
var resolveMarkdownReportPath = ({
|
|
2881
|
+
explicitReportFile,
|
|
2882
|
+
reportDir,
|
|
2883
|
+
strategyName,
|
|
2884
|
+
filePath,
|
|
2885
|
+
scope,
|
|
2886
|
+
generatedAt
|
|
2887
|
+
}) => {
|
|
2888
|
+
if (explicitReportFile) {
|
|
2889
|
+
return explicitReportFile;
|
|
2994
2890
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
`| ${headers.map(escapeCell).join(" | ")} |`,
|
|
3003
|
-
`| ${headers.map(() => "---").join(" | ")} |`,
|
|
3004
|
-
...rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`)
|
|
3005
|
-
].join("\n");
|
|
2891
|
+
const groupId = getMergedGroupId(filePath);
|
|
2892
|
+
const strategyToken = groupId?.strategyToken ?? (0, import_ai.toFileToken)(strategyName);
|
|
2893
|
+
const mergeToken = groupId?.mergeId ? `merged-${groupId.mergeId}` : "merged";
|
|
2894
|
+
return import_path.default.join(
|
|
2895
|
+
reportDir,
|
|
2896
|
+
`ai-pocket-search-${strategyToken}-${mergeToken}-${scope}-${toReportTimestamp(generatedAt)}.md`
|
|
2897
|
+
);
|
|
3006
2898
|
};
|
|
3007
|
-
var
|
|
3008
|
-
|
|
3009
|
-
|
|
2899
|
+
var writePendingAiPocketSearchReport = async ({
|
|
2900
|
+
explicitReportFile,
|
|
2901
|
+
reportDir,
|
|
2902
|
+
strategyName,
|
|
3010
2903
|
filePaths,
|
|
3011
|
-
selectedRows,
|
|
3012
|
-
reportPath
|
|
3013
|
-
}) => [
|
|
3014
|
-
"# AI Pocket Search Report",
|
|
3015
|
-
"",
|
|
3016
|
-
"Status: running",
|
|
3017
|
-
"",
|
|
3018
|
-
`Generated at: ${new Date(generatedAt).toISOString()}`,
|
|
3019
|
-
"",
|
|
3020
|
-
markdownPlainTable(
|
|
3021
|
-
["Field", "Value"],
|
|
3022
|
-
[
|
|
3023
|
-
["strategy", strategy],
|
|
3024
|
-
["selected_rows", String(selectedRows)],
|
|
3025
|
-
["report", reportPath]
|
|
3026
|
-
]
|
|
3027
|
-
),
|
|
3028
|
-
"",
|
|
3029
|
-
"## Source Files",
|
|
3030
|
-
"",
|
|
3031
|
-
...filePaths.map((filePath) => `- \`${filePath}\``),
|
|
3032
|
-
"",
|
|
3033
|
-
"Final metrics and pockets will be written when the search finishes.",
|
|
3034
|
-
""
|
|
3035
|
-
].join("\n");
|
|
3036
|
-
var normalizeInt = (value, fallback) => {
|
|
3037
|
-
const parsed = Number(value);
|
|
3038
|
-
return Number.isFinite(parsed) ? Math.max(0, Math.trunc(parsed)) : fallback;
|
|
3039
|
-
};
|
|
3040
|
-
var normalizePositiveInt = (value, fallback) => {
|
|
3041
|
-
const parsed = Number(value);
|
|
3042
|
-
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : fallback;
|
|
3043
|
-
};
|
|
3044
|
-
var normalizeNonNegativeNumber = (value, fallback) => {
|
|
3045
|
-
const parsed = Number(value);
|
|
3046
|
-
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
3047
|
-
};
|
|
3048
|
-
var normalizeRatio = (value, fallback) => {
|
|
3049
|
-
if (typeof value === "boolean") {
|
|
3050
|
-
return fallback;
|
|
3051
|
-
}
|
|
3052
|
-
const parsed = Number(value);
|
|
3053
|
-
if (!Number.isFinite(parsed)) {
|
|
3054
|
-
return fallback;
|
|
3055
|
-
}
|
|
3056
|
-
return Math.max(0, Math.min(0.9, parsed));
|
|
3057
|
-
};
|
|
3058
|
-
var normalizeUnitRatio = (value, fallback) => {
|
|
3059
|
-
const parsed = Number(value);
|
|
3060
|
-
return Number.isFinite(parsed) ? Math.max(0, Math.min(1, parsed)) : fallback;
|
|
3061
|
-
};
|
|
3062
|
-
var normalizeFeatureProfile = (value) => {
|
|
3063
|
-
const normalized = String(value || "all").trim().toLowerCase();
|
|
3064
|
-
return normalized === "compact" ? "compact" : "all";
|
|
3065
|
-
};
|
|
3066
|
-
var normalizeFeaturePolicy = (value) => {
|
|
3067
|
-
const normalized = String(value || "causal-stationary").trim().toLowerCase();
|
|
3068
|
-
if (!["causal-stationary", "all"].includes(normalized)) {
|
|
3069
|
-
throw new Error(
|
|
3070
|
-
`Unsupported --featurePolicy "${normalized}". Use causal-stationary or all.`
|
|
3071
|
-
);
|
|
3072
|
-
}
|
|
3073
|
-
return normalized;
|
|
3074
|
-
};
|
|
3075
|
-
var normalizeCoverageMode = (value) => {
|
|
3076
|
-
const normalized = String(value || "auto").trim().toLowerCase();
|
|
3077
|
-
if (!["auto", "full"].includes(normalized)) {
|
|
3078
|
-
throw new Error(
|
|
3079
|
-
`Unsupported --coverageMode "${normalized}". Use auto or full.`
|
|
3080
|
-
);
|
|
3081
|
-
}
|
|
3082
|
-
return normalized;
|
|
3083
|
-
};
|
|
3084
|
-
var normalizeCadenceMode = (value) => {
|
|
3085
|
-
const normalized = String(value || "auto").trim().toLowerCase();
|
|
3086
|
-
if (!["auto", "fixed"].includes(normalized)) {
|
|
3087
|
-
throw new Error(
|
|
3088
|
-
`Unsupported --cadenceMode "${normalized}". Use auto or fixed.`
|
|
3089
|
-
);
|
|
3090
|
-
}
|
|
3091
|
-
return normalized;
|
|
3092
|
-
};
|
|
3093
|
-
var normalizeObjective = (value, scope) => {
|
|
3094
|
-
const normalized = String(value || "auto").trim().toLowerCase();
|
|
3095
|
-
if (normalized === "auto") {
|
|
3096
|
-
if (scope === "approved") {
|
|
3097
|
-
return "filter-gate";
|
|
3098
|
-
}
|
|
3099
|
-
if (scope === "rejected" || scope === "candidates") {
|
|
3100
|
-
return "add-to-gate";
|
|
3101
|
-
}
|
|
3102
|
-
return "standalone";
|
|
3103
|
-
}
|
|
3104
|
-
if (!["standalone", "add-to-gate", "filter-gate"].includes(normalized)) {
|
|
3105
|
-
throw new Error(
|
|
3106
|
-
`Unsupported --objective "${normalized}". Use auto, standalone, add-to-gate, or filter-gate.`
|
|
3107
|
-
);
|
|
3108
|
-
}
|
|
3109
|
-
return normalized;
|
|
3110
|
-
};
|
|
3111
|
-
var normalizeQuality = (analysis) => {
|
|
3112
|
-
const quality = Number(analysis?.quality);
|
|
3113
|
-
return Number.isFinite(quality) ? Math.round(quality) : null;
|
|
3114
|
-
};
|
|
3115
|
-
var isAiApproval = (row, analysis, minQuality) => {
|
|
3116
|
-
const quality = normalizeQuality(analysis);
|
|
3117
|
-
return analysis.direction === row.direction && quality != null && quality >= minQuality;
|
|
3118
|
-
};
|
|
3119
|
-
var listMergedFiles = async (params) => {
|
|
3120
|
-
const { outDir, strategyName } = params;
|
|
3121
|
-
let entries = [];
|
|
3122
|
-
try {
|
|
3123
|
-
entries = await import_promises.default.readdir(outDir);
|
|
3124
|
-
} catch {
|
|
3125
|
-
return [];
|
|
3126
|
-
}
|
|
3127
|
-
const prefix = strategyName ? `ai-dataset-${(0, import_ai.toFileToken)(strategyName)}-merged-` : "ai-dataset-";
|
|
3128
|
-
return entries.filter((name) => name.startsWith(prefix)).filter((name) => {
|
|
3129
|
-
if (!name.endsWith(".jsonl") || !name.includes("-merged-")) {
|
|
3130
|
-
return false;
|
|
3131
|
-
}
|
|
3132
|
-
return /-merged-\d+\.jsonl$/.test(name) || /-merged-\d+-part\d+\.jsonl$/.test(name);
|
|
3133
|
-
}).sort().map((name) => import_path.default.join(outDir, name));
|
|
3134
|
-
};
|
|
3135
|
-
var deriveStrategyNameFromFile = (filePath) => {
|
|
3136
|
-
const match = import_path.default.basename(filePath).match(/^ai-dataset-(.+)-merged-\d+(?:-part\d+)?\.jsonl$/);
|
|
3137
|
-
return match?.[1] ? match[1] : "unknown";
|
|
3138
|
-
};
|
|
3139
|
-
var getMergedGroupId = (filePath) => {
|
|
3140
|
-
const match = import_path.default.basename(filePath).match(/^ai-dataset-(.+)-merged-(\d+)(?:-part(\d+))?\.jsonl$/);
|
|
3141
|
-
if (!match) {
|
|
3142
|
-
return null;
|
|
3143
|
-
}
|
|
3144
|
-
return {
|
|
3145
|
-
strategyToken: match[1],
|
|
3146
|
-
mergeId: match[2]
|
|
3147
|
-
};
|
|
3148
|
-
};
|
|
3149
|
-
var resolveMarkdownReportPath = ({
|
|
3150
|
-
explicitReportFile,
|
|
3151
|
-
reportDir,
|
|
3152
|
-
strategyName,
|
|
3153
|
-
filePath,
|
|
3154
2904
|
scope,
|
|
3155
|
-
generatedAt
|
|
2905
|
+
generatedAt,
|
|
2906
|
+
selectedRows
|
|
3156
2907
|
}) => {
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
}
|
|
3160
|
-
const groupId = getMergedGroupId(filePath);
|
|
3161
|
-
const strategyToken = groupId?.strategyToken ?? (0, import_ai.toFileToken)(strategyName);
|
|
3162
|
-
const mergeToken = groupId?.mergeId ? `merged-${groupId.mergeId}` : "merged";
|
|
3163
|
-
return import_path.default.join(
|
|
2908
|
+
const reportPath = resolveMarkdownReportPath({
|
|
2909
|
+
explicitReportFile,
|
|
3164
2910
|
reportDir,
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
const leftMatch = import_path.default.basename(left).match(/-part(\d+)\.jsonl$/);
|
|
3170
|
-
const rightMatch = import_path.default.basename(right).match(/-part(\d+)\.jsonl$/);
|
|
3171
|
-
const leftPart = leftMatch ? Number(leftMatch[1]) : 0;
|
|
3172
|
-
const rightPart = rightMatch ? Number(rightMatch[1]) : 0;
|
|
3173
|
-
return leftPart - rightPart || left.localeCompare(right);
|
|
3174
|
-
});
|
|
3175
|
-
var resolveMergedDatasetFiles = async ({
|
|
3176
|
-
outDir,
|
|
3177
|
-
strategyName,
|
|
3178
|
-
explicitFile
|
|
3179
|
-
}) => {
|
|
3180
|
-
const mergedFiles = await listMergedFiles({
|
|
3181
|
-
outDir,
|
|
3182
|
-
strategyName
|
|
2911
|
+
strategyName,
|
|
2912
|
+
filePath: filePaths[0] || "",
|
|
2913
|
+
scope,
|
|
2914
|
+
generatedAt
|
|
3183
2915
|
});
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
const groupedFiles = sortDatasetPartPaths(
|
|
3196
|
-
mergedFiles.filter((candidate) => {
|
|
3197
|
-
const candidateGroup = getMergedGroupId(candidate);
|
|
3198
|
-
return candidateGroup?.strategyToken === groupId.strategyToken && candidateGroup?.mergeId === groupId.mergeId;
|
|
3199
|
-
})
|
|
3200
|
-
);
|
|
3201
|
-
return groupedFiles.length ? groupedFiles : [resolvedExplicitFile];
|
|
3202
|
-
}
|
|
3203
|
-
const latestFile = mergedFiles[mergedFiles.length - 1];
|
|
3204
|
-
const latestGroupId = getMergedGroupId(latestFile);
|
|
3205
|
-
if (!latestGroupId) {
|
|
3206
|
-
return [latestFile];
|
|
3207
|
-
}
|
|
3208
|
-
return sortDatasetPartPaths(
|
|
3209
|
-
mergedFiles.filter((candidate) => {
|
|
3210
|
-
const candidateGroup = getMergedGroupId(candidate);
|
|
3211
|
-
return candidateGroup?.strategyToken === latestGroupId.strategyToken && candidateGroup?.mergeId === latestGroupId.mergeId;
|
|
3212
|
-
})
|
|
2916
|
+
await import_promises.default.mkdir(import_path.default.dirname(import_path.default.resolve(reportPath)), { recursive: true });
|
|
2917
|
+
await import_promises.default.writeFile(
|
|
2918
|
+
reportPath,
|
|
2919
|
+
buildPendingMarkdownReport({
|
|
2920
|
+
generatedAt,
|
|
2921
|
+
strategy: strategyName,
|
|
2922
|
+
filePaths,
|
|
2923
|
+
selectedRows,
|
|
2924
|
+
reportPath
|
|
2925
|
+
}),
|
|
2926
|
+
"utf8"
|
|
3213
2927
|
);
|
|
3214
|
-
|
|
3215
|
-
var getDatasetRowTimestamp = (row) => {
|
|
3216
|
-
const timestamp = Number(row.timestamp);
|
|
3217
|
-
return Number.isFinite(timestamp) ? timestamp : null;
|
|
3218
|
-
};
|
|
3219
|
-
var findMaxSelectedTimestamp = async ({
|
|
3220
|
-
filePaths,
|
|
3221
|
-
recent,
|
|
3222
|
-
skip
|
|
3223
|
-
}) => {
|
|
3224
|
-
let maxTimestamp = null;
|
|
3225
|
-
await (0, import_ai.streamAiDatasetRows)({
|
|
3226
|
-
filePaths,
|
|
3227
|
-
limitFromEnd: recent,
|
|
3228
|
-
skipFromEnd: skip,
|
|
3229
|
-
onRow: async (row) => {
|
|
3230
|
-
const timestamp = getDatasetRowTimestamp(row);
|
|
3231
|
-
if (timestamp != null) {
|
|
3232
|
-
maxTimestamp = maxTimestamp == null ? timestamp : Math.max(maxTimestamp, timestamp);
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
});
|
|
3236
|
-
return maxTimestamp;
|
|
3237
|
-
};
|
|
3238
|
-
var getDeterministicModelCandidate = (signal) => {
|
|
3239
|
-
if (!signal) {
|
|
3240
|
-
return false;
|
|
3241
|
-
}
|
|
3242
|
-
const payload = (0, import_ai2.buildAiPayload)(signal);
|
|
3243
|
-
const gateContext = (0, import_ai2.getDeterministicAiGateContext)(payload);
|
|
3244
|
-
const structuralHardBlockReasons = Array.isArray(
|
|
3245
|
-
gateContext?.structuralHardBlockReasons
|
|
3246
|
-
) ? gateContext.structuralHardBlockReasons.filter(
|
|
3247
|
-
(reason) => typeof reason === "string" && reason.trim().length > 0
|
|
3248
|
-
) : [];
|
|
3249
|
-
if (structuralHardBlockReasons.length) {
|
|
3250
|
-
return false;
|
|
3251
|
-
}
|
|
3252
|
-
if (typeof gateContext?.approvalAllowedNow === "boolean") {
|
|
3253
|
-
return gateContext.approvalAllowedNow;
|
|
3254
|
-
}
|
|
3255
|
-
return true;
|
|
3256
|
-
};
|
|
3257
|
-
var resolveScopeRows = (rows, scope) => {
|
|
3258
|
-
if (scope === "approved") {
|
|
3259
|
-
return rows.filter((row) => row.aiApproved);
|
|
3260
|
-
}
|
|
3261
|
-
if (scope === "rejected") {
|
|
3262
|
-
return rows.filter((row) => !row.aiApproved);
|
|
3263
|
-
}
|
|
3264
|
-
if (scope === "candidates") {
|
|
3265
|
-
return rows.filter((row) => row.modelCandidate && !row.aiApproved);
|
|
3266
|
-
}
|
|
3267
|
-
return rows;
|
|
2928
|
+
return reportPath;
|
|
3268
2929
|
};
|
|
3269
2930
|
var printSection = (title, table) => {
|
|
3270
2931
|
console.log(import_chalk.default.gray(`${title}:`));
|
|
@@ -3343,1003 +3004,1639 @@ var buildQualityRows = (qualityThresholds) => qualityThresholds.map(({ label, su
|
|
|
3343
3004
|
colorizeNumber(summary.avgApprovedTradesPerDay),
|
|
3344
3005
|
colorizeProfit(summary.avgProfitApprovedPerDay)
|
|
3345
3006
|
]);
|
|
3346
|
-
var
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
var searchPhaseLabels = {
|
|
3355
|
-
features: "features",
|
|
3356
|
-
predicates: "preds",
|
|
3357
|
-
masks: "masks",
|
|
3358
|
-
combinations: "search"
|
|
3359
|
-
};
|
|
3360
|
-
var hasCliOption2 = (longName, shortName) => process.argv.some(
|
|
3361
|
-
(arg) => arg === `--${longName}` || arg.startsWith(`--${longName}=`) || (shortName ? arg === `-${shortName}` : false)
|
|
3362
|
-
);
|
|
3363
|
-
var main = async () => {
|
|
3364
|
-
const outDir = String(flags.outDir || "data/ai/export");
|
|
3365
|
-
const strategyName = String(flags.strategy || "").trim() || void 0;
|
|
3366
|
-
const explicitFile = String(flags.file || "").trim();
|
|
3367
|
-
const skip = normalizeInt(flags.skip, 0);
|
|
3368
|
-
const minQuality = normalizeInt(flags.minQuality, 4);
|
|
3369
|
-
const sinceInput = parseTimestampFilter(flags.since);
|
|
3370
|
-
const untilTimestamp = parseTimestampFilter(flags.until);
|
|
3371
|
-
const trailingPeriodMs = parseTrailingPeriodMs(flags.period);
|
|
3372
|
-
const periodLabel = String(flags.period || "").trim() || null;
|
|
3373
|
-
const hasDateFilter = trailingPeriodMs != null || sinceInput != null || untilTimestamp != null;
|
|
3374
|
-
const recent = resolveAiTrainRecentLimit({
|
|
3375
|
-
argv: process.argv,
|
|
3376
|
-
recentValue: flags.recent,
|
|
3377
|
-
hasDateFilter
|
|
3007
|
+
var writeAiPocketSearchArtifacts = async ({
|
|
3008
|
+
result,
|
|
3009
|
+
outputPath
|
|
3010
|
+
}) => {
|
|
3011
|
+
const reportPath = result.run.reportPath;
|
|
3012
|
+
const markdownReport = buildAiPocketMarkdownReport(result);
|
|
3013
|
+
await import_promises.default.mkdir(import_path.default.dirname(import_path.default.resolve(reportPath)), {
|
|
3014
|
+
recursive: true
|
|
3378
3015
|
});
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
)
|
|
3016
|
+
await import_promises.default.writeFile(reportPath, markdownReport, "utf8");
|
|
3017
|
+
if (outputPath) {
|
|
3018
|
+
await import_promises.default.mkdir(import_path.default.dirname(import_path.default.resolve(outputPath)), {
|
|
3019
|
+
recursive: true
|
|
3020
|
+
});
|
|
3021
|
+
await import_promises.default.writeFile(outputPath, `${JSON.stringify(result, null, 2)}
|
|
3022
|
+
`);
|
|
3385
3023
|
}
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
}
|
|
3392
|
-
const
|
|
3393
|
-
const
|
|
3394
|
-
const
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3024
|
+
};
|
|
3025
|
+
var presentAiPocketSearchResult = ({
|
|
3026
|
+
result,
|
|
3027
|
+
outputPath
|
|
3028
|
+
}) => {
|
|
3029
|
+
const { run, pocketSearch: search } = result;
|
|
3030
|
+
const coverageSearches = result.coverageSearches ?? [];
|
|
3031
|
+
const currentGateQualityThresholds = result.currentGate.qualityThresholds;
|
|
3032
|
+
const errors = result.errors;
|
|
3033
|
+
const {
|
|
3034
|
+
strategy: resolvedStrategyName,
|
|
3035
|
+
filePaths,
|
|
3036
|
+
selectedRows,
|
|
3037
|
+
evaluatedRows,
|
|
3038
|
+
scope,
|
|
3039
|
+
direction,
|
|
3040
|
+
scopeRows: scopeRowCount,
|
|
3041
|
+
trainRows: trainRowCount,
|
|
3042
|
+
validationRows: validationRowCount,
|
|
3043
|
+
testRows: testRowCount,
|
|
3044
|
+
sourceRows: totalRows,
|
|
3045
|
+
dateSkipped,
|
|
3046
|
+
failed,
|
|
3047
|
+
recent,
|
|
3048
|
+
skip,
|
|
3049
|
+
since: sinceTimestamp,
|
|
3050
|
+
until: untilTimestamp,
|
|
3051
|
+
period: periodLabel,
|
|
3052
|
+
minQuality,
|
|
3053
|
+
includeSymbol,
|
|
3054
|
+
includeGateContext,
|
|
3055
|
+
validationSplit,
|
|
3056
|
+
testSplit,
|
|
3057
|
+
reportPath
|
|
3058
|
+
} = run;
|
|
3059
|
+
const objective = run.objective ?? search.objective;
|
|
3060
|
+
const cadenceMode = run.cadenceMode ?? "auto";
|
|
3061
|
+
const featureProfile = run.featureProfile ?? "all";
|
|
3062
|
+
const featurePolicy = run.featurePolicy ?? "causal-stationary";
|
|
3063
|
+
const coverageMode = run.coverageMode ?? "auto";
|
|
3064
|
+
const sealedTest = run.sealedTest ?? {
|
|
3065
|
+
sealed: false,
|
|
3066
|
+
rows: 0,
|
|
3067
|
+
events: 0,
|
|
3068
|
+
startTimestamp: null,
|
|
3069
|
+
endTimestamp: null
|
|
3070
|
+
};
|
|
3071
|
+
const {
|
|
3072
|
+
maxDepth,
|
|
3073
|
+
maxAtomicPredicates,
|
|
3074
|
+
maxCombinations,
|
|
3075
|
+
maxSymbolCountShare = 0.5,
|
|
3076
|
+
allowRiskRegression = false,
|
|
3077
|
+
requireValidationEligibility = true,
|
|
3078
|
+
validationSplit: _searchValidationSplit,
|
|
3079
|
+
testSplit: _searchTestSplit,
|
|
3080
|
+
dedupeEquivalentSelections
|
|
3081
|
+
} = run.search;
|
|
3082
|
+
const maxBatch = run.search.maxBatch ?? 0;
|
|
3083
|
+
console.log("");
|
|
3084
|
+
console.log(import_chalk.default.green("AI pocket search finished"));
|
|
3085
|
+
filePaths.forEach((filePath) => console.log(import_chalk.default.gray(filePath)));
|
|
3086
|
+
console.log("");
|
|
3087
|
+
printSection(
|
|
3088
|
+
"RUN",
|
|
3089
|
+
createTable(
|
|
3090
|
+
[import_chalk.default.gray("FIELD"), import_chalk.default.gray("VALUE")],
|
|
3091
|
+
[
|
|
3092
|
+
["strategy", import_chalk.default.yellow(resolvedStrategyName)],
|
|
3093
|
+
["selected", import_chalk.default.blue(String(selectedRows))],
|
|
3094
|
+
["evaluated", import_chalk.default.blue(String(evaluatedRows))],
|
|
3095
|
+
["scope", import_chalk.default.yellow(scope)],
|
|
3096
|
+
["objective", import_chalk.default.yellow(objective)],
|
|
3097
|
+
["direction", direction ? import_chalk.default.yellow(direction) : import_chalk.default.gray("all")],
|
|
3098
|
+
["scope_rows", import_chalk.default.blue(String(scopeRowCount))],
|
|
3099
|
+
["train_rows", import_chalk.default.blue(String(trainRowCount))],
|
|
3100
|
+
["validation_rows", import_chalk.default.blue(String(validationRowCount))],
|
|
3101
|
+
["test_rows", import_chalk.default.blue(String(testRowCount))],
|
|
3102
|
+
["source_rows", import_chalk.default.blue(String(totalRows))],
|
|
3103
|
+
["date_skipped", import_chalk.default.blue(String(dateSkipped))],
|
|
3104
|
+
[
|
|
3105
|
+
"failed",
|
|
3106
|
+
failed > 0 ? import_chalk.default.yellow(String(failed)) : import_chalk.default.green("0")
|
|
3107
|
+
],
|
|
3108
|
+
["recent", import_chalk.default.blue(recent === 0 ? "all" : String(recent))],
|
|
3109
|
+
["skip", import_chalk.default.blue(String(skip))],
|
|
3110
|
+
[
|
|
3111
|
+
"since",
|
|
3112
|
+
sinceTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(new Date(sinceTimestamp).toISOString())
|
|
3113
|
+
],
|
|
3114
|
+
[
|
|
3115
|
+
"until",
|
|
3116
|
+
untilTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(new Date(untilTimestamp).toISOString())
|
|
3117
|
+
],
|
|
3118
|
+
[
|
|
3119
|
+
"period",
|
|
3120
|
+
periodLabel == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(periodLabel)
|
|
3121
|
+
],
|
|
3122
|
+
["min_quality", import_chalk.default.magenta(String(minQuality))],
|
|
3123
|
+
["max_depth", import_chalk.default.magenta(String(maxDepth))],
|
|
3124
|
+
["cadence_mode", import_chalk.default.magenta(cadenceMode)],
|
|
3125
|
+
[
|
|
3126
|
+
"low_cadence",
|
|
3127
|
+
search.stats.cadence.lowCadence ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
|
|
3128
|
+
],
|
|
3129
|
+
[
|
|
3130
|
+
"sparse_sample",
|
|
3131
|
+
search.stats.cadence.sparseSample ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
|
|
3132
|
+
],
|
|
3133
|
+
[
|
|
3134
|
+
"adaptive_thresholds",
|
|
3135
|
+
search.stats.cadence.adaptiveThresholds ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
|
|
3136
|
+
],
|
|
3137
|
+
[
|
|
3138
|
+
"train_events",
|
|
3139
|
+
import_chalk.default.magenta(String(search.stats.cadence.trainEvents))
|
|
3140
|
+
],
|
|
3141
|
+
[
|
|
3142
|
+
"train_events_per_day",
|
|
3143
|
+
import_chalk.default.magenta(formatNumber2(search.stats.cadence.trainEventsPerDay))
|
|
3144
|
+
],
|
|
3145
|
+
["min_support", import_chalk.default.magenta(String(search.stats.cadence.minSupport))],
|
|
3146
|
+
["min_events", import_chalk.default.magenta(String(search.stats.cadence.minEvents))],
|
|
3147
|
+
[
|
|
3148
|
+
"min_validation_events",
|
|
3149
|
+
import_chalk.default.magenta(String(search.stats.cadence.minValidationEvents))
|
|
3150
|
+
],
|
|
3151
|
+
["max_batch", import_chalk.default.magenta(maxBatch > 0 ? String(maxBatch) : "off")],
|
|
3152
|
+
[
|
|
3153
|
+
"max_event_share",
|
|
3154
|
+
import_chalk.default.magenta(formatRatio(search.stats.cadence.maxEventCountShare))
|
|
3155
|
+
],
|
|
3156
|
+
["max_symbol_share", import_chalk.default.magenta(formatRatio(maxSymbolCountShare))],
|
|
3157
|
+
["max_atomic_predicates", import_chalk.default.magenta(String(maxAtomicPredicates))],
|
|
3158
|
+
["max_combinations", import_chalk.default.magenta(String(maxCombinations))],
|
|
3159
|
+
["validation_split", import_chalk.default.magenta(formatRatio(validationSplit))],
|
|
3160
|
+
["test_split", import_chalk.default.magenta(formatRatio(testSplit))],
|
|
3161
|
+
[
|
|
3162
|
+
"test_evidence",
|
|
3163
|
+
sealedTest.sealed ? import_chalk.default.green(
|
|
3164
|
+
`sealed (${sealedTest.rows} rows / ${sealedTest.events} events)`
|
|
3165
|
+
) : import_chalk.default.yellow("open")
|
|
3166
|
+
],
|
|
3167
|
+
[
|
|
3168
|
+
"min_validation_support",
|
|
3169
|
+
import_chalk.default.magenta(String(search.stats.cadence.minValidationSupport))
|
|
3170
|
+
],
|
|
3171
|
+
[
|
|
3172
|
+
"dedupe_equivalent",
|
|
3173
|
+
dedupeEquivalentSelections ? import_chalk.default.green("on") : import_chalk.default.gray("off")
|
|
3174
|
+
],
|
|
3175
|
+
[
|
|
3176
|
+
"include_symbol",
|
|
3177
|
+
includeSymbol ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
|
|
3178
|
+
],
|
|
3179
|
+
[
|
|
3180
|
+
"include_gate_context",
|
|
3181
|
+
includeGateContext ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
|
|
3182
|
+
],
|
|
3183
|
+
["feature_profile", import_chalk.default.magenta(featureProfile)],
|
|
3184
|
+
["feature_policy", import_chalk.default.magenta(featurePolicy)],
|
|
3185
|
+
["coverage_mode", import_chalk.default.magenta(coverageMode)],
|
|
3186
|
+
[
|
|
3187
|
+
"allow_risk_regression",
|
|
3188
|
+
allowRiskRegression ? import_chalk.default.yellow("on") : import_chalk.default.green("off")
|
|
3189
|
+
],
|
|
3190
|
+
[
|
|
3191
|
+
"validation_eligibility",
|
|
3192
|
+
requireValidationEligibility ? import_chalk.default.green("required") : import_chalk.default.yellow("off")
|
|
3193
|
+
],
|
|
3194
|
+
["report", import_chalk.default.gray(reportPath)],
|
|
3195
|
+
["output", outputPath ? import_chalk.default.gray(outputPath) : import_chalk.default.gray("off")]
|
|
3196
|
+
]
|
|
3197
|
+
)
|
|
3401
3198
|
);
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3199
|
+
printSection(
|
|
3200
|
+
"CURRENT GATE QN+ BASELINE",
|
|
3201
|
+
createTable(
|
|
3202
|
+
[
|
|
3203
|
+
import_chalk.default.gray("Q"),
|
|
3204
|
+
import_chalk.default.gray("APPROVED"),
|
|
3205
|
+
import_chalk.default.gray("WR"),
|
|
3206
|
+
import_chalk.default.gray("PF"),
|
|
3207
|
+
import_chalk.default.gray("PNL"),
|
|
3208
|
+
import_chalk.default.gray("MAX_DD"),
|
|
3209
|
+
import_chalk.default.gray("TRADES/D"),
|
|
3210
|
+
import_chalk.default.gray("PNL/D")
|
|
3211
|
+
],
|
|
3212
|
+
buildQualityRows(currentGateQualityThresholds)
|
|
3213
|
+
)
|
|
3409
3214
|
);
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
const maxAtomicPredicates = normalizePositiveInt(
|
|
3417
|
-
flags.maxAtomicPredicates,
|
|
3418
|
-
180
|
|
3215
|
+
printSection(
|
|
3216
|
+
"TRAIN BASELINE",
|
|
3217
|
+
createTable(
|
|
3218
|
+
[import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
|
|
3219
|
+
buildSummaryRows(search.baseline)
|
|
3220
|
+
)
|
|
3419
3221
|
);
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
) : 0.25;
|
|
3429
|
-
const testSplit = hasCliOption2("testSplit", "T") ? normalizeRatio(
|
|
3430
|
-
readAiPocketSearchCliOption({
|
|
3431
|
-
argv: process.argv,
|
|
3432
|
-
longName: "testSplit",
|
|
3433
|
-
shortName: "T"
|
|
3434
|
-
}) ?? flags.testSplit,
|
|
3435
|
-
0
|
|
3436
|
-
) : 0;
|
|
3437
|
-
const sealTest = Boolean(flags.sealTest);
|
|
3438
|
-
if (sealTest && testSplit <= 0) {
|
|
3439
|
-
throw new Error("--sealTest requires a positive --testSplit");
|
|
3222
|
+
if (search.validationBaseline) {
|
|
3223
|
+
printSection(
|
|
3224
|
+
"VALIDATION BASELINE",
|
|
3225
|
+
createTable(
|
|
3226
|
+
[import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
|
|
3227
|
+
buildSummaryRows(search.validationBaseline)
|
|
3228
|
+
)
|
|
3229
|
+
);
|
|
3440
3230
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
flags.minValidationEvents,
|
|
3448
|
-
0
|
|
3449
|
-
);
|
|
3450
|
-
const maxBatch = normalizeInt(flags.maxBatch, 5);
|
|
3451
|
-
const maxEventCountShare = normalizeUnitRatio(
|
|
3452
|
-
readAiPocketSearchCliOption({
|
|
3453
|
-
argv: process.argv,
|
|
3454
|
-
longName: "maxEventCountShare",
|
|
3455
|
-
shortName: "U"
|
|
3456
|
-
}) ?? flags.maxEventCountShare,
|
|
3457
|
-
0.25
|
|
3458
|
-
);
|
|
3459
|
-
const maxSymbolCountShare = normalizeUnitRatio(
|
|
3460
|
-
readAiPocketSearchCliOption({
|
|
3461
|
-
argv: process.argv,
|
|
3462
|
-
longName: "maxSymbolCountShare",
|
|
3463
|
-
shortName: "Z"
|
|
3464
|
-
}) ?? flags.maxSymbolCountShare,
|
|
3465
|
-
0.5
|
|
3466
|
-
);
|
|
3467
|
-
const objective = normalizeObjective(flags.objective, scope);
|
|
3468
|
-
const allowRiskRegression = Boolean(flags.allowRiskRegression);
|
|
3469
|
-
const requireValidationEligibility = !Boolean(
|
|
3470
|
-
flags.allowValidationRegression
|
|
3471
|
-
);
|
|
3472
|
-
const dedupeEquivalentSelections = Boolean(flags.dedupeEquivalentSelections);
|
|
3473
|
-
const top = normalizePositiveInt(flags.top, 30);
|
|
3474
|
-
const includeSymbol = Boolean(flags.includeSymbol);
|
|
3475
|
-
const includeGateContext = Boolean(flags.includeGateContext);
|
|
3476
|
-
const featureProfile = normalizeFeatureProfile(flags.featureProfile);
|
|
3477
|
-
const featurePolicy = normalizeFeaturePolicy(flags.featurePolicy);
|
|
3478
|
-
const coverageMode = normalizeCoverageMode(flags.coverageMode);
|
|
3479
|
-
const cadenceMode = normalizeCadenceMode(flags.cadenceMode);
|
|
3480
|
-
const jsonOutput = Boolean(flags.json);
|
|
3481
|
-
const outputPath = String(flags.output || "").trim();
|
|
3482
|
-
const reportDir = String(flags.reportDir || "data/ai/output").trim();
|
|
3483
|
-
const explicitReportFile = String(flags.reportFile || "").trim();
|
|
3484
|
-
const generatedAt = Date.now();
|
|
3485
|
-
const excludedFeaturePaths = /* @__PURE__ */ new Map();
|
|
3486
|
-
await (0, import_registry.ensureStrategyPluginsLoaded)();
|
|
3487
|
-
const filePaths = await resolveMergedDatasetFiles({
|
|
3488
|
-
outDir,
|
|
3489
|
-
strategyName,
|
|
3490
|
-
explicitFile
|
|
3491
|
-
});
|
|
3492
|
-
const { totalRows, selectedRows } = await (0, import_ai.countAiDatasetRows)({
|
|
3493
|
-
filePaths,
|
|
3494
|
-
limitFromEnd: recent,
|
|
3495
|
-
skipFromEnd: skip
|
|
3496
|
-
});
|
|
3497
|
-
const maxSelectedTimestamp = trailingPeriodMs == null ? null : await findMaxSelectedTimestamp({ filePaths, recent, skip });
|
|
3498
|
-
const sinceTimestamp = trailingPeriodMs != null && maxSelectedTimestamp != null ? maxSelectedTimestamp - trailingPeriodMs : sinceInput;
|
|
3499
|
-
if (!selectedRows) {
|
|
3500
|
-
console.log(
|
|
3501
|
-
import_chalk.default.yellow(
|
|
3502
|
-
`No AI prompt rows selected in ${filePaths.join(", ")} (recent=${recent || "all"}, skip=${skip})`
|
|
3231
|
+
if (search.testBaseline) {
|
|
3232
|
+
printSection(
|
|
3233
|
+
"UNTOUCHED TEST BASELINE",
|
|
3234
|
+
createTable(
|
|
3235
|
+
[import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
|
|
3236
|
+
buildSummaryRows(search.testBaseline)
|
|
3503
3237
|
)
|
|
3504
3238
|
);
|
|
3505
|
-
process.exit(0);
|
|
3506
3239
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
}
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
includeGateContext,
|
|
3578
|
-
featureProfile,
|
|
3579
|
-
featurePolicy,
|
|
3580
|
-
onFeatureExcluded: ({ path: featurePath, classification }) => {
|
|
3581
|
-
const paths = excludedFeaturePaths.get(classification) ?? /* @__PURE__ */ new Set();
|
|
3582
|
-
paths.add(featurePath);
|
|
3583
|
-
excludedFeaturePaths.set(classification, paths);
|
|
3584
|
-
}
|
|
3585
|
-
});
|
|
3586
|
-
rows.push({
|
|
3587
|
-
signalId: row.signalId,
|
|
3588
|
-
strategy: row.strategyName,
|
|
3589
|
-
symbol: row.symbol,
|
|
3590
|
-
direction: row.direction,
|
|
3591
|
-
timestamp,
|
|
3592
|
-
profit,
|
|
3593
|
-
profitableTrade: profit > 0,
|
|
3594
|
-
aiApproved: isAiApproval(row, analysis, minQuality),
|
|
3595
|
-
quality,
|
|
3596
|
-
modelDirectionMatches,
|
|
3597
|
-
modelCandidate: getDeterministicModelCandidate(signal),
|
|
3598
|
-
features: featureSnapshot.features,
|
|
3599
|
-
featureCoverage: featureSnapshot.featureCoverage
|
|
3600
|
-
});
|
|
3601
|
-
bar?.tick(1, {
|
|
3602
|
-
symbol: import_chalk.default.gray(row.symbol),
|
|
3603
|
-
status: import_chalk.default.green("ok")
|
|
3604
|
-
});
|
|
3605
|
-
} catch (error) {
|
|
3606
|
-
failed += 1;
|
|
3607
|
-
if (errors.length < 5) {
|
|
3608
|
-
errors.push(
|
|
3609
|
-
`[${row.symbol}/${row.signalId}] ${error instanceof Error ? error.message : String(error)}`
|
|
3610
|
-
);
|
|
3611
|
-
}
|
|
3612
|
-
bar?.tick(1, {
|
|
3613
|
-
symbol: import_chalk.default.gray(row.symbol),
|
|
3614
|
-
status: import_chalk.default.yellow("error")
|
|
3615
|
-
});
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
});
|
|
3619
|
-
const directionRows = direction ? rows.filter((row) => row.direction === direction) : rows;
|
|
3620
|
-
const scopeRows = resolveScopeRows(directionRows, scope);
|
|
3621
|
-
const fullSplit = splitAiPocketResearchRowsByTimestamp(
|
|
3622
|
-
directionRows,
|
|
3623
|
-
validationSplit,
|
|
3624
|
-
testSplit
|
|
3625
|
-
);
|
|
3626
|
-
const sealedFullSplit = sealAiPocketTestPartition(fullSplit, sealTest);
|
|
3627
|
-
const discoveryRows = sealedFullSplit.discoveryRows;
|
|
3628
|
-
const trainRows = resolveScopeRows(fullSplit.trainRows, scope);
|
|
3629
|
-
const validationRows = resolveScopeRows(fullSplit.validationRows, scope);
|
|
3630
|
-
const testRows = resolveScopeRows(sealedFullSplit.searchTestRows, scope);
|
|
3631
|
-
const baselineRows = fullSplit.trainRows.filter((row) => row.aiApproved);
|
|
3632
|
-
const validationBaselineRows = fullSplit.validationRows.filter(
|
|
3633
|
-
(row) => row.aiApproved
|
|
3240
|
+
if (search.objectiveBaseline) {
|
|
3241
|
+
printSection(
|
|
3242
|
+
"CURRENT GATE TRAIN BASELINE",
|
|
3243
|
+
createTable(
|
|
3244
|
+
[import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
|
|
3245
|
+
buildSummaryRows(search.objectiveBaseline)
|
|
3246
|
+
)
|
|
3247
|
+
);
|
|
3248
|
+
}
|
|
3249
|
+
printSection(
|
|
3250
|
+
"SEARCH STATS",
|
|
3251
|
+
createTable(
|
|
3252
|
+
[import_chalk.default.gray("FIELD"), import_chalk.default.gray("VALUE")],
|
|
3253
|
+
[
|
|
3254
|
+
["feature_keys", import_chalk.default.cyan(String(search.stats.featureKeys))],
|
|
3255
|
+
["predicates", import_chalk.default.cyan(String(search.stats.predicates))],
|
|
3256
|
+
["atomic_used", import_chalk.default.cyan(String(search.stats.atomicPredicatesUsed))],
|
|
3257
|
+
[
|
|
3258
|
+
"feature_families",
|
|
3259
|
+
import_chalk.default.cyan(search.stats.featureFamiliesUsed.join(", "))
|
|
3260
|
+
],
|
|
3261
|
+
[
|
|
3262
|
+
"required_feature_families",
|
|
3263
|
+
import_chalk.default.cyan(search.stats.requiredFeatureFamilies.join(", ") || "none")
|
|
3264
|
+
],
|
|
3265
|
+
[
|
|
3266
|
+
"excluded_feature_families",
|
|
3267
|
+
import_chalk.default.cyan(search.stats.excludedFeatureFamilies.join(", ") || "none")
|
|
3268
|
+
],
|
|
3269
|
+
[
|
|
3270
|
+
"estimated_combinations",
|
|
3271
|
+
import_chalk.default.cyan(String(search.stats.estimatedCombinations))
|
|
3272
|
+
],
|
|
3273
|
+
[
|
|
3274
|
+
"combinations_evaluated",
|
|
3275
|
+
import_chalk.default.cyan(String(search.stats.combinationsEvaluated))
|
|
3276
|
+
],
|
|
3277
|
+
["validation_rows", import_chalk.default.cyan(String(search.stats.validationRows))],
|
|
3278
|
+
["test_rows", import_chalk.default.cyan(String(search.stats.testRows))],
|
|
3279
|
+
["train_events", import_chalk.default.cyan(String(search.stats.cadence.trainEvents))],
|
|
3280
|
+
[
|
|
3281
|
+
"train_events_per_day",
|
|
3282
|
+
import_chalk.default.cyan(formatNumber2(search.stats.cadence.trainEventsPerDay))
|
|
3283
|
+
],
|
|
3284
|
+
[
|
|
3285
|
+
"effective_min_support",
|
|
3286
|
+
import_chalk.default.cyan(String(search.stats.cadence.minSupport))
|
|
3287
|
+
],
|
|
3288
|
+
[
|
|
3289
|
+
"effective_min_events",
|
|
3290
|
+
import_chalk.default.cyan(String(search.stats.cadence.minEvents))
|
|
3291
|
+
],
|
|
3292
|
+
[
|
|
3293
|
+
"effective_min_validation_support",
|
|
3294
|
+
import_chalk.default.cyan(String(search.stats.cadence.minValidationSupport))
|
|
3295
|
+
],
|
|
3296
|
+
[
|
|
3297
|
+
"effective_min_validation_events",
|
|
3298
|
+
import_chalk.default.cyan(String(search.stats.cadence.minValidationEvents))
|
|
3299
|
+
],
|
|
3300
|
+
[
|
|
3301
|
+
"duplicate_pockets_skipped",
|
|
3302
|
+
import_chalk.default.cyan(String(search.stats.duplicatePocketsSkipped))
|
|
3303
|
+
],
|
|
3304
|
+
[
|
|
3305
|
+
"truncated",
|
|
3306
|
+
search.stats.truncated ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
|
|
3307
|
+
]
|
|
3308
|
+
]
|
|
3309
|
+
)
|
|
3634
3310
|
);
|
|
3635
|
-
|
|
3636
|
-
|
|
3311
|
+
printSection(
|
|
3312
|
+
"TOP POSITIVE POCKETS",
|
|
3313
|
+
createTable(
|
|
3314
|
+
[
|
|
3315
|
+
import_chalk.default.gray("#"),
|
|
3316
|
+
import_chalk.default.gray("N"),
|
|
3317
|
+
import_chalk.default.gray("EVENTS"),
|
|
3318
|
+
import_chalk.default.gray("MAX_B"),
|
|
3319
|
+
import_chalk.default.gray("SUP"),
|
|
3320
|
+
import_chalk.default.gray("WR"),
|
|
3321
|
+
import_chalk.default.gray("PF"),
|
|
3322
|
+
import_chalk.default.gray("PNL"),
|
|
3323
|
+
import_chalk.default.gray("OBJ_PNL"),
|
|
3324
|
+
import_chalk.default.gray("MAX_DD"),
|
|
3325
|
+
import_chalk.default.gray("VAL_N"),
|
|
3326
|
+
import_chalk.default.gray("VAL_EVENTS"),
|
|
3327
|
+
import_chalk.default.gray("VAL_WR"),
|
|
3328
|
+
import_chalk.default.gray("VAL_PF"),
|
|
3329
|
+
import_chalk.default.gray("VAL_PNL"),
|
|
3330
|
+
import_chalk.default.gray("TEST_N"),
|
|
3331
|
+
import_chalk.default.gray("TEST_EVENTS"),
|
|
3332
|
+
import_chalk.default.gray("TEST_PF"),
|
|
3333
|
+
import_chalk.default.gray("TEST_PNL"),
|
|
3334
|
+
import_chalk.default.gray("TEST_OBJ"),
|
|
3335
|
+
import_chalk.default.gray("TR/D"),
|
|
3336
|
+
import_chalk.default.gray("LOSS_M"),
|
|
3337
|
+
import_chalk.default.gray("SCORE"),
|
|
3338
|
+
import_chalk.default.gray("READINESS"),
|
|
3339
|
+
import_chalk.default.gray("REASONS"),
|
|
3340
|
+
import_chalk.default.gray("POCKET")
|
|
3341
|
+
],
|
|
3342
|
+
buildPocketRows(search.positivePockets)
|
|
3343
|
+
)
|
|
3637
3344
|
);
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3345
|
+
printSection(
|
|
3346
|
+
"TOP LOSS POCKETS",
|
|
3347
|
+
createTable(
|
|
3348
|
+
[
|
|
3349
|
+
import_chalk.default.gray("#"),
|
|
3350
|
+
import_chalk.default.gray("N"),
|
|
3351
|
+
import_chalk.default.gray("EVENTS"),
|
|
3352
|
+
import_chalk.default.gray("MAX_B"),
|
|
3353
|
+
import_chalk.default.gray("SUP"),
|
|
3354
|
+
import_chalk.default.gray("WR"),
|
|
3355
|
+
import_chalk.default.gray("PF"),
|
|
3356
|
+
import_chalk.default.gray("PNL"),
|
|
3357
|
+
import_chalk.default.gray("OBJ_PNL"),
|
|
3358
|
+
import_chalk.default.gray("MAX_DD"),
|
|
3359
|
+
import_chalk.default.gray("VAL_N"),
|
|
3360
|
+
import_chalk.default.gray("VAL_EVENTS"),
|
|
3361
|
+
import_chalk.default.gray("VAL_WR"),
|
|
3362
|
+
import_chalk.default.gray("VAL_PF"),
|
|
3363
|
+
import_chalk.default.gray("VAL_PNL"),
|
|
3364
|
+
import_chalk.default.gray("TEST_N"),
|
|
3365
|
+
import_chalk.default.gray("TEST_EVENTS"),
|
|
3366
|
+
import_chalk.default.gray("TEST_PF"),
|
|
3367
|
+
import_chalk.default.gray("TEST_PNL"),
|
|
3368
|
+
import_chalk.default.gray("TEST_OBJ"),
|
|
3369
|
+
import_chalk.default.gray("TR/D"),
|
|
3370
|
+
import_chalk.default.gray("LOSS_M"),
|
|
3371
|
+
import_chalk.default.gray("SCORE"),
|
|
3372
|
+
import_chalk.default.gray("READINESS"),
|
|
3373
|
+
import_chalk.default.gray("REASONS"),
|
|
3374
|
+
import_chalk.default.gray("POCKET")
|
|
3375
|
+
],
|
|
3376
|
+
buildPocketRows(search.negativePockets)
|
|
3377
|
+
)
|
|
3642
3378
|
);
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
)
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
)
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3379
|
+
for (const cohort of coverageSearches) {
|
|
3380
|
+
const title = cohort.family === "cmc" ? "CMC" : "COINALYZE";
|
|
3381
|
+
printSection(
|
|
3382
|
+
`${title} COVERAGE COHORT`,
|
|
3383
|
+
createTable(
|
|
3384
|
+
[import_chalk.default.gray("FIELD"), import_chalk.default.gray("VALUE")],
|
|
3385
|
+
[
|
|
3386
|
+
["coverage_rows", import_chalk.default.cyan(String(cohort.coverage.rows))],
|
|
3387
|
+
["coverage_ratio", import_chalk.default.cyan(formatRatio(cohort.coverage.rowRatio))],
|
|
3388
|
+
["coverage_events", import_chalk.default.cyan(String(cohort.coverage.events))],
|
|
3389
|
+
["event_ratio", import_chalk.default.cyan(formatRatio(cohort.coverage.eventRatio))],
|
|
3390
|
+
[
|
|
3391
|
+
"coverage_from",
|
|
3392
|
+
cohort.coverage.minTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(
|
|
3393
|
+
new Date(cohort.coverage.minTimestamp).toISOString()
|
|
3394
|
+
)
|
|
3395
|
+
],
|
|
3396
|
+
[
|
|
3397
|
+
"coverage_to",
|
|
3398
|
+
cohort.coverage.maxTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(
|
|
3399
|
+
new Date(cohort.coverage.maxTimestamp).toISOString()
|
|
3400
|
+
)
|
|
3401
|
+
],
|
|
3402
|
+
["scope_rows", import_chalk.default.cyan(String(cohort.scopeRows))],
|
|
3403
|
+
["train_rows", import_chalk.default.cyan(String(cohort.trainRows))],
|
|
3404
|
+
["validation_rows", import_chalk.default.cyan(String(cohort.validationRows))],
|
|
3405
|
+
["test_rows", import_chalk.default.cyan(String(cohort.testRows))],
|
|
3406
|
+
[
|
|
3407
|
+
"low_cadence",
|
|
3408
|
+
cohort.search.stats.cadence.lowCadence ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
|
|
3409
|
+
],
|
|
3410
|
+
[
|
|
3411
|
+
"sparse_sample",
|
|
3412
|
+
cohort.search.stats.cadence.sparseSample ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
|
|
3413
|
+
],
|
|
3414
|
+
[
|
|
3415
|
+
"adaptive_thresholds",
|
|
3416
|
+
cohort.search.stats.cadence.adaptiveThresholds ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
|
|
3417
|
+
],
|
|
3418
|
+
[
|
|
3419
|
+
"train_events",
|
|
3420
|
+
import_chalk.default.cyan(String(cohort.search.stats.cadence.trainEvents))
|
|
3421
|
+
],
|
|
3422
|
+
[
|
|
3423
|
+
"effective_min_support",
|
|
3424
|
+
import_chalk.default.cyan(String(cohort.search.stats.cadence.minSupport))
|
|
3425
|
+
],
|
|
3426
|
+
[
|
|
3427
|
+
"effective_min_events",
|
|
3428
|
+
import_chalk.default.cyan(String(cohort.search.stats.cadence.minEvents))
|
|
3429
|
+
]
|
|
3430
|
+
]
|
|
3431
|
+
)
|
|
3432
|
+
);
|
|
3433
|
+
printSection(
|
|
3434
|
+
`${title} TRAIN BASELINE`,
|
|
3435
|
+
createTable(
|
|
3436
|
+
[import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
|
|
3437
|
+
buildSummaryRows(cohort.search.baseline)
|
|
3438
|
+
)
|
|
3439
|
+
);
|
|
3440
|
+
printSection(
|
|
3441
|
+
`${title} TOP POSITIVE POCKETS`,
|
|
3442
|
+
createTable(
|
|
3443
|
+
[
|
|
3444
|
+
import_chalk.default.gray("#"),
|
|
3445
|
+
import_chalk.default.gray("N"),
|
|
3446
|
+
import_chalk.default.gray("EVENTS"),
|
|
3447
|
+
import_chalk.default.gray("MAX_B"),
|
|
3448
|
+
import_chalk.default.gray("SUP"),
|
|
3449
|
+
import_chalk.default.gray("WR"),
|
|
3450
|
+
import_chalk.default.gray("PF"),
|
|
3451
|
+
import_chalk.default.gray("PNL"),
|
|
3452
|
+
import_chalk.default.gray("OBJ_PNL"),
|
|
3453
|
+
import_chalk.default.gray("MAX_DD"),
|
|
3454
|
+
import_chalk.default.gray("VAL_N"),
|
|
3455
|
+
import_chalk.default.gray("VAL_EVENTS"),
|
|
3456
|
+
import_chalk.default.gray("VAL_WR"),
|
|
3457
|
+
import_chalk.default.gray("VAL_PF"),
|
|
3458
|
+
import_chalk.default.gray("VAL_PNL"),
|
|
3459
|
+
import_chalk.default.gray("TEST_N"),
|
|
3460
|
+
import_chalk.default.gray("TEST_EVENTS"),
|
|
3461
|
+
import_chalk.default.gray("TEST_PF"),
|
|
3462
|
+
import_chalk.default.gray("TEST_PNL"),
|
|
3463
|
+
import_chalk.default.gray("TEST_OBJ"),
|
|
3464
|
+
import_chalk.default.gray("TR/D"),
|
|
3465
|
+
import_chalk.default.gray("LOSS_M"),
|
|
3466
|
+
import_chalk.default.gray("SCORE"),
|
|
3467
|
+
import_chalk.default.gray("READINESS"),
|
|
3468
|
+
import_chalk.default.gray("REASONS"),
|
|
3469
|
+
import_chalk.default.gray("POCKET")
|
|
3470
|
+
],
|
|
3471
|
+
buildPocketRows(cohort.search.positivePockets)
|
|
3472
|
+
)
|
|
3473
|
+
);
|
|
3474
|
+
printSection(
|
|
3475
|
+
`${title} TOP LOSS POCKETS`,
|
|
3476
|
+
createTable(
|
|
3477
|
+
[
|
|
3478
|
+
import_chalk.default.gray("#"),
|
|
3479
|
+
import_chalk.default.gray("N"),
|
|
3480
|
+
import_chalk.default.gray("EVENTS"),
|
|
3481
|
+
import_chalk.default.gray("MAX_B"),
|
|
3482
|
+
import_chalk.default.gray("SUP"),
|
|
3483
|
+
import_chalk.default.gray("WR"),
|
|
3484
|
+
import_chalk.default.gray("PF"),
|
|
3485
|
+
import_chalk.default.gray("PNL"),
|
|
3486
|
+
import_chalk.default.gray("OBJ_PNL"),
|
|
3487
|
+
import_chalk.default.gray("MAX_DD"),
|
|
3488
|
+
import_chalk.default.gray("VAL_N"),
|
|
3489
|
+
import_chalk.default.gray("VAL_EVENTS"),
|
|
3490
|
+
import_chalk.default.gray("VAL_WR"),
|
|
3491
|
+
import_chalk.default.gray("VAL_PF"),
|
|
3492
|
+
import_chalk.default.gray("VAL_PNL"),
|
|
3493
|
+
import_chalk.default.gray("TEST_N"),
|
|
3494
|
+
import_chalk.default.gray("TEST_EVENTS"),
|
|
3495
|
+
import_chalk.default.gray("TEST_PF"),
|
|
3496
|
+
import_chalk.default.gray("TEST_PNL"),
|
|
3497
|
+
import_chalk.default.gray("TEST_OBJ"),
|
|
3498
|
+
import_chalk.default.gray("TR/D"),
|
|
3499
|
+
import_chalk.default.gray("LOSS_M"),
|
|
3500
|
+
import_chalk.default.gray("SCORE"),
|
|
3501
|
+
import_chalk.default.gray("READINESS"),
|
|
3502
|
+
import_chalk.default.gray("REASONS"),
|
|
3503
|
+
import_chalk.default.gray("POCKET")
|
|
3504
|
+
],
|
|
3505
|
+
buildPocketRows(cohort.search.negativePockets)
|
|
3506
|
+
)
|
|
3507
|
+
);
|
|
3508
|
+
}
|
|
3509
|
+
if (errors.length) {
|
|
3510
|
+
console.log(import_chalk.default.yellow("Errors:"));
|
|
3511
|
+
errors.forEach((error) => console.log(import_chalk.default.yellow(`- ${error}`)));
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
|
|
3515
|
+
// src/lib/aiPocketSearch/dataset.ts
|
|
3516
|
+
var import_promises2 = __toESM(require("fs/promises"));
|
|
3517
|
+
var import_path2 = __toESM(require("path"));
|
|
3518
|
+
var import_ai2 = require("@tradejs/infra/ai");
|
|
3519
|
+
var import_ai3 = require("@tradejs/node/ai");
|
|
3520
|
+
var import_registry = require("@tradejs/node/registry");
|
|
3521
|
+
|
|
3522
|
+
// src/lib/aiTrainDataset.ts
|
|
3523
|
+
var extractSignalFromAiDatasetRow = (row) => {
|
|
3524
|
+
const { payload } = row;
|
|
3525
|
+
return {
|
|
3526
|
+
...payload.signal,
|
|
3527
|
+
strategy: payload.signal.strategy,
|
|
3528
|
+
figures: payload.figures ?? {},
|
|
3529
|
+
indicators: payload.indicators ?? {},
|
|
3530
|
+
additionalIndicators: payload.additionalIndicators ?? {},
|
|
3531
|
+
prices: payload.signal.prices
|
|
3732
3532
|
};
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
);
|
|
3757
|
-
const sealedCohortSplit = sealAiPocketTestPartition(
|
|
3758
|
-
cohortSplit,
|
|
3759
|
-
sealTest
|
|
3760
|
-
);
|
|
3761
|
-
const cohortTrainRows = resolveScopeRows(cohortSplit.trainRows, scope);
|
|
3762
|
-
const cohortValidationRows = resolveScopeRows(
|
|
3763
|
-
cohortSplit.validationRows,
|
|
3764
|
-
scope
|
|
3765
|
-
);
|
|
3766
|
-
const cohortTestRows = resolveScopeRows(
|
|
3767
|
-
sealedCohortSplit.searchTestRows,
|
|
3768
|
-
scope
|
|
3769
|
-
);
|
|
3770
|
-
const cohortSearch = runSearch({
|
|
3771
|
-
label: family,
|
|
3772
|
-
train: cohortTrainRows,
|
|
3773
|
-
validation: cohortValidationRows,
|
|
3774
|
-
test: cohortTestRows,
|
|
3775
|
-
trainBaseline: cohortSplit.trainRows.filter((row) => row.aiApproved),
|
|
3776
|
-
validationBaseline: cohortSplit.validationRows.filter(
|
|
3777
|
-
(row) => row.aiApproved
|
|
3778
|
-
),
|
|
3779
|
-
testBaseline: sealedCohortSplit.searchTestRows.filter(
|
|
3780
|
-
(row) => row.aiApproved
|
|
3781
|
-
),
|
|
3782
|
-
requiredFeatureFamilies: [family]
|
|
3783
|
-
});
|
|
3784
|
-
coverageSearches.push({
|
|
3785
|
-
family,
|
|
3786
|
-
coverage: summarizeAiPocketFeatureCoverage(
|
|
3787
|
-
sealedCohortSplit.discoveryRows,
|
|
3788
|
-
family
|
|
3789
|
-
),
|
|
3790
|
-
scopeRows: resolveScopeRows(
|
|
3791
|
-
sealedCohortSplit.discoveryRows.filter(
|
|
3792
|
-
(row) => row.featureCoverage?.[family] === true
|
|
3793
|
-
),
|
|
3794
|
-
scope
|
|
3795
|
-
).length,
|
|
3796
|
-
trainRows: cohortTrainRows.length,
|
|
3797
|
-
validationRows: cohortValidationRows.length,
|
|
3798
|
-
testRows: cohortTestRows.length,
|
|
3799
|
-
search: cohortSearch
|
|
3800
|
-
});
|
|
3533
|
+
};
|
|
3534
|
+
|
|
3535
|
+
// src/lib/aiPocketSearch/dataset.ts
|
|
3536
|
+
var normalizeQuality = (analysis) => {
|
|
3537
|
+
const quality = Number(analysis?.quality);
|
|
3538
|
+
return Number.isFinite(quality) ? Math.round(quality) : null;
|
|
3539
|
+
};
|
|
3540
|
+
var isAiApproval = (row, analysis, minQuality) => {
|
|
3541
|
+
const quality = normalizeQuality(analysis);
|
|
3542
|
+
return analysis.direction === row.direction && quality != null && quality >= minQuality;
|
|
3543
|
+
};
|
|
3544
|
+
var listMergedFiles = async (params) => {
|
|
3545
|
+
const { outDir, strategyName } = params;
|
|
3546
|
+
let entries = [];
|
|
3547
|
+
try {
|
|
3548
|
+
entries = await import_promises2.default.readdir(outDir);
|
|
3549
|
+
} catch {
|
|
3550
|
+
return [];
|
|
3551
|
+
}
|
|
3552
|
+
const prefix = strategyName ? `ai-dataset-${(0, import_ai2.toFileToken)(strategyName)}-merged-` : "ai-dataset-";
|
|
3553
|
+
return entries.filter((name) => name.startsWith(prefix)).filter((name) => {
|
|
3554
|
+
if (!name.endsWith(".jsonl") || !name.includes("-merged-")) {
|
|
3555
|
+
return false;
|
|
3801
3556
|
}
|
|
3557
|
+
return /-merged-\d+\.jsonl$/.test(name) || /-merged-\d+-part\d+\.jsonl$/.test(name);
|
|
3558
|
+
}).sort().map((name) => import_path2.default.join(outDir, name));
|
|
3559
|
+
};
|
|
3560
|
+
var deriveStrategyNameFromFile = (filePath) => {
|
|
3561
|
+
const match = import_path2.default.basename(filePath).match(/^ai-dataset-(.+)-merged-\d+(?:-part\d+)?\.jsonl$/);
|
|
3562
|
+
return match?.[1] ? match[1] : "unknown";
|
|
3563
|
+
};
|
|
3564
|
+
var getMergedGroupId2 = (filePath) => {
|
|
3565
|
+
const match = import_path2.default.basename(filePath).match(/^ai-dataset-(.+)-merged-(\d+)(?:-part(\d+))?\.jsonl$/);
|
|
3566
|
+
if (!match) {
|
|
3567
|
+
return null;
|
|
3802
3568
|
}
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
strategy: resolvedStrategyName,
|
|
3807
|
-
filePaths,
|
|
3808
|
-
sourceRows: totalRows,
|
|
3809
|
-
selectedRows,
|
|
3810
|
-
evaluatedRows: rows.length,
|
|
3811
|
-
scope,
|
|
3812
|
-
direction: direction || null,
|
|
3813
|
-
scopeRows: scopeRows.length,
|
|
3814
|
-
trainRows: trainRows.length,
|
|
3815
|
-
validationRows: validationRows.length,
|
|
3816
|
-
testRows: testRows.length,
|
|
3817
|
-
scanned,
|
|
3818
|
-
dateSkipped,
|
|
3819
|
-
failed,
|
|
3820
|
-
recent,
|
|
3821
|
-
skip,
|
|
3822
|
-
since: sinceTimestamp,
|
|
3823
|
-
until: untilTimestamp,
|
|
3824
|
-
period: periodLabel,
|
|
3825
|
-
minQuality,
|
|
3826
|
-
qualityThresholds,
|
|
3827
|
-
includeSymbol,
|
|
3828
|
-
includeGateContext,
|
|
3829
|
-
featureProfile,
|
|
3830
|
-
featurePolicy,
|
|
3831
|
-
coverageMode,
|
|
3832
|
-
cadenceMode,
|
|
3833
|
-
coverageSearches: coverageSearches.map(
|
|
3834
|
-
({
|
|
3835
|
-
family,
|
|
3836
|
-
coverage,
|
|
3837
|
-
scopeRows: scopeRows2,
|
|
3838
|
-
trainRows: trainRows2,
|
|
3839
|
-
validationRows: validationRows2,
|
|
3840
|
-
testRows: testRows2
|
|
3841
|
-
}) => ({
|
|
3842
|
-
family,
|
|
3843
|
-
coverage,
|
|
3844
|
-
scopeRows: scopeRows2,
|
|
3845
|
-
trainRows: trainRows2,
|
|
3846
|
-
validationRows: validationRows2,
|
|
3847
|
-
testRows: testRows2
|
|
3848
|
-
})
|
|
3849
|
-
),
|
|
3850
|
-
featurePolicyAudit: Object.fromEntries(
|
|
3851
|
-
[...excludedFeaturePaths.entries()].map(([classification, paths]) => [
|
|
3852
|
-
classification,
|
|
3853
|
-
{
|
|
3854
|
-
paths: paths.size,
|
|
3855
|
-
samples: [...paths].sort().slice(0, 5)
|
|
3856
|
-
}
|
|
3857
|
-
])
|
|
3858
|
-
),
|
|
3859
|
-
objective,
|
|
3860
|
-
validationSplit,
|
|
3861
|
-
testSplit,
|
|
3862
|
-
sealedTest: sealedFullSplit.evidence,
|
|
3863
|
-
minValidationSupport: search.stats.cadence.minValidationSupport,
|
|
3864
|
-
reportPath,
|
|
3865
|
-
search: {
|
|
3866
|
-
maxDepth,
|
|
3867
|
-
minSupport: search.stats.cadence.minSupport,
|
|
3868
|
-
minProfitFactor,
|
|
3869
|
-
minWinRate,
|
|
3870
|
-
minTotalProfit,
|
|
3871
|
-
maxAtomicPredicates,
|
|
3872
|
-
maxCombinations,
|
|
3873
|
-
minEvents: search.stats.cadence.minEvents,
|
|
3874
|
-
minValidationEvents: search.stats.cadence.minValidationEvents,
|
|
3875
|
-
...maxBatch > 0 ? { maxBatch } : {},
|
|
3876
|
-
maxEventCountShare: search.stats.cadence.maxEventCountShare,
|
|
3877
|
-
maxSymbolCountShare,
|
|
3878
|
-
allowRiskRegression,
|
|
3879
|
-
requireValidationEligibility,
|
|
3880
|
-
cadence: search.stats.cadence,
|
|
3881
|
-
validationSplit,
|
|
3882
|
-
testSplit,
|
|
3883
|
-
dedupeEquivalentSelections,
|
|
3884
|
-
top
|
|
3885
|
-
}
|
|
3886
|
-
},
|
|
3887
|
-
currentGate: {
|
|
3888
|
-
summary: currentGateSummary,
|
|
3889
|
-
qualityThresholds: currentGateQualityThresholds
|
|
3890
|
-
},
|
|
3891
|
-
pocketSearch: search,
|
|
3892
|
-
coverageSearches,
|
|
3893
|
-
errors
|
|
3569
|
+
return {
|
|
3570
|
+
strategyToken: match[1],
|
|
3571
|
+
mergeId: match[2]
|
|
3894
3572
|
};
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3573
|
+
};
|
|
3574
|
+
var sortDatasetPartPaths = (filePaths) => [...filePaths].sort((left, right) => {
|
|
3575
|
+
const leftMatch = import_path2.default.basename(left).match(/-part(\d+)\.jsonl$/);
|
|
3576
|
+
const rightMatch = import_path2.default.basename(right).match(/-part(\d+)\.jsonl$/);
|
|
3577
|
+
const leftPart = leftMatch ? Number(leftMatch[1]) : 0;
|
|
3578
|
+
const rightPart = rightMatch ? Number(rightMatch[1]) : 0;
|
|
3579
|
+
return leftPart - rightPart || left.localeCompare(right);
|
|
3580
|
+
});
|
|
3581
|
+
var resolveMergedDatasetFiles = async ({
|
|
3582
|
+
outDir,
|
|
3583
|
+
strategyName,
|
|
3584
|
+
explicitFile
|
|
3585
|
+
}) => {
|
|
3586
|
+
const mergedFiles = await listMergedFiles({
|
|
3587
|
+
outDir,
|
|
3588
|
+
strategyName
|
|
3898
3589
|
});
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
});
|
|
3904
|
-
await import_promises.default.writeFile(outputPath, `${JSON.stringify(result, null, 2)}
|
|
3905
|
-
`);
|
|
3590
|
+
if (!mergedFiles.length) {
|
|
3591
|
+
throw new Error(
|
|
3592
|
+
strategyName ? `No merged AI dataset found for strategy "${strategyName}" in ${outDir}. Run yarn ai-export first.` : `No merged AI dataset found in ${outDir}. Run yarn ai-export first.`
|
|
3593
|
+
);
|
|
3906
3594
|
}
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3595
|
+
const resolvedExplicitFile = explicitFile ? import_path2.default.resolve(explicitFile) : null;
|
|
3596
|
+
if (resolvedExplicitFile) {
|
|
3597
|
+
const groupId = getMergedGroupId2(resolvedExplicitFile);
|
|
3598
|
+
if (!groupId) {
|
|
3599
|
+
return [resolvedExplicitFile];
|
|
3600
|
+
}
|
|
3601
|
+
const groupedFiles = sortDatasetPartPaths(
|
|
3602
|
+
mergedFiles.filter((candidate) => {
|
|
3603
|
+
const candidateGroup = getMergedGroupId2(candidate);
|
|
3604
|
+
return candidateGroup?.strategyToken === groupId.strategyToken && candidateGroup?.mergeId === groupId.mergeId;
|
|
3605
|
+
})
|
|
3606
|
+
);
|
|
3607
|
+
return groupedFiles.length ? groupedFiles : [resolvedExplicitFile];
|
|
3910
3608
|
}
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
["selected", import_chalk.default.blue(String(selectedRows))],
|
|
3922
|
-
["evaluated", import_chalk.default.blue(String(rows.length))],
|
|
3923
|
-
["scope", import_chalk.default.yellow(scope)],
|
|
3924
|
-
["objective", import_chalk.default.yellow(objective)],
|
|
3925
|
-
["direction", direction ? import_chalk.default.yellow(direction) : import_chalk.default.gray("all")],
|
|
3926
|
-
["scope_rows", import_chalk.default.blue(String(scopeRows.length))],
|
|
3927
|
-
["train_rows", import_chalk.default.blue(String(trainRows.length))],
|
|
3928
|
-
["validation_rows", import_chalk.default.blue(String(validationRows.length))],
|
|
3929
|
-
["test_rows", import_chalk.default.blue(String(testRows.length))],
|
|
3930
|
-
["source_rows", import_chalk.default.blue(String(totalRows))],
|
|
3931
|
-
["date_skipped", import_chalk.default.blue(String(dateSkipped))],
|
|
3932
|
-
[
|
|
3933
|
-
"failed",
|
|
3934
|
-
failed > 0 ? import_chalk.default.yellow(String(failed)) : import_chalk.default.green("0")
|
|
3935
|
-
],
|
|
3936
|
-
["recent", import_chalk.default.blue(recent === 0 ? "all" : String(recent))],
|
|
3937
|
-
["skip", import_chalk.default.blue(String(skip))],
|
|
3938
|
-
[
|
|
3939
|
-
"since",
|
|
3940
|
-
sinceTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(new Date(sinceTimestamp).toISOString())
|
|
3941
|
-
],
|
|
3942
|
-
[
|
|
3943
|
-
"until",
|
|
3944
|
-
untilTimestamp == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(new Date(untilTimestamp).toISOString())
|
|
3945
|
-
],
|
|
3946
|
-
[
|
|
3947
|
-
"period",
|
|
3948
|
-
periodLabel == null ? import_chalk.default.gray("n/a") : import_chalk.default.gray(periodLabel)
|
|
3949
|
-
],
|
|
3950
|
-
["min_quality", import_chalk.default.magenta(String(minQuality))],
|
|
3951
|
-
["max_depth", import_chalk.default.magenta(String(maxDepth))],
|
|
3952
|
-
["cadence_mode", import_chalk.default.magenta(cadenceMode)],
|
|
3953
|
-
[
|
|
3954
|
-
"low_cadence",
|
|
3955
|
-
search.stats.cadence.lowCadence ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
|
|
3956
|
-
],
|
|
3957
|
-
[
|
|
3958
|
-
"sparse_sample",
|
|
3959
|
-
search.stats.cadence.sparseSample ? import_chalk.default.yellow("yes") : import_chalk.default.green("no")
|
|
3960
|
-
],
|
|
3961
|
-
[
|
|
3962
|
-
"adaptive_thresholds",
|
|
3963
|
-
search.stats.cadence.adaptiveThresholds ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
|
|
3964
|
-
],
|
|
3965
|
-
[
|
|
3966
|
-
"train_events",
|
|
3967
|
-
import_chalk.default.magenta(String(search.stats.cadence.trainEvents))
|
|
3968
|
-
],
|
|
3969
|
-
[
|
|
3970
|
-
"train_events_per_day",
|
|
3971
|
-
import_chalk.default.magenta(formatNumber2(search.stats.cadence.trainEventsPerDay))
|
|
3972
|
-
],
|
|
3973
|
-
["min_support", import_chalk.default.magenta(String(search.stats.cadence.minSupport))],
|
|
3974
|
-
["min_events", import_chalk.default.magenta(String(search.stats.cadence.minEvents))],
|
|
3975
|
-
[
|
|
3976
|
-
"min_validation_events",
|
|
3977
|
-
import_chalk.default.magenta(String(search.stats.cadence.minValidationEvents))
|
|
3978
|
-
],
|
|
3979
|
-
["max_batch", import_chalk.default.magenta(maxBatch > 0 ? String(maxBatch) : "off")],
|
|
3980
|
-
[
|
|
3981
|
-
"max_event_share",
|
|
3982
|
-
import_chalk.default.magenta(formatRatio(search.stats.cadence.maxEventCountShare))
|
|
3983
|
-
],
|
|
3984
|
-
["max_symbol_share", import_chalk.default.magenta(formatRatio(maxSymbolCountShare))],
|
|
3985
|
-
["max_atomic_predicates", import_chalk.default.magenta(String(maxAtomicPredicates))],
|
|
3986
|
-
["max_combinations", import_chalk.default.magenta(String(maxCombinations))],
|
|
3987
|
-
["validation_split", import_chalk.default.magenta(formatRatio(validationSplit))],
|
|
3988
|
-
["test_split", import_chalk.default.magenta(formatRatio(testSplit))],
|
|
3989
|
-
[
|
|
3990
|
-
"test_evidence",
|
|
3991
|
-
sealedFullSplit.evidence.sealed ? import_chalk.default.green(
|
|
3992
|
-
`sealed (${sealedFullSplit.evidence.rows} rows / ${sealedFullSplit.evidence.events} events)`
|
|
3993
|
-
) : import_chalk.default.yellow("open")
|
|
3994
|
-
],
|
|
3995
|
-
[
|
|
3996
|
-
"min_validation_support",
|
|
3997
|
-
import_chalk.default.magenta(String(search.stats.cadence.minValidationSupport))
|
|
3998
|
-
],
|
|
3999
|
-
[
|
|
4000
|
-
"dedupe_equivalent",
|
|
4001
|
-
dedupeEquivalentSelections ? import_chalk.default.green("on") : import_chalk.default.gray("off")
|
|
4002
|
-
],
|
|
4003
|
-
[
|
|
4004
|
-
"include_symbol",
|
|
4005
|
-
includeSymbol ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
|
|
4006
|
-
],
|
|
4007
|
-
[
|
|
4008
|
-
"include_gate_context",
|
|
4009
|
-
includeGateContext ? import_chalk.default.yellow("on") : import_chalk.default.gray("off")
|
|
4010
|
-
],
|
|
4011
|
-
["feature_profile", import_chalk.default.magenta(featureProfile)],
|
|
4012
|
-
["feature_policy", import_chalk.default.magenta(featurePolicy)],
|
|
4013
|
-
["coverage_mode", import_chalk.default.magenta(coverageMode)],
|
|
4014
|
-
[
|
|
4015
|
-
"allow_risk_regression",
|
|
4016
|
-
allowRiskRegression ? import_chalk.default.yellow("on") : import_chalk.default.green("off")
|
|
4017
|
-
],
|
|
4018
|
-
[
|
|
4019
|
-
"validation_eligibility",
|
|
4020
|
-
requireValidationEligibility ? import_chalk.default.green("required") : import_chalk.default.yellow("off")
|
|
4021
|
-
],
|
|
4022
|
-
["report", import_chalk.default.gray(reportPath)],
|
|
4023
|
-
["output", outputPath ? import_chalk.default.gray(outputPath) : import_chalk.default.gray("off")]
|
|
4024
|
-
]
|
|
4025
|
-
)
|
|
3609
|
+
const latestFile = mergedFiles[mergedFiles.length - 1];
|
|
3610
|
+
const latestGroupId = getMergedGroupId2(latestFile);
|
|
3611
|
+
if (!latestGroupId) {
|
|
3612
|
+
return [latestFile];
|
|
3613
|
+
}
|
|
3614
|
+
return sortDatasetPartPaths(
|
|
3615
|
+
mergedFiles.filter((candidate) => {
|
|
3616
|
+
const candidateGroup = getMergedGroupId2(candidate);
|
|
3617
|
+
return candidateGroup?.strategyToken === latestGroupId.strategyToken && candidateGroup?.mergeId === latestGroupId.mergeId;
|
|
3618
|
+
})
|
|
4026
3619
|
);
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
[import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
|
|
4055
|
-
buildSummaryRows(search.validationBaseline)
|
|
4056
|
-
)
|
|
4057
|
-
);
|
|
3620
|
+
};
|
|
3621
|
+
var getDatasetRowTimestamp = (row) => {
|
|
3622
|
+
const timestamp = Number(row.timestamp);
|
|
3623
|
+
return Number.isFinite(timestamp) ? timestamp : null;
|
|
3624
|
+
};
|
|
3625
|
+
var findMaxSelectedTimestamp = async ({
|
|
3626
|
+
filePaths,
|
|
3627
|
+
recent,
|
|
3628
|
+
skip
|
|
3629
|
+
}) => {
|
|
3630
|
+
let maxTimestamp = null;
|
|
3631
|
+
await (0, import_ai2.streamAiDatasetRows)({
|
|
3632
|
+
filePaths,
|
|
3633
|
+
limitFromEnd: recent,
|
|
3634
|
+
skipFromEnd: skip,
|
|
3635
|
+
onRow: async (row) => {
|
|
3636
|
+
const timestamp = getDatasetRowTimestamp(row);
|
|
3637
|
+
if (timestamp != null) {
|
|
3638
|
+
maxTimestamp = maxTimestamp == null ? timestamp : Math.max(maxTimestamp, timestamp);
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
});
|
|
3642
|
+
return maxTimestamp;
|
|
3643
|
+
};
|
|
3644
|
+
var getDeterministicModelCandidate = (signal) => {
|
|
3645
|
+
if (!signal) {
|
|
3646
|
+
return false;
|
|
4058
3647
|
}
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
3648
|
+
const payload = (0, import_ai3.buildAiPayload)(signal);
|
|
3649
|
+
const gateContext = (0, import_ai3.getDeterministicAiGateContext)(payload);
|
|
3650
|
+
const structuralHardBlockReasons = Array.isArray(
|
|
3651
|
+
gateContext?.structuralHardBlockReasons
|
|
3652
|
+
) ? gateContext.structuralHardBlockReasons.filter(
|
|
3653
|
+
(reason) => typeof reason === "string" && reason.trim().length > 0
|
|
3654
|
+
) : [];
|
|
3655
|
+
if (structuralHardBlockReasons.length) {
|
|
3656
|
+
return false;
|
|
4067
3657
|
}
|
|
4068
|
-
if (
|
|
4069
|
-
|
|
4070
|
-
"CURRENT GATE TRAIN BASELINE",
|
|
4071
|
-
createTable(
|
|
4072
|
-
[import_chalk.default.gray("METRIC"), import_chalk.default.gray("VALUE")],
|
|
4073
|
-
buildSummaryRows(search.objectiveBaseline)
|
|
4074
|
-
)
|
|
4075
|
-
);
|
|
3658
|
+
if (typeof gateContext?.approvalAllowedNow === "boolean") {
|
|
3659
|
+
return gateContext.approvalAllowedNow;
|
|
4076
3660
|
}
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
)
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
3661
|
+
return true;
|
|
3662
|
+
};
|
|
3663
|
+
var prepareAiPocketSearchDataset = async (options) => {
|
|
3664
|
+
await (0, import_registry.ensureStrategyPluginsLoaded)();
|
|
3665
|
+
const filePaths = await resolveMergedDatasetFiles({
|
|
3666
|
+
outDir: options.outDir,
|
|
3667
|
+
strategyName: options.strategyName,
|
|
3668
|
+
explicitFile: options.explicitFile
|
|
3669
|
+
});
|
|
3670
|
+
const { totalRows, selectedRows } = await (0, import_ai2.countAiDatasetRows)({
|
|
3671
|
+
filePaths,
|
|
3672
|
+
limitFromEnd: options.recent,
|
|
3673
|
+
skipFromEnd: options.skip
|
|
3674
|
+
});
|
|
3675
|
+
const maxSelectedTimestamp = options.trailingPeriodMs == null ? null : await findMaxSelectedTimestamp({
|
|
3676
|
+
filePaths,
|
|
3677
|
+
recent: options.recent,
|
|
3678
|
+
skip: options.skip
|
|
3679
|
+
});
|
|
3680
|
+
const sinceTimestamp = options.trailingPeriodMs != null && maxSelectedTimestamp != null ? maxSelectedTimestamp - options.trailingPeriodMs : options.sinceInput;
|
|
3681
|
+
return {
|
|
3682
|
+
filePaths,
|
|
3683
|
+
totalRows,
|
|
3684
|
+
selectedRows,
|
|
3685
|
+
sinceTimestamp,
|
|
3686
|
+
resolvedStrategyName: deriveStrategyNameFromFile(filePaths[0] || "")
|
|
3687
|
+
};
|
|
3688
|
+
};
|
|
3689
|
+
var evaluateAiPocketSearchDataset = async ({
|
|
3690
|
+
prepared,
|
|
3691
|
+
options,
|
|
3692
|
+
onProgress
|
|
3693
|
+
}) => {
|
|
3694
|
+
let resolvedStrategyName = prepared.resolvedStrategyName;
|
|
3695
|
+
let scanned = 0;
|
|
3696
|
+
let dateSkipped = 0;
|
|
3697
|
+
let failed = 0;
|
|
3698
|
+
const errors = [];
|
|
3699
|
+
const rows = [];
|
|
3700
|
+
const excludedFeaturePaths = /* @__PURE__ */ new Map();
|
|
3701
|
+
await (0, import_ai2.streamAiDatasetRows)({
|
|
3702
|
+
filePaths: prepared.filePaths,
|
|
3703
|
+
limitFromEnd: options.recent,
|
|
3704
|
+
skipFromEnd: options.skip,
|
|
3705
|
+
onRow: async (row) => {
|
|
3706
|
+
scanned += 1;
|
|
3707
|
+
if (resolvedStrategyName === "unknown" && typeof row.strategyName === "string" && row.strategyName.trim()) {
|
|
3708
|
+
resolvedStrategyName = row.strategyName.trim();
|
|
3709
|
+
}
|
|
3710
|
+
const timestamp = getDatasetRowTimestamp(row);
|
|
3711
|
+
if (prepared.sinceTimestamp != null && (timestamp == null || timestamp < prepared.sinceTimestamp) || options.untilTimestamp != null && (timestamp == null || timestamp > options.untilTimestamp)) {
|
|
3712
|
+
dateSkipped += 1;
|
|
3713
|
+
onProgress?.({ symbol: row.symbol, status: "date-skip" });
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
try {
|
|
3717
|
+
const signal = extractSignalFromAiDatasetRow(row);
|
|
3718
|
+
const payload = (0, import_ai3.buildAiPayload)(signal);
|
|
3719
|
+
const gateContext = (0, import_ai3.getDeterministicAiGateContext)(payload);
|
|
3720
|
+
const analysis = await (0, import_ai3.runAiPromptLocal)(signal, { payload });
|
|
3721
|
+
const quality = normalizeQuality(analysis);
|
|
3722
|
+
const modelDirection = typeof analysis.direction === "string" && analysis.direction.trim() ? analysis.direction : null;
|
|
3723
|
+
const modelDirectionMatches = modelDirection === row.direction;
|
|
3724
|
+
const profit = Number(row.profit);
|
|
3725
|
+
const featureSnapshot = collectAiPocketFeatureSnapshot({
|
|
3726
|
+
payload,
|
|
3727
|
+
gateContext,
|
|
3728
|
+
includeSymbol: options.includeSymbol,
|
|
3729
|
+
includeGateContext: options.includeGateContext,
|
|
3730
|
+
featureProfile: options.featureProfile,
|
|
3731
|
+
featurePolicy: options.featurePolicy,
|
|
3732
|
+
onFeatureExcluded: ({ path: featurePath, classification }) => {
|
|
3733
|
+
const paths = excludedFeaturePaths.get(classification) ?? /* @__PURE__ */ new Set();
|
|
3734
|
+
paths.add(featurePath);
|
|
3735
|
+
excludedFeaturePaths.set(classification, paths);
|
|
3736
|
+
}
|
|
3737
|
+
});
|
|
3738
|
+
rows.push({
|
|
3739
|
+
signalId: row.signalId,
|
|
3740
|
+
strategy: row.strategyName,
|
|
3741
|
+
symbol: row.symbol,
|
|
3742
|
+
direction: row.direction,
|
|
3743
|
+
timestamp,
|
|
3744
|
+
profit,
|
|
3745
|
+
profitableTrade: profit > 0,
|
|
3746
|
+
aiApproved: isAiApproval(row, analysis, options.minQuality),
|
|
3747
|
+
quality,
|
|
3748
|
+
modelDirectionMatches,
|
|
3749
|
+
modelCandidate: getDeterministicModelCandidate(signal),
|
|
3750
|
+
features: featureSnapshot.features,
|
|
3751
|
+
featureCoverage: featureSnapshot.featureCoverage
|
|
3752
|
+
});
|
|
3753
|
+
onProgress?.({ symbol: row.symbol, status: "ok" });
|
|
3754
|
+
} catch (error) {
|
|
3755
|
+
failed += 1;
|
|
3756
|
+
if (errors.length < 5) {
|
|
3757
|
+
errors.push(
|
|
3758
|
+
`[${row.symbol}/${row.signalId}] ${error instanceof Error ? error.message : String(error)}`
|
|
3759
|
+
);
|
|
3760
|
+
}
|
|
3761
|
+
onProgress?.({ symbol: row.symbol, status: "error" });
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
});
|
|
3765
|
+
return {
|
|
3766
|
+
rows,
|
|
3767
|
+
resolvedStrategyName,
|
|
3768
|
+
scanned,
|
|
3769
|
+
dateSkipped,
|
|
3770
|
+
failed,
|
|
3771
|
+
errors,
|
|
3772
|
+
excludedFeaturePaths
|
|
3773
|
+
};
|
|
3774
|
+
};
|
|
3775
|
+
|
|
3776
|
+
// src/lib/aiTrainMetrics.ts
|
|
3777
|
+
var divideOrNull = (num, denom) => {
|
|
3778
|
+
if (denom <= 0) {
|
|
3779
|
+
return null;
|
|
3780
|
+
}
|
|
3781
|
+
return num / denom;
|
|
3782
|
+
};
|
|
3783
|
+
var DAY_MS3 = 24 * 60 * 60 * 1e3;
|
|
3784
|
+
var DAYS_PER_WEEK2 = 7;
|
|
3785
|
+
var DAYS_PER_MONTH2 = 30.4375;
|
|
3786
|
+
var DAYS_PER_YEAR = 365;
|
|
3787
|
+
var getEvaluationPeriodDays = (evaluations) => {
|
|
3788
|
+
let minTimestamp = null;
|
|
3789
|
+
let maxTimestamp = null;
|
|
3790
|
+
for (const evaluation of evaluations) {
|
|
3791
|
+
const timestamp = typeof evaluation.timestamp === "number" && Number.isFinite(evaluation.timestamp) ? evaluation.timestamp : null;
|
|
3792
|
+
if (timestamp == null) {
|
|
3793
|
+
continue;
|
|
3794
|
+
}
|
|
3795
|
+
if (minTimestamp == null || timestamp < minTimestamp) {
|
|
3796
|
+
minTimestamp = timestamp;
|
|
3797
|
+
}
|
|
3798
|
+
if (maxTimestamp == null || timestamp > maxTimestamp) {
|
|
3799
|
+
maxTimestamp = timestamp;
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
if (minTimestamp == null || maxTimestamp == null) {
|
|
3803
|
+
return null;
|
|
3804
|
+
}
|
|
3805
|
+
return Math.max((maxTimestamp - minTimestamp) / DAY_MS3, 1);
|
|
3806
|
+
};
|
|
3807
|
+
var qualitySortKey = (quality) => quality == null ? Number.POSITIVE_INFINITY : quality;
|
|
3808
|
+
var calculateTradePnlRiskRatios = ({
|
|
3809
|
+
profits,
|
|
3810
|
+
totalProfit,
|
|
3811
|
+
maxDrawdown,
|
|
3812
|
+
periodDays
|
|
3813
|
+
}) => {
|
|
3814
|
+
if (!profits.length || periodDays == null || periodDays <= 0) {
|
|
3815
|
+
return {
|
|
3816
|
+
sharpeRatio: null,
|
|
3817
|
+
sortinoRatio: null,
|
|
3818
|
+
calmarRatio: null
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
const meanProfit = totalProfit / profits.length;
|
|
3822
|
+
const variance = profits.reduce((sum, profit) => {
|
|
3823
|
+
const diff = profit - meanProfit;
|
|
3824
|
+
return sum + diff * diff;
|
|
3825
|
+
}, 0) / profits.length;
|
|
3826
|
+
const stdDev = Math.sqrt(variance);
|
|
3827
|
+
const downsideDeviation = Math.sqrt(
|
|
3828
|
+
profits.reduce(
|
|
3829
|
+
(sum, profit) => profit < 0 ? sum + profit * profit : sum,
|
|
3830
|
+
0
|
|
3831
|
+
) / profits.length
|
|
3832
|
+
);
|
|
3833
|
+
const annualizationScale = Math.sqrt(
|
|
3834
|
+
profits.length / periodDays * DAYS_PER_YEAR
|
|
3835
|
+
);
|
|
3836
|
+
return {
|
|
3837
|
+
sharpeRatio: stdDev > 0 && annualizationScale > 0 ? meanProfit / stdDev * annualizationScale : null,
|
|
3838
|
+
sortinoRatio: downsideDeviation > 0 && annualizationScale > 0 ? meanProfit / downsideDeviation * annualizationScale : null,
|
|
3839
|
+
calmarRatio: maxDrawdown > 0 ? totalProfit / periodDays * DAYS_PER_YEAR / maxDrawdown : null
|
|
3840
|
+
};
|
|
3841
|
+
};
|
|
3842
|
+
var emptyRiskSummary = () => ({
|
|
3843
|
+
trades: 0,
|
|
3844
|
+
totalProfit: 0,
|
|
3845
|
+
grossProfit: 0,
|
|
3846
|
+
grossLoss: 0,
|
|
3847
|
+
profitFactor: null,
|
|
3848
|
+
payoffRatio: null,
|
|
3849
|
+
avgWin: null,
|
|
3850
|
+
avgLoss: null,
|
|
3851
|
+
largestWin: null,
|
|
3852
|
+
largestLoss: null,
|
|
3853
|
+
winRate: null,
|
|
3854
|
+
maxDrawdown: 0,
|
|
3855
|
+
maxDrawdownPctOfGrossProfit: null,
|
|
3856
|
+
maxDrawdownPctOfTotalProfit: null,
|
|
3857
|
+
sharpeRatio: null,
|
|
3858
|
+
sortinoRatio: null,
|
|
3859
|
+
calmarRatio: null,
|
|
3860
|
+
recoveryFactor: null,
|
|
3861
|
+
ulcerIndex: null,
|
|
3862
|
+
maxConsecutiveWins: 0,
|
|
3863
|
+
maxConsecutiveLosses: 0
|
|
3864
|
+
});
|
|
3865
|
+
var summarizeApprovedRisk = (evaluations, periodDays = getEvaluationPeriodDays(evaluations)) => {
|
|
3866
|
+
const approvedEvaluations = evaluations.filter((evaluation) => evaluation.aiApproved).sort((left, right) => {
|
|
3867
|
+
const leftTimestamp = typeof left.timestamp === "number" && Number.isFinite(left.timestamp) ? left.timestamp : Number.POSITIVE_INFINITY;
|
|
3868
|
+
const rightTimestamp = typeof right.timestamp === "number" && Number.isFinite(right.timestamp) ? right.timestamp : Number.POSITIVE_INFINITY;
|
|
3869
|
+
return leftTimestamp - rightTimestamp;
|
|
3870
|
+
});
|
|
3871
|
+
if (!approvedEvaluations.length) {
|
|
3872
|
+
return emptyRiskSummary();
|
|
3873
|
+
}
|
|
3874
|
+
let grossProfit = 0;
|
|
3875
|
+
let grossLoss = 0;
|
|
3876
|
+
let wins = 0;
|
|
3877
|
+
let losses = 0;
|
|
3878
|
+
let largestWin = null;
|
|
3879
|
+
let largestLoss = null;
|
|
3880
|
+
let equity = 0;
|
|
3881
|
+
let peakEquity = 0;
|
|
3882
|
+
let maxDrawdown = 0;
|
|
3883
|
+
let drawdownSquares = 0;
|
|
3884
|
+
let currentWinStreak = 0;
|
|
3885
|
+
let currentLossStreak = 0;
|
|
3886
|
+
let maxConsecutiveWins = 0;
|
|
3887
|
+
let maxConsecutiveLosses = 0;
|
|
3888
|
+
const approvedProfits = [];
|
|
3889
|
+
for (const evaluation of approvedEvaluations) {
|
|
3890
|
+
const profit = evaluation.profit;
|
|
3891
|
+
approvedProfits.push(profit);
|
|
3892
|
+
if (profit > 0) {
|
|
3893
|
+
grossProfit += profit;
|
|
3894
|
+
wins += 1;
|
|
3895
|
+
largestWin = largestWin == null ? profit : Math.max(largestWin, profit);
|
|
3896
|
+
currentWinStreak += 1;
|
|
3897
|
+
currentLossStreak = 0;
|
|
3898
|
+
} else if (profit < 0) {
|
|
3899
|
+
grossLoss += Math.abs(profit);
|
|
3900
|
+
losses += 1;
|
|
3901
|
+
largestLoss = largestLoss == null ? profit : Math.min(largestLoss, profit);
|
|
3902
|
+
currentLossStreak += 1;
|
|
3903
|
+
currentWinStreak = 0;
|
|
3904
|
+
} else {
|
|
3905
|
+
currentWinStreak = 0;
|
|
3906
|
+
currentLossStreak = 0;
|
|
3907
|
+
}
|
|
3908
|
+
maxConsecutiveWins = Math.max(maxConsecutiveWins, currentWinStreak);
|
|
3909
|
+
maxConsecutiveLosses = Math.max(maxConsecutiveLosses, currentLossStreak);
|
|
3910
|
+
equity += profit;
|
|
3911
|
+
peakEquity = Math.max(peakEquity, equity);
|
|
3912
|
+
const drawdown = Math.max(0, peakEquity - equity);
|
|
3913
|
+
maxDrawdown = Math.max(maxDrawdown, drawdown);
|
|
3914
|
+
drawdownSquares += drawdown * drawdown;
|
|
3915
|
+
}
|
|
3916
|
+
const totalProfit = grossProfit - grossLoss;
|
|
3917
|
+
const avgWin = divideOrNull(grossProfit, wins);
|
|
3918
|
+
const avgLoss = divideOrNull(grossLoss, losses);
|
|
3919
|
+
const riskRatios = calculateTradePnlRiskRatios({
|
|
3920
|
+
profits: approvedProfits,
|
|
3921
|
+
totalProfit,
|
|
3922
|
+
maxDrawdown,
|
|
3923
|
+
periodDays
|
|
3924
|
+
});
|
|
3925
|
+
return {
|
|
3926
|
+
trades: approvedEvaluations.length,
|
|
3927
|
+
totalProfit,
|
|
3928
|
+
grossProfit,
|
|
3929
|
+
grossLoss,
|
|
3930
|
+
profitFactor: grossLoss > 0 ? grossProfit / grossLoss : null,
|
|
3931
|
+
payoffRatio: avgWin != null && avgLoss != null && avgLoss > 0 ? avgWin / avgLoss : null,
|
|
3932
|
+
avgWin,
|
|
3933
|
+
avgLoss,
|
|
3934
|
+
largestWin,
|
|
3935
|
+
largestLoss,
|
|
3936
|
+
winRate: divideOrNull(wins, approvedEvaluations.length),
|
|
3937
|
+
maxDrawdown,
|
|
3938
|
+
maxDrawdownPctOfGrossProfit: grossProfit > 0 ? maxDrawdown / grossProfit : null,
|
|
3939
|
+
maxDrawdownPctOfTotalProfit: totalProfit > 0 ? maxDrawdown / totalProfit : null,
|
|
3940
|
+
...riskRatios,
|
|
3941
|
+
recoveryFactor: maxDrawdown > 0 ? totalProfit / maxDrawdown : null,
|
|
3942
|
+
ulcerIndex: Math.sqrt(drawdownSquares / approvedEvaluations.length),
|
|
3943
|
+
maxConsecutiveWins,
|
|
3944
|
+
maxConsecutiveLosses
|
|
3945
|
+
};
|
|
3946
|
+
};
|
|
3947
|
+
var summarizeAiTrainEvaluations = (evaluations) => {
|
|
3948
|
+
let approved = 0;
|
|
3949
|
+
let rejected = 0;
|
|
3950
|
+
let correct = 0;
|
|
3951
|
+
let incorrect = 0;
|
|
3952
|
+
let profitable = 0;
|
|
3953
|
+
let unprofitable = 0;
|
|
3954
|
+
let flat = 0;
|
|
3955
|
+
let truePositive = 0;
|
|
3956
|
+
let falsePositive = 0;
|
|
3957
|
+
let trueNegative = 0;
|
|
3958
|
+
let falseNegative = 0;
|
|
3959
|
+
let totalProfitAll = 0;
|
|
3960
|
+
let totalProfitApproved = 0;
|
|
3961
|
+
const bucketMap = /* @__PURE__ */ new Map();
|
|
3962
|
+
for (const evaluation of evaluations) {
|
|
3963
|
+
const { profit, profitableTrade, aiApproved, quality } = evaluation;
|
|
3964
|
+
totalProfitAll += profit;
|
|
3965
|
+
if (profitableTrade) {
|
|
3966
|
+
profitable += 1;
|
|
3967
|
+
} else if (profit < 0) {
|
|
3968
|
+
unprofitable += 1;
|
|
3969
|
+
} else {
|
|
3970
|
+
flat += 1;
|
|
3971
|
+
}
|
|
3972
|
+
if (aiApproved) {
|
|
3973
|
+
approved += 1;
|
|
3974
|
+
totalProfitApproved += profit;
|
|
3975
|
+
} else {
|
|
3976
|
+
rejected += 1;
|
|
3977
|
+
}
|
|
3978
|
+
const isCorrect = aiApproved === profitableTrade;
|
|
3979
|
+
if (isCorrect) {
|
|
3980
|
+
correct += 1;
|
|
3981
|
+
} else {
|
|
3982
|
+
incorrect += 1;
|
|
3983
|
+
}
|
|
3984
|
+
if (aiApproved && profitableTrade) {
|
|
3985
|
+
truePositive += 1;
|
|
3986
|
+
} else if (aiApproved && !profitableTrade) {
|
|
3987
|
+
falsePositive += 1;
|
|
3988
|
+
} else if (!aiApproved && profitableTrade) {
|
|
3989
|
+
falseNegative += 1;
|
|
3990
|
+
} else {
|
|
3991
|
+
trueNegative += 1;
|
|
3992
|
+
}
|
|
3993
|
+
const bucket = bucketMap.get(quality) ?? {
|
|
3994
|
+
quality,
|
|
3995
|
+
count: 0,
|
|
3996
|
+
approved: 0,
|
|
3997
|
+
profitable: 0,
|
|
3998
|
+
totalProfit: 0
|
|
3999
|
+
};
|
|
4000
|
+
bucket.count += 1;
|
|
4001
|
+
bucket.totalProfit += profit;
|
|
4002
|
+
if (aiApproved) {
|
|
4003
|
+
bucket.approved += 1;
|
|
4004
|
+
}
|
|
4005
|
+
if (profitableTrade) {
|
|
4006
|
+
bucket.profitable += 1;
|
|
4007
|
+
}
|
|
4008
|
+
bucketMap.set(quality, bucket);
|
|
4009
|
+
}
|
|
4010
|
+
const avgProfitAll = divideOrNull(totalProfitAll, evaluations.length);
|
|
4011
|
+
const avgProfitApproved = divideOrNull(totalProfitApproved, approved);
|
|
4012
|
+
const periodDays = getEvaluationPeriodDays(evaluations);
|
|
4013
|
+
const avgProfitApprovedPerDay = periodDays == null ? null : divideOrNull(totalProfitApproved, periodDays);
|
|
4014
|
+
const avgApprovedTradesPerDay = periodDays == null ? null : divideOrNull(approved, periodDays);
|
|
4015
|
+
const avgProfitApprovedPerMonth = avgProfitApprovedPerDay == null ? null : avgProfitApprovedPerDay * DAYS_PER_MONTH2;
|
|
4016
|
+
const avgApprovedTradesPerWeek = avgApprovedTradesPerDay == null ? null : avgApprovedTradesPerDay * DAYS_PER_WEEK2;
|
|
4017
|
+
const expectancyDelta = avgProfitAll == null || avgProfitApproved == null ? null : avgProfitApproved - avgProfitAll;
|
|
4018
|
+
return {
|
|
4019
|
+
approved,
|
|
4020
|
+
rejected,
|
|
4021
|
+
correct,
|
|
4022
|
+
incorrect,
|
|
4023
|
+
truePositive,
|
|
4024
|
+
falsePositive,
|
|
4025
|
+
trueNegative,
|
|
4026
|
+
falseNegative,
|
|
4027
|
+
profitable,
|
|
4028
|
+
unprofitable,
|
|
4029
|
+
flat,
|
|
4030
|
+
precisionApproved: divideOrNull(truePositive, approved),
|
|
4031
|
+
recallWinners: divideOrNull(truePositive, profitable),
|
|
4032
|
+
avgProfitAll,
|
|
4033
|
+
avgProfitApproved,
|
|
4034
|
+
avgProfitApprovedPerDay,
|
|
4035
|
+
avgProfitApprovedPerMonth,
|
|
4036
|
+
avgApprovedTradesPerDay,
|
|
4037
|
+
avgApprovedTradesPerWeek,
|
|
4038
|
+
expectancyDelta,
|
|
4039
|
+
approvedRisk: summarizeApprovedRisk(evaluations, periodDays),
|
|
4040
|
+
qualityBuckets: [...bucketMap.values()].sort(
|
|
4041
|
+
(a, b) => qualitySortKey(a.quality) - qualitySortKey(b.quality)
|
|
4042
|
+
)
|
|
4043
|
+
};
|
|
4044
|
+
};
|
|
4045
|
+
var isDirectionMatchAtThreshold = (evaluation, threshold) => {
|
|
4046
|
+
const directionMatches = typeof evaluation.modelDirectionMatches === "boolean" ? evaluation.modelDirectionMatches : evaluation.aiApproved;
|
|
4047
|
+
return directionMatches === true && evaluation.quality != null && evaluation.quality >= threshold;
|
|
4048
|
+
};
|
|
4049
|
+
var summarizeAiTrainEvaluationsByQualityThreshold = (evaluations, thresholds = [3, 4, 5]) => thresholds.map((threshold) => ({
|
|
4050
|
+
threshold,
|
|
4051
|
+
label: `q${threshold}+`,
|
|
4052
|
+
summary: summarizeAiTrainEvaluations(
|
|
4053
|
+
evaluations.map((evaluation) => ({
|
|
4054
|
+
...evaluation,
|
|
4055
|
+
aiApproved: isDirectionMatchAtThreshold(evaluation, threshold)
|
|
4056
|
+
}))
|
|
4057
|
+
)
|
|
4058
|
+
}));
|
|
4059
|
+
|
|
4060
|
+
// src/lib/aiPocketSearch/research.ts
|
|
4061
|
+
var resolveScopeRows = (rows, scope) => {
|
|
4062
|
+
if (scope === "approved") {
|
|
4063
|
+
return rows.filter((row) => row.aiApproved);
|
|
4336
4064
|
}
|
|
4337
|
-
if (
|
|
4338
|
-
|
|
4339
|
-
|
|
4065
|
+
if (scope === "rejected") {
|
|
4066
|
+
return rows.filter((row) => !row.aiApproved);
|
|
4067
|
+
}
|
|
4068
|
+
if (scope === "candidates") {
|
|
4069
|
+
return rows.filter((row) => row.modelCandidate && !row.aiApproved);
|
|
4070
|
+
}
|
|
4071
|
+
return rows;
|
|
4072
|
+
};
|
|
4073
|
+
var runAiPocketSearchResearch = ({
|
|
4074
|
+
rows,
|
|
4075
|
+
options,
|
|
4076
|
+
onProgress
|
|
4077
|
+
}) => {
|
|
4078
|
+
const {
|
|
4079
|
+
direction,
|
|
4080
|
+
scope,
|
|
4081
|
+
qualityThresholds,
|
|
4082
|
+
validationSplit,
|
|
4083
|
+
testSplit,
|
|
4084
|
+
sealTest,
|
|
4085
|
+
cadenceMode,
|
|
4086
|
+
explicitMinSupport,
|
|
4087
|
+
explicitMinEvents,
|
|
4088
|
+
explicitMinValidationSupport,
|
|
4089
|
+
explicitMinValidationEvents,
|
|
4090
|
+
maxEventCountShare,
|
|
4091
|
+
explicitMaxEventCountShare,
|
|
4092
|
+
minProfitFactor,
|
|
4093
|
+
minTotalProfit,
|
|
4094
|
+
minWinRate,
|
|
4095
|
+
maxDepth,
|
|
4096
|
+
maxAtomicPredicates,
|
|
4097
|
+
maxCombinations,
|
|
4098
|
+
top,
|
|
4099
|
+
maxBatch,
|
|
4100
|
+
maxSymbolCountShare,
|
|
4101
|
+
objective,
|
|
4102
|
+
allowRiskRegression,
|
|
4103
|
+
requireValidationEligibility,
|
|
4104
|
+
dedupeEquivalentSelections,
|
|
4105
|
+
coverageMode
|
|
4106
|
+
} = options;
|
|
4107
|
+
const directionRows = direction ? rows.filter((row) => row.direction === direction) : rows;
|
|
4108
|
+
const scopeRows = resolveScopeRows(directionRows, scope);
|
|
4109
|
+
const fullSplit = splitAiPocketResearchRowsByTimestamp(
|
|
4110
|
+
directionRows,
|
|
4111
|
+
validationSplit,
|
|
4112
|
+
testSplit
|
|
4113
|
+
);
|
|
4114
|
+
const sealedFullSplit = sealAiPocketTestPartition(fullSplit, sealTest);
|
|
4115
|
+
const discoveryRows = sealedFullSplit.discoveryRows;
|
|
4116
|
+
const trainRows = resolveScopeRows(fullSplit.trainRows, scope);
|
|
4117
|
+
const validationRows = resolveScopeRows(fullSplit.validationRows, scope);
|
|
4118
|
+
const testRows = resolveScopeRows(sealedFullSplit.searchTestRows, scope);
|
|
4119
|
+
const baselineRows = fullSplit.trainRows.filter((row) => row.aiApproved);
|
|
4120
|
+
const validationBaselineRows = fullSplit.validationRows.filter(
|
|
4121
|
+
(row) => row.aiApproved
|
|
4122
|
+
);
|
|
4123
|
+
const testBaselineRows = sealedFullSplit.searchTestRows.filter(
|
|
4124
|
+
(row) => row.aiApproved
|
|
4125
|
+
);
|
|
4126
|
+
const currentGateSummary = summarizeAiTrainEvaluations(discoveryRows);
|
|
4127
|
+
const currentGateQualityThresholds = summarizeAiTrainEvaluationsByQualityThreshold(
|
|
4128
|
+
discoveryRows,
|
|
4129
|
+
qualityThresholds
|
|
4130
|
+
);
|
|
4131
|
+
const runSearch = ({
|
|
4132
|
+
label,
|
|
4133
|
+
train,
|
|
4134
|
+
validation,
|
|
4135
|
+
test,
|
|
4136
|
+
trainBaseline,
|
|
4137
|
+
validationBaseline,
|
|
4138
|
+
testBaseline,
|
|
4139
|
+
requiredFeatureFamilies = [],
|
|
4140
|
+
excludedFeatureFamilies = []
|
|
4141
|
+
}) => {
|
|
4142
|
+
const cadenceProfile = resolveAiPocketCadenceProfile({
|
|
4143
|
+
trainRows: train,
|
|
4144
|
+
validationRows: validation,
|
|
4145
|
+
testRows: test,
|
|
4146
|
+
mode: cadenceMode,
|
|
4147
|
+
validationSplit,
|
|
4148
|
+
...explicitMinSupport != null ? { minSupport: explicitMinSupport } : {},
|
|
4149
|
+
...explicitMinEvents > 0 ? { minEvents: explicitMinEvents } : {},
|
|
4150
|
+
...explicitMinValidationSupport > 0 ? { minValidationSupport: explicitMinValidationSupport } : {},
|
|
4151
|
+
...explicitMinValidationEvents > 0 ? { minValidationEvents: explicitMinValidationEvents } : {},
|
|
4152
|
+
maxEventCountShare,
|
|
4153
|
+
explicitMaxEventCountShare
|
|
4154
|
+
});
|
|
4155
|
+
return searchAiPockets(train, {
|
|
4156
|
+
minSupport: cadenceProfile.minSupport,
|
|
4157
|
+
minProfitFactor,
|
|
4158
|
+
minTotalProfit,
|
|
4159
|
+
minWinRate,
|
|
4160
|
+
maxDepth,
|
|
4161
|
+
maxAtomicPredicates,
|
|
4162
|
+
maxCombinations,
|
|
4163
|
+
top,
|
|
4164
|
+
validationRows: validation,
|
|
4165
|
+
testRows: test,
|
|
4166
|
+
minValidationSupport: cadenceProfile.minValidationSupport,
|
|
4167
|
+
minEvents: cadenceProfile.minEvents,
|
|
4168
|
+
minValidationEvents: cadenceProfile.minValidationEvents,
|
|
4169
|
+
...maxBatch > 0 ? { maxBatch } : {},
|
|
4170
|
+
maxEventCountShare: cadenceProfile.maxEventCountShare,
|
|
4171
|
+
maxSymbolCountShare,
|
|
4172
|
+
objective,
|
|
4173
|
+
baselineRows: trainBaseline,
|
|
4174
|
+
validationBaselineRows: validationBaseline,
|
|
4175
|
+
testBaselineRows: testBaseline,
|
|
4176
|
+
allowRiskRegression,
|
|
4177
|
+
requireValidationEligibility,
|
|
4178
|
+
dedupeEquivalentSelections,
|
|
4179
|
+
requiredFeatureFamilies,
|
|
4180
|
+
excludedFeatureFamilies,
|
|
4181
|
+
cadenceProfile,
|
|
4182
|
+
progressInterval: 250,
|
|
4183
|
+
onProgress: (progress) => onProgress?.({ label, progress })
|
|
4184
|
+
});
|
|
4185
|
+
};
|
|
4186
|
+
const search = runSearch({
|
|
4187
|
+
label: "full",
|
|
4188
|
+
train: trainRows,
|
|
4189
|
+
validation: validationRows,
|
|
4190
|
+
test: testRows,
|
|
4191
|
+
trainBaseline: baselineRows,
|
|
4192
|
+
validationBaseline: validationBaselineRows,
|
|
4193
|
+
testBaseline: testBaselineRows,
|
|
4194
|
+
...coverageMode === "auto" ? {
|
|
4195
|
+
excludedFeatureFamilies: [
|
|
4196
|
+
"cmc",
|
|
4197
|
+
"coinalyze"
|
|
4198
|
+
]
|
|
4199
|
+
} : {}
|
|
4200
|
+
});
|
|
4201
|
+
const coverageSearches = [];
|
|
4202
|
+
if (coverageMode === "auto") {
|
|
4203
|
+
for (const family of ["cmc", "coinalyze"]) {
|
|
4204
|
+
const cohortSplit = splitAiPocketCoverageRowsByTimestamp(
|
|
4205
|
+
directionRows,
|
|
4206
|
+
family,
|
|
4207
|
+
validationSplit,
|
|
4208
|
+
testSplit
|
|
4209
|
+
);
|
|
4210
|
+
const sealedCohortSplit = sealAiPocketTestPartition(
|
|
4211
|
+
cohortSplit,
|
|
4212
|
+
sealTest
|
|
4213
|
+
);
|
|
4214
|
+
const cohortTrainRows = resolveScopeRows(cohortSplit.trainRows, scope);
|
|
4215
|
+
const cohortValidationRows = resolveScopeRows(
|
|
4216
|
+
cohortSplit.validationRows,
|
|
4217
|
+
scope
|
|
4218
|
+
);
|
|
4219
|
+
const cohortTestRows = resolveScopeRows(
|
|
4220
|
+
sealedCohortSplit.searchTestRows,
|
|
4221
|
+
scope
|
|
4222
|
+
);
|
|
4223
|
+
const cohortSearch = runSearch({
|
|
4224
|
+
label: family,
|
|
4225
|
+
train: cohortTrainRows,
|
|
4226
|
+
validation: cohortValidationRows,
|
|
4227
|
+
test: cohortTestRows,
|
|
4228
|
+
trainBaseline: cohortSplit.trainRows.filter((row) => row.aiApproved),
|
|
4229
|
+
validationBaseline: cohortSplit.validationRows.filter(
|
|
4230
|
+
(row) => row.aiApproved
|
|
4231
|
+
),
|
|
4232
|
+
testBaseline: sealedCohortSplit.searchTestRows.filter(
|
|
4233
|
+
(row) => row.aiApproved
|
|
4234
|
+
),
|
|
4235
|
+
requiredFeatureFamilies: [family]
|
|
4236
|
+
});
|
|
4237
|
+
coverageSearches.push({
|
|
4238
|
+
family,
|
|
4239
|
+
coverage: summarizeAiPocketFeatureCoverage(
|
|
4240
|
+
sealedCohortSplit.discoveryRows,
|
|
4241
|
+
family
|
|
4242
|
+
),
|
|
4243
|
+
scopeRows: resolveScopeRows(
|
|
4244
|
+
sealedCohortSplit.discoveryRows.filter(
|
|
4245
|
+
(row) => row.featureCoverage?.[family] === true
|
|
4246
|
+
),
|
|
4247
|
+
scope
|
|
4248
|
+
).length,
|
|
4249
|
+
trainRows: cohortTrainRows.length,
|
|
4250
|
+
validationRows: cohortValidationRows.length,
|
|
4251
|
+
testRows: cohortTestRows.length,
|
|
4252
|
+
search: cohortSearch
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
return {
|
|
4257
|
+
scopeRows,
|
|
4258
|
+
trainRows,
|
|
4259
|
+
validationRows,
|
|
4260
|
+
testRows,
|
|
4261
|
+
sealedTest: sealedFullSplit.evidence,
|
|
4262
|
+
currentGateSummary,
|
|
4263
|
+
currentGateQualityThresholds,
|
|
4264
|
+
search,
|
|
4265
|
+
coverageSearches
|
|
4266
|
+
};
|
|
4267
|
+
};
|
|
4268
|
+
|
|
4269
|
+
// src/lib/aiPocketSearch/command.ts
|
|
4270
|
+
var runAiPocketSearchCommand = async ({
|
|
4271
|
+
flags: flags2
|
|
4272
|
+
}) => {
|
|
4273
|
+
const options = resolveAiPocketSearchCommandOptions({
|
|
4274
|
+
flags: flags2,
|
|
4275
|
+
argv: process.argv
|
|
4276
|
+
});
|
|
4277
|
+
const {
|
|
4278
|
+
skip,
|
|
4279
|
+
minQuality,
|
|
4280
|
+
untilTimestamp,
|
|
4281
|
+
periodLabel,
|
|
4282
|
+
recent,
|
|
4283
|
+
qualityThresholds,
|
|
4284
|
+
scope,
|
|
4285
|
+
direction,
|
|
4286
|
+
maxDepth,
|
|
4287
|
+
minProfitFactor,
|
|
4288
|
+
minWinRate,
|
|
4289
|
+
minTotalProfit,
|
|
4290
|
+
maxAtomicPredicates,
|
|
4291
|
+
maxCombinations,
|
|
4292
|
+
validationSplit,
|
|
4293
|
+
testSplit,
|
|
4294
|
+
maxBatch,
|
|
4295
|
+
maxSymbolCountShare,
|
|
4296
|
+
objective,
|
|
4297
|
+
allowRiskRegression,
|
|
4298
|
+
requireValidationEligibility,
|
|
4299
|
+
dedupeEquivalentSelections,
|
|
4300
|
+
top,
|
|
4301
|
+
includeSymbol,
|
|
4302
|
+
includeGateContext,
|
|
4303
|
+
featureProfile,
|
|
4304
|
+
featurePolicy,
|
|
4305
|
+
coverageMode,
|
|
4306
|
+
cadenceMode,
|
|
4307
|
+
jsonOutput,
|
|
4308
|
+
outputPath,
|
|
4309
|
+
reportDir,
|
|
4310
|
+
explicitReportFile
|
|
4311
|
+
} = options;
|
|
4312
|
+
const generatedAt = Date.now();
|
|
4313
|
+
const preparedDataset = await prepareAiPocketSearchDataset(options);
|
|
4314
|
+
const { filePaths, totalRows, selectedRows, sinceTimestamp } = preparedDataset;
|
|
4315
|
+
if (!selectedRows) {
|
|
4316
|
+
presentEmptyAiPocketSearchDataset({ filePaths, recent, skip });
|
|
4317
|
+
process.exit(0);
|
|
4318
|
+
}
|
|
4319
|
+
let resolvedStrategyName = preparedDataset.resolvedStrategyName;
|
|
4320
|
+
const reportPath = await writePendingAiPocketSearchReport({
|
|
4321
|
+
explicitReportFile,
|
|
4322
|
+
reportDir,
|
|
4323
|
+
strategyName: resolvedStrategyName,
|
|
4324
|
+
filePaths,
|
|
4325
|
+
scope,
|
|
4326
|
+
generatedAt,
|
|
4327
|
+
selectedRows
|
|
4328
|
+
});
|
|
4329
|
+
presentPendingAiPocketSearchReport(reportPath);
|
|
4330
|
+
const evaluatedDataset = await evaluateAiPocketSearchDataset({
|
|
4331
|
+
prepared: preparedDataset,
|
|
4332
|
+
options,
|
|
4333
|
+
onProgress: createAiPocketSearchDatasetProgressPresenter(selectedRows)
|
|
4334
|
+
});
|
|
4335
|
+
const { rows, scanned, dateSkipped, failed, errors, excludedFeaturePaths } = evaluatedDataset;
|
|
4336
|
+
resolvedStrategyName = evaluatedDataset.resolvedStrategyName;
|
|
4337
|
+
const research = runAiPocketSearchResearch({
|
|
4338
|
+
rows,
|
|
4339
|
+
options,
|
|
4340
|
+
onProgress: createAiPocketSearchResearchProgressPresenter()
|
|
4341
|
+
});
|
|
4342
|
+
const {
|
|
4343
|
+
scopeRows,
|
|
4344
|
+
trainRows,
|
|
4345
|
+
validationRows,
|
|
4346
|
+
testRows,
|
|
4347
|
+
sealedTest,
|
|
4348
|
+
currentGateSummary,
|
|
4349
|
+
currentGateQualityThresholds,
|
|
4350
|
+
search,
|
|
4351
|
+
coverageSearches
|
|
4352
|
+
} = research;
|
|
4353
|
+
const result = {
|
|
4354
|
+
generatedAt,
|
|
4355
|
+
run: {
|
|
4356
|
+
strategy: resolvedStrategyName,
|
|
4357
|
+
filePaths,
|
|
4358
|
+
sourceRows: totalRows,
|
|
4359
|
+
selectedRows,
|
|
4360
|
+
evaluatedRows: rows.length,
|
|
4361
|
+
scope,
|
|
4362
|
+
direction: direction || null,
|
|
4363
|
+
scopeRows: scopeRows.length,
|
|
4364
|
+
trainRows: trainRows.length,
|
|
4365
|
+
validationRows: validationRows.length,
|
|
4366
|
+
testRows: testRows.length,
|
|
4367
|
+
scanned,
|
|
4368
|
+
dateSkipped,
|
|
4369
|
+
failed,
|
|
4370
|
+
recent,
|
|
4371
|
+
skip,
|
|
4372
|
+
since: sinceTimestamp,
|
|
4373
|
+
until: untilTimestamp,
|
|
4374
|
+
period: periodLabel,
|
|
4375
|
+
minQuality,
|
|
4376
|
+
qualityThresholds,
|
|
4377
|
+
includeSymbol,
|
|
4378
|
+
includeGateContext,
|
|
4379
|
+
featureProfile,
|
|
4380
|
+
featurePolicy,
|
|
4381
|
+
coverageMode,
|
|
4382
|
+
cadenceMode,
|
|
4383
|
+
coverageSearches: coverageSearches.map(
|
|
4384
|
+
({
|
|
4385
|
+
family,
|
|
4386
|
+
coverage,
|
|
4387
|
+
scopeRows: scopeRows2,
|
|
4388
|
+
trainRows: trainRows2,
|
|
4389
|
+
validationRows: validationRows2,
|
|
4390
|
+
testRows: testRows2
|
|
4391
|
+
}) => ({
|
|
4392
|
+
family,
|
|
4393
|
+
coverage,
|
|
4394
|
+
scopeRows: scopeRows2,
|
|
4395
|
+
trainRows: trainRows2,
|
|
4396
|
+
validationRows: validationRows2,
|
|
4397
|
+
testRows: testRows2
|
|
4398
|
+
})
|
|
4399
|
+
),
|
|
4400
|
+
featurePolicyAudit: Object.fromEntries(
|
|
4401
|
+
[...excludedFeaturePaths.entries()].map(([classification, paths]) => [
|
|
4402
|
+
classification,
|
|
4403
|
+
{
|
|
4404
|
+
paths: paths.size,
|
|
4405
|
+
samples: [...paths].sort().slice(0, 5)
|
|
4406
|
+
}
|
|
4407
|
+
])
|
|
4408
|
+
),
|
|
4409
|
+
objective,
|
|
4410
|
+
validationSplit,
|
|
4411
|
+
testSplit,
|
|
4412
|
+
sealedTest,
|
|
4413
|
+
minValidationSupport: search.stats.cadence.minValidationSupport,
|
|
4414
|
+
reportPath,
|
|
4415
|
+
search: {
|
|
4416
|
+
maxDepth,
|
|
4417
|
+
minSupport: search.stats.cadence.minSupport,
|
|
4418
|
+
minProfitFactor,
|
|
4419
|
+
minWinRate,
|
|
4420
|
+
minTotalProfit,
|
|
4421
|
+
maxAtomicPredicates,
|
|
4422
|
+
maxCombinations,
|
|
4423
|
+
minEvents: search.stats.cadence.minEvents,
|
|
4424
|
+
minValidationEvents: search.stats.cadence.minValidationEvents,
|
|
4425
|
+
...maxBatch > 0 ? { maxBatch } : {},
|
|
4426
|
+
maxEventCountShare: search.stats.cadence.maxEventCountShare,
|
|
4427
|
+
maxSymbolCountShare,
|
|
4428
|
+
allowRiskRegression,
|
|
4429
|
+
requireValidationEligibility,
|
|
4430
|
+
cadence: search.stats.cadence,
|
|
4431
|
+
validationSplit,
|
|
4432
|
+
testSplit,
|
|
4433
|
+
dedupeEquivalentSelections,
|
|
4434
|
+
top
|
|
4435
|
+
}
|
|
4436
|
+
},
|
|
4437
|
+
currentGate: {
|
|
4438
|
+
summary: currentGateSummary,
|
|
4439
|
+
qualityThresholds: currentGateQualityThresholds
|
|
4440
|
+
},
|
|
4441
|
+
pocketSearch: search,
|
|
4442
|
+
coverageSearches,
|
|
4443
|
+
errors
|
|
4444
|
+
};
|
|
4445
|
+
await writeAiPocketSearchArtifacts({ result, outputPath });
|
|
4446
|
+
if (jsonOutput) {
|
|
4447
|
+
console.log(JSON.stringify(result));
|
|
4448
|
+
process.exit(failed === selectedRows ? 1 : 0);
|
|
4340
4449
|
}
|
|
4450
|
+
presentAiPocketSearchResult({ result, outputPath });
|
|
4341
4451
|
process.exit(failed === selectedRows ? 1 : 0);
|
|
4342
4452
|
};
|
|
4453
|
+
|
|
4454
|
+
// src/scripts/aiPocketSearch.ts
|
|
4455
|
+
import_args.default.example(
|
|
4456
|
+
"yarn ai-pocket-search --strategy LiquidityZones -n 0 --maxDepth 2 --minSupport 25",
|
|
4457
|
+
"Search deterministic AI-gate pockets over the latest LiquidityZones AI export"
|
|
4458
|
+
);
|
|
4459
|
+
import_args.default.option(["o", "outDir"], "Dataset directory", "data/ai/export");
|
|
4460
|
+
import_args.default.option(["s", "strategy"], "Strategy name filter for merged file", "");
|
|
4461
|
+
import_args.default.option(["f", "file"], "Explicit merged dataset file path", "");
|
|
4462
|
+
import_args.default.option(
|
|
4463
|
+
["n", "recent"],
|
|
4464
|
+
"How many recent trades to evaluate from the end (0 = all)",
|
|
4465
|
+
50
|
|
4466
|
+
);
|
|
4467
|
+
import_args.default.option(
|
|
4468
|
+
["k", "skip"],
|
|
4469
|
+
"How many recent trades to skip from the end before selecting replay rows",
|
|
4470
|
+
0
|
|
4471
|
+
);
|
|
4472
|
+
import_args.default.option(
|
|
4473
|
+
["M", "minQuality"],
|
|
4474
|
+
"Minimum deterministic gate quality used for current qN+ baseline",
|
|
4475
|
+
4
|
|
4476
|
+
);
|
|
4477
|
+
import_args.default.option(
|
|
4478
|
+
["S", "since"],
|
|
4479
|
+
"Only evaluate rows at or after this timestamp (ISO date or epoch ms)",
|
|
4480
|
+
""
|
|
4481
|
+
);
|
|
4482
|
+
import_args.default.option(
|
|
4483
|
+
["u", "until"],
|
|
4484
|
+
"Only evaluate rows at or before this timestamp (ISO date or epoch ms)",
|
|
4485
|
+
""
|
|
4486
|
+
);
|
|
4487
|
+
import_args.default.option(
|
|
4488
|
+
["P", "period"],
|
|
4489
|
+
"Evaluate a trailing selected-row period such as last365d, last90d, or last30d",
|
|
4490
|
+
""
|
|
4491
|
+
);
|
|
4492
|
+
import_args.default.option(
|
|
4493
|
+
["q", "qualityThresholds"],
|
|
4494
|
+
"Comma-separated qN+ thresholds for current-gate baseline",
|
|
4495
|
+
"3,4,5"
|
|
4496
|
+
);
|
|
4497
|
+
import_args.default.option(
|
|
4498
|
+
["g", "scope"],
|
|
4499
|
+
"Search scope: all, approved, rejected, or candidates",
|
|
4500
|
+
"all"
|
|
4501
|
+
);
|
|
4502
|
+
import_args.default.option(
|
|
4503
|
+
["I", "direction"],
|
|
4504
|
+
"Only search rows with this signal direction: LONG or SHORT",
|
|
4505
|
+
""
|
|
4506
|
+
);
|
|
4507
|
+
import_args.default.option(["d", "maxDepth"], "Maximum predicate-combination depth", 2);
|
|
4508
|
+
import_args.default.option(
|
|
4509
|
+
["m", "minSupport"],
|
|
4510
|
+
"Minimum rows override; cadence auto derives it when omitted",
|
|
4511
|
+
20
|
|
4512
|
+
);
|
|
4513
|
+
import_args.default.option(
|
|
4514
|
+
["F", "minProfitFactor"],
|
|
4515
|
+
"Minimum profit factor required for positive pockets",
|
|
4516
|
+
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minProfitFactor
|
|
4517
|
+
);
|
|
4518
|
+
import_args.default.option(
|
|
4519
|
+
["W", "minWinRate"],
|
|
4520
|
+
"Minimum win rate required for positive pockets",
|
|
4521
|
+
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minWinRate
|
|
4522
|
+
);
|
|
4523
|
+
import_args.default.option(
|
|
4524
|
+
["R", "minTotalProfit"],
|
|
4525
|
+
"Minimum total PnL required for positive pockets",
|
|
4526
|
+
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.minTotalProfit
|
|
4527
|
+
);
|
|
4528
|
+
import_args.default.option(
|
|
4529
|
+
["a", "maxAtomicPredicates"],
|
|
4530
|
+
"Maximum strongest atomic predicates used for combinations",
|
|
4531
|
+
180
|
|
4532
|
+
);
|
|
4533
|
+
import_args.default.option(
|
|
4534
|
+
["C", "maxCombinations"],
|
|
4535
|
+
"Maximum predicate combinations to evaluate",
|
|
4536
|
+
6e4
|
|
4537
|
+
);
|
|
4538
|
+
import_args.default.option(
|
|
4539
|
+
["V", "validationSplit"],
|
|
4540
|
+
"Trailing time-ordered scope share reserved for validation (0 disables)",
|
|
4541
|
+
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.validationSplit
|
|
4542
|
+
);
|
|
4543
|
+
import_args.default.option(
|
|
4544
|
+
["T", "testSplit"],
|
|
4545
|
+
"Trailing timestamp-grouped scope share withheld as untouched test",
|
|
4546
|
+
AI_POCKET_SEARCH_CLI_DECIMAL_DEFAULTS.testSplit
|
|
4547
|
+
);
|
|
4548
|
+
import_args.default.option(
|
|
4549
|
+
["w", "sealTest"],
|
|
4550
|
+
"Reserve test bounds without exposing its rows or economics to discovery",
|
|
4551
|
+
false
|
|
4552
|
+
);
|
|
4553
|
+
import_args.default.option(
|
|
4554
|
+
["N", "minValidationSupport"],
|
|
4555
|
+
"Minimum validation rows required for positive pockets (0 = auto)",
|
|
4556
|
+
0
|
|
4557
|
+
);
|
|
4558
|
+
import_args.default.option(
|
|
4559
|
+
["H", "minEvents"],
|
|
4560
|
+
"Minimum independent timestamp events required (0 = auto)",
|
|
4561
|
+
0
|
|
4562
|
+
);
|
|
4563
|
+
import_args.default.option(
|
|
4564
|
+
["J", "minValidationEvents"],
|
|
4565
|
+
"Minimum validation timestamp events required (0 = auto)",
|
|
4566
|
+
0
|
|
4567
|
+
);
|
|
4568
|
+
import_args.default.option(
|
|
4569
|
+
["X", "maxBatch"],
|
|
4570
|
+
"Maximum selected rows at one timestamp (0 disables)",
|
|
4571
|
+
5
|
|
4572
|
+
);
|
|
4573
|
+
import_args.default.option(
|
|
4574
|
+
["U", "maxEventCountShare"],
|
|
4575
|
+
"Maximum row share contributed by one timestamp event",
|
|
4576
|
+
"0.25"
|
|
4577
|
+
);
|
|
4578
|
+
import_args.default.option(
|
|
4579
|
+
["Z", "maxSymbolCountShare"],
|
|
4580
|
+
"Maximum row share contributed by one symbol",
|
|
4581
|
+
"0.5"
|
|
4582
|
+
);
|
|
4583
|
+
import_args.default.option(
|
|
4584
|
+
["A", "objective"],
|
|
4585
|
+
"Search objective: auto, standalone, add-to-gate, or filter-gate",
|
|
4586
|
+
"auto"
|
|
4587
|
+
);
|
|
4588
|
+
import_args.default.option(
|
|
4589
|
+
["G", "allowRiskRegression"],
|
|
4590
|
+
"Allow incremental pockets to worsen q4+ PF, PnL, drawdown, or loss streak",
|
|
4591
|
+
false
|
|
4592
|
+
);
|
|
4593
|
+
import_args.default.option(
|
|
4594
|
+
["L", "allowValidationRegression"],
|
|
4595
|
+
"Keep train-positive pockets that fail validation PnL/PF thresholds",
|
|
4596
|
+
false
|
|
4597
|
+
);
|
|
4598
|
+
import_args.default.option(
|
|
4599
|
+
["D", "dedupeEquivalentSelections"],
|
|
4600
|
+
"Collapse pockets that select the same train rows",
|
|
4601
|
+
true
|
|
4602
|
+
);
|
|
4603
|
+
import_args.default.option(["t", "top"], "Top pockets to print in each section", 30);
|
|
4604
|
+
import_args.default.option(["Y", "includeSymbol"], "Allow symbol as a pocket feature", false);
|
|
4605
|
+
import_args.default.option(
|
|
4606
|
+
["E", "includeGateContext"],
|
|
4607
|
+
"Allow current deterministic gate output fields as pocket features",
|
|
4608
|
+
false
|
|
4609
|
+
);
|
|
4610
|
+
import_args.default.option(
|
|
4611
|
+
["p", "featureProfile"],
|
|
4612
|
+
"Feature extraction profile: compact or all",
|
|
4613
|
+
"all"
|
|
4614
|
+
);
|
|
4615
|
+
import_args.default.option(
|
|
4616
|
+
["K", "featurePolicy"],
|
|
4617
|
+
"Feature policy: causal-stationary or all",
|
|
4618
|
+
"causal-stationary"
|
|
4619
|
+
);
|
|
4620
|
+
import_args.default.option(
|
|
4621
|
+
["Q", "coverageMode"],
|
|
4622
|
+
"Coverage-aware search mode: auto or full",
|
|
4623
|
+
"auto"
|
|
4624
|
+
);
|
|
4625
|
+
import_args.default.option(
|
|
4626
|
+
["c", "cadenceMode"],
|
|
4627
|
+
"Cadence thresholds: auto adapts discovery support, fixed keeps legacy defaults",
|
|
4628
|
+
"auto"
|
|
4629
|
+
);
|
|
4630
|
+
import_args.default.option(
|
|
4631
|
+
["r", "reportDir"],
|
|
4632
|
+
"Directory for generated Markdown reports",
|
|
4633
|
+
"data/ai/output"
|
|
4634
|
+
);
|
|
4635
|
+
import_args.default.option(["B", "reportFile"], "Explicit Markdown report file path", "");
|
|
4636
|
+
import_args.default.option(["j", "json"], "Print structured JSON summary", false);
|
|
4637
|
+
import_args.default.option(["O", "output"], "Write structured JSON summary to file", "");
|
|
4638
|
+
var flags = import_args.default.parse(process.argv);
|
|
4639
|
+
var main = () => runAiPocketSearchCommand({ flags });
|
|
4343
4640
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4344
4641
|
0 && (module.exports = {
|
|
4345
4642
|
main
|