@tradejs/cli 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +29748 -13641
- package/dist/lib/aiPocketSearch.js +1316 -0
- package/dist/lib/aiTrainCharts.js +711 -0
- package/dist/lib/aiTrainDataset.js +40 -0
- package/dist/lib/aiTrainEvaluationDump.js +76 -0
- package/dist/lib/aiTrainMetrics.js +148 -8
- package/dist/lib/aiTrainOptions.js +130 -0
- package/dist/lib/aiTrainQuarantine.js +199 -0
- package/dist/lib/aiTrainResearch.js +467 -0
- package/dist/lib/binanceMarketContextBackfill.js +841 -0
- package/dist/lib/binanceMarketData.js +392 -0
- package/dist/lib/coinMarketCapContextBackfill.js +1088 -0
- package/dist/lib/continuity.js +60 -0
- package/dist/lib/derivativesContextBackfill.js +490 -77
- package/dist/lib/executionCalibration.js +705 -0
- package/dist/lib/marketContextPrepare.js +4403 -0
- package/dist/lib/paritySummary.js +101 -0
- package/dist/lib/runBot.js +1 -0
- package/dist/lib/runEnvironment.js +2254 -0
- package/dist/lib/runFormatting.js +1657 -0
- package/dist/lib/runtimeDebugEvidence.js +526 -0
- package/dist/lib/runtimeModeConfig.js +53 -0
- package/dist/lib/runtimeParity.js +88 -11
- package/dist/lib/runtimeParityDetails.js +751 -0
- package/dist/lib/runtimeRedis.js +157 -0
- package/dist/lib/runtimeSignalsLoader.js +187 -0
- package/dist/lib/runtimeSignalsStorage.js +185 -0
- 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 +28 -8
- package/dist/scripts/aiExport.js +1721 -7
- package/dist/scripts/aiExportSelect.js +16 -4
- package/dist/scripts/aiPocketSearch.js +4190 -0
- package/dist/scripts/aiTrain.js +1866 -87
- package/dist/scripts/backtest.js +4509 -818
- package/dist/scripts/binanceMarketIngest.js +565 -0
- package/dist/scripts/bot.js +16 -2
- package/dist/scripts/candlesMigrateProvider.js +32 -22
- package/dist/scripts/cleanDir.js +15 -2
- package/dist/scripts/cleanRedis.js +15 -2
- package/dist/scripts/cleanTests.js +38 -2
- package/dist/scripts/cleanupMarketContext.js +69 -0
- package/dist/scripts/continuity.js +148 -67
- package/dist/scripts/derivativesIngest.js +14 -4
- package/dist/scripts/derivativesIngestCoinalyzeAll.js +14 -4
- package/dist/scripts/doctor.js +14 -4
- package/dist/scripts/executionCalibration.js +792 -0
- package/dist/scripts/infraCommon.js +10 -0
- package/dist/scripts/infraDown.js +14 -4
- package/dist/scripts/infraInit.js +24 -4
- package/dist/scripts/infraUp.js +14 -4
- package/dist/scripts/marketWs.js +222 -0
- package/dist/scripts/migration.js +15 -2
- package/dist/scripts/mlExport.js +1698 -10
- package/dist/scripts/mlExportSelect.js +16 -4
- package/dist/scripts/mlInspect.js +13 -3
- package/dist/scripts/mlTrainLatestSelect.js +14 -4
- package/dist/scripts/replay.js +8824 -0
- package/dist/scripts/replayRunner.js +8827 -0
- package/dist/scripts/replayRuntimeEvidence.js +868 -0
- package/dist/scripts/researchAuto.js +289 -60
- package/dist/scripts/results.js +3 -3
- package/dist/scripts/runtimeEvidence.js +605 -0
- package/dist/scripts/runtimeParity.js +6360 -1004
- package/dist/scripts/serverHealth.js +588 -0
- package/dist/scripts/signals.js +5623 -486
- package/dist/scripts/signalsDaemon.js +6167 -0
- package/dist/scripts/signalsSummary.js +954 -312
- 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,14 +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,
|
|
44
|
+
main: () => main,
|
|
40
45
|
parseJsonOutput: () => parseJsonOutput,
|
|
46
|
+
releaseResearchAutoLock: () => releaseResearchAutoLock,
|
|
47
|
+
resolveHeapMbFromEnv: () => resolveHeapMbFromEnv,
|
|
41
48
|
resolveStrategyNameByConfigKey: () => resolveStrategyNameByConfigKey,
|
|
42
49
|
resolveTarget: () => resolveTarget,
|
|
43
50
|
runCliCommand: () => runCliCommand,
|
|
@@ -46,15 +53,83 @@ __export(researchAuto_exports, {
|
|
|
46
53
|
});
|
|
47
54
|
module.exports = __toCommonJS(researchAuto_exports);
|
|
48
55
|
var import_args = __toESM(require("args"));
|
|
49
|
-
var
|
|
56
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
50
57
|
var import_node_child_process = require("child_process");
|
|
51
58
|
var import_promises = __toESM(require("fs/promises"));
|
|
59
|
+
var import_node_os = __toESM(require("os"));
|
|
52
60
|
var import_node_path = __toESM(require("path"));
|
|
61
|
+
var import_constants = require("@tradejs/core/constants");
|
|
53
62
|
var import_ai = require("@tradejs/infra/ai");
|
|
54
63
|
var import_logger = require("@tradejs/infra/logger");
|
|
55
|
-
var
|
|
64
|
+
var import_redis2 = require("@tradejs/infra/redis");
|
|
56
65
|
var import_strategies = require("@tradejs/strategies");
|
|
57
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
|
+
|
|
58
133
|
// src/lib/telegramReports.ts
|
|
59
134
|
var import_cli = require("@tradejs/node/cli");
|
|
60
135
|
var TELEGRAM_REPORT_CHUNK_LIMIT = 3900;
|
|
@@ -99,8 +174,16 @@ ${line}` : line;
|
|
|
99
174
|
};
|
|
100
175
|
var sendTelegramReport = async (message, options = {}) => {
|
|
101
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
|
+
};
|
|
102
184
|
if (chunks.length === 1) {
|
|
103
185
|
await (0, import_cli.sendTextToTG)(chunks[0], options);
|
|
186
|
+
await sendAttachments();
|
|
104
187
|
return chunks.length;
|
|
105
188
|
}
|
|
106
189
|
for (const [index, chunk] of chunks.entries()) {
|
|
@@ -112,6 +195,7 @@ ${chunk}`, {
|
|
|
112
195
|
markup: index === chunks.length - 1 ? options.markup : void 0
|
|
113
196
|
});
|
|
114
197
|
}
|
|
198
|
+
await sendAttachments();
|
|
115
199
|
return chunks.length;
|
|
116
200
|
};
|
|
117
201
|
|
|
@@ -127,16 +211,150 @@ import_args.default.option(["o", "connector"], "Backtest connector", "bybit");
|
|
|
127
211
|
import_args.default.option(["f", "timeframe"], "Backtest timeframe", "15");
|
|
128
212
|
import_args.default.option(["d", "days"], "Backtest days window", 45);
|
|
129
213
|
import_args.default.option(["n", "recent"], "AI train recent rows window", 1e3);
|
|
130
|
-
import_args.default.option("skip", "AI train skip rows from end", 0);
|
|
131
|
-
import_args.default.option(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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);
|
|
135
223
|
var flags = import_args.default.parse(process.argv);
|
|
136
224
|
var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
137
225
|
var dotenvPath = String(
|
|
138
226
|
process.env.DOTENV_CONFIG_PATH || import_node_path.default.join(projectRoot, ".env")
|
|
139
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
|
+
);
|
|
140
358
|
var trimTextTail = (value, limit = 4e3) => value.length <= limit ? value : value.slice(-limit);
|
|
141
359
|
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
142
360
|
var createEmptySteps = () => ({
|
|
@@ -177,11 +395,11 @@ var createEmptySteps = () => ({
|
|
|
177
395
|
}
|
|
178
396
|
});
|
|
179
397
|
var saveRun = async (run) => {
|
|
180
|
-
await (0,
|
|
181
|
-
expire:
|
|
398
|
+
await (0, import_redis2.setData)(import_redis2.redisKeys.researchRun(run.userName, run.runId), run, {
|
|
399
|
+
expire: import_constants.TTL_1M
|
|
182
400
|
});
|
|
183
|
-
await (0,
|
|
184
|
-
expire:
|
|
401
|
+
await (0, import_redis2.setData)(import_redis2.redisKeys.researchLatestRun(run.userName, run.strategy), run, {
|
|
402
|
+
expire: import_constants.TTL_1M
|
|
185
403
|
});
|
|
186
404
|
};
|
|
187
405
|
var listMergedFiles = async (outDir, strategyName) => {
|
|
@@ -194,24 +412,13 @@ var listMergedFiles = async (outDir, strategyName) => {
|
|
|
194
412
|
}
|
|
195
413
|
};
|
|
196
414
|
var getBacktestResultsPrefix = (userName, config) => `users:${userName}:backtests:results:${config}:`;
|
|
197
|
-
var resolveStrategyNameByConfigKey = (userName, key) => {
|
|
198
|
-
const parts = key.split(":");
|
|
199
|
-
if (parts.length !== 5) {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
const [users, keyUserName, strategiesKey, strategyName, configKey] = parts;
|
|
203
|
-
if (users !== "users" || keyUserName !== userName || strategiesKey !== "strategies" || configKey !== "config" || !strategyName) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
return strategyName;
|
|
207
|
-
};
|
|
208
415
|
var listRuntimeStrategyNames = async (userName) => {
|
|
209
|
-
const
|
|
210
|
-
return
|
|
416
|
+
const strategyConfigs = await loadRuntimeStrategyConfigs(userName);
|
|
417
|
+
return strategyConfigs.filter(({ strategyConfig }) => isRuntimeStrategyEnabled(strategyConfig)).map(({ strategyName }) => strategyName).sort((left, right) => left.localeCompare(right));
|
|
211
418
|
};
|
|
212
419
|
var toStrategyConfigGrid = (strategyConfig) => {
|
|
213
420
|
return Object.fromEntries(
|
|
214
|
-
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]])
|
|
215
422
|
);
|
|
216
423
|
};
|
|
217
424
|
var formatMskDateTime = (value) => {
|
|
@@ -320,17 +527,16 @@ var runCliCommand = async (params) => {
|
|
|
320
527
|
...process.env,
|
|
321
528
|
PROJECT_CWD: projectRoot,
|
|
322
529
|
DOTENV_CONFIG_PATH: dotenvPath,
|
|
323
|
-
...params.
|
|
324
|
-
NODE_OPTIONS:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
].filter(Boolean).join(" ")
|
|
530
|
+
...params.nodeHeapMb ? {
|
|
531
|
+
NODE_OPTIONS: buildNodeOptions({
|
|
532
|
+
heapMb: params.nodeHeapMb
|
|
533
|
+
})
|
|
328
534
|
} : {}
|
|
329
535
|
};
|
|
330
536
|
return new Promise((resolve, reject) => {
|
|
331
537
|
const child = (0, import_node_child_process.spawn)(
|
|
332
538
|
"bash",
|
|
333
|
-
["./bin/run-cli-
|
|
539
|
+
["./bin/run-cli-runtime.sh", params.command, ...params.args],
|
|
334
540
|
{
|
|
335
541
|
cwd: projectRoot,
|
|
336
542
|
env,
|
|
@@ -350,14 +556,14 @@ var runCliCommand = async (params) => {
|
|
|
350
556
|
const emitLines = (source, text) => {
|
|
351
557
|
const prefix = params.liveLogPrefix ? `[research:auto:${params.liveLogPrefix}:${source}] ` : `[research:auto:${source}] `;
|
|
352
558
|
for (const line of text.split(/\r?\n/).map((value) => value.trimEnd()).filter(Boolean)) {
|
|
353
|
-
import_logger.logger.info(
|
|
559
|
+
import_logger.logger.info(import_chalk2.default.gray(`${prefix}${line}`));
|
|
354
560
|
}
|
|
355
561
|
};
|
|
356
562
|
if (params.heartbeatMs && params.heartbeatMs > 0) {
|
|
357
563
|
heartbeatTimer = setInterval(() => {
|
|
358
564
|
const elapsedMs = Date.now() - startedAt;
|
|
359
565
|
import_logger.logger.info(
|
|
360
|
-
|
|
566
|
+
import_chalk2.default.gray(
|
|
361
567
|
`[research:auto:${params.liveLogPrefix || params.command}] still running after ${Math.round(elapsedMs / 1e3)}s`
|
|
362
568
|
)
|
|
363
569
|
);
|
|
@@ -418,8 +624,8 @@ var resolveTarget = async () => {
|
|
|
418
624
|
const candidates = [];
|
|
419
625
|
for (const strategy of availableStrategies) {
|
|
420
626
|
const config = `${strategy}:research`;
|
|
421
|
-
const latestRun = await (0,
|
|
422
|
-
|
|
627
|
+
const latestRun = await (0, import_redis2.getData)(
|
|
628
|
+
import_redis2.redisKeys.researchLatestRun(userName, strategy),
|
|
423
629
|
null
|
|
424
630
|
);
|
|
425
631
|
const lastFinishedAt = latestRun?.finishedAt ? Date.parse(latestRun.finishedAt) : 0;
|
|
@@ -456,7 +662,7 @@ var parseJsonOutput = (stdout, label) => {
|
|
|
456
662
|
return JSON.parse(trimmed);
|
|
457
663
|
};
|
|
458
664
|
var logResearch = (message) => {
|
|
459
|
-
import_logger.logger.info(
|
|
665
|
+
import_logger.logger.info(import_chalk2.default.cyan(`[research:auto] ${message}`));
|
|
460
666
|
};
|
|
461
667
|
var executeNonBlockingStep = async (run, stepName, command, commandArgs, options = {}) => {
|
|
462
668
|
logResearch(
|
|
@@ -473,7 +679,7 @@ var executeNonBlockingStep = async (run, stepName, command, commandArgs, options
|
|
|
473
679
|
const result = await runCliCommand({
|
|
474
680
|
command,
|
|
475
681
|
args: commandArgs,
|
|
476
|
-
|
|
682
|
+
nodeHeapMb: options.nodeHeapMb,
|
|
477
683
|
liveLogPrefix: options.liveLog ? stepName : void 0,
|
|
478
684
|
heartbeatMs: options.liveLog ? options.heartbeatMs || 6e4 : void 0,
|
|
479
685
|
captureMode: options.captureMode
|
|
@@ -533,6 +739,7 @@ var main = async () => {
|
|
|
533
739
|
steps: createEmptySteps(),
|
|
534
740
|
artifacts: {}
|
|
535
741
|
};
|
|
742
|
+
let lockPath = null;
|
|
536
743
|
const executeStep = async (stepName, command, commandArgs, options = {}) => {
|
|
537
744
|
logResearch(
|
|
538
745
|
`starting step ${stepName}: ${command} ${commandArgs.join(" ")}`.trim()
|
|
@@ -548,7 +755,7 @@ var main = async () => {
|
|
|
548
755
|
const result = await runCliCommand({
|
|
549
756
|
command,
|
|
550
757
|
args: commandArgs,
|
|
551
|
-
|
|
758
|
+
nodeHeapMb: options.nodeHeapMb,
|
|
552
759
|
liveLogPrefix: options.liveLog ? stepName : void 0,
|
|
553
760
|
heartbeatMs: options.liveLog ? options.heartbeatMs || 6e4 : void 0,
|
|
554
761
|
captureMode: options.captureMode
|
|
@@ -596,13 +803,13 @@ var main = async () => {
|
|
|
596
803
|
startedAt
|
|
597
804
|
};
|
|
598
805
|
await saveRun(run);
|
|
599
|
-
const strategyConfigKey =
|
|
806
|
+
const strategyConfigKey = import_redis2.redisKeys.strategyConfig(
|
|
600
807
|
userName,
|
|
601
808
|
target.strategy
|
|
602
809
|
);
|
|
603
|
-
const backtestConfigKey =
|
|
810
|
+
const backtestConfigKey = import_redis2.redisKeys.backtestConfig(userName, target.config);
|
|
604
811
|
const defaults = (0, import_strategies.getBuiltInStrategyDefaultConfig)(target.strategy) || {};
|
|
605
|
-
const userStrategyConfig = await (0,
|
|
812
|
+
const userStrategyConfig = await (0, import_redis2.getData)(
|
|
606
813
|
strategyConfigKey,
|
|
607
814
|
{}
|
|
608
815
|
);
|
|
@@ -616,7 +823,7 @@ var main = async () => {
|
|
|
616
823
|
);
|
|
617
824
|
}
|
|
618
825
|
const backtestConfig = toStrategyConfigGrid(strategyConfig);
|
|
619
|
-
await (0,
|
|
826
|
+
await (0, import_redis2.setData)(backtestConfigKey, backtestConfig, { expire: 0 });
|
|
620
827
|
logResearch(
|
|
621
828
|
`stored backtest config at ${backtestConfigKey} with ${Object.keys(strategyConfig).length} top-level fields`
|
|
622
829
|
);
|
|
@@ -638,13 +845,25 @@ var main = async () => {
|
|
|
638
845
|
const message = buildTelegramReport(run);
|
|
639
846
|
await sendTelegramReport(message, { userName });
|
|
640
847
|
};
|
|
641
|
-
await saveRun(run);
|
|
642
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);
|
|
643
859
|
logResearch(
|
|
644
860
|
`run ${runId} selected target strategy=${target.strategy}, config=${target.config}, selectedBy=${target.selectedBy}`
|
|
645
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
|
+
);
|
|
646
865
|
const backtestKeysBefore = new Set(
|
|
647
|
-
await (0,
|
|
866
|
+
await (0, import_redis2.getKeys)(getBacktestResultsPrefix(userName, target.config))
|
|
648
867
|
);
|
|
649
868
|
const aiFilesBefore = new Set(
|
|
650
869
|
await listMergedFiles(outDir, target.strategy)
|
|
@@ -674,7 +893,11 @@ var main = async () => {
|
|
|
674
893
|
String(days),
|
|
675
894
|
"--ai"
|
|
676
895
|
],
|
|
677
|
-
{
|
|
896
|
+
{
|
|
897
|
+
nodeHeapMb: RESEARCH_AUTO_BACKTEST_PARENT_HEAP_MB,
|
|
898
|
+
liveLog: true,
|
|
899
|
+
heartbeatMs: 3e4
|
|
900
|
+
}
|
|
678
901
|
);
|
|
679
902
|
await executeStep("aiExport", "ai-export", [
|
|
680
903
|
"--strategy",
|
|
@@ -697,16 +920,19 @@ var main = async () => {
|
|
|
697
920
|
"--localOnly",
|
|
698
921
|
"--json"
|
|
699
922
|
],
|
|
700
|
-
{
|
|
923
|
+
{
|
|
924
|
+
nodeHeapMb: RESEARCH_AUTO_AI_TRAIN_PARENT_HEAP_MB,
|
|
925
|
+
captureMode: "full"
|
|
926
|
+
}
|
|
701
927
|
);
|
|
702
|
-
const backtestKeysAfter = await (0,
|
|
928
|
+
const backtestKeysAfter = await (0, import_redis2.getKeys)(
|
|
703
929
|
getBacktestResultsPrefix(userName, target.config)
|
|
704
930
|
);
|
|
705
931
|
const newBacktestKeys = backtestKeysAfter.filter((key) => !backtestKeysBefore.has(key)).sort();
|
|
706
932
|
const backtestResultKey = newBacktestKeys[newBacktestKeys.length - 1] || backtestKeysAfter.sort().at(-1);
|
|
707
933
|
if (backtestResultKey) {
|
|
708
934
|
logResearch(`detected backtest result key ${backtestResultKey}`);
|
|
709
|
-
const backtestResult = await (0,
|
|
935
|
+
const backtestResult = await (0, import_redis2.getData)(backtestResultKey, null);
|
|
710
936
|
run.artifacts.backtestResultKey = backtestResultKey;
|
|
711
937
|
run.artifacts.backtestResult = backtestResult ? {
|
|
712
938
|
finishedAt: backtestResult.finishedAt,
|
|
@@ -754,7 +980,7 @@ var main = async () => {
|
|
|
754
980
|
"--json"
|
|
755
981
|
],
|
|
756
982
|
{
|
|
757
|
-
|
|
983
|
+
nodeHeapMb: RESEARCH_AUTO_AGENT_PARENT_HEAP_MB,
|
|
758
984
|
liveLog: true,
|
|
759
985
|
heartbeatMs: 3e4,
|
|
760
986
|
captureMode: "full"
|
|
@@ -782,19 +1008,19 @@ var main = async () => {
|
|
|
782
1008
|
console.log(JSON.stringify(run));
|
|
783
1009
|
process.exit(0);
|
|
784
1010
|
}
|
|
785
|
-
console.log(
|
|
1011
|
+
console.log(import_chalk2.default.green("Research auto finished"));
|
|
786
1012
|
console.log(
|
|
787
|
-
|
|
1013
|
+
import_chalk2.default.gray(
|
|
788
1014
|
`strategy=${run.strategy}, config=${run.config}, runId=${run.runId}`
|
|
789
1015
|
)
|
|
790
1016
|
);
|
|
791
1017
|
if (run.artifacts.backtestResultKey) {
|
|
792
1018
|
console.log(
|
|
793
|
-
|
|
1019
|
+
import_chalk2.default.gray(`backtestResultKey=${run.artifacts.backtestResultKey}`)
|
|
794
1020
|
);
|
|
795
1021
|
}
|
|
796
1022
|
if (run.artifacts.aiExportFile) {
|
|
797
|
-
console.log(
|
|
1023
|
+
console.log(import_chalk2.default.gray(`aiExportFile=${run.artifacts.aiExportFile}`));
|
|
798
1024
|
}
|
|
799
1025
|
process.exit(0);
|
|
800
1026
|
} catch (error) {
|
|
@@ -816,27 +1042,30 @@ Telegram report failed: ${suffix}`;
|
|
|
816
1042
|
if (jsonOutput) {
|
|
817
1043
|
console.log(JSON.stringify(run));
|
|
818
1044
|
} else {
|
|
819
|
-
console.error(
|
|
1045
|
+
console.error(import_chalk2.default.red(run.error));
|
|
820
1046
|
}
|
|
821
1047
|
process.exit(1);
|
|
1048
|
+
} finally {
|
|
1049
|
+
await releaseResearchAutoLock(lockPath);
|
|
822
1050
|
}
|
|
823
1051
|
};
|
|
824
|
-
if (require.main === module) {
|
|
825
|
-
main().catch((error) => {
|
|
826
|
-
console.error(import_chalk.default.red(error?.message || String(error)));
|
|
827
|
-
process.exit(1);
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
1052
|
// Annotate the CommonJS export names for ESM import in node:
|
|
831
1053
|
0 && (module.exports = {
|
|
1054
|
+
acquireResearchAutoLock,
|
|
1055
|
+
buildNodeOptions,
|
|
832
1056
|
buildTelegramReport,
|
|
833
1057
|
createEmptySteps,
|
|
834
1058
|
escapeHtml,
|
|
835
1059
|
formatMskDateTime,
|
|
836
1060
|
getBacktestResultsPrefix,
|
|
1061
|
+
getResearchAutoLockPath,
|
|
1062
|
+
isProcessAlive,
|
|
837
1063
|
listMergedFiles,
|
|
838
1064
|
listRuntimeStrategyNames,
|
|
1065
|
+
main,
|
|
839
1066
|
parseJsonOutput,
|
|
1067
|
+
releaseResearchAutoLock,
|
|
1068
|
+
resolveHeapMbFromEnv,
|
|
840
1069
|
resolveStrategyNameByConfigKey,
|
|
841
1070
|
resolveTarget,
|
|
842
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
|
});
|