@backtest-kit/cli 12.4.0 → 12.5.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",
@@ -3358,14 +3362,14 @@ const cli = {
3358
3362
  };
3359
3363
  init();
3360
3364
 
3361
- const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3365
+ const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3362
3366
  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
3367
+ const HELP_TEXT$1 = `
3368
+ Example:
3369
+
3370
+ node ${ENTRY_PATH$1} --help
3367
3371
  `.trimStart();
3368
- const main$g = async () => {
3372
+ const main$h = async () => {
3369
3373
  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
3374
  return;
3371
3375
  }
@@ -3373,14 +3377,14 @@ const main$g = async () => {
3373
3377
  if (MODES.some((mode) => values[mode])) {
3374
3378
  return;
3375
3379
  }
3376
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
3380
+ process.stdout.write(`@backtest-kit/cli ${"12.5.0"}\n`);
3377
3381
  process.stdout.write("\n");
3378
3382
  process.stdout.write(`Run with --help to see available commands.\n`);
3379
3383
  process.stdout.write("\n");
3380
3384
  process.stdout.write(HELP_TEXT$1);
3381
3385
  process.exit(0);
3382
3386
  };
3383
- main$g();
3387
+ main$h();
3384
3388
 
3385
3389
  const notifyShutdown = functoolsKit.singleshot(async () => {
3386
3390
  console.log("Graceful shutdown initiated.");
@@ -3396,7 +3400,7 @@ const flush = async (entryPoint) => {
3396
3400
  console.log(`Removed: ${target}`);
3397
3401
  }
3398
3402
  };
3399
- const main$f = async () => {
3403
+ const main$g = async () => {
3400
3404
  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
3405
  return;
3402
3406
  }
@@ -3413,10 +3417,10 @@ const main$f = async () => {
3413
3417
  }
3414
3418
  process.exit(0);
3415
3419
  };
3416
- main$f();
3420
+ main$g();
3417
3421
 
3418
- const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3419
- process.off("SIGINT", BEFORE_EXIT_FN$5);
3422
+ const BEFORE_EXIT_FN$6 = functoolsKit.singleshot(async () => {
3423
+ process.off("SIGINT", BEFORE_EXIT_FN$6);
3420
3424
  const [running = null] = await BacktestKit.Backtest.list();
3421
3425
  if (!running) {
3422
3426
  return;
@@ -3433,10 +3437,10 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3433
3437
  frameName,
3434
3438
  });
3435
3439
  });
3436
- const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
3437
- process.on("SIGINT", BEFORE_EXIT_FN$5);
3440
+ const listenGracefulShutdown$6 = functoolsKit.singleshot(() => {
3441
+ process.on("SIGINT", BEFORE_EXIT_FN$6);
3438
3442
  });
3439
- const main$e = async () => {
3443
+ const main$f = async () => {
3440
3444
  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
3445
  return;
3442
3446
  }
@@ -3452,12 +3456,12 @@ const main$e = async () => {
3452
3456
  entryPoint && await flush(entryPoint);
3453
3457
  }
3454
3458
  await cli.backtestMainService.connect();
3455
- listenGracefulShutdown$5();
3459
+ listenGracefulShutdown$6();
3456
3460
  };
3457
- main$e();
3461
+ main$f();
3458
3462
 
3459
- const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3460
- process.off("SIGINT", BEFORE_EXIT_FN$4);
3463
+ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3464
+ process.off("SIGINT", BEFORE_EXIT_FN$5);
3461
3465
  const [running = null] = await BacktestKit.Walker.list();
3462
3466
  if (!running) {
3463
3467
  return;
@@ -3470,10 +3474,10 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3470
3474
  }
3471
3475
  BacktestKit.Walker.stop(symbol, { walkerName });
3472
3476
  });
3473
- const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
3474
- process.on("SIGINT", BEFORE_EXIT_FN$4);
3477
+ const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
3478
+ process.on("SIGINT", BEFORE_EXIT_FN$5);
3475
3479
  });
