@tradejs/cli 1.0.9 → 1.0.11
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/README.md +13 -4
- package/dist/cli.js +29531 -13520
- package/dist/lib/aiPocketSearch.js +1316 -0
- package/dist/lib/aiTrainCharts.js +711 -0
- package/dist/lib/aiTrainDataset.js +40 -0
- package/dist/lib/aiTrainEvaluationDump.js +76 -0
- package/dist/lib/aiTrainMetrics.js +148 -8
- package/dist/lib/aiTrainOptions.js +130 -0
- package/dist/lib/aiTrainQuarantine.js +199 -0
- package/dist/lib/aiTrainResearch.js +467 -0
- package/dist/lib/binanceMarketContextBackfill.js +841 -0
- package/dist/lib/binanceMarketData.js +392 -0
- package/dist/lib/coinMarketCapContextBackfill.js +1126 -0
- package/dist/lib/continuity.js +60 -0
- package/dist/lib/derivativesContextBackfill.js +490 -77
- package/dist/lib/executionCalibration.js +705 -0
- package/dist/lib/marketContextPrepare.js +4441 -0
- package/dist/lib/paritySummary.js +101 -0
- package/dist/lib/runBot.js +1 -0
- package/dist/lib/runEnvironment.js +2259 -0
- package/dist/lib/runFormatting.js +1657 -0
- package/dist/lib/runtimeDebugEvidence.js +526 -0
- package/dist/lib/runtimeModeConfig.js +53 -0
- package/dist/lib/runtimeParity.js +88 -11
- package/dist/lib/runtimeParityDetails.js +751 -0
- package/dist/lib/runtimeRedis.js +157 -0
- package/dist/lib/runtimeSignalsLoader.js +40 -6
- package/dist/lib/runtimeSignalsStorage.js +45 -22
- package/dist/lib/runtimeStrategyBacktest.js +134 -0
- package/dist/lib/runtimeTradeSync.js +263 -0
- package/dist/lib/telegramReports.js +9 -0
- package/dist/lib/tickerUniverseCache.js +116 -0
- package/dist/lib/timeWindow.js +3 -3
- package/dist/scripts/agentRun.js +14 -8
- package/dist/scripts/aiExport.js +1721 -7
- package/dist/scripts/aiExportSelect.js +16 -4
- package/dist/scripts/aiPocketSearch.js +4190 -0
- package/dist/scripts/aiTrain.js +1866 -87
- package/dist/scripts/backtest.js +4534 -803
- package/dist/scripts/binanceMarketIngest.js +565 -0
- package/dist/scripts/bot.js +16 -2
- package/dist/scripts/candlesMigrateProvider.js +32 -22
- package/dist/scripts/cleanDir.js +15 -2
- package/dist/scripts/cleanRedis.js +15 -2
- package/dist/scripts/cleanTests.js +38 -2
- package/dist/scripts/cleanupMarketContext.js +69 -0
- package/dist/scripts/continuity.js +148 -67
- package/dist/scripts/derivativesIngest.js +14 -4
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +14 -4
- package/dist/scripts/doctor.js +14 -4
- package/dist/scripts/executionCalibration.js +792 -0
- package/dist/scripts/infraCommon.js +18 -5
- package/dist/scripts/infraDown.js +14 -4
- package/dist/scripts/infraInit.js +32 -9
- package/dist/scripts/infraUp.js +24 -6
- package/dist/scripts/marketWs.js +222 -0
- package/dist/scripts/migration.js +15 -2
- package/dist/scripts/mlExport.js +1698 -10
- package/dist/scripts/mlExportSelect.js +16 -4
- package/dist/scripts/mlInspect.js +13 -3
- package/dist/scripts/mlTrainLatestSelect.js +14 -4
- package/dist/scripts/replay.js +8867 -0
- package/dist/scripts/replayRunner.js +8870 -0
- package/dist/scripts/replayRuntimeEvidence.js +868 -0
- package/dist/scripts/researchAuto.js +286 -59
- package/dist/scripts/results.js +3 -3
- package/dist/scripts/runtimeEvidence.js +605 -0
- package/dist/scripts/runtimeParity.js +6400 -1043
- package/dist/scripts/serverHealth.js +588 -0
- package/dist/scripts/signals.js +5628 -511
- package/dist/scripts/signalsDaemon.js +6205 -0
- package/dist/scripts/signalsSummary.js +833 -248
- package/dist/scripts/test-ml.js +25 -3
- package/dist/scripts/test.js +26 -1
- package/dist/scripts/user-add.js +15 -2
- package/dist/workers/testerWorker.js +130 -21
- package/package.json +18 -12
|
@@ -30,15 +30,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/scripts/researchAuto.ts
|
|
31
31
|
var researchAuto_exports = {};
|
|
32
32
|
__export(researchAuto_exports, {
|
|
33
|
+
acquireResearchAutoLock: () => acquireResearchAutoLock,
|
|
34
|
+
buildNodeOptions: () => buildNodeOptions,
|
|
33
35
|
buildTelegramReport: () => buildTelegramReport,
|
|
34
36
|
createEmptySteps: () => createEmptySteps,
|
|
35
37
|
escapeHtml: () => escapeHtml,
|
|
36
38
|
formatMskDateTime: () => formatMskDateTime,
|
|
37
39
|
getBacktestResultsPrefix: () => getBacktestResultsPrefix,
|
|
40
|
+
getResearchAutoLockPath: () => getResearchAutoLockPath,
|
|
41
|
+
isProcessAlive: () => isProcessAlive,
|
|
38
42
|
listMergedFiles: () => listMergedFiles,
|
|
39
43
|
listRuntimeStrategyNames: () => listRuntimeStrategyNames,
|
|
40
44
|
main: () => main,
|
|
41
45
|
parseJsonOutput: () => parseJsonOutput,
|
|
46
|
+
releaseResearchAutoLock: () => releaseResearchAutoLock,
|
|
47
|
+
resolveHeapMbFromEnv: () => resolveHeapMbFromEnv,
|
|
42
48
|
resolveStrategyNameByConfigKey: () => resolveStrategyNameByConfigKey,
|
|
43
49
|
resolveTarget: () => resolveTarget,
|
|
44
50
|
runCliCommand: () => runCliCommand,
|
|
@@ -47,15 +53,83 @@ __export(researchAuto_exports, {
|
|
|
47
53
|
});
|
|
48
54
|
module.exports = __toCommonJS(researchAuto_exports);
|
|
49
55
|
var import_args = __toESM(require("args"));
|
|
50
|
-
var
|
|
56
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
51
57
|
var import_node_child_process = require("child_process");
|
|
52
58
|
var import_promises = __toESM(require("fs/promises"));
|
|
59
|
+
var import_node_os = __toESM(require("os"));
|
|
53
60
|
var import_node_path = __toESM(require("path"));
|
|
61
|
+
var import_constants = require("@tradejs/core/constants");
|
|
54
62
|
var import_ai = require("@tradejs/infra/ai");
|
|
55
63
|
var import_logger = require("@tradejs/infra/logger");
|
|
56
|
-
var
|
|
64
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
57
65
|
var import_strategies = require("@tradejs/strategies");
|
|
58
66
|
|
|
67
|
+
// src/lib/runtimeRedis.ts
|
|
68
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
69
|
+
var import_time = require("@tradejs/core/time");
|
|
70
|
+
var RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
|
|
71
|
+
var RESERVED_CONFIG_IDS = /* @__PURE__ */ new Set(["results"]);
|
|
72
|
+
var resolveStrategyConfigIdentityByKey = (userName, key) => {
|
|
73
|
+
const parts = key.split(":");
|
|
74
|
+
if (parts.length !== 5) return null;
|
|
75
|
+
const [users, keyUserName, strategiesKey, strategyName, configId] = parts;
|
|
76
|
+
if (users !== "users" || keyUserName !== userName || strategiesKey !== "strategies" || !strategyName || !configId || RESERVED_STRATEGY_NAMES.has(strategyName) || RESERVED_CONFIG_IDS.has(configId) || !/^[a-zA-Z0-9_-]+$/.test(configId)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return { strategyName, configId };
|
|
80
|
+
};
|
|
81
|
+
var isRuntimeStrategyEnabled = (strategyConfig) => {
|
|
82
|
+
const enabled = strategyConfig.ENABLE;
|
|
83
|
+
return enabled !== false;
|
|
84
|
+
};
|
|
85
|
+
var resolveStrategyNameByConfigKey = (userName, key) => {
|
|
86
|
+
return resolveStrategyConfigIdentityByKey(userName, key)?.strategyName ?? null;
|
|
87
|
+
};
|
|
88
|
+
var getRuntimeStrategyConfigKeys = async (userName) => {
|
|
89
|
+
const keys = await (0, import_redis.getKeys)(`${import_redis.redisKeys.strategies(userName)}:`);
|
|
90
|
+
return keys.filter((key) => resolveStrategyConfigIdentityByKey(userName, key)).sort((left, right) => left.localeCompare(right));
|
|
91
|
+
};
|
|
92
|
+
var loadRuntimeStrategyConfigs = async (userName, {
|
|
93
|
+
onInvalidConfig
|
|
94
|
+
} = {}) => {
|
|
95
|
+
const configKeys = await getRuntimeStrategyConfigKeys(userName);
|
|
96
|
+
const strategyConfigs = await Promise.all(
|
|
97
|
+
configKeys.map(async (key) => {
|
|
98
|
+
const strategyName = resolveStrategyNameByConfigKey(userName, key);
|
|
99
|
+
const identity = resolveStrategyConfigIdentityByKey(userName, key);
|
|
100
|
+
if (!strategyName || !identity) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const strategyConfig = await (0, import_redis.getData)(
|
|
104
|
+
key,
|
|
105
|
+
null
|
|
106
|
+
);
|
|
107
|
+
if (!strategyConfig || typeof strategyConfig !== "object" || Array.isArray(strategyConfig)) {
|
|
108
|
+
onInvalidConfig?.(key);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
key,
|
|
113
|
+
strategyName,
|
|
114
|
+
configId: identity.configId,
|
|
115
|
+
strategyConfig
|
|
116
|
+
};
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
return strategyConfigs.filter(Boolean);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/lib/runtimeStrategyBacktest.ts
|
|
123
|
+
var import_chalk = __toESM(require("chalk"));
|
|
124
|
+
var BACKTEST_CLI_RUNTIME_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
125
|
+
"ENV",
|
|
126
|
+
"INTERVAL",
|
|
127
|
+
"MAKE_ORDERS",
|
|
128
|
+
"CLOSE_OPPOSITE_POSITIONS",
|
|
129
|
+
"BACKTEST_PRICE_MODE",
|
|
130
|
+
"BACKTEST_ENTRY_DELAY_BARS"
|
|
131
|
+
]);
|
|
132
|
+
|
|
59
133
|
// src/lib/telegramReports.ts
|
|
60
134
|
var import_cli = require("@tradejs/node/cli");
|
|
61
135
|
var TELEGRAM_REPORT_CHUNK_LIMIT = 3900;
|
|
@@ -100,8 +174,16 @@ ${line}` : line;
|
|
|
100
174
|
};
|
|
101
175
|
var sendTelegramReport = async (message, options = {}) => {
|
|
102
176
|
const chunks = splitTelegramReport(message);
|
|
177
|
+
const sendAttachments = async () => {
|
|
178
|
+
for (const attachment of options.attachments || []) {
|
|
179
|
+
await (0, import_cli.sendDocumentToTG)(attachment, {
|
|
180
|
+
userName: options.userName
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
};
|
|
103
184
|
if (chunks.length === 1) {
|
|
104
185
|
await (0, import_cli.sendTextToTG)(chunks[0], options);
|
|
186
|
+
await sendAttachments();
|
|
105
187
|
return chunks.length;
|
|
106
188
|
}
|
|
107
189
|
for (const [index, chunk] of chunks.entries()) {
|
|
@@ -113,6 +195,7 @@ ${chunk}`, {
|
|
|
113
195
|
markup: index === chunks.length - 1 ? options.markup : void 0
|
|
114
196
|
});
|
|
115
197
|
}
|
|
198
|
+
await sendAttachments();
|
|
116
199
|
return chunks.length;
|
|
117
200
|
};
|
|
118
201
|
|
|
@@ -128,16 +211,150 @@ import_args.default.option(["o", "connector"], "Backtest connector", "bybit");
|
|
|
128
211
|
import_args.default.option(["f", "timeframe"], "Backtest timeframe", "15");
|
|
129
212
|
import_args.default.option(["d", "days"], "Backtest days window", 45);
|
|
130
213
|
import_args.default.option(["n", "recent"], "AI train recent rows window", 1e3);
|
|
131
|
-
import_args.default.option("skip", "AI train skip rows from end", 0);
|
|
132
|
-
import_args.default.option(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
214
|
+
import_args.default.option(["k", "skip"], "AI train skip rows from end", 0);
|
|
215
|
+
import_args.default.option(
|
|
216
|
+
["M", "minQuality"],
|
|
217
|
+
"Minimum AI quality required to approve entry",
|
|
218
|
+
4
|
|
219
|
+
);
|
|
220
|
+
import_args.default.option(["O", "outDir"], "AI export output directory", "data/ai/export");
|
|
221
|
+
import_args.default.option(["A", "skipAgent"], "Skip direct research agent invocation", false);
|
|
222
|
+
import_args.default.option(["j", "json"], "Print structured JSON summary", false);
|
|
136
223
|
var flags = import_args.default.parse(process.argv);
|
|
137
224
|
var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
138
225
|
var dotenvPath = String(
|
|
139
226
|
process.env.DOTENV_CONFIG_PATH || import_node_path.default.join(projectRoot, ".env")
|
|
140
227
|
).trim() || import_node_path.default.join(projectRoot, ".env");
|
|
228
|
+
var getResearchAutoLockPath = (userName) => import_node_path.default.join(
|
|
229
|
+
projectRoot,
|
|
230
|
+
"data",
|
|
231
|
+
"cache",
|
|
232
|
+
"research-auto",
|
|
233
|
+
`${(0, import_ai.toFileToken)(userName)}.lock.json`
|
|
234
|
+
);
|
|
235
|
+
var readResearchAutoLock = async (lockPath) => {
|
|
236
|
+
try {
|
|
237
|
+
const raw = await import_promises.default.readFile(lockPath, "utf8");
|
|
238
|
+
return JSON.parse(raw);
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
var isProcessAlive = (pid) => {
|
|
244
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
process.kill(pid, 0);
|
|
249
|
+
return true;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code || "") : "";
|
|
252
|
+
return code === "EPERM";
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var acquireResearchAutoLock = async ({
|
|
256
|
+
runId,
|
|
257
|
+
userName,
|
|
258
|
+
strategy,
|
|
259
|
+
startedAt
|
|
260
|
+
}) => {
|
|
261
|
+
const lockPath = getResearchAutoLockPath(userName);
|
|
262
|
+
const nextLock = {
|
|
263
|
+
runId,
|
|
264
|
+
userName,
|
|
265
|
+
strategy,
|
|
266
|
+
pid: process.pid,
|
|
267
|
+
ppid: process.ppid,
|
|
268
|
+
hostname: import_node_os.default.hostname(),
|
|
269
|
+
startedAt,
|
|
270
|
+
argv: [...process.argv]
|
|
271
|
+
};
|
|
272
|
+
await import_promises.default.mkdir(import_node_path.default.dirname(lockPath), { recursive: true });
|
|
273
|
+
const writeLock = async () => {
|
|
274
|
+
await import_promises.default.writeFile(lockPath, JSON.stringify(nextLock, null, 2), {
|
|
275
|
+
flag: "wx"
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
try {
|
|
279
|
+
await writeLock();
|
|
280
|
+
return lockPath;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code || "") : "";
|
|
283
|
+
if (code !== "EEXIST") {
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const existingLock = await readResearchAutoLock(lockPath);
|
|
288
|
+
if (existingLock?.pid && isProcessAlive(existingLock.pid)) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`research:auto is already running for user ${userName} (runId=${existingLock.runId}, strategy=${existingLock.strategy}, pid=${existingLock.pid}, ppid=${existingLock.ppid}, startedAt=${existingLock.startedAt}, host=${existingLock.hostname})`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
await import_promises.default.unlink(lockPath).catch(() => void 0);
|
|
294
|
+
await writeLock();
|
|
295
|
+
return lockPath;
|
|
296
|
+
};
|
|
297
|
+
var releaseResearchAutoLock = async (lockPath) => {
|
|
298
|
+
if (!lockPath) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const existingLock = await readResearchAutoLock(lockPath);
|
|
302
|
+
if (existingLock && existingLock.pid !== process.pid) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
await import_promises.default.unlink(lockPath).catch(() => void 0);
|
|
306
|
+
};
|
|
307
|
+
var resolvePositiveInteger = (value, fallback) => {
|
|
308
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
309
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
310
|
+
return parsed;
|
|
311
|
+
}
|
|
312
|
+
return fallback;
|
|
313
|
+
};
|
|
314
|
+
var resolveHeapMbFromEnv = (envNames, fallback) => {
|
|
315
|
+
for (const envName of envNames) {
|
|
316
|
+
const resolved = resolvePositiveInteger(process.env[envName], void 0);
|
|
317
|
+
if (resolved != null) {
|
|
318
|
+
return resolved;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return fallback;
|
|
322
|
+
};
|
|
323
|
+
var buildNodeOptions = ({
|
|
324
|
+
heapMb,
|
|
325
|
+
baseNodeOptions = String(process.env.NODE_OPTIONS || "").trim()
|
|
326
|
+
}) => [
|
|
327
|
+
heapMb ? `--max-old-space-size=${Math.max(256, heapMb)}` : "",
|
|
328
|
+
...baseNodeOptions.split(/\s+/).map((value) => value.trim()).filter((value) => value && !value.startsWith("--max-old-space-size="))
|
|
329
|
+
].filter(Boolean).join(" ");
|
|
330
|
+
var RESEARCH_AUTO_PARENT_HEAP_MB = resolveHeapMbFromEnv(
|
|
331
|
+
["RESEARCH_AUTO_PARENT_HEAP_MB"],
|
|
332
|
+
1024
|
|
333
|
+
);
|
|
334
|
+
var RESEARCH_AUTO_BACKTEST_PARENT_HEAP_MB = resolveHeapMbFromEnv(
|
|
335
|
+
[
|
|
336
|
+
"RESEARCH_AUTO_BACKTEST_PARENT_HEAP_MB",
|
|
337
|
+
"BACKTEST_PARENT_HEAP_MB",
|
|
338
|
+
"CLI_PARENT_HEAP_MB"
|
|
339
|
+
],
|
|
340
|
+
1536
|
|
341
|
+
);
|
|
342
|
+
var RESEARCH_AUTO_AI_TRAIN_PARENT_HEAP_MB = resolveHeapMbFromEnv(
|
|
343
|
+
[
|
|
344
|
+
"RESEARCH_AUTO_AI_TRAIN_PARENT_HEAP_MB",
|
|
345
|
+
"AI_TRAIN_PARENT_HEAP_MB",
|
|
346
|
+
"CLI_PARENT_HEAP_MB"
|
|
347
|
+
],
|
|
348
|
+
2048
|
|
349
|
+
);
|
|
350
|
+
var RESEARCH_AUTO_AGENT_PARENT_HEAP_MB = resolveHeapMbFromEnv(
|
|
351
|
+
[
|
|
352
|
+
"RESEARCH_AUTO_AGENT_PARENT_HEAP_MB",
|
|
353
|
+
"AGENT_RUN_PARENT_HEAP_MB",
|
|
354
|
+
"CLI_PARENT_HEAP_MB"
|
|
355
|
+
],
|
|
356
|
+
2048
|
|
357
|
+
);
|
|
141
358
|
var trimTextTail = (value, limit = 4e3) => value.length <= limit ? value : value.slice(-limit);
|
|
142
359
|
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
143
360
|
var createEmptySteps = () => ({
|
|
@@ -178,11 +395,11 @@ var createEmptySteps = () => ({
|
|
|
178
395
|
}
|
|
179
396
|
});
|
|
180
397
|
var saveRun = async (run) => {
|
|
181
|
-
await (0,
|
|
182
|
-
expire:
|
|
398
|
+
await (0, import_redis2.setData)(import_redis2.redisKeys.researchRun(run.userName, run.runId), run, {
|
|
399
|
+
expire: import_constants.TTL_1M
|
|
183
400
|
});
|
|
184
|
-
await (0,
|
|
185
|
-
expire:
|
|
401
|
+
await (0, import_redis2.setData)(import_redis2.redisKeys.researchLatestRun(run.userName, run.strategy), run, {
|
|
402
|
+
expire: import_constants.TTL_1M
|
|
186
403
|
});
|
|
187
404
|
};
|
|
188
405
|
var listMergedFiles = async (outDir, strategyName) => {
|
|
@@ -195,24 +412,13 @@ var listMergedFiles = async (outDir, strategyName) => {
|
|
|
195
412
|
}
|
|
196
413
|
};
|
|
197
414
|
var getBacktestResultsPrefix = (userName, config) => `users:${userName}:backtests:results:${config}:`;
|
|
198
|
-
var resolveStrategyNameByConfigKey = (userName, key) => {
|
|
199
|
-
const parts = key.split(":");
|
|
200
|
-
if (parts.length !== 5) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
const [users, keyUserName, strategiesKey, strategyName, configKey] = parts;
|
|
204
|
-
if (users !== "users" || keyUserName !== userName || strategiesKey !== "strategies" || configKey !== "config" || !strategyName) {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
return strategyName;
|
|
208
|
-
};
|
|
209
415
|
var listRuntimeStrategyNames = async (userName) => {
|
|
210
|
-
const
|
|
211
|
-
return
|
|
416
|
+
const strategyConfigs = await loadRuntimeStrategyConfigs(userName);
|
|
417
|
+
return strategyConfigs.filter(({ strategyConfig }) => isRuntimeStrategyEnabled(strategyConfig)).map(({ strategyName }) => strategyName).sort((left, right) => left.localeCompare(right));
|
|
212
418
|
};
|
|
213
419
|
var toStrategyConfigGrid = (strategyConfig) => {
|
|
214
420
|
return Object.fromEntries(
|
|
215
|
-
Object.entries(strategyConfig).map(([key, value]) => [key, [value]])
|
|
421
|
+
Object.entries(strategyConfig).filter(([key]) => !BACKTEST_CLI_RUNTIME_CONFIG_KEYS.has(key)).map(([key, value]) => [key, [value]])
|
|
216
422
|
);
|
|
217
423
|
};
|
|
218
424
|
var formatMskDateTime = (value) => {
|
|
@@ -321,11 +527,10 @@ var runCliCommand = async (params) => {
|
|
|
321
527
|
...process.env,
|
|
322
528
|
PROJECT_CWD: projectRoot,
|
|
323
529
|
DOTENV_CONFIG_PATH: dotenvPath,
|
|
324
|
-
...params.
|
|
325
|
-
NODE_OPTIONS:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
].filter(Boolean).join(" ")
|
|
530
|
+
...params.nodeHeapMb ? {
|
|
531
|
+
NODE_OPTIONS: buildNodeOptions({
|
|
532
|
+
heapMb: params.nodeHeapMb
|
|
533
|
+
})
|
|
329
534
|
} : {}
|
|
330
535
|
};
|
|
331
536
|
return new Promise((resolve, reject) => {
|
|
@@ -351,14 +556,14 @@ var runCliCommand = async (params) => {
|
|
|
351
556
|
const emitLines = (source, text) => {
|
|
352
557
|
const prefix = params.liveLogPrefix ? `[research:auto:${params.liveLogPrefix}:${source}] ` : `[research:auto:${source}] `;
|
|
353
558
|
for (const line of text.split(/\r?\n/).map((value) => value.trimEnd()).filter(Boolean)) {
|
|
354
|
-
import_logger.logger.info(
|
|
559
|
+
import_logger.logger.info(import_chalk2.default.gray(`${prefix}${line}`));
|
|
355
560
|
}
|
|
356
561
|
};
|
|
357
562
|
if (params.heartbeatMs && params.heartbeatMs > 0) {
|
|
358
563
|
heartbeatTimer = setInterval(() => {
|
|
359
564
|
const elapsedMs = Date.now() - startedAt;
|
|
360
565
|
import_logger.logger.info(
|
|
361
|
-
|
|
566
|
+
import_chalk2.default.gray(
|
|
362
567
|
`[research:auto:${params.liveLogPrefix || params.command}] still running after ${Math.round(elapsedMs / 1e3)}s`
|
|
363
568
|
)
|
|
364
569
|
);
|
|
@@ -419,8 +624,8 @@ var resolveTarget = async () => {
|
|
|
419
624
|
const candidates = [];
|
|
420
625
|
for (const strategy of availableStrategies) {
|
|
421
626
|
const config = `${strategy}:research`;
|
|
422
|
-
const latestRun = await (0,
|
|
423
|
-
|
|
627
|
+
const latestRun = await (0, import_redis2.getData)(
|
|
628
|
+
import_redis2.redisKeys.researchLatestRun(userName, strategy),
|
|
424
629
|
null
|
|
425
630
|
);
|
|
426
631
|
const lastFinishedAt = latestRun?.finishedAt ? Date.parse(latestRun.finishedAt) : 0;
|
|
@@ -457,7 +662,7 @@ var parseJsonOutput = (stdout, label) => {
|
|
|
457
662
|
return JSON.parse(trimmed);
|
|
458
663
|
};
|
|
459
664
|
var logResearch = (message) => {
|
|
460
|
-
import_logger.logger.info(
|
|
665
|
+
import_logger.logger.info(import_chalk2.default.cyan(`[research:auto] ${message}`));
|
|
461
666
|
};
|
|
462
667
|
var executeNonBlockingStep = async (run, stepName, command, commandArgs, options = {}) => {
|
|
463
668
|
logResearch(
|
|
@@ -474,7 +679,7 @@ var executeNonBlockingStep = async (run, stepName, command, commandArgs, options
|
|
|
474
679
|
const result = await runCliCommand({
|
|
475
680
|
command,
|
|
476
681
|
args: commandArgs,
|
|
477
|
-
|
|
682
|
+
nodeHeapMb: options.nodeHeapMb,
|
|
478
683
|
liveLogPrefix: options.liveLog ? stepName : void 0,
|
|
479
684
|
heartbeatMs: options.liveLog ? options.heartbeatMs || 6e4 : void 0,
|
|
480
685
|
captureMode: options.captureMode
|
|
@@ -534,6 +739,7 @@ var main = async () => {
|
|
|
534
739
|
steps: createEmptySteps(),
|
|
535
740
|
artifacts: {}
|
|
536
741
|
};
|
|
742
|
+
let lockPath = null;
|
|
537
743
|
const executeStep = async (stepName, command, commandArgs, options = {}) => {
|
|
538
744
|
logResearch(
|
|
539
745
|
`starting step ${stepName}: ${command} ${commandArgs.join(" ")}`.trim()
|
|
@@ -549,7 +755,7 @@ var main = async () => {
|
|
|
549
755
|
const result = await runCliCommand({
|
|
550
756
|
command,
|
|
551
757
|
args: commandArgs,
|
|
552
|
-
|
|
758
|
+
nodeHeapMb: options.nodeHeapMb,
|
|
553
759
|
liveLogPrefix: options.liveLog ? stepName : void 0,
|
|
554
760
|
heartbeatMs: options.liveLog ? options.heartbeatMs || 6e4 : void 0,
|
|
555
761
|
captureMode: options.captureMode
|
|
@@ -597,13 +803,13 @@ var main = async () => {
|
|
|
597
803
|
startedAt
|
|
598
804
|
};
|
|
599
805
|
await saveRun(run);
|
|
600
|
-
const strategyConfigKey =
|
|
806
|
+
const strategyConfigKey = import_redis2.redisKeys.strategyConfig(
|
|
601
807
|
userName,
|
|
602
808
|
target.strategy
|
|
603
809
|
);
|
|
604
|
-
const backtestConfigKey =
|
|
810
|
+
const backtestConfigKey = import_redis2.redisKeys.backtestConfig(userName, target.config);
|
|
605
811
|
const defaults = (0, import_strategies.getBuiltInStrategyDefaultConfig)(target.strategy) || {};
|
|
606
|
-
const userStrategyConfig = await (0,
|
|
812
|
+
const userStrategyConfig = await (0, import_redis2.getData)(
|
|
607
813
|
strategyConfigKey,
|
|
608
814
|
{}
|
|
609
815
|
);
|
|
@@ -617,7 +823,7 @@ var main = async () => {
|
|
|
617
823
|
);
|
|
618
824
|
}
|
|
619
825
|
const backtestConfig = toStrategyConfigGrid(strategyConfig);
|
|
620
|
-
await (0,
|
|
826
|
+
await (0, import_redis2.setData)(backtestConfigKey, backtestConfig, { expire: 0 });
|
|
621
827
|
logResearch(
|
|
622
828
|
`stored backtest config at ${backtestConfigKey} with ${Object.keys(strategyConfig).length} top-level fields`
|
|
623
829
|
);
|
|
@@ -639,13 +845,25 @@ var main = async () => {
|
|
|
639
845
|
const message = buildTelegramReport(run);
|
|
640
846
|
await sendTelegramReport(message, { userName });
|
|
641
847
|
};
|
|
642
|
-
await saveRun(run);
|
|
643
848
|
try {
|
|
849
|
+
lockPath = await acquireResearchAutoLock({
|
|
850
|
+
runId,
|
|
851
|
+
userName,
|
|
852
|
+
strategy: target.strategy,
|
|
853
|
+
startedAt: run.startedAt
|
|
854
|
+
});
|
|
855
|
+
logResearch(
|
|
856
|
+
`run ${runId} acquired lock ${lockPath} (pid=${process.pid}, ppid=${process.ppid}, host=${import_node_os.default.hostname()})`
|
|
857
|
+
);
|
|
858
|
+
await saveRun(run);
|
|
644
859
|
logResearch(
|
|
645
860
|
`run ${runId} selected target strategy=${target.strategy}, config=${target.config}, selectedBy=${target.selectedBy}`
|
|
646
861
|
);
|
|
862
|
+
logResearch(
|
|
863
|
+
`resource knobs: researchAutoHeapMb=${RESEARCH_AUTO_PARENT_HEAP_MB}, backtestParentHeapMb=${RESEARCH_AUTO_BACKTEST_PARENT_HEAP_MB}, aiTrainParentHeapMb=${RESEARCH_AUTO_AI_TRAIN_PARENT_HEAP_MB}, agentParentHeapMb=${RESEARCH_AUTO_AGENT_PARENT_HEAP_MB}, backtestWorkerHeapMb=${process.env.BACKTEST_WORKER_HEAP_MB || "auto"}, backtestMaxParallel=${process.env.BACKTEST_MAX_PARALLEL || "auto"}, klineConcurrency=${process.env.KLINE_CONCURRENCY_LIMIT || "default"}`
|
|
864
|
+
);
|
|
647
865
|
const backtestKeysBefore = new Set(
|
|
648
|
-
await (0,
|
|
866
|
+
await (0, import_redis2.getKeys)(getBacktestResultsPrefix(userName, target.config))
|
|
649
867
|
);
|
|
650
868
|
const aiFilesBefore = new Set(
|
|
651
869
|
await listMergedFiles(outDir, target.strategy)
|
|
@@ -675,7 +893,11 @@ var main = async () => {
|
|
|
675
893
|
String(days),
|
|
676
894
|
"--ai"
|
|
677
895
|
],
|
|
678
|
-
{
|
|
896
|
+
{
|
|
897
|
+
nodeHeapMb: RESEARCH_AUTO_BACKTEST_PARENT_HEAP_MB,
|
|
898
|
+
liveLog: true,
|
|
899
|
+
heartbeatMs: 3e4
|
|
900
|
+
}
|
|
679
901
|
);
|
|
680
902
|
await executeStep("aiExport", "ai-export", [
|
|
681
903
|
"--strategy",
|
|
@@ -698,16 +920,19 @@ var main = async () => {
|
|
|
698
920
|
"--localOnly",
|
|
699
921
|
"--json"
|
|
700
922
|
],
|
|
701
|
-
{
|
|
923
|
+
{
|
|
924
|
+
nodeHeapMb: RESEARCH_AUTO_AI_TRAIN_PARENT_HEAP_MB,
|
|
925
|
+
captureMode: "full"
|
|
926
|
+
}
|
|
702
927
|
);
|
|
703
|
-
const backtestKeysAfter = await (0,
|
|
928
|
+
const backtestKeysAfter = await (0, import_redis2.getKeys)(
|
|
704
929
|
getBacktestResultsPrefix(userName, target.config)
|
|
705
930
|
);
|
|
706
931
|
const newBacktestKeys = backtestKeysAfter.filter((key) => !backtestKeysBefore.has(key)).sort();
|
|
707
932
|
const backtestResultKey = newBacktestKeys[newBacktestKeys.length - 1] || backtestKeysAfter.sort().at(-1);
|
|
708
933
|
if (backtestResultKey) {
|
|
709
934
|
logResearch(`detected backtest result key ${backtestResultKey}`);
|
|
710
|
-
const backtestResult = await (0,
|
|
935
|
+
const backtestResult = await (0, import_redis2.getData)(backtestResultKey, null);
|
|
711
936
|
run.artifacts.backtestResultKey = backtestResultKey;
|
|
712
937
|
run.artifacts.backtestResult = backtestResult ? {
|
|
713
938
|
finishedAt: backtestResult.finishedAt,
|
|
@@ -755,7 +980,7 @@ var main = async () => {
|
|
|
755
980
|
"--json"
|
|
756
981
|
],
|
|
757
982
|
{
|
|
758
|
-
|
|
983
|
+
nodeHeapMb: RESEARCH_AUTO_AGENT_PARENT_HEAP_MB,
|
|
759
984
|
liveLog: true,
|
|
760
985
|
heartbeatMs: 3e4,
|
|
761
986
|
captureMode: "full"
|
|
@@ -783,19 +1008,19 @@ var main = async () => {
|
|
|
783
1008
|
console.log(JSON.stringify(run));
|
|
784
1009
|
process.exit(0);
|
|
785
1010
|
}
|
|
786
|
-
console.log(
|
|
1011
|
+
console.log(import_chalk2.default.green("Research auto finished"));
|
|
787
1012
|
console.log(
|
|
788
|
-
|
|
1013
|
+
import_chalk2.default.gray(
|
|
789
1014
|
`strategy=${run.strategy}, config=${run.config}, runId=${run.runId}`
|
|
790
1015
|
)
|
|
791
1016
|
);
|
|
792
1017
|
if (run.artifacts.backtestResultKey) {
|
|
793
1018
|
console.log(
|
|
794
|
-
|
|
1019
|
+
import_chalk2.default.gray(`backtestResultKey=${run.artifacts.backtestResultKey}`)
|
|
795
1020
|
);
|
|
796
1021
|
}
|
|
797
1022
|
if (run.artifacts.aiExportFile) {
|
|
798
|
-
console.log(
|
|
1023
|
+
console.log(import_chalk2.default.gray(`aiExportFile=${run.artifacts.aiExportFile}`));
|
|
799
1024
|
}
|
|
800
1025
|
process.exit(0);
|
|
801
1026
|
} catch (error) {
|
|
@@ -817,28 +1042,30 @@ Telegram report failed: ${suffix}`;
|
|
|
817
1042
|
if (jsonOutput) {
|
|
818
1043
|
console.log(JSON.stringify(run));
|
|
819
1044
|
} else {
|
|
820
|
-
console.error(
|
|
1045
|
+
console.error(import_chalk2.default.red(run.error));
|
|
821
1046
|
}
|
|
822
1047
|
process.exit(1);
|
|
1048
|
+
} finally {
|
|
1049
|
+
await releaseResearchAutoLock(lockPath);
|
|
823
1050
|
}
|
|
824
1051
|
};
|
|
825
|
-
if (require.main === module) {
|
|
826
|
-
main().catch((error) => {
|
|
827
|
-
console.error(import_chalk.default.red(error?.message || String(error)));
|
|
828
|
-
process.exit(1);
|
|
829
|
-
});
|
|
830
|
-
}
|
|
831
1052
|
// Annotate the CommonJS export names for ESM import in node:
|
|
832
1053
|
0 && (module.exports = {
|
|
1054
|
+
acquireResearchAutoLock,
|
|
1055
|
+
buildNodeOptions,
|
|
833
1056
|
buildTelegramReport,
|
|
834
1057
|
createEmptySteps,
|
|
835
1058
|
escapeHtml,
|
|
836
1059
|
formatMskDateTime,
|
|
837
1060
|
getBacktestResultsPrefix,
|
|
1061
|
+
getResearchAutoLockPath,
|
|
1062
|
+
isProcessAlive,
|
|
838
1063
|
listMergedFiles,
|
|
839
1064
|
listRuntimeStrategyNames,
|
|
840
1065
|
main,
|
|
841
1066
|
parseJsonOutput,
|
|
1067
|
+
releaseResearchAutoLock,
|
|
1068
|
+
resolveHeapMbFromEnv,
|
|
842
1069
|
resolveStrategyNameByConfigKey,
|
|
843
1070
|
resolveTarget,
|
|
844
1071
|
runCliCommand,
|
package/dist/scripts/results.js
CHANGED
|
@@ -1629,6 +1629,7 @@ var require_list_it = __commonJS({
|
|
|
1629
1629
|
// src/scripts/results.ts
|
|
1630
1630
|
var results_exports = {};
|
|
1631
1631
|
__export(results_exports, {
|
|
1632
|
+
main: () => main,
|
|
1632
1633
|
results: () => results
|
|
1633
1634
|
});
|
|
1634
1635
|
module.exports = __toCommonJS(results_exports);
|
|
@@ -1924,10 +1925,9 @@ var results = async () => {
|
|
|
1924
1925
|
}
|
|
1925
1926
|
process.exit();
|
|
1926
1927
|
};
|
|
1927
|
-
|
|
1928
|
-
void results();
|
|
1929
|
-
}
|
|
1928
|
+
var main = results;
|
|
1930
1929
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1931
1930
|
0 && (module.exports = {
|
|
1931
|
+
main,
|
|
1932
1932
|
results
|
|
1933
1933
|
});
|