@backtest-kit/cli 12.4.0 → 12.6.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
@@ -98,11 +98,11 @@ const getEntrySubject = functoolsKit.singleshot(() => {
98
98
  return new functoolsKit.BehaviorSubject();
99
99
  });
100
100
 
101
- const main$h = () => {
101
+ const main$i = () => {
102
102
  const entrySubject = getEntrySubject();
103
103
  entrySubject.subscribe((path) => console.log("Running", path));
104
104
  };
105
- main$h();
105
+ main$i();
106
106
 
107
107
  const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
108
108
  function dumpStackTrace() {
@@ -663,6 +663,10 @@ const getArgs = functoolsKit.singleshot(() => {
663
663
  type: "boolean",
664
664
  default: false,
665
665
  },
666
+ main: {
667
+ type: "boolean",
668
+ default: false,
669
+ },
666
670
  cacheInterval: {
667
671
  type: "string",
668
672
  default: "1m, 15m, 30m, 4h",
@@ -1611,6 +1615,8 @@ const getEnv = functoolsKit.singleshot(() => {
1611
1615
  };
1612
1616
  });
1613
1617
 
1618
+ const READY_SUBJECT = new functoolsKit.BehaviorSubject();
1619
+ const MAX_WAIT_TIME = 5000;
1614
1620
  const GET_SYMBOL_EXPORTS_FN = async (self) => {
1615
1621
  const exports = await self.configConnectionService.loadConfig("symbol.config");
1616
1622
  if (!exports) {
@@ -1655,6 +1661,17 @@ const MAP_SYMBOL_CONFIG_FN = (config) => {
1655
1661
  symbolList.sort(({ priority: a_p, index: a_x }, { priority: b_p, index: b_x }) => b_p - a_p || a_x - b_x);
1656
1662
  return symbolList;
1657
1663
  };
1664
+ const CREATE_LISTEN_FN = (self) => async () => {
1665
+ const promise = READY_SUBJECT.toPromise();
1666
+ self.enable();
1667
+ if (READY_SUBJECT.data) {
1668
+ return;
1669
+ }
1670
+ await Promise.race([
1671
+ promise,
1672
+ functoolsKit.sleep(MAX_WAIT_TIME),
1673
+ ]);
1674
+ };
1658
1675
  class FrontendProviderService {
1659
1676
  constructor() {
1660
1677
  this.loggerService = inject(TYPES.loggerService);
@@ -1672,7 +1689,7 @@ class FrontendProviderService {
1672
1689
  BacktestKitUi.lib.symbolConnectionService.getSymbolList.setValue(Promise.resolve(symbolList));
1673
1690
  }
1674
1691
  }
1675
- unServer = BacktestKitUi.serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, this.resolveService.PROJECT_ROOT_DIR);
1692
+ unServer = BacktestKitUi.serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, this.resolveService.PROJECT_ROOT_DIR, () => READY_SUBJECT.next(true));
1676
1693
  };
1677
1694
  init();
1678
1695
  return () => {
@@ -1692,7 +1709,7 @@ class FrontendProviderService {
1692
1709
  if (!getArgs().values.ui) {
1693
1710
  return;
1694
1711
  }
1695
- return getEntrySubject().subscribe(this.enable);
1712
+ return getEntrySubject().subscribe(CREATE_LISTEN_FN(this));
1696
1713
  });
1697
1714
  }
1698
1715
  }
@@ -3358,14 +3375,14 @@ const cli = {
3358
3375
  };
3359
3376
  init();
3360
3377
 
3361
- const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3378
+ const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3362
3379
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
3363
- const HELP_TEXT$1 = `
3364
- Example:
3365
-
3366
- node ${ENTRY_PATH$1} --help
3380
+ const HELP_TEXT$1 = `
3381
+ Example:
3382
+
3383
+ node ${ENTRY_PATH$1} --help
3367
3384
  `.trimStart();
3368
- const main$g = async () => {
3385
+ const main$h = async () => {
3369
3386
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3370
3387
  return;
3371
3388
  }
@@ -3373,14 +3390,14 @@ const main$g = async () => {
3373
3390
  if (MODES.some((mode) => values[mode])) {
3374
3391
  return;
3375
3392
  }
3376
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
3393
+ process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n`);
3377
3394
  process.stdout.write("\n");
3378
3395
  process.stdout.write(`Run with --help to see available commands.\n`);
