@backtest-kit/cli 9.8.3 → 10.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/README.md +1854 -1854
- package/build/index.cjs +386 -238
- package/build/index.mjs +388 -240
- package/config/notification.config.mjs +13 -13
- package/config/symbol.config.mjs +460 -460
- package/docker/.env.example +2 -2
- package/docker/content/feb_2026/feb_2026.strategy.ts +11 -11
- package/docker/content/feb_2026/modules/backtest.module.ts +83 -83
- package/docker/docker-compose.yaml +46 -46
- package/docker/package.json +38 -38
- package/docker/tsconfig.json +36 -36
- package/package.json +126 -126
- package/template/average-buy.mustache +22 -22
- package/template/breakeven.mustache +21 -21
- package/template/cancel-scheduled.mustache +14 -14
- package/template/cancelled.mustache +21 -21
- package/template/close-pending.mustache +16 -16
- package/template/closed.mustache +23 -23
- package/template/opened.mustache +22 -22
- package/template/partial-loss.mustache +22 -22
- package/template/partial-profit.mustache +22 -22
- package/template/project/config/symbol.config.ts +460 -460
- package/template/project/package.mustache +28 -28
- package/template/risk.mustache +19 -19
- package/template/scheduled.mustache +22 -22
- package/template/signal-close.mustache +22 -22
- package/template/signal-info.mustache +20 -20
- package/template/signal-open.mustache +22 -22
- package/template/source/CLAUDE.md +160 -160
- package/template/trailing-stop.mustache +21 -21
- package/template/trailing-take.mustache +21 -21
- package/types.d.ts +0 -1
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { setConfig, Notification, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, listenSignalNotify, Exchange } from 'backtest-kit';
|
|
4
|
-
import { getErrorMessage, errorData, singleshot, str, compose, BehaviorSubject, createAwaiter, execpool, queued,
|
|
3
|
+
import { setConfig, Notification, Cron, Recent, Storage, Markdown, Report, Dump, State, Memory, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, PersistStateAdapter, PersistSessionAdapter, Log, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, RecentLive, RecentBacktest, Broker, SessionLive, SessionBacktest, MemoryLive, MemoryBacktest, StateLive, StateBacktest, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, System, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, listenSignalNotify, Exchange } from 'backtest-kit';
|
|
4
|
+
import { getErrorMessage, errorData, singleshot, str, compose, BehaviorSubject, sleep, createAwaiter, execpool, queued, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize, isObject } from 'functools-kit';
|
|
5
5
|
import fs, { constants, realpathSync } from 'fs';
|
|
6
6
|
import * as stackTrace from 'stack-trace';
|
|
7
7
|
import path, { join, resolve, dirname, basename, extname } from 'path';
|
|
@@ -11,6 +11,7 @@ import { createActivator } from 'di-kit';
|
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import ccxt from 'ccxt';
|
|
13
13
|
import { parseArgs } from 'util';
|
|
14
|
+
import { exec, spawn } from 'child_process';
|
|
14
15
|
import * as BacktestKitUi from '@backtest-kit/ui';
|
|
15
16
|
import { lib, serve } from '@backtest-kit/ui';
|
|
16
17
|
import QuickChart from 'quickchart-js';
|
|
@@ -31,7 +32,6 @@ import * as BacktestKitPinets from '@backtest-kit/pinets';
|
|
|
31
32
|
import { run as run$1, Code, toMarkdown } from '@backtest-kit/pinets';
|
|
32
33
|
import * as BacktestKitSignals from '@backtest-kit/signals';
|
|
33
34
|
import open from 'open';
|
|
34
|
-
import { spawn } from 'child_process';
|
|
35
35
|
|
|
36
36
|
setConfig({
|
|
37
37
|
CC_MAX_NOTIFICATIONS: 5000,
|
|
@@ -254,6 +254,9 @@ class SetupUtils {
|
|
|
254
254
|
const config = cli.configService.getNotificationConfig();
|
|
255
255
|
Notification.enable(config);
|
|
256
256
|
}
|
|
257
|
+
{
|
|
258
|
+
Cron.enable();
|
|
259
|
+
}
|
|
257
260
|
{
|
|
258
261
|
Recent.enable();
|
|
259
262
|
Storage.enable();
|
|
@@ -275,6 +278,9 @@ class SetupUtils {
|
|
|
275
278
|
return;
|
|
276
279
|
}
|
|
277
280
|
this.enable.clear();
|
|
281
|
+
{
|
|
282
|
+
Cron.disable();
|
|
283
|
+
}
|
|
278
284
|
{
|
|
279
285
|
Recent.disable();
|
|
280
286
|
Storage.disable();
|
|
@@ -831,6 +837,129 @@ const notifyVerbose = singleshot(() => {
|
|
|
831
837
|
|
|
832
838
|
const entrySubject = new BehaviorSubject();
|
|
833
839
|
|
|
840
|
+
function killPid(pid, signal) {
|
|
841
|
+
try {
|
|
842
|
+
process.kill(pid, signal);
|
|
843
|
+
}
|
|
844
|
+
catch (err) {
|
|
845
|
+
if (err.code !== "ESRCH")
|
|
846
|
+
throw err;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function killAll(tree, signal, callback) {
|
|
850
|
+
const killed = {};
|
|
851
|
+
try {
|
|
852
|
+
for (const pid of Object.keys(tree).map(Number)) {
|
|
853
|
+
for (const child of tree[pid]) {
|
|
854
|
+
if (!killed[child]) {
|
|
855
|
+
killPid(child, signal);
|
|
856
|
+
killed[child] = true;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (!killed[pid]) {
|
|
860
|
+
killPid(pid, signal);
|
|
861
|
+
killed[pid] = true;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
catch (err) {
|
|
866
|
+
if (callback)
|
|
867
|
+
return callback(err);
|
|
868
|
+
throw err;
|
|
869
|
+
}
|
|
870
|
+
callback?.();
|
|
871
|
+
}
|
|
872
|
+
function buildProcessTree(parentPid, tree, pidsToProcess, spawnList, cb) {
|
|
873
|
+
const ps = spawnList(parentPid);
|
|
874
|
+
let allData = "";
|
|
875
|
+
ps.stdout.on("data", (data) => {
|
|
876
|
+
allData += data.toString("ascii");
|
|
877
|
+
});
|
|
878
|
+
ps.on("close", (code) => {
|
|
879
|
+
delete pidsToProcess[parentPid];
|
|
880
|
+
if (code !== 0) {
|
|
881
|
+
if (Object.keys(pidsToProcess).length === 0)
|
|
882
|
+
cb();
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const matches = allData.match(/\d+/g);
|
|
886
|
+
if (matches) {
|
|
887
|
+
for (const pidStr of matches) {
|
|
888
|
+
const pid = parseInt(pidStr, 10);
|
|
889
|
+
tree[parentPid].push(pid);
|
|
890
|
+
tree[pid] = [];
|
|
891
|
+
pidsToProcess[pid] = 1;
|
|
892
|
+
buildProcessTree(pid, tree, pidsToProcess, spawnList, cb);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
function treeKill(pid, signalOrCallback, callback) {
|
|
898
|
+
let signal;
|
|
899
|
+
if (typeof signalOrCallback === "function") {
|
|
900
|
+
callback = signalOrCallback;
|
|
901
|
+
signal = undefined;
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
signal = signalOrCallback;
|
|
905
|
+
}
|
|
906
|
+
pid = parseInt(pid, 10);
|
|
907
|
+
if (Number.isNaN(pid)) {
|
|
908
|
+
const err = new Error("pid must be a number");
|
|
909
|
+
if (callback)
|
|
910
|
+
return callback(err);
|
|
911
|
+
throw err;
|
|
912
|
+
}
|
|
913
|
+
const tree = { [pid]: [] };
|
|
914
|
+
const pidsToProcess = { [pid]: 1 };
|
|
915
|
+
switch (process.platform) {
|
|
916
|
+
case "win32":
|
|
917
|
+
exec(`taskkill /pid ${pid} /T /F`, (err) => callback?.(err ?? undefined));
|
|
918
|
+
break;
|
|
919
|
+
case "darwin":
|
|
920
|
+
buildProcessTree(pid, tree, pidsToProcess, (parentPid) => spawn("pgrep", ["-P", String(parentPid)]), () => killAll(tree, signal, callback));
|
|
921
|
+
break;
|
|
922
|
+
default: // Linux
|
|
923
|
+
buildProcessTree(pid, tree, pidsToProcess, (parentPid) => spawn("ps", ["-o", "pid", "--no-headers", "--ppid", String(parentPid)]), () => killAll(tree, signal, callback));
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const DRAIN_MAX_AWAIT = 250;
|
|
929
|
+
const drainStream = (stream) => new Promise((resolve) => {
|
|
930
|
+
if (stream.writableLength === 0) {
|
|
931
|
+
stream.write("", () => resolve());
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
stream.once("drain", () => {
|
|
935
|
+
stream.write("", () => resolve());
|
|
936
|
+
});
|
|
937
|
+
});
|
|
938
|
+
const flushStream = (stream) => {
|
|
939
|
+
const handle = stream._handle;
|
|
940
|
+
if (handle && typeof handle.setBlocking === "function") {
|
|
941
|
+
handle.setBlocking(true);
|
|
942
|
+
return new Promise((resolve) => stream.write("", () => resolve()));
|
|
943
|
+
}
|
|
944
|
+
return drainStream(stream);
|
|
945
|
+
};
|
|
946
|
+
const kill = singleshot(async (code = -1) => {
|
|
947
|
+
await Promise.race([
|
|
948
|
+
Promise.all([
|
|
949
|
+
flushStream(process.stdout),
|
|
950
|
+
flushStream(process.stderr)
|
|
951
|
+
]),
|
|
952
|
+
sleep(DRAIN_MAX_AWAIT),
|
|
953
|
+
]);
|
|
954
|
+
treeKill(process.pid, "SIGKILL", () => {
|
|
955
|
+
process.exit(code);
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
const notifyKill = singleshot(() => {
|
|
959
|
+
console.log("Press Ctrl+C again to force quit.");
|
|
960
|
+
process.on("SIGINT", () => kill());
|
|
961
|
+
});
|
|
962
|
+
|
|
834
963
|
const DEFAULT_CACHE_LIST$1 = ["1m", "15m", "30m", "1h", "4h"];
|
|
835
964
|
const GET_CACHE_INTERVAL_LIST_FN$1 = () => {
|
|
836
965
|
const { values } = getArgs();
|
|
@@ -858,6 +987,10 @@ class BacktestMainService {
|
|
|
858
987
|
this.loggerService.log("backtestMainService run", {
|
|
859
988
|
payload,
|
|
860
989
|
});
|
|
990
|
+
{
|
|
991
|
+
const cwd = process.cwd();
|
|
992
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
993
|
+
}
|
|
861
994
|
await this.configConnectionService.loadConfig("setup.config");
|
|
862
995
|
{
|
|
863
996
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -871,7 +1004,8 @@ class BacktestMainService {
|
|
|
871
1004
|
}
|
|
872
1005
|
catch (error) {
|
|
873
1006
|
console.error("Module loader failed", error);
|
|
874
|
-
|
|
1007
|
+
kill(-1);
|
|
1008
|
+
return;
|
|
875
1009
|
}
|
|
876
1010
|
}
|
|
877
1011
|
{
|
|
@@ -882,10 +1016,6 @@ class BacktestMainService {
|
|
|
882
1016
|
this.frontendProviderService.connect();
|
|
883
1017
|
this.telegramProviderService.connect();
|
|
884
1018
|
}
|
|
885
|
-
{
|
|
886
|
-
const cwd = process.cwd();
|
|
887
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
888
|
-
}
|
|
889
1019
|
let absolutePath;
|
|
890
1020
|
{
|
|
891
1021
|
absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
|
|
@@ -994,6 +1124,10 @@ class WalkerMainService {
|
|
|
994
1124
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
995
1125
|
this.run = singleshot(async (payload) => {
|
|
996
1126
|
this.loggerService.log("walkerMainService run", { payload });
|
|
1127
|
+
{
|
|
1128
|
+
const cwd = process.cwd();
|
|
1129
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1130
|
+
}
|
|
997
1131
|
await this.configConnectionService.loadConfig("setup.config");
|
|
998
1132
|
{
|
|
999
1133
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -1007,7 +1141,8 @@ class WalkerMainService {
|
|
|
1007
1141
|
}
|
|
1008
1142
|
catch (error) {
|
|
1009
1143
|
console.error("Module loader failed", error);
|
|
1010
|
-
|
|
1144
|
+
kill(-1);
|
|
1145
|
+
return;
|
|
1011
1146
|
}
|
|
1012
1147
|
}
|
|
1013
1148
|
{
|
|
@@ -1043,10 +1178,6 @@ class WalkerMainService {
|
|
|
1043
1178
|
Cache.resetCounter();
|
|
1044
1179
|
Interval.resetCounter();
|
|
1045
1180
|
}
|
|
1046
|
-
{
|
|
1047
|
-
const cwd = process.cwd();
|
|
1048
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1049
|
-
}
|
|
1050
1181
|
await this.moduleConnectionService.loadModule("walker.module");
|
|
1051
1182
|
{
|
|
1052
1183
|
this.exchangeSchemaService.addSchema();
|
|
@@ -1175,7 +1306,7 @@ class WalkerMainService {
|
|
|
1175
1306
|
await mkdir(dumpDir, { recursive: true });
|
|
1176
1307
|
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
1177
1308
|
console.log(`Saved: ${filePath}`);
|
|
1178
|
-
|
|
1309
|
+
kill(0);
|
|
1179
1310
|
return;
|
|
1180
1311
|
}
|
|
1181
1312
|
if (payload.markdown) {
|
|
@@ -1184,12 +1315,13 @@ class WalkerMainService {
|
|
|
1184
1315
|
await mkdir(dumpDir, { recursive: true });
|
|
1185
1316
|
await writeFile(filePath, report, "utf-8");
|
|
1186
1317
|
console.log(`Saved: ${filePath}`);
|
|
1187
|
-
|
|
1318
|
+
kill(0);
|
|
1188
1319
|
return;
|
|
1189
1320
|
}
|
|
1190
1321
|
const report = await Walker.getReport(symbol, { walkerName: WalkerName$1.DefaultWalker });
|
|
1191
1322
|
console.log(report);
|
|
1192
|
-
|
|
1323
|
+
kill(0);
|
|
1324
|
+
return;
|
|
1193
1325
|
});
|
|
1194
1326
|
this.connect = singleshot(async () => {
|
|
1195
1327
|
this.loggerService.log("walkerMainService connect");
|
|
@@ -1234,6 +1366,10 @@ class LiveMainService {
|
|
|
1234
1366
|
this.loggerService.log("liveMainService run", {
|
|
1235
1367
|
payload,
|
|
1236
1368
|
});
|
|
1369
|
+
{
|
|
1370
|
+
const cwd = process.cwd();
|
|
1371
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1372
|
+
}
|
|
1237
1373
|
await this.configConnectionService.loadConfig("setup.config");
|
|
1238
1374
|
{
|
|
1239
1375
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -1247,7 +1383,8 @@ class LiveMainService {
|
|
|
1247
1383
|
}
|
|
1248
1384
|
catch (error) {
|
|
1249
1385
|
console.error("Module loader failed", error);
|
|
1250
|
-
|
|
1386
|
+
kill(-1);
|
|
1387
|
+
return;
|
|
1251
1388
|
}
|
|
1252
1389
|
}
|
|
1253
1390
|
{
|
|
@@ -1258,10 +1395,6 @@ class LiveMainService {
|
|
|
1258
1395
|
this.frontendProviderService.connect();
|
|
1259
1396
|
this.telegramProviderService.connect();
|
|
1260
1397
|
}
|
|
1261
|
-
{
|
|
1262
|
-
const cwd = process.cwd();
|
|
1263
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1264
|
-
}
|
|
1265
1398
|
let absolutePath;
|
|
1266
1399
|
{
|
|
1267
1400
|
absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
|
|
@@ -1337,6 +1470,10 @@ class PaperMainService {
|
|
|
1337
1470
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
1338
1471
|
this.run = singleshot(async (payload) => {
|
|
1339
1472
|
this.loggerService.log("paperMainService init");
|
|
1473
|
+
{
|
|
1474
|
+
const cwd = process.cwd();
|
|
1475
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1476
|
+
}
|
|
1340
1477
|
await this.configConnectionService.loadConfig("setup.config");
|
|
1341
1478
|
{
|
|
1342
1479
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -1350,7 +1487,8 @@ class PaperMainService {
|
|
|
1350
1487
|
}
|
|
1351
1488
|
catch (error) {
|
|
1352
1489
|
console.error("Module loader failed", error);
|
|
1353
|
-
|
|
1490
|
+
kill(-1);
|
|
1491
|
+
return;
|
|
1354
1492
|
}
|
|
1355
1493
|
}
|
|
1356
1494
|
{
|
|
@@ -1361,10 +1499,6 @@ class PaperMainService {
|
|
|
1361
1499
|
this.frontendProviderService.connect();
|
|
1362
1500
|
this.telegramProviderService.connect();
|
|
1363
1501
|
}
|
|
1364
|
-
{
|
|
1365
|
-
const cwd = process.cwd();
|
|
1366
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1367
|
-
}
|
|
1368
1502
|
let absolutePath;
|
|
1369
1503
|
{
|
|
1370
1504
|
absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
|
|
@@ -1832,7 +1966,7 @@ class TelegramApiService {
|
|
|
1832
1966
|
console.error("Telegram publish failure", {
|
|
1833
1967
|
error,
|
|
1834
1968
|
});
|
|
1835
|
-
setTimeout(() =>
|
|
1969
|
+
setTimeout(() => kill(-1), 5000);
|
|
1836
1970
|
});
|
|
1837
1971
|
const result = await Promise.race([
|
|
1838
1972
|
waitForResult,
|
|
@@ -1846,7 +1980,7 @@ class TelegramApiService {
|
|
|
1846
1980
|
return "Message scheduled for publication";
|
|
1847
1981
|
}
|
|
1848
1982
|
if (TIMEOUT_COUNTER > MAX_TIMEOUT_COUNT) {
|
|
1849
|
-
setTimeout(() =>
|
|
1983
|
+
setTimeout(() => kill(-1), 5000);
|
|
1850
1984
|
}
|
|
1851
1985
|
return "Message published successfully";
|
|
1852
1986
|
};
|
|
@@ -2559,7 +2693,8 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
|
|
|
2559
2693
|
}
|
|
2560
2694
|
catch {
|
|
2561
2695
|
console.warn(`Module module import failed filePath=${filePath} baseDir=${baseDir}`);
|
|
2562
|
-
|
|
2696
|
+
kill(-1);
|
|
2697
|
+
return false;
|
|
2563
2698
|
}
|
|
2564
2699
|
}
|
|
2565
2700
|
return false;
|
|
@@ -2679,7 +2814,8 @@ const TRANSPILE_FN = memoize(([path]) => `${path}`, (path, code, self, require)
|
|
|
2679
2814
|
}
|
|
2680
2815
|
catch (error) {
|
|
2681
2816
|
console.log(`Error during transpilation error=\`${getErrorMessage(error)}\` path=\`${path}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
2682
|
-
|
|
2817
|
+
kill(-1);
|
|
2818
|
+
return;
|
|
2683
2819
|
}
|
|
2684
2820
|
return {
|
|
2685
2821
|
require,
|
|
@@ -2939,7 +3075,7 @@ const GET_ALIAS_EXPORTS_FN = (self) => {
|
|
|
2939
3075
|
}
|
|
2940
3076
|
return null;
|
|
2941
3077
|
};
|
|
2942
|
-
const INIT_ALIAS_FN = (self) => {
|
|
3078
|
+
const INIT_ALIAS_FN = singleshot((self) => {
|
|
2943
3079
|
const alias = GET_ALIAS_EXPORTS_FN(self);
|
|
2944
3080
|
if (!alias) {
|
|
2945
3081
|
return;
|
|
@@ -2951,18 +3087,21 @@ const INIT_ALIAS_FN = (self) => {
|
|
|
2951
3087
|
Object.entries(alias).forEach(([name, module]) => overrideModule(name, module));
|
|
2952
3088
|
Object.assign(IMPORT_ALIAS, alias);
|
|
2953
3089
|
}
|
|
2954
|
-
};
|
|
3090
|
+
});
|
|
2955
3091
|
class LoaderService {
|
|
2956
3092
|
constructor() {
|
|
2957
3093
|
this.babelService = inject(TYPES.babelService);
|
|
2958
3094
|
this.loggerService = inject(TYPES.loggerService);
|
|
2959
3095
|
this.resolveService = inject(TYPES.resolveService);
|
|
2960
|
-
this.getInstance = memoize(([basePath]) => `${basePath}`, (basePath) =>
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
3096
|
+
this.getInstance = memoize(([basePath]) => `${basePath}`, (basePath) => {
|
|
3097
|
+
INIT_ALIAS_FN(this);
|
|
3098
|
+
return new ClientLoader({
|
|
3099
|
+
babel: this.babelService,
|
|
3100
|
+
logger: this.loggerService,
|
|
3101
|
+
resolve: this.resolveService,
|
|
3102
|
+
path: basePath,
|
|
3103
|
+
});
|
|
3104
|
+
});
|
|
2966
3105
|
this.import = (filePath, basePath = process.cwd()) => {
|
|
2967
3106
|
this.loggerService.log("loaderService import", {
|
|
2968
3107
|
filePath,
|
|
@@ -2979,10 +3118,6 @@ class LoaderService {
|
|
|
2979
3118
|
const instance = this.getInstance(basePath);
|
|
2980
3119
|
return instance.check(filePath);
|
|
2981
3120
|
};
|
|
2982
|
-
this.init = singleshot(() => {
|
|
2983
|
-
this.loggerService.log("loaderService init");
|
|
2984
|
-
INIT_ALIAS_FN(this);
|
|
2985
|
-
});
|
|
2986
3121
|
}
|
|
2987
3122
|
}
|
|
2988
3123
|
|
|
@@ -3011,7 +3146,8 @@ const LOAD_CONFIG_CONFIG_FN = async (fileName, self) => {
|
|
|
3011
3146
|
}
|
|
3012
3147
|
catch {
|
|
3013
3148
|
console.warn(`Module module import failed filePath=${filePath} baseDir=${baseDir}`);
|
|
3014
|
-
|
|
3149
|
+
kill(-1);
|
|
3150
|
+
return;
|
|
3015
3151
|
}
|
|
3016
3152
|
}
|
|
3017
3153
|
return null;
|
|
@@ -3183,10 +3319,10 @@ init();
|
|
|
3183
3319
|
|
|
3184
3320
|
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3185
3321
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3186
|
-
const HELP_TEXT$1 = `
|
|
3187
|
-
Example:
|
|
3188
|
-
|
|
3189
|
-
node ${ENTRY_PATH$1} --help
|
|
3322
|
+
const HELP_TEXT$1 = `
|
|
3323
|
+
Example:
|
|
3324
|
+
|
|
3325
|
+
node ${ENTRY_PATH$1} --help
|
|
3190
3326
|
`.trimStart();
|
|
3191
3327
|
const main$g = async () => {
|
|
3192
3328
|
if (!getEntry(import.meta.url)) {
|
|
@@ -3196,7 +3332,7 @@ const main$g = async () => {
|
|
|
3196
3332
|
if (MODES.some((mode) => values[mode])) {
|
|
3197
3333
|
return;
|
|
3198
3334
|
}
|
|
3199
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
3335
|
+
process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
|
|
3200
3336
|
process.stdout.write("\n");
|
|
3201
3337
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3202
3338
|
process.stdout.write("\n");
|
|
@@ -3206,7 +3342,7 @@ const main$g = async () => {
|
|
|
3206
3342
|
main$g();
|
|
3207
3343
|
|
|
3208
3344
|
const notifyShutdown = singleshot(async () => {
|
|
3209
|
-
console.log("Graceful shutdown initiated.
|
|
3345
|
+
console.log("Graceful shutdown initiated.");
|
|
3210
3346
|
});
|
|
3211
3347
|
|
|
3212
3348
|
const FLUSH_DIRS = ["report", "log", "markdown", "agent"];
|
|
@@ -3245,6 +3381,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
3245
3381
|
return;
|
|
3246
3382
|
}
|
|
3247
3383
|
notifyShutdown();
|
|
3384
|
+
notifyKill();
|
|
3248
3385
|
const { exchangeName, frameName, strategyName, symbol, status } = running;
|
|
3249
3386
|
if (status === "fulfilled") {
|
|
3250
3387
|
return;
|
|
@@ -3285,6 +3422,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
3285
3422
|
return;
|
|
3286
3423
|
}
|
|
3287
3424
|
notifyShutdown();
|
|
3425
|
+
notifyKill();
|
|
3288
3426
|
const { walkerName, symbol, status } = running;
|
|
3289
3427
|
if (status === "fulfilled") {
|
|
3290
3428
|
return;
|
|
@@ -3322,6 +3460,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
3322
3460
|
return;
|
|
3323
3461
|
}
|
|
3324
3462
|
notifyShutdown();
|
|
3463
|
+
notifyKill();
|
|
3325
3464
|
const { exchangeName, strategyName, symbol, status } = running;
|
|
3326
3465
|
if (status === "fulfilled") {
|
|
3327
3466
|
return;
|
|
@@ -3357,6 +3496,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
3357
3496
|
return;
|
|
3358
3497
|
}
|
|
3359
3498
|
notifyShutdown();
|
|
3499
|
+
notifyKill();
|
|
3360
3500
|
const { exchangeName, strategyName, symbol, status } = running;
|
|
3361
3501
|
if (status === "fulfilled") {
|
|
3362
3502
|
return;
|
|
@@ -3457,6 +3597,7 @@ const createGracefulShutdown = (mode) => {
|
|
|
3457
3597
|
const handler = singleshot(async () => {
|
|
3458
3598
|
process.off("SIGINT", handler);
|
|
3459
3599
|
notifyShutdown();
|
|
3600
|
+
notifyKill();
|
|
3460
3601
|
await stop();
|
|
3461
3602
|
});
|
|
3462
3603
|
return singleshot(() => {
|
|
@@ -3474,13 +3615,17 @@ const main$a = async () => {
|
|
|
3474
3615
|
const mode = resolveMode(values);
|
|
3475
3616
|
if (!mode) {
|
|
3476
3617
|
console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
|
|
3477
|
-
|
|
3618
|
+
kill(1);
|
|
3478
3619
|
return;
|
|
3479
3620
|
}
|
|
3480
3621
|
const entryPoints = getPositionals();
|
|
3481
3622
|
if (!entryPoints.length) {
|
|
3482
3623
|
throw new Error("At least one entry point is required");
|
|
3483
3624
|
}
|
|
3625
|
+
{
|
|
3626
|
+
const cwd = process.cwd();
|
|
3627
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3628
|
+
}
|
|
3484
3629
|
await cli.configConnectionService.loadConfig("setup.config");
|
|
3485
3630
|
{
|
|
3486
3631
|
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
@@ -3494,7 +3639,8 @@ const main$a = async () => {
|
|
|
3494
3639
|
}
|
|
3495
3640
|
catch (error) {
|
|
3496
3641
|
console.error("Module loader failed", error);
|
|
3497
|
-
|
|
3642
|
+
kill(-1);
|
|
3643
|
+
return;
|
|
3498
3644
|
}
|
|
3499
3645
|
}
|
|
3500
3646
|
{
|
|
@@ -3504,9 +3650,6 @@ const main$a = async () => {
|
|
|
3504
3650
|
cli.frontendProviderService.connect();
|
|
3505
3651
|
cli.telegramProviderService.connect();
|
|
3506
3652
|
const cwd = process.cwd();
|
|
3507
|
-
{
|
|
3508
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3509
|
-
}
|
|
3510
3653
|
if (entryPoints.length === 1) {
|
|
3511
3654
|
const absolutePath = path.resolve(entryPoints[0]);
|
|
3512
3655
|
const moduleRoot = path.dirname(absolutePath);
|
|
@@ -3536,6 +3679,7 @@ main$a();
|
|
|
3536
3679
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
3537
3680
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
3538
3681
|
notifyShutdown();
|
|
3682
|
+
notifyKill();
|
|
3539
3683
|
cli.frontendProviderService.disable();
|
|
3540
3684
|
});
|
|
3541
3685
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
@@ -3556,6 +3700,7 @@ main$9();
|
|
|
3556
3700
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
3557
3701
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
3558
3702
|
notifyShutdown();
|
|
3703
|
+
notifyKill();
|
|
3559
3704
|
cli.telegramProviderService.disable();
|
|
3560
3705
|
});
|
|
3561
3706
|
const listenGracefulShutdown = singleshot(() => {
|
|
@@ -3705,7 +3850,12 @@ const main$6 = async () => {
|
|
|
3705
3850
|
}
|
|
3706
3851
|
if (values.pine) {
|
|
3707
3852
|
console.warn("--editor and --pine are mutually exclusive. Use one at a time.");
|
|
3708
|
-
|
|
3853
|
+
kill(1);
|
|
3854
|
+
return;
|
|
3855
|
+
}
|
|
3856
|
+
{
|
|
3857
|
+
const cwd = process.cwd();
|
|
3858
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3709
3859
|
}
|
|
3710
3860
|
await cli.configConnectionService.loadConfig("setup.config");
|
|
3711
3861
|
{
|
|
@@ -3720,17 +3870,14 @@ const main$6 = async () => {
|
|
|
3720
3870
|
}
|
|
3721
3871
|
catch (error) {
|
|
3722
3872
|
console.error("Module loader failed", error);
|
|
3723
|
-
|
|
3873
|
+
kill(-1);
|
|
3874
|
+
return;
|
|
3724
3875
|
}
|
|
3725
3876
|
}
|
|
3726
3877
|
{
|
|
3727
3878
|
await cli.configService.waitForInit();
|
|
3728
3879
|
Setup.enable();
|
|
3729
3880
|
}
|
|
3730
|
-
{
|
|
3731
|
-
const cwd = process.cwd();
|
|
3732
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3733
|
-
}
|
|
3734
3881
|
await cli.moduleConnectionService.loadModule("editor.module");
|
|
3735
3882
|
{
|
|
3736
3883
|
await cli.exchangeSchemaService.addSchema();
|
|
@@ -3750,7 +3897,8 @@ const main$6 = async () => {
|
|
|
3750
3897
|
const beforeExit = () => {
|
|
3751
3898
|
process.off("SIGINT", beforeExit);
|
|
3752
3899
|
unServer();
|
|
3753
|
-
|
|
3900
|
+
kill(0);
|
|
3901
|
+
return;
|
|
3754
3902
|
};
|
|
3755
3903
|
process.on("SIGINT", beforeExit);
|
|
3756
3904
|
};
|
|
@@ -4135,183 +4283,183 @@ const main$2 = async () => {
|
|
|
4135
4283
|
main$2();
|
|
4136
4284
|
|
|
4137
4285
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
4138
|
-
const HELP_TEXT = `
|
|
4139
|
-
Usage:
|
|
4140
|
-
node index.mjs --<mode> [flags] [entry-point]
|
|
4141
|
-
|
|
4142
|
-
Modes:
|
|
4143
|
-
|
|
4144
|
-
--backtest <entry> Run strategy against historical candle data
|
|
4145
|
-
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4146
|
-
--paper <entry> Paper trading (live prices, no real orders)
|
|
4147
|
-
--live <entry> Live trading with real orders
|
|
4148
|
-
--pine <entry> Execute a local .pine indicator file
|
|
4149
|
-
--editor Open the Pine Script visual editor in the browser
|
|
4150
|
-
--dump Fetch and save raw OHLCV candles
|
|
4151
|
-
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4152
|
-
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4153
|
-
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4154
|
-
--init Scaffold a new project in the current directory
|
|
4155
|
-
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4156
|
-
--help Print this help message
|
|
4157
|
-
|
|
4158
|
-
Backtest flags:
|
|
4159
|
-
|
|
4160
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4161
|
-
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4162
|
-
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4163
|
-
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4164
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4165
|
-
--noCache Skip candle cache warming before the run
|
|
4166
|
-
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4167
|
-
--verbose Log every candle fetch to stdout
|
|
4168
|
-
--ui Start web dashboard at http://localhost:60050
|
|
4169
|
-
--telegram Send trade notifications to Telegram
|
|
4170
|
-
|
|
4171
|
-
Walker flags (--walker):
|
|
4172
|
-
|
|
4173
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4174
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4175
|
-
--noCache Skip candle cache warming before the run
|
|
4176
|
-
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4177
|
-
--verbose Log every candle fetch to stdout
|
|
4178
|
-
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4179
|
-
--json Save results as JSON to ./dump/<output>.json
|
|
4180
|
-
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4181
|
-
|
|
4182
|
-
Each positional argument is a strategy entry point. All strategy files are loaded without
|
|
4183
|
-
changing process.cwd() — .env is read from the working directory only.
|
|
4184
|
-
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
4185
|
-
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
4186
|
-
|
|
4187
|
-
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
4188
|
-
|
|
4189
|
-
Paper / Live flags:
|
|
4190
|
-
|
|
4191
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4192
|
-
--strategy <string> Strategy name (default: first registered)
|
|
4193
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4194
|
-
--verbose Log every candle fetch to stdout
|
|
4195
|
-
--ui Start web dashboard
|
|
4196
|
-
--telegram Send Telegram notifications
|
|
4197
|
-
|
|
4198
|
-
PineScript flags (--pine):
|
|
4199
|
-
|
|
4200
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4201
|
-
--timeframe <string> Candle interval (default: 15m)
|
|
4202
|
-
--limit <string> Number of candles to fetch (default: 250)
|
|
4203
|
-
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4204
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4205
|
-
--output <string> Output file base name without extension
|
|
4206
|
-
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
4207
|
-
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
4208
|
-
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
4209
|
-
|
|
4210
|
-
Only plot() calls with display=display.data_window produce output columns.
|
|
4211
|
-
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
4212
|
-
|
|
4213
|
-
Candle dump flags (--dump):
|
|
4214
|
-
|
|
4215
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4216
|
-
--timeframe <string> Candle interval (default: 15m)
|
|
4217
|
-
--limit <string> Number of candles (default: 250)
|
|
4218
|
-
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4219
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4220
|
-
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
4221
|
-
--json Save as JSON array to ./dump/<output>.json
|
|
4222
|
-
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4223
|
-
|
|
4224
|
-
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
4225
|
-
|
|
4226
|
-
PnL debug flags (--pnldebug):
|
|
4227
|
-
|
|
4228
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4229
|
-
--priceopen <number> Entry price (required)
|
|
4230
|
-
--direction <string> Position direction: long or short (default: long)
|
|
4231
|
-
--when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
|
|
4232
|
-
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
4233
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4234
|
-
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
4235
|
-
--json Save as JSON array to ./dump/<output>.json
|
|
4236
|
-
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4237
|
-
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4238
|
-
|
|
4239
|
-
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
4240
|
-
|
|
4241
|
-
Broker debug flags (--brokerdebug):
|
|
4242
|
-
|
|
4243
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4244
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4245
|
-
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
4246
|
-
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
4247
|
-
(default: signal-open)
|
|
4248
|
-
|
|
4249
|
-
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4250
|
-
the selected broker commit with synthetic payload values derived from current price.
|
|
4251
|
-
|
|
4252
|
-
Flush flags (--flush):
|
|
4253
|
-
|
|
4254
|
-
One or more positional entry points. For each entry point the following
|
|
4255
|
-
subdirectories are removed from <entry-dir>/dump/:
|
|
4256
|
-
|
|
4257
|
-
report log markdown agent
|
|
4258
|
-
|
|
4259
|
-
Init flags (--init):
|
|
4260
|
-
|
|
4261
|
-
--output <string> Target directory name (default: backtest-kit-project)
|
|
4262
|
-
|
|
4263
|
-
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
4264
|
-
|
|
4265
|
-
Docker flags (--docker):
|
|
4266
|
-
|
|
4267
|
-
--output <string> Target directory name (default: backtest-kit-docker)
|
|
4268
|
-
|
|
4269
|
-
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
4270
|
-
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
4271
|
-
docker compose up to start the container.
|
|
4272
|
-
|
|
4273
|
-
Module hooks (loaded automatically by each mode):
|
|
4274
|
-
|
|
4275
|
-
modules/backtest.module --backtest Broker adapter for backtest
|
|
4276
|
-
modules/walker.module --walker Broker adapter for walker comparison
|
|
4277
|
-
modules/paper.module --paper Broker adapter for paper trading
|
|
4278
|
-
modules/live.module --live Broker adapter for live trading
|
|
4279
|
-
modules/pine.module --pine Exchange schema for PineScript runs
|
|
4280
|
-
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
4281
|
-
modules/dump.module --dump Exchange schema for candle dumps
|
|
4282
|
-
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4283
|
-
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
4284
|
-
|
|
4285
|
-
--flush has no associated module. It only removes dump subdirectories.
|
|
4286
|
-
|
|
4287
|
-
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
4288
|
-
|
|
4289
|
-
Environment variables:
|
|
4290
|
-
|
|
4291
|
-
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
4292
|
-
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
4293
|
-
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
4294
|
-
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
4295
|
-
|
|
4296
|
-
Examples:
|
|
4297
|
-
|
|
4298
|
-
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
4299
|
-
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
4300
|
-
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
4301
|
-
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
4302
|
-
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
4303
|
-
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
4304
|
-
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
4305
|
-
node ${ENTRY_PATH} --editor
|
|
4306
|
-
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
4307
|
-
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
4308
|
-
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4309
|
-
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4310
|
-
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
4311
|
-
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4312
|
-
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4313
|
-
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
4314
|
-
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
4286
|
+
const HELP_TEXT = `
|
|
4287
|
+
Usage:
|
|
4288
|
+
node index.mjs --<mode> [flags] [entry-point]
|
|
4289
|
+
|
|
4290
|
+
Modes:
|
|
4291
|
+
|
|
4292
|
+
--backtest <entry> Run strategy against historical candle data
|
|
4293
|
+
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4294
|
+
--paper <entry> Paper trading (live prices, no real orders)
|
|
4295
|
+
--live <entry> Live trading with real orders
|
|
4296
|
+
--pine <entry> Execute a local .pine indicator file
|
|
4297
|
+
--editor Open the Pine Script visual editor in the browser
|
|
4298
|
+
--dump Fetch and save raw OHLCV candles
|
|
4299
|
+
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4300
|
+
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4301
|
+
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4302
|
+
--init Scaffold a new project in the current directory
|
|
4303
|
+
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4304
|
+
--help Print this help message
|
|
4305
|
+
|
|
4306
|
+
Backtest flags:
|
|
4307
|
+
|
|
4308
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4309
|
+
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4310
|
+
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4311
|
+
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4312
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4313
|
+
--noCache Skip candle cache warming before the run
|
|
4314
|
+
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4315
|
+
--verbose Log every candle fetch to stdout
|
|
4316
|
+
--ui Start web dashboard at http://localhost:60050
|
|
4317
|
+
--telegram Send trade notifications to Telegram
|
|
4318
|
+
|
|
4319
|
+
Walker flags (--walker):
|
|
4320
|
+
|
|
4321
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4322
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4323
|
+
--noCache Skip candle cache warming before the run
|
|
4324
|
+
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4325
|
+
--verbose Log every candle fetch to stdout
|
|
4326
|
+
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4327
|
+
--json Save results as JSON to ./dump/<output>.json
|
|
4328
|
+
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4329
|
+
|
|
4330
|
+
Each positional argument is a strategy entry point. All strategy files are loaded without
|
|
4331
|
+
changing process.cwd() — .env is read from the working directory only.
|
|
4332
|
+
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
4333
|
+
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
4334
|
+
|
|
4335
|
+
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
4336
|
+
|
|
4337
|
+
Paper / Live flags:
|
|
4338
|
+
|
|
4339
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4340
|
+
--strategy <string> Strategy name (default: first registered)
|
|
4341
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4342
|
+
--verbose Log every candle fetch to stdout
|
|
4343
|
+
--ui Start web dashboard
|
|
4344
|
+
--telegram Send Telegram notifications
|
|
4345
|
+
|
|
4346
|
+
PineScript flags (--pine):
|
|
4347
|
+
|
|
4348
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4349
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4350
|
+
--limit <string> Number of candles to fetch (default: 250)
|
|
4351
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4352
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4353
|
+
--output <string> Output file base name without extension
|
|
4354
|
+
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
4355
|
+
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
4356
|
+
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
4357
|
+
|
|
4358
|
+
Only plot() calls with display=display.data_window produce output columns.
|
|
4359
|
+
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
4360
|
+
|
|
4361
|
+
Candle dump flags (--dump):
|
|
4362
|
+
|
|
4363
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4364
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4365
|
+
--limit <string> Number of candles (default: 250)
|
|
4366
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4367
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4368
|
+
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
4369
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4370
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4371
|
+
|
|
4372
|
+
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
4373
|
+
|
|
4374
|
+
PnL debug flags (--pnldebug):
|
|
4375
|
+
|
|
4376
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4377
|
+
--priceopen <number> Entry price (required)
|
|
4378
|
+
--direction <string> Position direction: long or short (default: long)
|
|
4379
|
+
--when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
|
|
4380
|
+
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
4381
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4382
|
+
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
4383
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4384
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4385
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4386
|
+
|
|
4387
|
+
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
4388
|
+
|
|
4389
|
+
Broker debug flags (--brokerdebug):
|
|
4390
|
+
|
|
4391
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4392
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4393
|
+
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
4394
|
+
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
4395
|
+
(default: signal-open)
|
|
4396
|
+
|
|
4397
|
+
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4398
|
+
the selected broker commit with synthetic payload values derived from current price.
|
|
4399
|
+
|
|
4400
|
+
Flush flags (--flush):
|
|
4401
|
+
|
|
4402
|
+
One or more positional entry points. For each entry point the following
|
|
4403
|
+
subdirectories are removed from <entry-dir>/dump/:
|
|
4404
|
+
|
|
4405
|
+
report log markdown agent
|
|
4406
|
+
|
|
4407
|
+
Init flags (--init):
|
|
4408
|
+
|
|
4409
|
+
--output <string> Target directory name (default: backtest-kit-project)
|
|
4410
|
+
|
|
4411
|
+
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
4412
|
+
|
|
4413
|
+
Docker flags (--docker):
|
|
4414
|
+
|
|
4415
|
+
--output <string> Target directory name (default: backtest-kit-docker)
|
|
4416
|
+
|
|
4417
|
+
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
4418
|
+
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
4419
|
+
docker compose up to start the container.
|
|
4420
|
+
|
|
4421
|
+
Module hooks (loaded automatically by each mode):
|
|
4422
|
+
|
|
4423
|
+
modules/backtest.module --backtest Broker adapter for backtest
|
|
4424
|
+
modules/walker.module --walker Broker adapter for walker comparison
|
|
4425
|
+
modules/paper.module --paper Broker adapter for paper trading
|
|
4426
|
+
modules/live.module --live Broker adapter for live trading
|
|
4427
|
+
modules/pine.module --pine Exchange schema for PineScript runs
|
|
4428
|
+
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
4429
|
+
modules/dump.module --dump Exchange schema for candle dumps
|
|
4430
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4431
|
+
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
4432
|
+
|
|
4433
|
+
--flush has no associated module. It only removes dump subdirectories.
|
|
4434
|
+
|
|
4435
|
+
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
4436
|
+
|
|
4437
|
+
Environment variables:
|
|
4438
|
+
|
|
4439
|
+
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
4440
|
+
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
4441
|
+
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
4442
|
+
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
4443
|
+
|
|
4444
|
+
Examples:
|
|
4445
|
+
|
|
4446
|
+
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
4447
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
4448
|
+
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
4449
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
4450
|
+
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
4451
|
+
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
4452
|
+
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
4453
|
+
node ${ENTRY_PATH} --editor
|
|
4454
|
+
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
4455
|
+
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
4456
|
+
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4457
|
+
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4458
|
+
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
4459
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4460
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4461
|
+
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
4462
|
+
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
4315
4463
|
`.trimStart();
|
|
4316
4464
|
const main$1 = async () => {
|
|
4317
4465
|
if (!getEntry(import.meta.url)) {
|
|
@@ -4321,7 +4469,7 @@ const main$1 = async () => {
|
|
|
4321
4469
|
if (!values.help) {
|
|
4322
4470
|
return;
|
|
4323
4471
|
}
|
|
4324
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4472
|
+
process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n\n`);
|
|
4325
4473
|
process.stdout.write(HELP_TEXT);
|
|
4326
4474
|
process.exit(0);
|
|
4327
4475
|
};
|
|
@@ -4335,7 +4483,7 @@ const main = async () => {
|
|
|
4335
4483
|
if (!values.version) {
|
|
4336
4484
|
return;
|
|
4337
4485
|
}
|
|
4338
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4486
|
+
process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
|
|
4339
4487
|
process.exit(0);
|
|
4340
4488
|
};
|
|
4341
4489
|
main();
|