@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.mjs CHANGED
@@ -73,11 +73,11 @@ const getEntrySubject = singleshot(() => {
73
73
  return new BehaviorSubject();
74
74
  });
75
75
 
76
- const main$h = () => {
76
+ const main$i = () => {
77
77
  const entrySubject = getEntrySubject();
78
78
  entrySubject.subscribe((path) => console.log("Running", path));
79
79
  };
80
- main$h();
80
+ main$i();
81
81
 
82
82
  const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
83
83
  function dumpStackTrace() {
@@ -638,6 +638,10 @@ const getArgs = singleshot(() => {
638
638
  type: "boolean",
639
639
  default: false,
640
640
  },
641
+ main: {
642
+ type: "boolean",
643
+ default: false,
644
+ },
641
645
  cacheInterval: {
642
646
  type: "string",
643
647
  default: "1m, 15m, 30m, 4h",
@@ -1586,6 +1590,8 @@ const getEnv = singleshot(() => {
1586
1590
  };
1587
1591
  });
1588
1592
 
1593
+ const READY_SUBJECT = new BehaviorSubject();
1594
+ const MAX_WAIT_TIME = 5000;
1589
1595
  const GET_SYMBOL_EXPORTS_FN = async (self) => {
1590
1596
  const exports = await self.configConnectionService.loadConfig("symbol.config");
1591
1597
  if (!exports) {
@@ -1630,6 +1636,17 @@ const MAP_SYMBOL_CONFIG_FN = (config) => {
1630
1636
  symbolList.sort(({ priority: a_p, index: a_x }, { priority: b_p, index: b_x }) => b_p - a_p || a_x - b_x);
1631
1637
  return symbolList;
1632
1638
  };
1639
+ const CREATE_LISTEN_FN = (self) => async () => {
1640
+ const promise = READY_SUBJECT.toPromise();
1641
+ self.enable();
1642
+ if (READY_SUBJECT.data) {
1643
+ return;
1644
+ }
1645
+ await Promise.race([
1646
+ promise,
1647
+ sleep(MAX_WAIT_TIME),
1648
+ ]);
1649
+ };
1633
1650
  class FrontendProviderService {
1634
1651
  constructor() {
1635
1652
  this.loggerService = inject(TYPES.loggerService);
@@ -1647,7 +1664,7 @@ class FrontendProviderService {
1647
1664
  lib.symbolConnectionService.getSymbolList.setValue(Promise.resolve(symbolList));
1648
1665
  }
1649
1666
  }
1650
- unServer = serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, this.resolveService.PROJECT_ROOT_DIR);
1667
+ unServer = serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, this.resolveService.PROJECT_ROOT_DIR, () => READY_SUBJECT.next(true));
1651
1668
  };
1652
1669
  init();
1653
1670
  return () => {
@@ -1667,7 +1684,7 @@ class FrontendProviderService {
1667
1684
  if (!getArgs().values.ui) {
1668
1685
  return;
1669
1686
  }
1670
- return getEntrySubject().subscribe(this.enable);
1687
+ return getEntrySubject().subscribe(CREATE_LISTEN_FN(this));
1671
1688
  });
1672
1689
  }
1673
1690
  }
@@ -3329,14 +3346,14 @@ const cli = {
3329
3346
  };
3330
3347
  init();
3331
3348
 
3332
- const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3349
+ const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3333
3350
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
3334
- const HELP_TEXT$1 = `
3335
- Example:
3336
-
3337
- node ${ENTRY_PATH$1} --help
3351
+ const HELP_TEXT$1 = `
3352
+ Example:
3353
+
3354
+ node ${ENTRY_PATH$1} --help
3338
3355
  `.trimStart();
3339
- const main$g = async () => {
3356
+ const main$h = async () => {
3340
3357
  if (!getEntry(import.meta.url)) {
3341
3358
  return;
3342
3359
  }
@@ -3344,14 +3361,14 @@ const main$g = async () => {
3344
3361
  if (MODES.some((mode) => values[mode])) {
3345
3362
  return;
3346
3363
  }
3347
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
3364
+ process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n`);
3348
3365
  process.stdout.write("\n");
3349
3366
  process.stdout.write(`Run with --help to see available commands.\n`);
3350
3367
  process.stdout.write("\n");
3351
3368
  process.stdout.write(HELP_TEXT$1);
3352
3369
  process.exit(0);
3353
3370
  };
3354
- main$g();
3371
+ main$h();
3355
3372
 