3476
- const main$d = async () => {
3480
+ const main$e = async () => {
3477
3481
  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
3482
  return;
3479
3483
  }
@@ -3489,13 +3493,13 @@ const main$d = async () => {
3489
3493
  await flush(entryPoint);
3490
3494
  }
3491
3495
  }
3492
- listenGracefulShutdown$4();
3496
+ listenGracefulShutdown$5();
3493
3497
  await cli.walkerMainService.connect();
3494
3498
  };
3495
- main$d();
3499
+ main$e();
3496
3500
 
3497
- const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3498
- process.off("SIGINT", BEFORE_EXIT_FN$3);
3501
+ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3502
+ process.off("SIGINT", BEFORE_EXIT_FN$4);
3499
3503
  const [running = null] = await BacktestKit.Live.list();
3500
3504
  if (!running) {
3501
3505
  return;
@@ -3511,10 +3515,10 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3511
3515
  strategyName,
3512
3516
  });
3513
3517
  });
3514
- const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
3515
- process.on("SIGINT", BEFORE_EXIT_FN$3);
3518
+ const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
3519
+ process.on("SIGINT", BEFORE_EXIT_FN$4);
3516
3520
  });
3517
- const main$c = async () => {
3521
+ const main$d = async () => {
3518
3522
  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
3523
  return;
3520
3524
  }
@@ -3526,12 +3530,12 @@ const main$c = async () => {
3526
3530
  return;
3527
3531
  }
3528
3532
  cli.paperMainService.connect();
3529
- listenGracefulShutdown$3();
3533
+ listenGracefulShutdown$4();
3530
3534
  };
3531
- main$c();
3535
+ main$d();
3532
3536
 
3533
- const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3534
- process.off("SIGINT", BEFORE_EXIT_FN$2);
3537
+ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3538
+ process.off("SIGINT", BEFORE_EXIT_FN$3);
3535
3539
  const [running = null] = await BacktestKit.Live.list();
3536
3540
  if (!running) {
3537
3541
  return;
@@ -3547,6 +3551,84 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3547
3551
  strategyName,
3548
3552
  });
3549
3553
  });
3554
+ const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
3555
+ process.on("SIGINT", BEFORE_EXIT_FN$3);
3556
+ });
3557
+ const main$c = async () => {
3558
+ 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)))) {
3559
+ return;
3560
+ }
3561
+ const { values } = getArgs();
3562
+ if (!values.live) {
3563
+ return;
3564
+ }
3565
+ if (values.entry) {
3566
+ return;
3567
+ }
3568
+ await cli.liveMainService.connect();
3569
+ listenGracefulShutdown$3();
3570
+ };
3571
+ main$c();
3572
+
3573
+ const stopBacktestList$1 = async () => {
3574
+ for (const item of await BacktestKit.Backtest.list()) {
3575
+ if (item.status === "fulfilled") {
3576
+ continue;
3577
+ }
3578
+ BacktestKit.Backtest.stop(item.symbol, {
3579
+ exchangeName: item.exchangeName,
3580
+ strategyName: item.strategyName,
3581
+ frameName: item.frameName,
3582
+ });
3583
+ }
3584
+ };
3585
+ const stopLiveList$1 = async () => {
3586
+ for (const item of await BacktestKit.Live.list()) {
3587
+ if (item.status === "fulfilled") {
3588
+ continue;
3589
+ }
3590
+ BacktestKit.Live.stop(item.symbol, {
3591
+ exchangeName: item.exchangeName,
3592
+ strategyName: item.strategyName,
3593
+ });
3594
+ }
3595
+ };
3596
+ const stopWalkerList$1 = async () => {
3597
+ for (const item of await BacktestKit.Walker.list()) {
3598
+ if (item.status === "fulfilled") {
3599
+ continue;
3600
+ }
3601
+ BacktestKit.Walker.stop(item.symbol, { walkerName: item.walkerName });
3602
+ }
3603
+ };
3604
+ const stopMain$1 = async () => {
3605
+ await stopBacktestList$1();
3606
+ await stopLiveList$1();
3607
+ await stopWalkerList$1();
3608
+ };
3609
+ const listenFinish$1 = functoolsKit.singleshot(() => {
3610
+ let disposeRef;
3611
+ const unBacktest = BacktestKit.listenDoneBacktest(() => {
3612
+ console.log("Backtest trading finished");
3613
+ disposeRef && disposeRef();
3614
+ });
3615
+ const unLive = BacktestKit.listenDoneLive(() => {
3616
+ console.log("Live trading finished");
3617
+ disposeRef && disposeRef();
3618
+ });
3619
+ const unWalker = BacktestKit.listenDoneWalker(() => {
3620
+ console.log("Walker comparison finished");
3621
+ disposeRef && disposeRef();
3622
+ });
3623
+ disposeRef = functoolsKit.compose(() => unBacktest(), () => unLive(), () => unWalker());
3624
+ BacktestKit.shutdown();
3625
+ });
3626
+ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3627
+ process.off("SIGINT", BEFORE_EXIT_FN$2);
3628
+ notifyShutdown();
3629
+ notifyKill();
3630
+ await stopMain$1();
3631
+ });
3550
3632
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
3551
3633
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3552
3634
  });
