@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/build/index.cjs CHANGED
@@ -12,6 +12,7 @@ var diKit = require('di-kit');
12
12
  var url = require('url');
13
13
  var ccxt = require('ccxt');
14
14
  var util = require('util');
15
+ var child_process = require('child_process');
15
16
  var BacktestKitUi = require('@backtest-kit/ui');
16
17
  var QuickChart = require('quickchart-js');
17
18
  var telegraf = require('telegraf');
@@ -30,7 +31,6 @@ var BacktestKitOllama = require('@backtest-kit/ollama');
30
31
  var BacktestKitPinets = require('@backtest-kit/pinets');
31
32
  var BacktestKitSignals = require('@backtest-kit/signals');
32
33
  var open = require('open');
33
- var child_process = require('child_process');
34
34
 
35
35
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
36
36
  function _interopNamespaceDefault(e) {
@@ -279,6 +279,9 @@ class SetupUtils {
279
279
  const config = cli.configService.getNotificationConfig();
280
280
  BacktestKit.Notification.enable(config);
281
281
  }
282
+ {
283
+ BacktestKit.Cron.enable();
284
+ }
282
285
  {
283
286
  BacktestKit.Recent.enable();
284
287
  BacktestKit.Storage.enable();
@@ -300,6 +303,9 @@ class SetupUtils {
300
303
  return;
301
304
  }
302
305
  this.enable.clear();
306
+ {
307
+ BacktestKit.Cron.disable();
308
+ }
303
309
  {
304
310
  BacktestKit.Recent.disable();
305
311
  BacktestKit.Storage.disable();
@@ -856,6 +862,129 @@ const notifyVerbose = functoolsKit.singleshot(() => {
856
862
 
857
863
  const entrySubject = new functoolsKit.BehaviorSubject();
858
864
 
865
+ function killPid(pid, signal) {
866
+ try {
867
+ process.kill(pid, signal);
868
+ }
869
+ catch (err) {
870
+ if (err.code !== "ESRCH")
871
+ throw err;
872
+ }
873
+ }
874
+ function killAll(tree, signal, callback) {
875
+ const killed = {};
876
+ try {
877
+ for (const pid of Object.keys(tree).map(Number)) {
878
+ for (const child of tree[pid]) {
879
+ if (!killed[child]) {
880
+ killPid(child, signal);
881
+ killed[child] = true;
882
+ }
883
+ }
884
+ if (!killed[pid]) {
885
+ killPid(pid, signal);
886
+ killed[pid] = true;
887
+ }
888
+ }
889
+ }
890
+ catch (err) {
891
+ if (callback)
892
+ return callback(err);
893
+ throw err;
894
+ }
895
+ callback?.();
896
+ }
897
+ function buildProcessTree(parentPid, tree, pidsToProcess, spawnList, cb) {
898
+ const ps = spawnList(parentPid);
899
+ let allData = "";
900
+ ps.stdout.on("data", (data) => {
901
+ allData += data.toString("ascii");
902
+ });
903
+ ps.on("close", (code) => {
904
+ delete pidsToProcess[parentPid];
905
+ if (code !== 0) {
906
+ if (Object.keys(pidsToProcess).length === 0)
907
+ cb();
908
+ return;
909
+ }
910
+ const matches = allData.match(/\d+/g);
911
+ if (matches) {
912
+ for (const pidStr of matches) {
913
+ const pid = parseInt(pidStr, 10);
914
+ tree[parentPid].push(pid);
915
+ tree[pid] = [];
916
+ pidsToProcess[pid] = 1;
917
+ buildProcessTree(pid, tree, pidsToProcess, spawnList, cb);
918
+ }
919
+ }
920
+ });
921
+ }
922
+ function treeKill(pid, signalOrCallback, callback) {
923
+ let signal;
924
+ if (typeof signalOrCallback === "function") {
925
+ callback = signalOrCallback;
926
+ signal = undefined;
927
+ }
928
+ else {
929
+ signal = signalOrCallback;
930
+ }
931
+ pid = parseInt(pid, 10);
932
+ if (Number.isNaN(pid)) {
933
+ const err = new Error("pid must be a number");
934
+ if (callback)
935
+ return callback(err);
936
+ throw err;
937
+ }
938
+ const tree = { [pid]: [] };
939
+ const pidsToProcess = { [pid]: 1 };
940
+ switch (process.platform) {
941
+ case "win32":
942
+ child_process.exec(`taskkill /pid ${pid} /T /F`, (err) => callback?.(err ?? undefined));
943
+ break;
944
+ case "darwin":
945
+ buildProcessTree(pid, tree, pidsToProcess, (parentPid) => child_process.spawn("pgrep", ["-P", String(parentPid)]), () => killAll(tree, signal, callback));
946
+ break;
947
+ default: // Linux
948
+ buildProcessTree(pid, tree, pidsToProcess, (parentPid) => child_process.spawn("ps", ["-o", "pid", "--no-headers", "--ppid", String(parentPid)]), () => killAll(tree, signal, callback));
949
+ break;
950
+ }
951
+ }
952
+
953
+ const DRAIN_MAX_AWAIT = 250;
954
+ const drainStream = (stream) => new Promise((resolve) => {
955
+ if (stream.writableLength === 0) {
956
+ stream.write("", () => resolve());
957
+ return;
958
+ }
959
+ stream.once("drain", () => {
960
+ stream.write("", () => resolve());
961
+ });
962
+ });
963
+ const flushStream = (stream) => {
964
+ const handle = stream._handle;
965
+ if (handle && typeof handle.setBlocking === "function") {
966
+ handle.setBlocking(true);
967
+ return new Promise((resolve) => stream.write("", () => resolve()));
968
+ }
969
+ return drainStream(stream);
970
+ };
971
+ const kill = functoolsKit.singleshot(async (code = -1) => {
972
+ await Promise.race([
973
+ Promise.all([
974
+ flushStream(process.stdout),
975
+ flushStream(process.stderr)
976
+ ]),
977
+ functoolsKit.sleep(DRAIN_MAX_AWAIT),
978
+ ]);
979
+ treeKill(process.pid, "SIGKILL", () => {
980
+ process.exit(code);
981
+ });
982
+ });
983
+ const notifyKill = functoolsKit.singleshot(() => {
984
+ console.log("Press Ctrl+C again to force quit.");
985
+ process.on("SIGINT", () => kill());
986
+ });
987
+
859
988
  const DEFAULT_CACHE_LIST$1 = ["1m", "15m", "30m", "1h", "4h"];
860
989
  const GET_CACHE_INTERVAL_LIST_FN$1 = () => {
861
990
  const { values } = getArgs();
@@ -883,6 +1012,10 @@ class BacktestMainService {
883
1012
  this.loggerService.log("backtestMainService run", {
884
1013
  payload,
885
1014
  });
1015
+ {
1016
+ const cwd = process.cwd();
1017
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
1018
+ }
886
1019
  await this.configConnectionService.loadConfig("setup.config");
887
1020
  {
888
1021
  const loader = await this.configConnectionService.loadConfig("loader.config");
@@ -896,7 +1029,8 @@ class BacktestMainService {
896
1029
  }
897
1030
  catch (error) {
898
1031
  console.error("Module loader failed", error);
899
- process.exit(-1);
1032
+ kill(-1);
1033
+ return;
900
1034
  }
901
1035
  }
902
1036
  {
@@ -907,10 +1041,6 @@ class BacktestMainService {
907
1041
  this.frontendProviderService.connect();
908
1042
  this.telegramProviderService.connect();
909
1043
  }
910
- {
911
- const cwd = process.cwd();
912
- dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
913
- }
914
1044
  let absolutePath;
915
1045
  {
916
1046
  absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
@@ -1019,6 +1149,10 @@ class WalkerMainService {
1019
1149
  this.configConnectionService = inject(TYPES.configConnectionService);
1020
1150
  this.run = functoolsKit.singleshot(async (payload) => {
1021
1151
  this.loggerService.log("walkerMainService run", { payload });
1152
+ {
1153
+ const cwd = process.cwd();
1154
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
1155
+ }
1022
1156
  await this.configConnectionService.loadConfig("setup.config");
1023
1157
  {
1024
1158
  const loader = await this.configConnectionService.loadConfig("loader.config");
@@ -1032,7 +1166,8 @@ class WalkerMainService {
1032
1166
  }
1033
1167
  catch (error) {
1034
1168
  console.error("Module loader failed", error);
1035
- process.exit(-1);
1169
+ kill(-1);
1170
+ return;
1036
1171
  }
1037
1172
  }
1038
1173
  {
@@ -1068,10 +1203,6 @@ class WalkerMainService {
1068
1203
  BacktestKit.Cache.resetCounter();
1069
1204
  BacktestKit.Interval.resetCounter();
1070
1205
  }
1071
- {
1072
- const cwd = process.cwd();
1073
- dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
1074
- }
1075
1206
  await this.moduleConnectionService.loadModule("walker.module");
1076
1207
  {
1077
1208
  this.exchangeSchemaService.addSchema();
@@ -1200,7 +1331,7 @@ class WalkerMainService {
1200
1331
  await fs$1.mkdir(dumpDir, { recursive: true });
1201
1332
  await fs$1.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
1202
1333
  console.log(`Saved: ${filePath}`);
1203
- process.exit(0);
1334
+ kill(0);
1204
1335
  return;
1205
1336
  }
1206
1337
  if (payload.markdown) {
@@ -1209,12 +1340,13 @@ class WalkerMainService {
1209
1340
  await fs$1.mkdir(dumpDir, { recursive: true });
1210
1341
  await fs$1.writeFile(filePath, report, "utf-8");
1211
1342
  console.log(`Saved: ${filePath}`);
1212
- process.exit(0);
1343
+ kill(0);
1213
1344
  return;
1214
1345
  }
1215
1346
  const report = await BacktestKit.Walker.getReport(symbol, { walkerName: WalkerName$1.DefaultWalker });
1216
1347
  console.log(report);
1217
- process.exit(0);
1348
+ kill(0);
1349
+ return;
1218
1350
  });
1219
1351
  this.connect = functoolsKit.singleshot(async () => {
1220
1352
  this.loggerService.log("walkerMainService connect");
@@ -1259,6 +1391,10 @@ class LiveMainService {
1259
1391
  this.loggerService.log("liveMainService run", {
1260
1392
  payload,
1261
1393
  });
1394
+ {
1395
+ const cwd = process.cwd();
1396
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
1397
+ }
1262
1398
  await this.configConnectionService.loadConfig("setup.config");
1263
1399
  {
1264
1400
  const loader = await this.configConnectionService.loadConfig("loader.config");
@@ -1272,7 +1408,8 @@ class LiveMainService {
1272
1408
  }
1273
1409
  catch (error) {
1274
1410
  console.error("Module loader failed", error);
1275
- process.exit(-1);
1411
+ kill(-1);
1412
+ return;
1276
1413
  }
1277
1414
  }
1278
1415
  {
@@ -1283,10 +1420,6 @@ class LiveMainService {
1283
1420
  this.frontendProviderService.connect();
1284
1421
  this.telegramProviderService.connect();
1285
1422
  }
1286
- {
1287
- const cwd = process.cwd();
1288
- dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
1289
- }
1290
1423
  let absolutePath;
1291
1424
  {
1292
1425
  absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
@@ -1362,6 +1495,10 @@ class PaperMainService {
1362
1495
  this.configConnectionService = inject(TYPES.configConnectionService);
1363
1496
  this.run = functoolsKit.singleshot(async (payload) => {
1364
1497
  this.loggerService.log("paperMainService init");
1498
+ {
1499
+ const cwd = process.cwd();
1500
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
1501
+ }
1365
1502
  await this.configConnectionService.loadConfig("setup.config");
1366
1503
  {
1367
1504
  const loader = await this.configConnectionService.loadConfig("loader.config");
@@ -1375,7 +1512,8 @@ class PaperMainService {
1375
1512
  }
1376
1513
  catch (error) {
1377
1514
  console.error("Module loader failed", error);
1378
- process.exit(-1);
1515
+ kill(-1);
1516
+ return;
1379
1517
  }
1380
1518
  }
1381
1519
  {
@@ -1386,10 +1524,6 @@ class PaperMainService {
1386
1524
  this.frontendProviderService.connect();
1387
1525
  this.telegramProviderService.connect();
1388
1526
  }
1389
- {
1390
- const cwd = process.cwd();
1391
- dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
1392
- }
1393
1527
  let absolutePath;
1394
1528
  {
1395
1529
  absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
@@ -1857,7 +1991,7 @@ class TelegramApiService {
1857
1991
  console.error("Telegram publish failure", {
1858
1992
  error,
1859
1993
  });
1860
- setTimeout(() => process.exit(-1), 5000);
1994
+ setTimeout(() => kill(-1), 5000);
1861
1995
  });
1862
1996
  const result = await Promise.race([
1863
1997
  waitForResult,
@@ -1871,7 +2005,7 @@ class TelegramApiService {
1871
2005
  return "Message scheduled for publication";
1872
2006
  }
1873
2007
  if (TIMEOUT_COUNTER > MAX_TIMEOUT_COUNT) {
1874
- setTimeout(() => process.exit(-1), 5000);
2008
+ setTimeout(() => kill(-1), 5000);
1875
2009
  }
1876
2010
  return "Message published successfully";
1877
2011
  };
@@ -2584,7 +2718,8 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
2584
2718
  }
2585
2719
  catch {
2586
2720
  console.warn(`Module module import failed filePath=${filePath} baseDir=${baseDir}`);
2587
- process.exit(-1);
2721
+ kill(-1);
2722
+ return false;
2588
2723
  }
2589
2724
  }
2590
2725
  return false;
@@ -2704,7 +2839,8 @@ const TRANSPILE_FN = functoolsKit.memoize(([path]) => `${path}`, (path, code, se
2704
2839
  }
2705
2840
  catch (error) {
2706
2841
  console.log(`Error during transpilation error=\`${functoolsKit.getErrorMessage(error)}\` path=\`${path}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
2707
- process.exit(-1);
2842
+ kill(-1);
2843
+ return;
2708
2844
  }
2709
2845
  return {
2710
2846
  require,
@@ -2968,7 +3104,7 @@ const GET_ALIAS_EXPORTS_FN = (self) => {
2968
3104
  }
2969
3105
  return null;
2970
3106
  };
2971
- const INIT_ALIAS_FN = (self) => {
3107
+ const INIT_ALIAS_FN = functoolsKit.singleshot((self) => {
2972
3108
  const alias = GET_ALIAS_EXPORTS_FN(self);
2973
3109
  if (!alias) {
2974
3110
  return;
@@ -2980,18 +3116,21 @@ const INIT_ALIAS_FN = (self) => {
2980
3116
  Object.entries(alias).forEach(([name, module]) => overrideModule(name, module));
2981
3117
  Object.assign(IMPORT_ALIAS, alias);
2982
3118
  }
2983
- };
3119
+ });
2984
3120
  class LoaderService {
2985
3121
  constructor() {
2986
3122
  this.babelService = inject(TYPES.babelService);
2987
3123
  this.loggerService = inject(TYPES.loggerService);
2988
3124
  this.resolveService = inject(TYPES.resolveService);
2989
- this.getInstance = functoolsKit.memoize(([basePath]) => `${basePath}`, (basePath) => new ClientLoader({
2990
- babel: this.babelService,
2991
- logger: this.loggerService,
2992
- resolve: this.resolveService,
2993
- path: basePath,
2994
- }));
3125
+ this.getInstance = functoolsKit.memoize(([basePath]) => `${basePath}`, (basePath) => {
3126
+ INIT_ALIAS_FN(this);
3127
+ return new ClientLoader({
3128
+ babel: this.babelService,
3129
+ logger: this.loggerService,
3130
+ resolve: this.resolveService,
3131
+ path: basePath,
3132
+ });
3133
+ });
2995
3134
  this.import = (filePath, basePath = process.cwd()) => {
2996
3135
  this.loggerService.log("loaderService import", {
2997
3136
  filePath,
@@ -3008,10 +3147,6 @@ class LoaderService {
3008
3147
  const instance = this.getInstance(basePath);
3009
3148
  return instance.check(filePath);
3010
3149
  };
3011
- this.init = functoolsKit.singleshot(() => {
3012
- this.loggerService.log("loaderService init");
3013
- INIT_ALIAS_FN(this);
3014
- });
3015
3150
  }
3016
3151
  }
3017
3152
 
@@ -3040,7 +3175,8 @@ const LOAD_CONFIG_CONFIG_FN = async (fileName, self) => {
3040
3175
  }
3041
3176
  catch {
3042
3177
  console.warn(`Module module import failed filePath=${filePath} baseDir=${baseDir}`);
3043
- process.exit(-1);
3178
+ kill(-1);
3179
+ return;
3044
3180
  }
3045
3181
  }
3046
3182
  return null;
@@ -3212,10 +3348,10 @@ init();
3212
3348
 
3213
3349
  const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3214
3350
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
3215
- const HELP_TEXT$1 = `
3216
- Example:
3217
-
3218
- node ${ENTRY_PATH$1} --help
3351
+ const HELP_TEXT$1 = `
3352
+ Example:
3353
+
3354
+ node ${ENTRY_PATH$1} --help
3219
3355
  `.trimStart();
3220
3356
  const main$g = async () => {
3221
3357
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
@@ -3225,7 +3361,7 @@ const main$g = async () => {
3225
3361
  if (MODES.some((mode) => values[mode])) {
3226
3362
  return;
3227
3363
  }
3228
- process.stdout.write(`@backtest-kit/cli ${"9.8.3"}\n`);
3364
+ process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
3229
3365
  process.stdout.write("\n");
3230
3366
  process.stdout.write(`Run with --help to see available commands.\n`);
3231
3367
  process.stdout.write("\n");
@@ -3235,7 +3371,7 @@ const main$g = async () => {
3235
3371
  main$g();
3236
3372
 
3237
3373
  const notifyShutdown = functoolsKit.singleshot(async () => {
3238
- console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
3374
+ console.log("Graceful shutdown initiated.");
3239
3375
  });
3240
3376
 
3241
3377
  const FLUSH_DIRS = ["report", "log", "markdown", "agent"];
@@ -3274,6 +3410,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3274
3410
  return;
3275
3411
  }
3276
3412
  notifyShutdown();
3413
+ notifyKill();
3277
3414
  const { exchangeName, frameName, strategyName, symbol, status } = running;
3278
3415
  if (status === "fulfilled") {
3279
3416
  return;
@@ -3314,6 +3451,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3314
3451
  return;
3315
3452
  }
3316
3453
  notifyShutdown();
3454
+ notifyKill();
3317
3455
  const { walkerName, symbol, status } = running;
3318
3456
  if (status === "fulfilled") {
3319
3457
  return;
@@ -3351,6 +3489,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3351
3489
  return;
3352
3490
  }
3353
3491
  notifyShutdown();
3492
+ notifyKill();
3354
3493
  const { exchangeName, strategyName, symbol, status } = running;
3355
3494
  if (status === "fulfilled") {
3356
3495
  return;
@@ -3386,6 +3525,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3386
3525
  return;
3387
3526
  }
3388
3527
  notifyShutdown();
3528
+ notifyKill();
3389
3529
  const { exchangeName, strategyName, symbol, status } = running;
3390
3530
  if (status === "fulfilled") {
3391
3531
  return;
@@ -3486,6 +3626,7 @@ const createGracefulShutdown = (mode) => {
3486
3626
  const handler = functoolsKit.singleshot(async () => {
3487
3627
  process.off("SIGINT", handler);
3488
3628
  notifyShutdown();
3629
+ notifyKill();
3489
3630
  await stop();
3490
3631
  });
3491
3632
  return functoolsKit.singleshot(() => {
@@ -3503,13 +3644,17 @@ const main$a = async () => {
3503
3644
  const mode = resolveMode(values);
3504
3645
  if (!mode) {
3505
3646
  console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
3506
- process.exit(1);
3647
+ kill(1);
3507
3648
  return;
3508
3649
  }
3509
3650
  const entryPoints = getPositionals();
3510
3651
  if (!entryPoints.length) {
3511
3652
  throw new Error("At least one entry point is required");
3512
3653
  }
3654
+ {
3655
+ const cwd = process.cwd();
3656
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3657
+ }
3513
3658
  await cli.configConnectionService.loadConfig("setup.config");
3514
3659
  {
3515
3660
  const loader = await cli.configConnectionService.loadConfig("loader.config");
@@ -3523,7 +3668,8 @@ const main$a = async () => {
3523
3668
  }
3524
3669
  catch (error) {
3525
3670
  console.error("Module loader failed", error);
3526
- process.exit(-1);
3671
+ kill(-1);
3672
+ return;
3527
3673
  }
3528
3674
  }
3529
3675
  {
@@ -3533,9 +3679,6 @@ const main$a = async () => {
3533
3679
  cli.frontendProviderService.connect();
3534
3680
  cli.telegramProviderService.connect();
3535
3681
  const cwd = process.cwd();
3536
- {
3537
- dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3538
- }
3539
3682
  if (entryPoints.length === 1) {
3540
3683
  const absolutePath = path.resolve(entryPoints[0]);
3541
3684
  const moduleRoot = path.dirname(absolutePath);
@@ -3565,6 +3708,7 @@ main$a();
3565
3708
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
3566
3709
  process.off("SIGINT", BEFORE_EXIT_FN$1);
3567
3710
  notifyShutdown();
3711
+ notifyKill();
3568
3712
  cli.frontendProviderService.disable();
3569
3713
  });
3570
3714
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
@@ -3585,6 +3729,7 @@ main$9();
3585
3729
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
3586
3730
  process.off("SIGINT", BEFORE_EXIT_FN);
3587
3731
  notifyShutdown();
3732
+ notifyKill();
3588
3733
  cli.telegramProviderService.disable();
3589
3734
  });
3590
3735
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
@@ -3734,7 +3879,12 @@ const main$6 = async () => {
3734
3879
  }
3735
3880
  if (values.pine) {
3736
3881
  console.warn("--editor and --pine are mutually exclusive. Use one at a time.");
3737
- process.exit(1);
3882
+ kill(1);
3883
+ return;
3884
+ }
3885
+ {
3886
+ const cwd = process.cwd();
3887
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3738
3888
  }
3739
3889
  await cli.configConnectionService.loadConfig("setup.config");
3740
3890
  {
@@ -3749,17 +3899,14 @@ const main$6 = async () => {
3749
3899
  }
3750
3900
  catch (error) {
3751
3901
  console.error("Module loader failed", error);
3752
- process.exit(-1);
3902
+ kill(-1);
3903
+ return;
3753
3904
  }
3754
3905
  }
3755
3906
  {
3756
3907
  await cli.configService.waitForInit();
3757
3908
  Setup.enable();
3758
3909
  }
3759
- {
3760
- const cwd = process.cwd();
3761
- dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3762
- }
3763
3910
  await cli.moduleConnectionService.loadModule("editor.module");
3764
3911
  {
3765
3912
  await cli.exchangeSchemaService.addSchema();
@@ -3779,7 +3926,8 @@ const main$6 = async () => {
3779
3926
  const beforeExit = () => {
3780
3927
  process.off("SIGINT", beforeExit);
3781
3928
  unServer();
3782
- process.exit(0);
3929
+ kill(0);
3930
+ return;
3783
3931
  };
3784
3932
  process.on("SIGINT", beforeExit);
3785
3933
  };
@@ -4164,183 +4312,183 @@ const main$2 = async () => {
4164
4312
  main$2();
4165
4313
 
4166
4314
  const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
4167
- const HELP_TEXT = `
4168
- Usage:
4169
- node index.mjs --<mode> [flags] [entry-point]
4170
-
4171
- Modes:
4172
-
4173
- --backtest <entry> Run strategy against historical candle data
4174
- --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4175
- --paper <entry> Paper trading (live prices, no real orders)
4176
- --live <entry> Live trading with real orders
4177
- --pine <entry> Execute a local .pine indicator file
4178
- --editor Open the Pine Script visual editor in the browser
4179
- --dump Fetch and save raw OHLCV candles
4180
- --pnldebug Simulate PnL per minute for a given entry price and direction
4181
- --brokerdebug Fire a single broker commit against the live broker adapter
4182
- --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4183
- --init Scaffold a new project in the current directory
4184
- --docker Scaffold a Docker workspace for running strategies in a container
4185
- --help Print this help message
4186
-
4187
- Backtest flags:
4188
-
4189
- --symbol <string> Trading pair (default: BTCUSDT)
4190
- --strategy <string> Strategy name from addStrategySchema (default: first registered)
4191
- --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4192
- --frame <string> Frame name from addFrameSchema (default: first registered)
4193
- --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4194
- --noCache Skip candle cache warming before the run
4195
- --noFlush Skip removing report/log/markdown/agent folders before backtest run
4196
- --verbose Log every candle fetch to stdout
4197
- --ui Start web dashboard at http://localhost:60050
4198
- --telegram Send trade notifications to Telegram
4199
-
4200
- Walker flags (--walker):
4201
-
4202
- --symbol <string> Trading pair (default: BTCUSDT)
4203
- --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4204
- --noCache Skip candle cache warming before the run
4205
- --noFlush Skip removing report/log/markdown/agent folders before walker run
4206
- --verbose Log every candle fetch to stdout
4207
- --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4208
- --json Save results as JSON to ./dump/<output>.json
4209
- --markdown Save report as Markdown to ./dump/<output>.md
4210
-
4211
- Each positional argument is a strategy entry point. All strategy files are loaded without
4212
- changing process.cwd() — .env is read from the working directory only.
4213
- addWalkerSchema is called automatically using the registered exchange and frame.
4214
- After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4215
-
4216
- Module file ./modules/walker.module is loaded automatically if it exists.
4217
-
4218
- Paper / Live flags:
4219
-
4220
- --symbol <string> Trading pair (default: BTCUSDT)
4221
- --strategy <string> Strategy name (default: first registered)
4222
- --exchange <string> Exchange name (default: first registered)
4223
- --verbose Log every candle fetch to stdout
4224
- --ui Start web dashboard
4225
- --telegram Send Telegram notifications
4226
-
4227
- PineScript flags (--pine):
4228
-
4229
- --symbol <string> Trading pair (default: BTCUSDT)
4230
- --timeframe <string> Candle interval (default: 15m)
4231
- --limit <string> Number of candles to fetch (default: 250)
4232
- --when <string> End date — ISO 8601 or Unix ms (default: now)
4233
- --exchange <string> Exchange name (default: first registered)
4234
- --output <string> Output file base name without extension
4235
- --json Save output as JSON array to <pine-dir>/dump/<output>.json
4236
- --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4237
- --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4238
-
4239
- Only plot() calls with display=display.data_window produce output columns.
4240
- Module file ./modules/pine.module is loaded automatically if it exists.
4241
-
4242
- Candle dump flags (--dump):
4243
-
4244
- --symbol <string> Trading pair (default: BTCUSDT)
4245
- --timeframe <string> Candle interval (default: 15m)
4246
- --limit <string> Number of candles (default: 250)
4247
- --when <string> End date — ISO 8601 or Unix ms (default: now)
4248
- --exchange <string> Exchange name (default: first registered)
4249
- --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4250
- --json Save as JSON array to ./dump/<output>.json
4251
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4252
-
4253
- Module file ./modules/dump.module is loaded automatically if it exists.
4254
-
4255
- PnL debug flags (--pnldebug):
4256
-
4257
- --symbol <string> Trading pair (default: BTCUSDT)
4258
- --priceopen <number> Entry price (required)
4259
- --direction <string> Position direction: long or short (default: long)
4260
- --when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
4261
- --minutes <string> Number of 1m candles to simulate (default: 60)
4262
- --exchange <string> Exchange name (default: first registered)
4263
- --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4264
- --json Save as JSON array to ./dump/<output>.json
4265
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4266
- --markdown Save as Markdown table to ./dump/<output>.md
4267
-
4268
- Module file ./modules/pnldebug.module is loaded automatically if it exists.
4269
-
4270
- Broker debug flags (--brokerdebug):
4271
-
4272
- --symbol <string> Trading pair (default: BTCUSDT)
4273
- --exchange <string> Exchange name (default: first registered)
4274
- --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4275
- partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4276
- (default: signal-open)
4277
-
4278
- Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4279
- the selected broker commit with synthetic payload values derived from current price.
4280
-
4281
- Flush flags (--flush):
4282
-
4283
- One or more positional entry points. For each entry point the following
4284
- subdirectories are removed from <entry-dir>/dump/:
4285
-
4286
- report log markdown agent
4287
-
4288
- Init flags (--init):
4289
-
4290
- --output <string> Target directory name (default: backtest-kit-project)
4291
-
4292
- Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4293
-
4294
- Docker flags (--docker):
4295
-
4296
- --output <string> Target directory name (default: backtest-kit-docker)
4297
-
4298
- Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4299
- tsconfig.json, and a sample strategy under content/. Run npm install then
4300
- docker compose up to start the container.
4301
-
4302
- Module hooks (loaded automatically by each mode):
4303
-
4304
- modules/backtest.module --backtest Broker adapter for backtest
4305
- modules/walker.module --walker Broker adapter for walker comparison
4306
- modules/paper.module --paper Broker adapter for paper trading
4307
- modules/live.module --live Broker adapter for live trading
4308
- modules/pine.module --pine Exchange schema for PineScript runs
4309
- modules/editor.module --editor Exchange schema for the visual Pine editor
4310
- modules/dump.module --dump Exchange schema for candle dumps
4311
- modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4312
- modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4313
-
4314
- --flush has no associated module. It only removes dump subdirectories.
4315
-
4316
- Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4317
-
4318
- Environment variables:
4319
-
4320
- CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4321
- CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4322
- CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4323
- CC_WWWROOT_PORT UI server port (default: 60050)
4324
-
4325
- Examples:
4326
-
4327
- node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4328
- node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4329
- node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4330
- node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4331
- node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4332
- node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4333
- node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4334
- node ${ENTRY_PATH} --editor
4335
- node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4336
- node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4337
- node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4338
- node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4339
- node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4340
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4341
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4342
- node ${ENTRY_PATH} --init --output my-trading-bot
4343
- node ${ENTRY_PATH} --docker --output my-docker-workspace
4315
+ const HELP_TEXT = `
4316
+ Usage:
4317
+ node index.mjs --<mode> [flags] [entry-point]
4318
+
4319
+ Modes:
4320
+
4321
+ --backtest <entry> Run strategy against historical candle data
4322
+ --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4323
+ --paper <entry> Paper trading (live prices, no real orders)
4324
+ --live <entry> Live trading with real orders
4325
+ --pine <entry> Execute a local .pine indicator file
4326
+ --editor Open the Pine Script visual editor in the browser
4327
+ --dump Fetch and save raw OHLCV candles
4328
+ --pnldebug Simulate PnL per minute for a given entry price and direction
4329
+ --brokerdebug Fire a single broker commit against the live broker adapter
4330
+ --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4331
+ --init Scaffold a new project in the current directory
4332
+ --docker Scaffold a Docker workspace for running strategies in a container
4333
+ --help Print this help message
4334
+
4335
+ Backtest flags:
4336
+
4337
+ --symbol <string> Trading pair (default: BTCUSDT)
4338
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
4339
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4340
+ --frame <string> Frame name from addFrameSchema (default: first registered)
4341
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4342
+ --noCache Skip candle cache warming before the run
4343
+ --noFlush Skip removing report/log/markdown/agent folders before backtest run
4344
+ --verbose Log every candle fetch to stdout
4345
+ --ui Start web dashboard at http://localhost:60050
4346
+ --telegram Send trade notifications to Telegram
4347
+
4348
+ Walker flags (--walker):
4349
+
4350
+ --symbol <string> Trading pair (default: BTCUSDT)
4351
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4352
+ --noCache Skip candle cache warming before the run
4353
+ --noFlush Skip removing report/log/markdown/agent folders before walker run
4354
+ --verbose Log every candle fetch to stdout
4355
+ --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4356
+ --json Save results as JSON to ./dump/<output>.json
4357
+ --markdown Save report as Markdown to ./dump/<output>.md
4358
+
4359
+ Each positional argument is a strategy entry point. All strategy files are loaded without
4360
+ changing process.cwd() — .env is read from the working directory only.
4361
+ addWalkerSchema is called automatically using the registered exchange and frame.
4362
+ After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4363
+
4364
+ Module file ./modules/walker.module is loaded automatically if it exists.
4365
+
4366
+ Paper / Live flags:
4367
+
4368
+ --symbol <string> Trading pair (default: BTCUSDT)
4369
+ --strategy <string> Strategy name (default: first registered)
4370
+ --exchange <string> Exchange name (default: first registered)
4371
+ --verbose Log every candle fetch to stdout
4372
+ --ui Start web dashboard
4373
+ --telegram Send Telegram notifications
4374
+
4375
+ PineScript flags (--pine):
4376
+
4377
+ --symbol <string> Trading pair (default: BTCUSDT)
4378
+ --timeframe <string> Candle interval (default: 15m)
4379
+ --limit <string> Number of candles to fetch (default: 250)
4380
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
4381
+ --exchange <string> Exchange name (default: first registered)
4382
+ --output <string> Output file base name without extension
4383
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
4384
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4385
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4386
+
4387
+ Only plot() calls with display=display.data_window produce output columns.
4388
+ Module file ./modules/pine.module is loaded automatically if it exists.
4389
+
4390
+ Candle dump flags (--dump):
4391
+
4392
+ --symbol <string> Trading pair (default: BTCUSDT)
4393
+ --timeframe <string> Candle interval (default: 15m)
4394
+ --limit <string> Number of candles (default: 250)
4395
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
4396
+ --exchange <string> Exchange name (default: first registered)
4397
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4398
+ --json Save as JSON array to ./dump/<output>.json
4399
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4400
+
4401
+ Module file ./modules/dump.module is loaded automatically if it exists.
4402
+
4403
+ PnL debug flags (--pnldebug):
4404
+
4405
+ --symbol <string> Trading pair (default: BTCUSDT)
4406
+ --priceopen <number> Entry price (required)
4407
+ --direction <string> Position direction: long or short (default: long)
4408
+ --when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
4409
+ --minutes <string> Number of 1m candles to simulate (default: 60)
4410
+ --exchange <string> Exchange name (default: first registered)
4411
+ --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4412
+ --json Save as JSON array to ./dump/<output>.json
4413
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4414
+ --markdown Save as Markdown table to ./dump/<output>.md
4415
+
4416
+ Module file ./modules/pnldebug.module is loaded automatically if it exists.
4417
+
4418
+ Broker debug flags (--brokerdebug):
4419
+
4420
+ --symbol <string> Trading pair (default: BTCUSDT)
4421
+ --exchange <string> Exchange name (default: first registered)
4422
+ --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4423
+ partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4424
+ (default: signal-open)
4425
+
4426
+ Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4427
+ the selected broker commit with synthetic payload values derived from current price.
4428
+
4429
+ Flush flags (--flush):
4430
+
4431
+ One or more positional entry points. For each entry point the following
4432
+ subdirectories are removed from <entry-dir>/dump/:
4433
+
4434
+ report log markdown agent
4435
+
4436
+ Init flags (--init):
4437
+
4438
+ --output <string> Target directory name (default: backtest-kit-project)
4439
+
4440
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4441
+
4442
+ Docker flags (--docker):
4443
+
4444
+ --output <string> Target directory name (default: backtest-kit-docker)
4445
+
4446
+ Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4447
+ tsconfig.json, and a sample strategy under content/. Run npm install then
4448
+ docker compose up to start the container.
4449
+
4450
+ Module hooks (loaded automatically by each mode):
4451
+
4452
+ modules/backtest.module --backtest Broker adapter for backtest
4453
+ modules/walker.module --walker Broker adapter for walker comparison
4454
+ modules/paper.module --paper Broker adapter for paper trading
4455
+ modules/live.module --live Broker adapter for live trading
4456
+ modules/pine.module --pine Exchange schema for PineScript runs
4457
+ modules/editor.module --editor Exchange schema for the visual Pine editor
4458
+ modules/dump.module --dump Exchange schema for candle dumps
4459
+ modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4460
+ modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4461
+
4462
+ --flush has no associated module. It only removes dump subdirectories.
4463
+
4464
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4465
+
4466
+ Environment variables:
4467
+
4468
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4469
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4470
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4471
+ CC_WWWROOT_PORT UI server port (default: 60050)
4472
+
4473
+ Examples:
4474
+
4475
+ node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4476
+ node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4477
+ node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4478
+ node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4479
+ node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4480
+ node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4481
+ node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4482
+ node ${ENTRY_PATH} --editor
4483
+ node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4484
+ node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4485
+ node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4486
+ node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4487
+ node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4488
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4489
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4490
+ node ${ENTRY_PATH} --init --output my-trading-bot
4491
+ node ${ENTRY_PATH} --docker --output my-docker-workspace
4344
4492
  `.trimStart();
4345
4493
  const main$1 = async () => {
4346
4494
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
@@ -4350,7 +4498,7 @@ const main$1 = async () => {
4350
4498
  if (!values.help) {
4351
4499
  return;
4352
4500
  }
4353
- process.stdout.write(`@backtest-kit/cli ${"9.8.3"}\n\n`);
4501
+ process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n\n`);
4354
4502
  process.stdout.write(HELP_TEXT);
4355
4503
  process.exit(0);
4356
4504
  };
@@ -4364,7 +4512,7 @@ const main = async () => {
4364
4512
  if (!values.version) {
4365
4513
  return;
4366
4514
  }
4367
- process.stdout.write(`@backtest-kit/cli ${"9.8.3"}\n`);
4515
+ process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
4368
4516
  process.exit(0);
4369
4517
  };
4370
4518
  main();