@backtest-kit/cli 9.8.4 → 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;
@@ -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.4"}\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
  };
@@ -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.4"}\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.4"}\n`);
4515
+ process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
4368
4516
  process.exit(0);
4369
4517
  };
4370
4518
  main();
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, sleep, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize, isObject } from 'functools-kit';
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
- process.exit(-1);
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
- process.exit(-1);
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
- process.exit(0);
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
- process.exit(0);
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
- process.exit(0);
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
- process.exit(-1);
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
- process.exit(-1);
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(() => process.exit(-1), 5000);
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(() => process.exit(-1), 5000);
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
- process.exit(-1);
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
- process.exit(-1);
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) => new ClientLoader({
2961
- babel: this.babelService,
2962
- logger: this.loggerService,
2963
- resolve: this.resolveService,
2964
- path: basePath,
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
- process.exit(-1);
3149
+ kill(-1);
3150
+ return;
3015
3151
  }
3016
3152
  }
3017
3153
  return null;
@@ -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 ${"9.8.4"}\n`);
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. Press Ctrl+C again to force quit.");
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
- process.exit(1);
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
- process.exit(-1);
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
- process.exit(1);
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
- process.exit(-1);
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
- process.exit(0);
3900
+ kill(0);
3901
+ return;
3754
3902
  };
3755
3903
  process.on("SIGINT", beforeExit);
3756
3904
  };
@@ -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 ${"9.8.4"}\n\n`);
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 ${"9.8.4"}\n`);
4486
+ process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
4339
4487
  process.exit(0);
4340
4488
  };
4341
4489
  main();
@@ -15,17 +15,17 @@
15
15
  "@types/node": "25.6.0"
16
16
  },
17
17
  "dependencies": {
18
- "@backtest-kit/cli": "9.8.4",
19
- "@backtest-kit/graph": "9.8.4",
20
- "@backtest-kit/pinets": "9.8.4",
21
- "@backtest-kit/signals": "9.8.4",
22
- "@backtest-kit/ui": "9.8.4",
18
+ "@backtest-kit/cli": "10.1.0",
19
+ "@backtest-kit/graph": "10.1.0",
20
+ "@backtest-kit/pinets": "10.1.0",
21
+ "@backtest-kit/signals": "10.1.0",
22
+ "@backtest-kit/ui": "10.1.0",
23
23
  "@tavily/core": "0.7.2",
24
24
  "@tensorflow/tfjs": "4.22.0",
25
25
  "@tensorflow/tfjs-backend-wasm": "4.22.0",
26
26
  "@tensorflow/tfjs-core": "4.22.0",
27
27
  "agent-swarm-kit": "2.7.0",
28
- "backtest-kit": "9.8.4",
28
+ "backtest-kit": "10.1.0",
29
29
  "dayjs": "1.11.20",
30
30
  "functools-kit": "2.3.0",
31
31
  "garch": "1.2.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "9.8.4",
3
+ "version": "10.1.0",
4
4
  "description": "Zero-boilerplate CLI runner for backtest-kit strategies. Run backtests, paper trading, and live bots with candle cache warming, web dashboard, and Telegram notifications — no setup code required.",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
@@ -63,11 +63,11 @@
63
63
  "devDependencies": {
64
64
  "@babel/plugin-transform-modules-umd": "7.27.1",
65
65
  "@babel/standalone": "7.29.1",
66
- "@backtest-kit/graph": "9.8.4",
67
- "@backtest-kit/ollama": "9.8.4",
68
- "@backtest-kit/pinets": "9.8.4",
69
- "@backtest-kit/signals": "9.8.4",
70
- "@backtest-kit/ui": "9.8.4",
66
+ "@backtest-kit/graph": "10.1.0",
67
+ "@backtest-kit/ollama": "10.1.0",
68
+ "@backtest-kit/pinets": "10.1.0",
69
+ "@backtest-kit/signals": "10.1.0",
70
+ "@backtest-kit/ui": "10.1.0",
71
71
  "@rollup/plugin-replace": "6.0.3",
72
72
  "@rollup/plugin-typescript": "11.1.6",
73
73
  "@types/image-size": "0.7.0",
@@ -75,7 +75,7 @@
75
75
  "@types/mustache": "4.2.6",
76
76
  "@types/node": "22.9.0",
77
77
  "@types/stack-trace": "0.0.33",
78
- "backtest-kit": "9.8.4",
78
+ "backtest-kit": "10.1.0",
79
79
  "glob": "11.0.1",
80
80
  "markdown-it": "14.1.1",
81
81
  "rimraf": "6.0.1",
@@ -90,12 +90,12 @@
90
90
  "peerDependencies": {
91
91
  "@babel/plugin-transform-modules-umd": "^7.27.1",
92
92
  "@babel/standalone": "^7.29.1",
93
- "@backtest-kit/graph": "^9.8.4",
94
- "@backtest-kit/ollama": "^9.8.4",
95
- "@backtest-kit/pinets": "^9.8.4",
96
- "@backtest-kit/signals": "^9.8.4",
97
- "@backtest-kit/ui": "^9.8.4",
98
- "backtest-kit": "^9.8.4",
93
+ "@backtest-kit/graph": "^10.1.0",
94
+ "@backtest-kit/ollama": "^10.1.0",
95
+ "@backtest-kit/pinets": "^10.1.0",
96
+ "@backtest-kit/signals": "^10.1.0",
97
+ "@backtest-kit/ui": "^10.1.0",
98
+ "backtest-kit": "^10.1.0",
99
99
  "markdown-it": "^14.1.1",
100
100
  "typescript": "^5.0.0"
101
101
  },
@@ -13,12 +13,12 @@
13
13
  "license": "ISC",
14
14
  "type": "commonjs",
15
15
  "dependencies": {
16
- "@backtest-kit/cli": "^9.8.4",
17
- "@backtest-kit/graph": "^9.8.4",
18
- "@backtest-kit/pinets": "^9.8.4",
19
- "@backtest-kit/ui": "^9.8.4",
16
+ "@backtest-kit/cli": "^10.1.0",
17
+ "@backtest-kit/graph": "^10.1.0",
18
+ "@backtest-kit/pinets": "^10.1.0",
19
+ "@backtest-kit/ui": "^10.1.0",
20
20
  "agent-swarm-kit": "^2.7.0",
21
- "backtest-kit": "^9.8.4",
21
+ "backtest-kit": "^10.1.0",
22
22
  "functools-kit": "^2.3.0",
23
23
  "garch": "^1.2.3",
24
24
  "get-moment-stamp": "^2.0.0",
package/types.d.ts CHANGED
@@ -209,7 +209,6 @@ declare class LoaderService {
209
209
  getInstance: ((basePath: string) => ClientLoader) & functools_kit.IClearableMemoize<string> & functools_kit.IControlMemoize<string, ClientLoader>;
210
210
  import: (filePath: string, basePath?: string) => any;
211
211
  check: (filePath: string, basePath?: string) => Promise<boolean>;
212
- init: (() => void) & functools_kit.ISingleshotClearable<() => void>;
213
212
  }
214
213
 
215
214
  declare class ResolveService implements IResolve {