@@ -3555,14 +3637,56 @@ const main$b = async () => {
3555
3637
  return;
3556
3638
  }
3557
3639
  const { values } = getArgs();
3558
- if (!values.live) {
3640
+ if (!values.main) {
3559
3641
  return;
3560
3642
  }
3561
3643
  if (values.entry) {
3562
3644
  return;
3563
3645
  }
3564
- await cli.liveMainService.connect();
3646
+ const [entryPoint = null] = getPositionals();
3647
+ if (!entryPoint) {
3648
+ throw new Error("Entry point is required");
3649
+ }
3650
+ {
3651
+ const cwd = process.cwd();
3652
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3653
+ }
3654
+ await cli.configConnectionService.loadConfig("setup.config");
3655
+ {
3656
+ const loader = await cli.configConnectionService.loadConfig("loader.config");
3657
+ try {
3658
+ if (typeof loader === "function") {
3659
+ await loader();
3660
+ }
3661
+ if (typeof loader?.loader === "function") {
3662
+ await loader.loader();
3663
+ }
3664
+ }
3665
+ catch (error) {
3666
+ console.error("Module loader failed", error);
3667
+ kill(-1);
3668
+ return;
3669
+ }
3670
+ }
3671
+ {
3672
+ await cli.configService.waitForInit();
3673
+ Setup.enable();
3674
+ }
3675
+ const cwd = process.cwd();
3676
+ {
3677
+ const absolutePath = path.resolve(entryPoint);
3678
+ const moduleRoot = path.dirname(absolutePath);
3679
+ process.chdir(moduleRoot);
3680
+ cwd !== moduleRoot && Setup.update();
3681
+ dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
3682
+ }
3683
+ if (!values.noFlush) {
3684
+ await flush(path.resolve(cwd, entryPoint));
3685
+ }
3686
+ await cli.moduleConnectionService.loadModule("main.module");
3687
+ listenFinish$1();
3565
3688
  listenGracefulShutdown$2();
3689
+ await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
3566
3690
  };
3567
3691
  main$b();
3568
3692
 
@@ -3571,13 +3695,29 @@ const MODE_MODULE = {
3571
3695
  live: "live.module",
3572
3696
  paper: "paper.module",
3573
3697
  walker: "walker.module",
3698
+ main: "main.module",
3574
3699
  };