3379
3396
  process.stdout.write("\n");
3380
3397
  process.stdout.write(HELP_TEXT$1);
3381
3398
  process.exit(0);
3382
3399
  };
3383
- main$g();
3400
+ main$h();
3384
3401
 
3385
3402
  const notifyShutdown = functoolsKit.singleshot(async () => {
3386
3403
  console.log("Graceful shutdown initiated.");
@@ -3396,7 +3413,7 @@ const flush = async (entryPoint) => {
3396
3413
  console.log(`Removed: ${target}`);
3397
3414
  }
3398
3415
  };
3399
- const main$f = async () => {
3416
+ const main$g = async () => {
3400
3417
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3401
3418
  return;
3402
3419
  }
@@ -3413,10 +3430,10 @@ const main$f = async () => {
3413
3430
  }
3414
3431
  process.exit(0);
3415
3432
  };
3416
- main$f();
3433
+ main$g();
3417
3434
 
3418
- const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3419
- process.off("SIGINT", BEFORE_EXIT_FN$5);
3435
+ const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
3436
+ process.off("SIGINT", BEFORE_EXIT_FN$6);
3420
3437
  const [running = null] = await BacktestKit.Backtest.list();
3421
3438
  if (!running) {
3422
3439
  return;
@@ -3433,10 +3450,10 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3433
3450
  frameName,
3434
3451
  });
3435
3452
  });
3436
- const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
3437
- process.on("SIGINT", BEFORE_EXIT_FN$5);
3453
+ const listenGracefulShutdown$6 = functoolsKit.singleshot(() => {
3454
+ process.on("SIGINT", BEFORE_EXIT_FN$6);
3438
3455
  });
3439
- const main$e = async () => {
3456
+ const main$f = async () => {
3440
3457
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3441
3458
  return;
3442
3459
  }
@@ -3452,12 +3469,12 @@ const main$e = async () => {
3452
3469
  entryPoint && await flush(entryPoint);
3453
3470
  }
3454
3471
  await cli.backtestMainService.connect();
3455
- listenGracefulShutdown$5();
3472
+ listenGracefulShutdown$6();
3456
3473
  };
3457
- main$e();
3474
+ main$f();
3458
3475
 
3459
- const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3460
- process.off("SIGINT", BEFORE_EXIT_FN$4);
3476
+ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3477
+ process.off("SIGINT", BEFORE_EXIT_FN$5);
3461
3478
  const [running = null] = await BacktestKit.Walker.list();
3462
3479
  if (!running) {
3463
3480
  return;
@@ -3470,10 +3487,10 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3470
3487
  }
3471
3488
  BacktestKit.Walker.stop(symbol, { walkerName });
3472
3489
  });
3473
- const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
3474
- process.on("SIGINT", BEFORE_EXIT_FN$4);
3490
+ const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
3491
+ process.on("SIGINT", BEFORE_EXIT_FN$5);
3475
3492
  });
3476
- const main$d = async () => {
3493
+ const main$e = async () => {
3477
3494
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3478
3495
  return;
3479
3496
  }
@@ -3489,13 +3506,13 @@ const main$d = async () => {
3489
3506
  await flush(entryPoint);
3490
3507
  }
3491
3508
  }
3492
- listenGracefulShutdown$4();
3509
+ listenGracefulShutdown$5();
3493
3510
  await cli.walkerMainService.connect();
3494
3511
  };
3495
- main$d();
3512
+ main$e();
3496
3513
 
3497
- const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3498
- process.off("SIGINT", BEFORE_EXIT_FN$3);
3514
+ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3515
+ process.off("SIGINT", BEFORE_EXIT_FN$4);
3499
3516
  const [running = null] = await BacktestKit.Live.list();
3500
3517
  if (!running) {
3501
3518
  return;
@@ -3511,10 +3528,10 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3511
3528
  strategyName,
3512
3529
  });
3513
3530
  });
3514
- const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
3515
- process.on("SIGINT", BEFORE_EXIT_FN$3);
3531
+ const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
3532
+ process.on("SIGINT", BEFORE_EXIT_FN$4);
3516
3533
  });
3517
- const main$c = async () => {
3534
+ const main$d = async () => {
3518
3535
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3519
3536
  return;
3520
3537
  }
@@ -3526,12 +3543,12 @@ const main$c = async () => {
3526
3543
  return;
3527
3544
  }
3528
3545
  cli.paperMainService.connect();
3529
- listenGracefulShutdown$3();
3546
+ listenGracefulShutdown$4();
3530
3547
  };
