@backtest-kit/cli 9.8.4 → 10.2.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.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;
@@ -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 ${"9.8.4"}\n`);
3335
+ process.stdout.write(`@backtest-kit/cli ${"10.2.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
  };
@@ -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 ${"9.8.4"}\n\n`);
4472
+ process.stdout.write(`@backtest-kit/cli ${"10.2.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.2.0"}\n`);
4339
4487
  process.exit(0);
4340
4488
  };
4341
4489
  main();