3700
+ const MODE_LIST = ["backtest", "live", "paper", "walker", "main"];
3575
3701
  const resolveMode = (values) => {
3576
- const enabled = ["backtest", "live", "paper", "walker"].filter((mode) => Boolean(values[mode]));
3577
- if (enabled.length !== 1) {
3702
+ if (MODE_LIST.filter((mode) => values[mode]).length > 1) {
3578
3703
  return null;
3579
3704
  }
3580
- return enabled[0];
3705
+ if (values.main) {
3706
+ return "main";
3707
+ }
3708
+ if (values.backtest) {
3709
+ return "backtest";
3710
+ }
3711
+ if (values.live) {
3712
+ return "live";
3713
+ }
3714
+ if (values.paper) {
3715
+ return "paper";
3716
+ }
3717
+ if (values.walker) {
3718
+ return "walker";
3719
+ }
3720
+ return null;
3581
3721
  };
3582
3722
  const stopBacktestList = async () => {
3583
3723
  for (const item of await BacktestKit.Backtest.list()) {
@@ -3610,11 +3750,17 @@ const stopWalkerList = async () => {
3610
3750
  BacktestKit.Walker.stop(item.symbol, { walkerName: item.walkerName });
3611
3751
  }
3612
3752
  };
3753
+ const stopMain = async () => {
3754
+ await stopBacktestList();
3755
+ await stopLiveList();
3756
+ await stopWalkerList();
3757
+ };
3613
3758
  const MODE_STOP = {
3614
3759
  backtest: stopBacktestList,
3615
3760
  live: stopLiveList,
3616
3761
  paper: stopLiveList,
3617
3762
  walker: stopWalkerList,
3763
+ main: stopMain,
3618
3764
  };
3619
3765
  const listenFinish = functoolsKit.singleshot(() => {
3620
3766
  let disposeRef;
@@ -3655,12 +3801,12 @@ const main$a = async () => {
3655
3801
  }
3656
3802
  const mode = resolveMode(values);
3657
3803
  if (!mode) {
3658
- console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
3804
+ console.error("--entry requires exactly one of --backtest, --live, --paper, --walker, --main");
3659
3805
  kill(1);
3660
3806
  return;
3661
3807
  }
3662
3808
  const entryPoints = getPositionals();
3663
- if (!entryPoints.length) {
3809
+ if (mode !== "main" && !entryPoints.length) {
3664
3810
  throw new Error("At least one entry point is required");
3665
3811
  }
3666
3812
  {
@@ -3707,12 +3853,18 @@ const main$a = async () => {
3707
3853
  }
3708
3854
  await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
3709
3855
  listenFinish();
3710
- createGracefulShutdown(mode)();
3856
+ {
3857
+ const listenShutdown = createGracefulShutdown(mode);
3858
+ listenShutdown();
3859
+ }
3711
3860
  let absolutePath;
3712
3861
  for (const entryPoint of entryPoints) {
3713
3862
  absolutePath = path.resolve(cwd, entryPoint);
3714
3863
  await cli.resolveService.attachEntry(absolutePath);
3715
3864
  }
3865
+ if (mode === "main") {
3866
+ return;
3867
+ }
3716
3868
  await getEntrySubject().next(absolutePath);
3717
3869
  };
3718
3870
  main$a();
@@ -4324,183 +4476,204 @@ const main$2 = async () => {
4324
4476
  main$2();
4325
4477
 
4326
4478
  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
4479
+ const HELP_TEXT = `
4480
+ Usage:
4481
+ node index.mjs --<mode> [flags] [entry-point]
4482
+
4483
+ Modes:
4484
+
4485
+ --backtest <entry> Run strategy against historical candle data
4486
+ --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4487
+ --paper <entry> Paper trading (live prices, no real orders)
4488
+ --live <entry> Live trading with real orders
4489
+ --main <entry> Run an entry point with prepared environment, no trading harness
4490
+ --pine <entry> Execute a local .pine indicator file
4491
+ --editor Open the Pine Script visual editor in the browser
4492
+ --dump Fetch and save raw OHLCV candles
4493
+ --pnldebug Simulate PnL per minute for a given entry price and direction
4494
+ --brokerdebug Fire a single broker commit against the live broker adapter
4495
+ --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4496
+ --init Scaffold a new project in the current directory
4497
+ --docker Scaffold a Docker workspace for running strategies in a container
4498
+ --help Print this help message
4499
+
4500
+ Backtest flags:
4501
+
4502
+ --symbol <string> Trading pair (default: BTCUSDT)
4503
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
4504
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4505
+ --frame <string> Frame name from addFrameSchema (default: first registered)
4506
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4507
+ --noCache Skip candle cache warming before the run
4508
+ --noFlush Skip removing report/log/markdown/agent folders before backtest run
4509
+ --verbose Log every candle fetch to stdout
4510
+ --ui Start web dashboard at http://localhost:60050
4511
+ --telegram Send trade notifications to Telegram
4512
+
4513
+ Walker flags (--walker):
4514
+
4515
+ --symbol <string> Trading pair (default: BTCUSDT)
4516
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4517
+ --noCache Skip candle cache warming before the run
4518
+ --noFlush Skip removing report/log/markdown/agent folders before walker run
4519
+ --verbose Log every candle fetch to stdout
4520
+ --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4521
+ --json Save results as JSON to ./dump/<output>.json
4522
+ --markdown Save report as Markdown to ./dump/<output>.md
4523
+
4524
+ Each positional argument is a strategy entry point. All strategy files are loaded without
4525
+ changing process.cwd() — .env is read from the working directory only.
4526
+ addWalkerSchema is called automatically using the registered exchange and frame.
4527
+ After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4528
+
4529
+ Module file ./modules/walker.module is loaded automatically if it exists.
4530
+
4531
+ Paper / Live flags:
4532
+
4533
+ --symbol <string> Trading pair (default: BTCUSDT)
4534
+ --strategy <string> Strategy name (default: first registered)
4535
+ --exchange <string> Exchange name (default: first registered)
4536
+ --verbose Log every candle fetch to stdout
4537
+ --ui Start web dashboard
4538
+ --telegram Send Telegram notifications
4539
+
4540
+ Main flags (--main):
4541
+
4542
+ --noFlush Skip removing report/log/markdown/agent folders before the run
4543
+
4544
+ Prepares the runtime environment (loads .env, setup.config, loader.config and
4545
+ modules/main.module) and runs the single positional entry point — but does NOT
4546
+ start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
4547
+ Backtest/Live/Walker.background; the entry point decides what to run.
4548
+
4549
+ Exactly one positional entry point is required. process.cwd() is changed to the
4550
+ entry point directory and its local .env is loaded.
4551
+
4552
+ Backtest, Live and Walker runs started from userspace are still tracked: the run
4553
+ finishes automatically when one of them completes, Ctrl+C stops any active run,
4554
+ and a second Ctrl+C force-quits.
4555
+
4556
+ Module file ./modules/main.module is loaded automatically if it exists.
4557
+
4558
+ PineScript flags (--pine):
4559
+
4560
+ --symbol <string> Trading pair (default: BTCUSDT)
4561
+ --timeframe <string> Candle interval (default: 15m)
4562
+ --limit <string> Number of candles to fetch (default: 250)
4563
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4564
+ --exchange <string> Exchange name (default: first registered)
4565
+ --output <string> Output file base name without extension
4566
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
4567
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4568
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4569
+
4570
+ Only plot() calls with display=display.data_window produce output columns.
4571
+ Module file ./modules/pine.module is loaded automatically if it exists.
4572
+
4573
+ Candle dump flags (--dump):
4574
+
4575
+ --symbol <string> Trading pair (default: BTCUSDT)
4576
+ --timeframe <string> Candle interval (default: 15m)
4577
+ --limit <string> Number of candles (default: 250)
4578
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4579
+ --exchange <string> Exchange name (default: first registered)
4580
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4581
+ --json Save as JSON array to ./dump/<output>.json
4582
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4583
+
4584
+ Module file ./modules/dump.module is loaded automatically if it exists.
4585
+
4586
+ PnL debug flags (--pnldebug):
4587
+
4588
+ --symbol <string> Trading pair (default: BTCUSDT)
4589
+ --priceopen <number> Entry price (required)
4590
+ --direction <string> Position direction: long or short (default: long)
4591
+ --when <string> Start timestamp ISO 8601 or Unix ms (default: now)
4592
+ --minutes <string> Number of 1m candles to simulate (default: 60)
4593
+ --exchange <string> Exchange name (default: first registered)
4594
+ --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4595
+ --json Save as JSON array to ./dump/<output>.json
4596
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4597
+ --markdown Save as Markdown table to ./dump/<output>.md
4598
+
4599
+ Module file ./modules/pnldebug.module is loaded automatically if it exists.
4600
+
4601
+ Broker debug flags (--brokerdebug):
4602
+
4603
+ --symbol <string> Trading pair (default: BTCUSDT)
4604
+ --exchange <string> Exchange name (default: first registered)
4605
+ --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4606
+ partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4607
+ (default: signal-open)
4608
+
4609
+ Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4610
+ the selected broker commit with synthetic payload values derived from current price.
4611
+
4612
+ Flush flags (--flush):
4613
+
4614
+ One or more positional entry points. For each entry point the following
4615
+ subdirectories are removed from <entry-dir>/dump/:
4616
+
4617
+ report log markdown agent
4618
+
4619
+ Init flags (--init):
4620
+
4621
+ --output <string> Target directory name (default: backtest-kit-project)
4622
+
4623
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4624
+
4625
+ Docker flags (--docker):
4626
+
4627
+ --output <string> Target directory name (default: backtest-kit-docker)
4628
+
4629
+ Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4630
+ tsconfig.json, and a sample strategy under content/. Run npm install then
4631
+ docker compose up to start the container.
4632
+
4633
+ Module hooks (loaded automatically by each mode):
4634
+
4635
+ modules/backtest.module --backtest Broker adapter for backtest
4636
+ modules/walker.module --walker Broker adapter for walker comparison
4637
+ modules/paper.module --paper Broker adapter for paper trading
4638
+ modules/live.module --live Broker adapter for live trading
4639
+ modules/main.module --main Environment setup for a custom entry point
4640
+ modules/pine.module --pine Exchange schema for PineScript runs
4641
+ modules/editor.module --editor Exchange schema for the visual Pine editor
4642
+ modules/dump.module --dump Exchange schema for candle dumps
4643
+ modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4644
+ modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4645
+
4646
+ --flush has no associated module. It only removes dump subdirectories.
4647
+
4648
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4649
+
4650
+ Environment variables:
4651
+
4652
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4653
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4654
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4655
+ CC_WWWROOT_PORT UI server port (default: 60050)
4656
+
4657
+ Examples:
4658
+
4659
+ node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4660
+ node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4661
+ node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4662
+ node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4663
+ node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4664
+ node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4665
+ node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
4666
+ node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4667
+ node ${ENTRY_PATH} --editor
4668
+ node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4669
+ node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4670
+ node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4671
+ node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4672
+ node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4673
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4674
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4675
+ node ${ENTRY_PATH} --init --output my-trading-bot
4676
+ node ${ENTRY_PATH} --docker --output my-docker-workspace
4504
4677
  `.trimStart();
4505
4678
  const main$1 = async () => {
4506
4679
  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 +4683,7 @@ const main$1 = async () => {
4510
4683
  if (!values.help) {
4511
4684
  return;
4512
4685
  }
4513
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n\n`);
4686
+ process.stdout.write(`@backtest-kit/cli ${"12.5.0"}\n\n`);
4514
4687
  process.stdout.write(HELP_TEXT);
4515
4688
  process.exit(0);
4516
4689
  };
@@ -4524,7 +4697,7 @@ const main = async () => {
4524
4697
  if (!values.version) {
4525
4698
  return;
4526
4699
  }
4527
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
4700
+ process.stdout.write(`@backtest-kit/cli ${"12.5.0"}\n`);
4528
4701
  process.exit(0);
4529
4702
  };
4530
4703
  main();
@@ -4546,17 +4719,17 @@ async function run(mode, args) {
4546
4719
  }
4547
4720
  if (mode === "backtest") {
4548
4721
  await cli.backtestMainService.run(args);
4549
- listenGracefulShutdown$5();
4722
+ listenGracefulShutdown$6();
4550
4723
  return;
4551
4724
  }
4552
4725
  if (mode === "paper") {
4553
4726
  await cli.paperMainService.run(args);
4554
- listenGracefulShutdown$3();
4727
+ listenGracefulShutdown$4();
4555
4728
  return;
4556
4729
  }
4557
4730
  if (mode === "live") {
4558
4731
  await cli.liveMainService.run(args);
4559
- listenGracefulShutdown$2();
4732
+ listenGracefulShutdown$3();
4560
4733
  return;
4561
4734
  }
4562
4735
  throw new Error(`Invalid mode: ${mode}`);