3531
- main$c();
3548
+ main$d();
3532
3549
 
3533
- const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3534
- process.off("SIGINT", BEFORE_EXIT_FN$2);
3550
+ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3551
+ process.off("SIGINT", BEFORE_EXIT_FN$3);
3535
3552
  const [running = null] = await BacktestKit.Live.list();
3536
3553
  if (!running) {
3537
3554
  return;
@@ -3547,6 +3564,84 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3547
3564
  strategyName,
3548
3565
  });
3549
3566
  });
3567
+ const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
3568
+ process.on("SIGINT", BEFORE_EXIT_FN$3);
3569
+ });
3570
+ const main$c = async () => {
3571
+ if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
3572
+ return;
3573
+ }
3574
+ const { values } = getArgs();
3575
+ if (!values.live) {
3576
+ return;
3577
+ }
3578
+ if (values.entry) {
3579
+ return;
3580
+ }
3581
+ await cli.liveMainService.connect();
3582
+ listenGracefulShutdown$3();
3583
+ };
3584
+ main$c();
3585
+
3586
+ const stopBacktestList$1 = async () => {
3587
+ for (const item of await BacktestKit.Backtest.list()) {
3588
+ if (item.status === "fulfilled") {
3589
+ continue;
3590
+ }
3591
+ BacktestKit.Backtest.stop(item.symbol, {
3592
+ exchangeName: item.exchangeName,
3593
+ strategyName: item.strategyName,
3594
+ frameName: item.frameName,
3595
+ });
3596
+ }
3597
+ };
3598
+ const stopLiveList$1 = async () => {
3599
+ for (const item of await BacktestKit.Live.list()) {
3600
+ if (item.status === "fulfilled") {
3601
+ continue;
3602
+ }
3603
+ BacktestKit.Live.stop(item.symbol, {
3604
+ exchangeName: item.exchangeName,
3605
+ strategyName: item.strategyName,
3606
+ });
3607
+ }
3608
+ };
3609
+ const stopWalkerList$1 = async () => {
3610
+ for (const item of await BacktestKit.Walker.list()) {
3611
+ if (item.status === "fulfilled") {
3612
+ continue;
3613
+ }
3614
+ BacktestKit.Walker.stop(item.symbol, { walkerName: item.walkerName });
3615
+ }
3616
+ };
3617
+ const stopMain$1 = async () => {
3618
+ await stopBacktestList$1();
3619
+ await stopLiveList$1();
3620
+ await stopWalkerList$1();
3621
+ };
3622
+ const listenFinish$1 = functoolsKit.singleshot(() => {
3623
+ let disposeRef;
3624
+ const unBacktest = BacktestKit.listenDoneBacktest(() => {
3625
+ console.log("Backtest trading finished");
3626
+ disposeRef && disposeRef();
3627
+ });
3628
+ const unLive = BacktestKit.listenDoneLive(() => {
3629
+ console.log("Live trading finished");
3630
+ disposeRef && disposeRef();
3631
+ });
3632
+ const unWalker = BacktestKit.listenDoneWalker(() => {
3633
+ console.log("Walker comparison finished");
3634
+ disposeRef && disposeRef();
3635
+ });
3636
+ disposeRef = functoolsKit.compose(() => unBacktest(), () => unLive(), () => unWalker());
3637
+ BacktestKit.shutdown();
3638
+ });
3639
+ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3640
+ process.off("SIGINT", BEFORE_EXIT_FN$2);
3641
+ notifyShutdown();
3642
+ notifyKill();
3643
+ await stopMain$1();
3644
+ });
3550
3645
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
3551
3646
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3552
3647
  });
@@ -3555,14 +3650,56 @@ const main$b = async () => {
3555
3650
  return;
3556
3651
  }
3557
3652
  const { values } = getArgs();