3356
3373
  const notifyShutdown = singleshot(async () => {
3357
3374
  console.log("Graceful shutdown initiated.");
@@ -3367,7 +3384,7 @@ const flush = async (entryPoint) => {
3367
3384
  console.log(`Removed: ${target}`);
3368
3385
  }
3369
3386
  };
3370
- const main$f = async () => {
3387
+ const main$g = async () => {
3371
3388
  if (!getEntry(import.meta.url)) {
3372
3389
  return;
3373
3390
  }
@@ -3384,10 +3401,10 @@ const main$f = async () => {
3384
3401
  }
3385
3402
  process.exit(0);
3386
3403
  };
3387
- main$f();
3404
+ main$g();
3388
3405
 
3389
- const BEFORE_EXIT_FN$5 = singleshot(async () => {
3390
- process.off("SIGINT", BEFORE_EXIT_FN$5);
3406
+ const BEFORE_EXIT_FN$6 = singleshot(async () => {
3407
+ process.off("SIGINT", BEFORE_EXIT_FN$6);
3391
3408
  const [running = null] = await Backtest.list();
3392
3409
  if (!running) {
3393
3410
  return;
@@ -3404,10 +3421,10 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
3404
3421
  frameName,
3405
3422
  });
3406
3423
  });
3407
- const listenGracefulShutdown$5 = singleshot(() => {
3408
- process.on("SIGINT", BEFORE_EXIT_FN$5);
3424
+ const listenGracefulShutdown$6 = singleshot(() => {
3425
+ process.on("SIGINT", BEFORE_EXIT_FN$6);
3409
3426
  });
3410
- const main$e = async () => {
3427
+ const main$f = async () => {
3411
3428
  if (!getEntry(import.meta.url)) {
3412
3429
  return;
3413
3430
  }
@@ -3423,12 +3440,12 @@ const main$e = async () => {
3423
3440
  entryPoint && await flush(entryPoint);
3424
3441
  }
3425
3442
  await cli.backtestMainService.connect();
3426
- listenGracefulShutdown$5();
3443
+ listenGracefulShutdown$6();
3427
3444
  };
3428
- main$e();
3445
+ main$f();
3429
3446
 
3430
- const BEFORE_EXIT_FN$4 = singleshot(async () => {
3431
- process.off("SIGINT", BEFORE_EXIT_FN$4);
3447
+ const BEFORE_EXIT_FN$5 = singleshot(async () => {
3448
+ process.off("SIGINT", BEFORE_EXIT_FN$5);
3432
3449
  const [running = null] = await Walker.list();
3433
3450
  if (!running) {
3434
3451
  return;
@@ -3441,10 +3458,10 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
3441
3458
  }
3442
3459
  Walker.stop(symbol, { walkerName });
3443
3460
  });
3444
- const listenGracefulShutdown$4 = singleshot(() => {
3445
- process.on("SIGINT", BEFORE_EXIT_FN$4);
3461
+ const listenGracefulShutdown$5 = singleshot(() => {
3462
+ process.on("SIGINT", BEFORE_EXIT_FN$5);
3446
3463
  });
3447
- const main$d = async () => {
3464
+ const main$e = async () => {
3448
3465
  if (!getEntry(import.meta.url)) {
3449
3466
  return;
3450
3467
  }
@@ -3460,13 +3477,13 @@ const main$d = async () => {
3460
3477
  await flush(entryPoint);
3461
3478
  }
3462
3479
  }
3463
- listenGracefulShutdown$4();
3480
+ listenGracefulShutdown$5();
3464
3481
  await cli.walkerMainService.connect();
3465
3482
  };
3466
- main$d();
3483
+ main$e();
3467
3484
 
3468
- const BEFORE_EXIT_FN$3 = singleshot(async () => {
3469
- process.off("SIGINT", BEFORE_EXIT_FN$3);
3485
+ const BEFORE_EXIT_FN$4 = singleshot(async () => {
3486
+ process.off("SIGINT", BEFORE_EXIT_FN$4);
3470
3487
  const [running = null] = await Live.list();
3471
3488
  if (!running) {
3472
3489
  return;
@@ -3482,10 +3499,10 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
3482
3499
  strategyName,
3483
3500
  });
3484
3501
  });
3485
- const listenGracefulShutdown$3 = singleshot(() => {
3486
- process.on("SIGINT", BEFORE_EXIT_FN$3);
3502
+ const listenGracefulShutdown$4 = singleshot(() => {
3503
+ process.on("SIGINT", BEFORE_EXIT_FN$4);
3487
3504
  });
3488
- const main$c = async () => {
3505
+ const main$d = async () => {
3489
3506
  if (!getEntry(import.meta.url)) {
3490
3507
  return;
3491
3508
  }
@@ -3497,12 +3514,12 @@ const main$c = async () => {
3497
3514
  return;
3498
3515
  }
3499
3516
  cli.paperMainService.connect();
3500
- listenGracefulShutdown$3();
3517
+ listenGracefulShutdown$4();
3501
3518
  };
3502
- main$c();
3519
+ main$d();
3503
3520
 
3504
- const BEFORE_EXIT_FN$2 = singleshot(async () => {
3505
- process.off("SIGINT", BEFORE_EXIT_FN$2);
3521
+ const BEFORE_EXIT_FN$3 = singleshot(async () => {
3522
+ process.off("SIGINT", BEFORE_EXIT_FN$3);
3506
3523
  const [running = null] = await Live.list();
3507
3524
  if (!running) {
3508
3525
  return;
@@ -3518,6 +3535,84 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
3518
3535
  strategyName,
3519
3536
  });
3520
3537
  });
3538
+ const listenGracefulShutdown$3 = singleshot(() => {
3539
+ process.on("SIGINT", BEFORE_EXIT_FN$3);
3540
+ });
3541
+ const main$c = async () => {
3542
+ if (!getEntry(import.meta.url)) {
3543
+ return;
3544
+ }
3545
+ const { values } = getArgs();
3546
+ if (!values.live) {
3547
+ return;
3548
+ }
3549
+ if (values.entry) {
3550
+ return;
3551
+ }
3552
+ await cli.liveMainService.connect();
3553
+ listenGracefulShutdown$3();
3554
+ };
3555
+ main$c();
3556
+
3557
+ const stopBacktestList$1 = async () => {
3558
+ for (const item of await Backtest.list()) {
3559
+ if (item.status === "fulfilled") {
3560
+ continue;
3561
+ }
3562
+ Backtest.stop(item.symbol, {
3563
+ exchangeName: item.exchangeName,
3564
+ strategyName: item.strategyName,
3565
+ frameName: item.frameName,
3566
+ });
3567
+ }
3568
+ };
3569
+ const stopLiveList$1 = async () => {
3570
+ for (const item of await Live.list()) {
3571
+ if (item.status === "fulfilled") {
3572
+ continue;
3573
+ }
3574
+ Live.stop(item.symbol, {
3575
+ exchangeName: item.exchangeName,
3576
+ strategyName: item.strategyName,
3577
+ });
3578
+ }
3579
+ };
3580
+ const stopWalkerList$1 = async () => {
3581
+ for (const item of await Walker.list()) {
3582
+ if (item.status === "fulfilled") {
3583
+ continue;
3584
+ }
3585
+ Walker.stop(item.symbol, { walkerName: item.walkerName });
3586
+ }
3587
+ };
3588
+ const stopMain$1 = async () => {
3589
+ await stopBacktestList$1();
3590
+ await stopLiveList$1();
3591
+ await stopWalkerList$1();
3592
+ };
3593
+ const listenFinish$1 = singleshot(() => {
3594
+ let disposeRef;
3595
+ const unBacktest = listenDoneBacktest(() => {
3596
+ console.log("Backtest trading finished");
3597
+ disposeRef && disposeRef();
3598
+ });
3599
+ const unLive = listenDoneLive(() => {
3600
+ console.log("Live trading finished");
3601
+ disposeRef && disposeRef();
3602
+ });
3603
+ const unWalker = listenDoneWalker(() => {
3604
+ console.log("Walker comparison finished");
3605
+ disposeRef && disposeRef();
3606
+ });
3607
+ disposeRef = compose(() => unBacktest(), () => unLive(), () => unWalker());
3608
+ shutdown();
3609
+ });
3610
+ const BEFORE_EXIT_FN$2 = singleshot(async () => {
3611
+ process.off("SIGINT", BEFORE_EXIT_FN$2);
3612
+ notifyShutdown();
3613
+ notifyKill();
3614
+ await stopMain$1();
3615
+ });
3521
3616
  const listenGracefulShutdown$2 = singleshot(() => {
3522
3617
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3523
3618
  });
@@ -3526,14 +3621,56 @@ const main$b = async () => {
3526
3621
  return;
3527
3622
  }
3528
3623
  const { values } = getArgs();
3529
- if (!values.live) {
3624
+ if (!values.main) {
3530
3625
  return;
3531
3626
  }
3532
3627
  if (values.entry) {
3533
3628
  return;
3534
3629
  }
3535
- await cli.liveMainService.connect();
3630
+ const [entryPoint = null] = getPositionals();
3631
+ if (!entryPoint) {
3632
+ throw new Error("Entry point is required");
3633
+ }
3634
+ {
3635
+ const cwd = process.cwd();
3636
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3637
+ }
3638
+ await cli.configConnectionService.loadConfig("setup.config");
3639
+ {
3640
+ const loader = await cli.configConnectionService.loadConfig("loader.config");
3641
+ try {
3642
+ if (typeof loader === "function") {
3643
+ await loader();
3644
+ }
3645
+ if (typeof loader?.loader === "function") {
3646
+ await loader.loader();
3647
+ }
3648
+ }
3649
+ catch (error) {
3650
+ console.error("Module loader failed", error);
3651
+ kill(-1);
3652
+ return;
3653
+ }
3654
+ }
3655
+ {
3656
+ await cli.configService.waitForInit();
3657
+ Setup.enable();
3658
+ }
3659
+ const cwd = process.cwd();
3660
+ {
3661
+ const absolutePath = path.resolve(entryPoint);
3662
+ const moduleRoot = path.dirname(absolutePath);
3663
+ process.chdir(moduleRoot);
3664
+ cwd !== moduleRoot && Setup.update();
3665
+ dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
3666
+ }
3667
+ if (!values.noFlush) {
3668
+ await flush(path.resolve(cwd, entryPoint));
3669
+ }
3670
+ await cli.moduleConnectionService.loadModule("main.module");
3671
+ listenFinish$1();
3536
3672
  listenGracefulShutdown$2();
3673
+ await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
3537
3674
  };
3538
3675
  main$b();
3539
3676
 
@@ -3542,13 +3679,29 @@ const MODE_MODULE = {
3542
3679
  live: "live.module",
3543
3680
  paper: "paper.module",
3544
3681
  walker: "walker.module",
3682
+ main: "main.module",
3545
3683
  };
3684
+ const MODE_LIST = ["backtest", "live", "paper", "walker", "main"];
3546
3685
  const resolveMode = (values) => {
3547
- const enabled = ["backtest", "live", "paper", "walker"].filter((mode) => Boolean(values[mode]));
3548
- if (enabled.length !== 1) {
3686
+ if (MODE_LIST.filter((mode) => values[mode]).length > 1) {
3549
3687
  return null;
3550
3688
  }
3551
- return enabled[0];
3689
+ if (values.main) {
3690
+ return "main";
3691
+ }
3692
+ if (values.backtest) {
3693
+ return "backtest";
3694
+ }
3695
+ if (values.live) {
3696
+ return "live";
3697
+ }
3698
+ if (values.paper) {
3699
+ return "paper";
3700
+ }
3701
+ if (values.walker) {
3702
+ return "walker";
3703
+ }
3704
+ return null;
3552
3705
  };
3553
3706
  const stopBacktestList = async () => {
3554
3707
  for (const item of await Backtest.list()) {
@@ -3581,11 +3734,17 @@ const stopWalkerList = async () => {
3581
3734
  Walker.stop(item.symbol, { walkerName: item.walkerName });
3582
3735
  }
3583
3736
  };
3737
+ const stopMain = async () => {
3738
+ await stopBacktestList();
3739
+ await stopLiveList();
3740
+ await stopWalkerList();
3741
+ };
3584
3742
  const MODE_STOP = {
3585
3743
  backtest: stopBacktestList,
3586
3744
  live: stopLiveList,
3587
3745
  paper: stopLiveList,
3588
3746
  walker: stopWalkerList,
3747
+ main: stopMain,
3589
3748
  };
3590
3749
  const listenFinish = singleshot(() => {
3591
3750
  let disposeRef;
@@ -3626,12 +3785,12 @@ const main$a = async () => {
3626
3785
  }
3627
3786
  const mode = resolveMode(values);
3628
3787
  if (!mode) {
3629
- console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
3788
+ console.error("--entry requires exactly one of --backtest, --live, --paper, --walker, --main");
3630
3789
  kill(1);
3631
3790
  return;
3632
3791
  }
3633
3792
  const entryPoints = getPositionals();
3634
- if (!entryPoints.length) {
3793
+ if (mode !== "main" && !entryPoints.length) {
3635
3794
  throw new Error("At least one entry point is required");
3636
3795
  }
3637
3796
  {
@@ -3678,12 +3837,18 @@ const main$a = async () => {
3678
3837
  }
3679
3838
  await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
3680
3839
  listenFinish();
3681
- createGracefulShutdown(mode)();
3840
+ {
3841
+ const listenShutdown = createGracefulShutdown(mode);
3842
+ listenShutdown();
3843
+ }
3682
3844
  let absolutePath;
3683
3845
  for (const entryPoint of entryPoints) {
3684
3846
  absolutePath = path.resolve(cwd, entryPoint);
3685
3847
  await cli.resolveService.attachEntry(absolutePath);
3686
3848
  }
3849
+ if (mode === "main") {
3850
+ return;
3851
+ }
3687
3852
  await getEntrySubject().next(absolutePath);
3688
3853
  };
3689
3854
  main$a();
@@ -4295,183 +4460,204 @@ const main$2 = async () => {
4295
4460
  main$2();
4296
4461
 
4297
4462
  const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
4298
- const HELP_TEXT = `
4299
- Usage:
4300
- node index.mjs --<mode> [flags] [entry-point]
4301
-
4302
- Modes:
4303
-
4304
- --backtest <entry> Run strategy against historical candle data
4305
- --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4306
- --paper <entry> Paper trading (live prices, no real orders)
4307
- --live <entry> Live trading with real orders
4308
- --pine <entry> Execute a local .pine indicator file
4309
- --editor Open the Pine Script visual editor in the browser
4310
- --dump Fetch and save raw OHLCV candles
4311
- --pnldebug Simulate PnL per minute for a given entry price and direction
4312
- --brokerdebug Fire a single broker commit against the live broker adapter
4313
- --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4314
- --init Scaffold a new project in the current directory
4315
- --docker Scaffold a Docker workspace for running strategies in a container
4316
- --help Print this help message
4317
-
4318
- Backtest flags:
4319
-
4320
- --symbol <string> Trading pair (default: BTCUSDT)
4321
- --strategy <string> Strategy name from addStrategySchema (default: first registered)
4322
- --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4323
- --frame <string> Frame name from addFrameSchema (default: first registered)
4324
- --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4325
- --noCache Skip candle cache warming before the run
4326
- --noFlush Skip removing report/log/markdown/agent folders before backtest run
4327
- --verbose Log every candle fetch to stdout
4328
- --ui Start web dashboard at http://localhost:60050
4329
- --telegram Send trade notifications to Telegram
4330
-
4331
- Walker flags (--walker):
4332
-
4333
- --symbol <string> Trading pair (default: BTCUSDT)
4334
- --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4335
- --noCache Skip candle cache warming before the run
4336
- --noFlush Skip removing report/log/markdown/agent folders before walker run
4337
- --verbose Log every candle fetch to stdout
4338
- --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4339
- --json Save results as JSON to ./dump/<output>.json
4340
- --markdown Save report as Markdown to ./dump/<output>.md
4341
-
4342
- Each positional argument is a strategy entry point. All strategy files are loaded without
4343
- changing process.cwd() .env is read from the working directory only.
4344
- addWalkerSchema is called automatically using the registered exchange and frame.
4345
- After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4346
-
4347
- Module file ./modules/walker.module is loaded automatically if it exists.
4348
-
4349
- Paper / Live flags:
4350
-
4351
- --symbol <string> Trading pair (default: BTCUSDT)
4352
- --strategy <string> Strategy name (default: first registered)
4353
- --exchange <string> Exchange name (default: first registered)
4354
- --verbose Log every candle fetch to stdout
4355
- --ui Start web dashboard
4356
- --telegram Send Telegram notifications
4357
-
4358
- PineScript flags (--pine):
4359
-
4360
- --symbol <string> Trading pair (default: BTCUSDT)
4361
- --timeframe <string> Candle interval (default: 15m)
4362
- --limit <string> Number of candles to fetch (default: 250)
4363
- --when <string> End date ISO 8601 or Unix ms (default: now)
4364
- --exchange <string> Exchange name (default: first registered)
4365
- --output <string> Output file base name without extension
4366
- --json Save output as JSON array to <pine-dir>/dump/<output>.json
4367
- --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4368
- --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4369
-
4370
- Only plot() calls with display=display.data_window produce output columns.
4371
- Module file ./modules/pine.module is loaded automatically if it exists.
4372
-
4373
- Candle dump flags (--dump):
4374
-
4375
- --symbol <string> Trading pair (default: BTCUSDT)
4376
- --timeframe <string> Candle interval (default: 15m)
4377
- --limit <string> Number of candles (default: 250)
4378
- --when <string> End date — ISO 8601 or Unix ms (default: now)
4379
- --exchange <string> Exchange name (default: first registered)
4380
- --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4381
- --json Save as JSON array to ./dump/<output>.json
4382
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4383
-
4384
- Module file ./modules/dump.module is loaded automatically if it exists.
4385
-
4386
- PnL debug flags (--pnldebug):
4387
-
4388
- --symbol <string> Trading pair (default: BTCUSDT)
4389
- --priceopen <number> Entry price (required)
4390
- --direction <string> Position direction: long or short (default: long)
4391
- --when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
4392
- --minutes <string> Number of 1m candles to simulate (default: 60)
4393
- --exchange <string> Exchange name (default: first registered)
4394
- --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4395
- --json Save as JSON array to ./dump/<output>.json
4396
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4397
- --markdown Save as Markdown table to ./dump/<output>.md
4398
-
4399
- Module file ./modules/pnldebug.module is loaded automatically if it exists.
4400
-
4401
- Broker debug flags (--brokerdebug):
4402
-
4403
- --symbol <string> Trading pair (default: BTCUSDT)
4404
- --exchange <string> Exchange name (default: first registered)
4405
- --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4406
- partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4407
- (default: signal-open)
4408
-
4409
- Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4410
- the selected broker commit with synthetic payload values derived from current price.
4411
-
4412
- Flush flags (--flush):
4413
-
4414
- One or more positional entry points. For each entry point the following
4415
- subdirectories are removed from <entry-dir>/dump/:
4416
-
4417
- report log markdown agent
4418
-
4419
- Init flags (--init):
4420
-
4421
- --output <string> Target directory name (default: backtest-kit-project)
4422
-
4423
- Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4424
-
4425
- Docker flags (--docker):
4426
-
4427
- --output <string> Target directory name (default: backtest-kit-docker)
4428
-
4429
- Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4430
- tsconfig.json, and a sample strategy under content/. Run npm install then
4431
- docker compose up to start the container.
4432
-
4433
- Module hooks (loaded automatically by each mode):
4434
-
4435
- modules/backtest.module --backtest Broker adapter for backtest
4436
- modules/walker.module --walker Broker adapter for walker comparison
4437
- modules/paper.module --paper Broker adapter for paper trading
4438
- modules/live.module --live Broker adapter for live trading
4439
- modules/pine.module --pine Exchange schema for PineScript runs
4440
- modules/editor.module --editor Exchange schema for the visual Pine editor
4441
- modules/dump.module --dump Exchange schema for candle dumps
4442
- modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4443
- modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4444
-
4445
- --flush has no associated module. It only removes dump subdirectories.
4446
-
4447
- Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4448
-
4449
- Environment variables:
4450
-
4451
- CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4452
- CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4453
- CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4454
- CC_WWWROOT_PORT UI server port (default: 60050)
4455
-
4456
- Examples:
4457
-
4458
- node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4459
- node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4460
- node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4461
- node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4462
- node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4463
- node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4464
- node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4465
- node ${ENTRY_PATH} --editor
4466
- node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4467
- node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4468
- node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4469
- node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4470
- node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4471
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4472
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4473
- node ${ENTRY_PATH} --init --output my-trading-bot
4474
- node ${ENTRY_PATH} --docker --output my-docker-workspace
4463
+ const HELP_TEXT = `
4464
+ Usage:
4465
+ node index.mjs --<mode> [flags] [entry-point]
4466
+
4467
+ Modes:
4468
+
4469
+ --backtest <entry> Run strategy against historical candle data
4470
+ --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4471
+ --paper <entry> Paper trading (live prices, no real orders)
4472
+ --live <entry> Live trading with real orders
4473
+ --main <entry> Run an entry point with prepared environment, no trading harness
4474
+ --pine <entry> Execute a local .pine indicator file
4475
+ --editor Open the Pine Script visual editor in the browser
4476
+ --dump Fetch and save raw OHLCV candles
4477
+ --pnldebug Simulate PnL per minute for a given entry price and direction
4478
+ --brokerdebug Fire a single broker commit against the live broker adapter
4479
+ --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4480
+ --init Scaffold a new project in the current directory
4481
+ --docker Scaffold a Docker workspace for running strategies in a container
4482
+ --help Print this help message
4483
+
4484
+ Backtest flags:
4485
+
4486
+ --symbol <string> Trading pair (default: BTCUSDT)
4487
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
4488
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4489
+ --frame <string> Frame name from addFrameSchema (default: first registered)
4490
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4491
+ --noCache Skip candle cache warming before the run
4492
+ --noFlush Skip removing report/log/markdown/agent folders before backtest run
4493
+ --verbose Log every candle fetch to stdout
4494
+ --ui Start web dashboard at http://localhost:60050
4495
+ --telegram Send trade notifications to Telegram
4496
+
4497
+ Walker flags (--walker):
4498
+
4499
+ --symbol <string> Trading pair (default: BTCUSDT)
4500
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4501
+ --noCache Skip candle cache warming before the run
4502
+ --noFlush Skip removing report/log/markdown/agent folders before walker run
4503
+ --verbose Log every candle fetch to stdout
4504
+ --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4505
+ --json Save results as JSON to ./dump/<output>.json
4506
+ --markdown Save report as Markdown to ./dump/<output>.md
4507
+
4508
+ Each positional argument is a strategy entry point. All strategy files are loaded without
4509
+ changing process.cwd() — .env is read from the working directory only.
4510
+ addWalkerSchema is called automatically using the registered exchange and frame.
4511
+ After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4512
+
4513
+ Module file ./modules/walker.module is loaded automatically if it exists.
4514
+
4515
+ Paper / Live flags:
4516
+
4517
+ --symbol <string> Trading pair (default: BTCUSDT)
4518
+ --strategy <string> Strategy name (default: first registered)
4519
+ --exchange <string> Exchange name (default: first registered)
4520
+ --verbose Log every candle fetch to stdout
4521
+ --ui Start web dashboard
4522
+ --telegram Send Telegram notifications
4523
+
4524
+ Main flags (--main):
4525
+
4526
+ --noFlush Skip removing report/log/markdown/agent folders before the run
4527
+
4528
+ Prepares the runtime environment (loads .env, setup.config, loader.config and
4529
+ modules/main.module) and runs the single positional entry point — but does NOT
4530
+ start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
4531
+ Backtest/Live/Walker.background; the entry point decides what to run.
4532
+
4533
+ Exactly one positional entry point is required. process.cwd() is changed to the
4534
+ entry point directory and its local .env is loaded.
4535
+
4536
+ Backtest, Live and Walker runs started from userspace are still tracked: the run
4537
+ finishes automatically when one of them completes, Ctrl+C stops any active run,
4538
+ and a second Ctrl+C force-quits.
4539
+
4540
+ Module file ./modules/main.module is loaded automatically if it exists.
4541
+
4542
+ PineScript flags (--pine):
4543
+
4544
+ --symbol <string> Trading pair (default: BTCUSDT)
4545
+ --timeframe <string> Candle interval (default: 15m)
4546
+ --limit <string> Number of candles to fetch (default: 250)
4547
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4548
+ --exchange <string> Exchange name (default: first registered)
4549
+ --output <string> Output file base name without extension
4550
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
4551
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4552
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4553
+
4554
+ Only plot() calls with display=display.data_window produce output columns.
4555
+ Module file ./modules/pine.module is loaded automatically if it exists.
4556
+
4557
+ Candle dump flags (--dump):
4558
+
4559
+ --symbol <string> Trading pair (default: BTCUSDT)
4560
+ --timeframe <string> Candle interval (default: 15m)
4561
+ --limit <string> Number of candles (default: 250)
4562
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4563
+ --exchange <string> Exchange name (default: first registered)
4564
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4565
+ --json Save as JSON array to ./dump/<output>.json
4566
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4567
+
4568
+ Module file ./modules/dump.module is loaded automatically if it exists.
4569
+
4570
+ PnL debug flags (--pnldebug):
4571
+
4572
+ --symbol <string> Trading pair (default: BTCUSDT)
4573
+ --priceopen <number> Entry price (required)
4574
+ --direction <string> Position direction: long or short (default: long)
4575
+ --when <string> Start timestamp ISO 8601 or Unix ms (default: now)
4576
+ --minutes <string> Number of 1m candles to simulate (default: 60)
4577
+ --exchange <string> Exchange name (default: first registered)
4578
+ --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4579
+ --json Save as JSON array to ./dump/<output>.json
4580
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4581
+ --markdown Save as Markdown table to ./dump/<output>.md
4582
+
4583
+ Module file ./modules/pnldebug.module is loaded automatically if it exists.
4584
+
4585
+ Broker debug flags (--brokerdebug):
4586
+
4587
+ --symbol <string> Trading pair (default: BTCUSDT)
4588
+ --exchange <string> Exchange name (default: first registered)
4589
+ --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4590
+ partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4591
+ (default: signal-open)
4592
+
4593
+ Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4594
+ the selected broker commit with synthetic payload values derived from current price.
4595
+
4596
+ Flush flags (--flush):
4597
+
4598
+ One or more positional entry points. For each entry point the following
4599
+ subdirectories are removed from <entry-dir>/dump/:
4600
+
4601
+ report log markdown agent
4602
+
4603
+ Init flags (--init):
4604
+
4605
+ --output <string> Target directory name (default: backtest-kit-project)
4606
+
4607
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4608
+
4609
+ Docker flags (--docker):
4610
+
4611
+ --output <string> Target directory name (default: backtest-kit-docker)
4612
+
4613
+ Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4614
+ tsconfig.json, and a sample strategy under content/. Run npm install then
4615
+ docker compose up to start the container.
4616
+
4617
+ Module hooks (loaded automatically by each mode):
4618
+
4619
+ modules/backtest.module --backtest Broker adapter for backtest
4620
+ modules/walker.module --walker Broker adapter for walker comparison
4621
+ modules/paper.module --paper Broker adapter for paper trading
4622
+ modules/live.module --live Broker adapter for live trading
4623
+ modules/main.module --main Environment setup for a custom entry point
4624
+ modules/pine.module --pine Exchange schema for PineScript runs
4625
+ modules/editor.module --editor Exchange schema for the visual Pine editor
4626
+ modules/dump.module --dump Exchange schema for candle dumps
4627
+ modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4628
+ modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4629
+
4630
+ --flush has no associated module. It only removes dump subdirectories.
4631
+
4632
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4633
+
4634
+ Environment variables:
4635
+
4636
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4637
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4638
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4639
+ CC_WWWROOT_PORT UI server port (default: 60050)
4640
+
4641
+ Examples:
4642
+
4643
+ node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4644
+ node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4645
+ node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4646
+ node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4647
+ node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4648
+ node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4649
+ node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
4650
+ node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4651
+ node ${ENTRY_PATH} --editor
4652
+ node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4653
+ node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4654
+ node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4655
+ node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4656
+ node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4657
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4658
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4659
+ node ${ENTRY_PATH} --init --output my-trading-bot
4660
+ node ${ENTRY_PATH} --docker --output my-docker-workspace
4475
4661
  `.trimStart();
4476
4662
  const main$1 = async () => {
4477
4663
  if (!getEntry(import.meta.url)) {
@@ -4481,7 +4667,7 @@ const main$1 = async () => {
4481
4667
  if (!values.help) {
4482
4668
  return;
4483
4669
  }
4484
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n\n`);
4670
+ process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n\n`);
4485
4671
  process.stdout.write(HELP_TEXT);
4486
4672
  process.exit(0);
4487
4673
  };
@@ -4495,7 +4681,7 @@ const main = async () => {
4495
4681
  if (!values.version) {
4496
4682
  return;
4497
4683
  }
4498
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
4684
+ process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n`);
4499
4685
  process.exit(0);
4500
4686
  };
4501
4687
  main();
@@ -4517,17 +4703,17 @@ async function run(mode, args) {
4517
4703
  }
4518
4704
  if (mode === "backtest") {
4519
4705
  await cli.backtestMainService.run(args);
4520
- listenGracefulShutdown$5();
4706
+ listenGracefulShutdown$6();
4521
4707
  return;
4522
4708
  }
4523
4709
  if (mode === "paper") {
4524
4710
  await cli.paperMainService.run(args);
4525
- listenGracefulShutdown$3();
4711
+ listenGracefulShutdown$4();
4526
4712
  return;
4527
4713
  }
4528
4714
  if (mode === "live") {
4529
4715
  await cli.liveMainService.run(args);
4530
- listenGracefulShutdown$2();
4716
+ listenGracefulShutdown$3();
4531
4717
  return;
4532
4718
  }
4533
4719
  throw new Error(`Invalid mode: ${mode}`);