3558
- if (!values.live) {
3653
+ if (!values.main) {
3559
3654
  return;
3560
3655
  }
3561
3656
  if (values.entry) {
3562
3657
  return;
3563
3658
  }
3564
- await cli.liveMainService.connect();
3659
+ const [entryPoint = null] = getPositionals();
3660
+ if (!entryPoint) {
3661
+ throw new Error("Entry point is required");
3662
+ }
3663
+ {
3664
+ const cwd = process.cwd();
3665
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3666
+ }
3667
+ await cli.configConnectionService.loadConfig("setup.config");
3668
+ {
3669
+ const loader = await cli.configConnectionService.loadConfig("loader.config");
3670
+ try {
3671
+ if (typeof loader === "function") {
3672
+ await loader();
3673
+ }
3674
+ if (typeof loader?.loader === "function") {
3675
+ await loader.loader();
3676
+ }
3677
+ }
3678
+ catch (error) {
3679
+ console.error("Module loader failed", error);
3680
+ kill(-1);
3681
+ return;
3682
+ }
3683
+ }
3684
+ {
3685
+ await cli.configService.waitForInit();
3686
+ Setup.enable();
3687
+ }
3688
+ const cwd = process.cwd();
3689
+ {
3690
+ const absolutePath = path.resolve(entryPoint);
3691
+ const moduleRoot = path.dirname(absolutePath);
3692
+ process.chdir(moduleRoot);
3693
+ cwd !== moduleRoot && Setup.update();
3694
+ dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
3695
+ }
3696
+ if (!values.noFlush) {
3697
+ await flush(path.resolve(cwd, entryPoint));
3698
+ }
3699
+ await cli.moduleConnectionService.loadModule("main.module");
3700
+ listenFinish$1();
3565
3701
  listenGracefulShutdown$2();
3702
+ await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
3566
3703
  };
3567
3704
  main$b();
3568
3705
 
@@ -3571,13 +3708,29 @@ const MODE_MODULE = {
3571
3708
  live: "live.module",
3572
3709
  paper: "paper.module",
3573
3710
  walker: "walker.module",
3711
+ main: "main.module",
3574
3712
  };
3713
+ const MODE_LIST = ["backtest", "live", "paper", "walker", "main"];
3575
3714
  const resolveMode = (values) => {
3576
- const enabled = ["backtest", "live", "paper", "walker"].filter((mode) => Boolean(values[mode]));
3577
- if (enabled.length !== 1) {
3715
+ if (MODE_LIST.filter((mode) => values[mode]).length > 1) {
3578
3716
  return null;
3579
3717
  }
3580
- return enabled[0];
3718
+ if (values.main) {
3719
+ return "main";
3720
+ }
3721
+ if (values.backtest) {
3722
+ return "backtest";
3723
+ }
3724
+ if (values.live) {
3725
+ return "live";
3726
+ }
3727
+ if (values.paper) {
3728
+ return "paper";
3729
+ }
3730
+ if (values.walker) {
3731
+ return "walker";
3732
+ }
3733
+ return null;
3581
3734
  };
3582
3735
  const stopBacktestList = async () => {
3583
3736
  for (const item of await BacktestKit.Backtest.list()) {
@@ -3610,11 +3763,17 @@ const stopWalkerList = async () => {
3610
3763
  BacktestKit.Walker.stop(item.symbol, { walkerName: item.walkerName });
3611
3764
  }
3612
3765
  };
3766
+ const stopMain = async () => {
3767
+ await stopBacktestList();
3768
+ await stopLiveList();
3769
+ await stopWalkerList();
3770
+ };
3613
3771
  const MODE_STOP = {
3614
3772
  backtest: stopBacktestList,
3615
3773
  live: stopLiveList,
3616
3774
  paper: stopLiveList,
3617
3775
  walker: stopWalkerList,
3776
+ main: stopMain,
3618
3777
  };
3619
3778
  const listenFinish = functoolsKit.singleshot(() => {
3620
3779
  let disposeRef;
@@ -3655,12 +3814,12 @@ const main$a = async () => {
3655
3814
  }
3656
3815
  const mode = resolveMode(values);
3657
3816
  if (!mode) {
3658
- console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
3817
+ console.error("--entry requires exactly one of --backtest, --live, --paper, --walker, --main");
3659
3818
  kill(1);
3660
3819
  return;
3661
3820
  }
3662
3821
  const entryPoints = getPositionals();
3663
- if (!entryPoints.length) {
3822
+ if (mode !== "main" && !entryPoints.length) {
3664
3823
  throw new Error("At least one entry point is required");
3665
3824
  }
3666
3825
  {
@@ -3707,12 +3866,18 @@ const main$a = async () => {
3707
3866
  }
3708
3867
  await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
3709
3868
  listenFinish();
3710
- createGracefulShutdown(mode)();
3869
+ {
3870
+ const listenShutdown = createGracefulShutdown(mode);
3871
+ listenShutdown();
3872
+ }
3711
3873
  let absolutePath;
3712
3874
  for (const entryPoint of entryPoints) {
3713
3875
  absolutePath = path.resolve(cwd, entryPoint);
3714
3876
  await cli.resolveService.attachEntry(absolutePath);
3715
3877
  }
3878
+ if (mode === "main") {
3879
+ return;
3880
+ }
3716
3881
  await getEntrySubject().next(absolutePath);
3717
3882
  };
3718
3883
  main$a();
@@ -4324,183 +4489,204 @@ const main$2 = async () => {
4324
4489
  main$2();
4325
4490
 
4326
4491
  const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
4327
- const HELP_TEXT = `
4328
- Usage:
4329
- node index.mjs --<mode> [flags] [entry-point]
4330
-
4331
- Modes:
4332
-
4333
- --backtest <entry> Run strategy against historical candle data
4334
- --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4335
- --paper <entry> Paper trading (live prices, no real orders)
4336
- --live <entry> Live trading with real orders
4337
- --pine <entry> Execute a local .pine indicator file
4338
- --editor Open the Pine Script visual editor in the browser
4339
- --dump Fetch and save raw OHLCV candles
4340
- --pnldebug Simulate PnL per minute for a given entry price and direction
4341
- --brokerdebug Fire a single broker commit against the live broker adapter
4342
- --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4343
- --init Scaffold a new project in the current directory
4344
- --docker Scaffold a Docker workspace for running strategies in a container
4345
- --help Print this help message
4346
-
4347
- Backtest flags:
4348
-
4349
- --symbol <string> Trading pair (default: BTCUSDT)
4350
- --strategy <string> Strategy name from addStrategySchema (default: first registered)
4351
- --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4352
- --frame <string> Frame name from addFrameSchema (default: first registered)
4353
- --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4354
- --noCache Skip candle cache warming before the run
4355
- --noFlush Skip removing report/log/markdown/agent folders before backtest run
4356
- --verbose Log every candle fetch to stdout
4357
- --ui Start web dashboard at http://localhost:60050
4358
- --telegram Send trade notifications to Telegram
4359
-
4360
- Walker flags (--walker):
4361
-
4362
- --symbol <string> Trading pair (default: BTCUSDT)
4363
- --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4364
- --noCache Skip candle cache warming before the run
4365
- --noFlush Skip removing report/log/markdown/agent folders before walker run
4366
- --verbose Log every candle fetch to stdout
4367
- --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4368
- --json Save results as JSON to ./dump/<output>.json
4369
- --markdown Save report as Markdown to ./dump/<output>.md
4370
-
4371
- Each positional argument is a strategy entry point. All strategy files are loaded without
4372
- changing process.cwd() .env is read from the working directory only.
4373
- addWalkerSchema is called automatically using the registered exchange and frame.
4374
- After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4375
-
4376
- Module file ./modules/walker.module is loaded automatically if it exists.
4377
-
4378
- Paper / Live flags:
4379
-
4380
- --symbol <string> Trading pair (default: BTCUSDT)
4381
- --strategy <string> Strategy name (default: first registered)
4382
- --exchange <string> Exchange name (default: first registered)
4383
- --verbose Log every candle fetch to stdout
4384
- --ui Start web dashboard
4385
- --telegram Send Telegram notifications
4386
-
4387
- PineScript flags (--pine):
4388
-
4389
- --symbol <string> Trading pair (default: BTCUSDT)
4390
- --timeframe <string> Candle interval (default: 15m)
4391
- --limit <string> Number of candles to fetch (default: 250)
4392
- --when <string> End date ISO 8601 or Unix ms (default: now)
4393
- --exchange <string> Exchange name (default: first registered)
4394
- --output <string> Output file base name without extension
4395
- --json Save output as JSON array to <pine-dir>/dump/<output>.json
4396
- --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4397
- --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4398
-
4399
- Only plot() calls with display=display.data_window produce output columns.
4400
- Module file ./modules/pine.module is loaded automatically if it exists.
4401
-
4402
- Candle dump flags (--dump):
4403
-
4404
- --symbol <string> Trading pair (default: BTCUSDT)
4405
- --timeframe <string> Candle interval (default: 15m)
4406
- --limit <string> Number of candles (default: 250)
4407
- --when <string> End date — ISO 8601 or Unix ms (default: now)
4408
- --exchange <string> Exchange name (default: first registered)
4409
- --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4410
- --json Save as JSON array to ./dump/<output>.json
4411
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4412
-
4413
- Module file ./modules/dump.module is loaded automatically if it exists.
4414
-
4415
- PnL debug flags (--pnldebug):
4416
-
4417
- --symbol <string> Trading pair (default: BTCUSDT)
4418
- --priceopen <number> Entry price (required)
4419
- --direction <string> Position direction: long or short (default: long)
4420
- --when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
4421
- --minutes <string> Number of 1m candles to simulate (default: 60)
4422
- --exchange <string> Exchange name (default: first registered)
4423
- --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4424
- --json Save as JSON array to ./dump/<output>.json
4425
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4426
- --markdown Save as Markdown table to ./dump/<output>.md
4427
-
4428
- Module file ./modules/pnldebug.module is loaded automatically if it exists.
4429
-
4430
- Broker debug flags (--brokerdebug):
4431
-
4432
- --symbol <string> Trading pair (default: BTCUSDT)
4433
- --exchange <string> Exchange name (default: first registered)
4434
- --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4435
- partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4436
- (default: signal-open)
4437
-
4438
- Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4439
- the selected broker commit with synthetic payload values derived from current price.
4440
-
4441
- Flush flags (--flush):
4442
-
4443
- One or more positional entry points. For each entry point the following
4444
- subdirectories are removed from <entry-dir>/dump/:
4445
-
4446
- report log markdown agent
4447
-
4448
- Init flags (--init):
4449
-
4450
- --output <string> Target directory name (default: backtest-kit-project)
4451
-
4452
- Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4453
-
4454
- Docker flags (--docker):
4455
-
4456
- --output <string> Target directory name (default: backtest-kit-docker)
4457
-
4458
- Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4459
- tsconfig.json, and a sample strategy under content/. Run npm install then
4460
- docker compose up to start the container.
4461
-
4462
- Module hooks (loaded automatically by each mode):
4463
-
4464
- modules/backtest.module --backtest Broker adapter for backtest
4465
- modules/walker.module --walker Broker adapter for walker comparison
4466
- modules/paper.module --paper Broker adapter for paper trading
4467
- modules/live.module --live Broker adapter for live trading
4468
- modules/pine.module --pine Exchange schema for PineScript runs
4469
- modules/editor.module --editor Exchange schema for the visual Pine editor
4470
- modules/dump.module --dump Exchange schema for candle dumps
4471
- modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4472
- modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4473
-
4474
- --flush has no associated module. It only removes dump subdirectories.
4475
-
4476
- Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4477
-
4478
- Environment variables:
4479
-
4480
- CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4481
- CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4482
- CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4483
- CC_WWWROOT_PORT UI server port (default: 60050)
4484
-
4485
- Examples:
4486
-
4487
- node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4488
- node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4489
- node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4490
- node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4491
- node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4492
- node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4493
- node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4494
- node ${ENTRY_PATH} --editor
4495
- node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4496
- node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4497
- node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4498
- node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4499
- node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4500
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4501
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4502
- node ${ENTRY_PATH} --init --output my-trading-bot
4503
- node ${ENTRY_PATH} --docker --output my-docker-workspace
4492
+ const HELP_TEXT = `
4493
+ Usage:
4494
+ node index.mjs --<mode> [flags] [entry-point]
4495
+
4496
+ Modes:
4497
+
4498
+ --backtest <entry> Run strategy against historical candle data
4499
+ --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4500
+ --paper <entry> Paper trading (live prices, no real orders)
4501
+ --live <entry> Live trading with real orders
4502
+ --main <entry> Run an entry point with prepared environment, no trading harness
4503
+ --pine <entry> Execute a local .pine indicator file
4504
+ --editor Open the Pine Script visual editor in the browser
4505
+ --dump Fetch and save raw OHLCV candles
4506
+ --pnldebug Simulate PnL per minute for a given entry price and direction
4507
+ --brokerdebug Fire a single broker commit against the live broker adapter
4508
+ --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4509
+ --init Scaffold a new project in the current directory
4510
+ --docker Scaffold a Docker workspace for running strategies in a container
4511
+ --help Print this help message
4512
+
4513
+ Backtest flags:
4514
+
4515
+ --symbol <string> Trading pair (default: BTCUSDT)
4516
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
4517
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4518
+ --frame <string> Frame name from addFrameSchema (default: first registered)
4519
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4520
+ --noCache Skip candle cache warming before the run
4521
+ --noFlush Skip removing report/log/markdown/agent folders before backtest run
4522
+ --verbose Log every candle fetch to stdout
4523
+ --ui Start web dashboard at http://localhost:60050
4524
+ --telegram Send trade notifications to Telegram
4525
+
4526
+ Walker flags (--walker):
4527
+
4528
+ --symbol <string> Trading pair (default: BTCUSDT)
4529
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4530
+ --noCache Skip candle cache warming before the run
4531
+ --noFlush Skip removing report/log/markdown/agent folders before walker run
4532
+ --verbose Log every candle fetch to stdout
4533
+ --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4534
+ --json Save results as JSON to ./dump/<output>.json
4535
+ --markdown Save report as Markdown to ./dump/<output>.md
4536
+
4537
+ Each positional argument is a strategy entry point. All strategy files are loaded without
4538
+ changing process.cwd() — .env is read from the working directory only.
4539
+ addWalkerSchema is called automatically using the registered exchange and frame.
4540
+ After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4541
+
4542
+ Module file ./modules/walker.module is loaded automatically if it exists.
4543
+
4544
+ Paper / Live flags:
4545
+
4546
+ --symbol <string> Trading pair (default: BTCUSDT)
4547
+ --strategy <string> Strategy name (default: first registered)
4548
+ --exchange <string> Exchange name (default: first registered)
4549
+ --verbose Log every candle fetch to stdout
4550
+ --ui Start web dashboard
4551
+ --telegram Send Telegram notifications
4552
+
4553
+ Main flags (--main):
4554
+
4555
+ --noFlush Skip removing report/log/markdown/agent folders before the run
4556
+
4557
+ Prepares the runtime environment (loads .env, setup.config, loader.config and
4558
+ modules/main.module) and runs the single positional entry point — but does NOT
4559
+ start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
4560
+ Backtest/Live/Walker.background; the entry point decides what to run.
4561
+
4562
+ Exactly one positional entry point is required. process.cwd() is changed to the
4563
+ entry point directory and its local .env is loaded.
4564
+
4565
+ Backtest, Live and Walker runs started from userspace are still tracked: the run
4566
+ finishes automatically when one of them completes, Ctrl+C stops any active run,
4567
+ and a second Ctrl+C force-quits.
4568
+
4569
+ Module file ./modules/main.module is loaded automatically if it exists.
4570
+
4571
+ PineScript flags (--pine):
4572
+
4573
+ --symbol <string> Trading pair (default: BTCUSDT)
4574
+ --timeframe <string> Candle interval (default: 15m)
4575
+ --limit <string> Number of candles to fetch (default: 250)
4576
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4577
+ --exchange <string> Exchange name (default: first registered)
4578
+ --output <string> Output file base name without extension
4579
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
4580
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4581
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4582
+
4583
+ Only plot() calls with display=display.data_window produce output columns.
4584
+ Module file ./modules/pine.module is loaded automatically if it exists.
4585
+
4586
+ Candle dump flags (--dump):
4587
+
4588
+ --symbol <string> Trading pair (default: BTCUSDT)
4589
+ --timeframe <string> Candle interval (default: 15m)
4590
+ --limit <string> Number of candles (default: 250)
4591
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4592
+ --exchange <string> Exchange name (default: first registered)
4593
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4594
+ --json Save as JSON array to ./dump/<output>.json
4595
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4596
+
4597
+ Module file ./modules/dump.module is loaded automatically if it exists.
4598
+
4599
+ PnL debug flags (--pnldebug):
4600
+
4601
+ --symbol <string> Trading pair (default: BTCUSDT)
4602
+ --priceopen <number> Entry price (required)
4603
+ --direction <string> Position direction: long or short (default: long)
4604
+ --when <string> Start timestamp ISO 8601 or Unix ms (default: now)
4605
+ --minutes <string> Number of 1m candles to simulate (default: 60)
4606
+ --exchange <string> Exchange name (default: first registered)
4607
+ --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4608
+ --json Save as JSON array to ./dump/<output>.json
4609
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4610
+ --markdown Save as Markdown table to ./dump/<output>.md
4611
+
4612
+ Module file ./modules/pnldebug.module is loaded automatically if it exists.
4613
+
4614
+ Broker debug flags (--brokerdebug):
4615
+
4616
+ --symbol <string> Trading pair (default: BTCUSDT)
4617
+ --exchange <string> Exchange name (default: first registered)
4618
+ --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4619
+ partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4620
+ (default: signal-open)
4621
+
4622
+ Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4623
+ the selected broker commit with synthetic payload values derived from current price.
4624
+
4625
+ Flush flags (--flush):
4626
+
4627
+ One or more positional entry points. For each entry point the following
4628
+ subdirectories are removed from <entry-dir>/dump/:
4629
+
4630
+ report log markdown agent
4631
+
4632
+ Init flags (--init):
4633
+
4634
+ --output <string> Target directory name (default: backtest-kit-project)
4635
+
4636
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4637
+
4638
+ Docker flags (--docker):
4639
+
4640
+ --output <string> Target directory name (default: backtest-kit-docker)
4641
+
4642
+ Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4643
+ tsconfig.json, and a sample strategy under content/. Run npm install then
4644
+ docker compose up to start the container.
4645
+
4646
+ Module hooks (loaded automatically by each mode):
4647
+
4648
+ modules/backtest.module --backtest Broker adapter for backtest
4649
+ modules/walker.module --walker Broker adapter for walker comparison
4650
+ modules/paper.module --paper Broker adapter for paper trading
4651
+ modules/live.module --live Broker adapter for live trading
4652
+ modules/main.module --main Environment setup for a custom entry point
4653
+ modules/pine.module --pine Exchange schema for PineScript runs
4654
+ modules/editor.module --editor Exchange schema for the visual Pine editor
4655
+ modules/dump.module --dump Exchange schema for candle dumps
4656
+ modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4657
+ modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4658
+
4659
+ --flush has no associated module. It only removes dump subdirectories.
4660
+
4661
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4662
+
4663
+ Environment variables:
4664
+
4665
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4666
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4667
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4668
+ CC_WWWROOT_PORT UI server port (default: 60050)
4669
+
4670
+ Examples:
4671
+
4672
+ node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4673
+ node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4674
+ node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4675
+ node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4676
+ node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4677
+ node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4678
+ node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
4679
+ node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4680
+ node ${ENTRY_PATH} --editor
4681
+ node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4682
+ node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4683
+ node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4684
+ node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4685
+ node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4686
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4687
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4688
+ node ${ENTRY_PATH} --init --output my-trading-bot
4689
+ node ${ENTRY_PATH} --docker --output my-docker-workspace
4504
4690
  `.trimStart();
4505
4691
  const main$1 = async () => {
4506
4692
  if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
@@ -4510,7 +4696,7 @@ const main$1 = async () => {
4510
4696
  if (!values.help) {
4511
4697
  return;
4512
4698
  }
4513
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n\n`);
4699
+ process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n\n`);
4514
4700
  process.stdout.write(HELP_TEXT);
4515
4701
  process.exit(0);
4516
4702
  };
@@ -4524,7 +4710,7 @@ const main = async () => {
4524
4710
  if (!values.version) {
4525
4711
  return;
4526
4712
  }
4527
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
4713
+ process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n`);
4528
4714
  process.exit(0);
4529
4715
  };
4530
4716
  main();
@@ -4546,17 +4732,17 @@ async function run(mode, args) {
4546
4732
  }
4547
4733
  if (mode === "backtest") {
4548
4734
  await cli.backtestMainService.run(args);
4549
- listenGracefulShutdown$5();
4735
+ listenGracefulShutdown$6();
4550
4736
  return;
4551
4737
  }
4552
4738
  if (mode === "paper") {
4553
4739
  await cli.paperMainService.run(args);
4554
- listenGracefulShutdown$3();
4740
+ listenGracefulShutdown$4();
4555
4741
  return;
4556
4742
  }
4557
4743
  if (mode === "live") {
4558
4744
  await cli.liveMainService.run(args);
4559
- listenGracefulShutdown$2();
4745
+ listenGracefulShutdown$3();
4560
4746
  return;
4561
4747
  }
4562
4748
  throw new Error(`Invalid mode: ${mode}`);