@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.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",
@@ -3329,14 +3333,14 @@ const cli = {
3329
3333
  };
3330
3334
  init();
3331
3335
 
3332
- const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3336
+ const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3333
3337
  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
3338
+ const HELP_TEXT$1 = `
3339
+ Example:
3340
+
3341
+ node ${ENTRY_PATH$1} --help
3338
3342
  `.trimStart();
3339
- const main$g = async () => {
3343
+ const main$h = async () => {
3340
3344
  if (!getEntry(import.meta.url)) {
3341
3345
  return;
3342
3346
  }
@@ -3344,14 +3348,14 @@ const main$g = async () => {
3344
3348
  if (MODES.some((mode) => values[mode])) {
3345
3349
  return;
3346
3350
  }
3347
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
3351
+ process.stdout.write(`@backtest-kit/cli ${"12.5.0"}\n`);
3348
3352
  process.stdout.write("\n");
3349
3353
  process.stdout.write(`Run with --help to see available commands.\n`);
3350
3354
  process.stdout.write("\n");
3351
3355
  process.stdout.write(HELP_TEXT$1);
3352
3356
  process.exit(0);
3353
3357
  };
3354
- main$g();
3358
+ main$h();
3355
3359
 
3356
3360
  const notifyShutdown = singleshot(async () => {
3357
3361
  console.log("Graceful shutdown initiated.");
@@ -3367,7 +3371,7 @@ const flush = async (entryPoint) => {
3367
3371
  console.log(`Removed: ${target}`);
3368
3372
  }
3369
3373
  };
3370
- const main$f = async () => {
3374
+ const main$g = async () => {
3371
3375
  if (!getEntry(import.meta.url)) {
3372
3376
  return;
3373
3377
  }
@@ -3384,10 +3388,10 @@ const main$f = async () => {
3384
3388
  }
3385
3389
  process.exit(0);
3386
3390
  };
3387
- main$f();
3391
+ main$g();
3388
3392
 
3389
- const BEFORE_EXIT_FN$5 = singleshot(async () => {
3390
- process.off("SIGINT", BEFORE_EXIT_FN$5);
3393
+ const BEFORE_EXIT_FN$6 = singleshot(async () => {
3394
+ process.off("SIGINT", BEFORE_EXIT_FN$6);
3391
3395
  const [running = null] = await Backtest.list();
3392
3396
  if (!running) {
3393
3397
  return;
@@ -3404,10 +3408,10 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
3404
3408
  frameName,
3405
3409
  });
3406
3410
  });
3407
- const listenGracefulShutdown$5 = singleshot(() => {
3408
- process.on("SIGINT", BEFORE_EXIT_FN$5);
3411
+ const listenGracefulShutdown$6 = singleshot(() => {
3412
+ process.on("SIGINT", BEFORE_EXIT_FN$6);
3409
3413
  });
3410
- const main$e = async () => {
3414
+ const main$f = async () => {
3411
3415
  if (!getEntry(import.meta.url)) {
3412
3416
  return;
3413
3417
  }
@@ -3423,12 +3427,12 @@ const main$e = async () => {
3423
3427
  entryPoint && await flush(entryPoint);
3424
3428
  }
3425
3429
  await cli.backtestMainService.connect();
3426
- listenGracefulShutdown$5();
3430
+ listenGracefulShutdown$6();
3427
3431
  };
3428
- main$e();
3432
+ main$f();
3429
3433
 
3430
- const BEFORE_EXIT_FN$4 = singleshot(async () => {
3431
- process.off("SIGINT", BEFORE_EXIT_FN$4);
3434
+ const BEFORE_EXIT_FN$5 = singleshot(async () => {
3435
+ process.off("SIGINT", BEFORE_EXIT_FN$5);
3432
3436
  const [running = null] = await Walker.list();
3433
3437
  if (!running) {
3434
3438
  return;
@@ -3441,10 +3445,10 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
3441
3445
  }
3442
3446
  Walker.stop(symbol, { walkerName });
3443
3447
  });
3444
- const listenGracefulShutdown$4 = singleshot(() => {
3445
- process.on("SIGINT", BEFORE_EXIT_FN$4);
3448
+ const listenGracefulShutdown$5 = singleshot(() => {
3449
+ process.on("SIGINT", BEFORE_EXIT_FN$5);
3446
3450
  });
3447
- const main$d = async () => {
3451
+ const main$e = async () => {
3448
3452
  if (!getEntry(import.meta.url)) {
3449
3453
  return;
3450
3454
  }
@@ -3460,13 +3464,13 @@ const main$d = async () => {
3460
3464
  await flush(entryPoint);
3461
3465
  }
3462
3466
  }
3463
- listenGracefulShutdown$4();
3467
+ listenGracefulShutdown$5();
3464
3468
  await cli.walkerMainService.connect();
3465
3469
  };
3466
- main$d();
3470
+ main$e();
3467
3471
 
3468
- const BEFORE_EXIT_FN$3 = singleshot(async () => {
3469
- process.off("SIGINT", BEFORE_EXIT_FN$3);
3472
+ const BEFORE_EXIT_FN$4 = singleshot(async () => {
3473
+ process.off("SIGINT", BEFORE_EXIT_FN$4);
3470
3474
  const [running = null] = await Live.list();
3471
3475
  if (!running) {
3472
3476
  return;
@@ -3482,10 +3486,10 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
3482
3486
  strategyName,
3483
3487
  });
3484
3488
  });
3485
- const listenGracefulShutdown$3 = singleshot(() => {
3486
- process.on("SIGINT", BEFORE_EXIT_FN$3);
3489
+ const listenGracefulShutdown$4 = singleshot(() => {
3490
+ process.on("SIGINT", BEFORE_EXIT_FN$4);
3487
3491
  });
3488
- const main$c = async () => {
3492
+ const main$d = async () => {
3489
3493
  if (!getEntry(import.meta.url)) {
3490
3494
  return;
3491
3495
  }
@@ -3497,12 +3501,12 @@ const main$c = async () => {
3497
3501
  return;
3498
3502
  }
3499
3503
  cli.paperMainService.connect();
3500
- listenGracefulShutdown$3();
3504
+ listenGracefulShutdown$4();
3501
3505
  };
3502
- main$c();
3506
+ main$d();
3503
3507
 
3504
- const BEFORE_EXIT_FN$2 = singleshot(async () => {
3505
- process.off("SIGINT", BEFORE_EXIT_FN$2);
3508
+ const BEFORE_EXIT_FN$3 = singleshot(async () => {
3509
+ process.off("SIGINT", BEFORE_EXIT_FN$3);
3506
3510
  const [running = null] = await Live.list();
3507
3511
  if (!running) {
3508
3512
  return;
@@ -3518,6 +3522,84 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
3518
3522
  strategyName,
3519
3523
  });
3520
3524
  });
3525
+ const listenGracefulShutdown$3 = singleshot(() => {
3526
+ process.on("SIGINT", BEFORE_EXIT_FN$3);
3527
+ });
3528
+ const main$c = async () => {
3529
+ if (!getEntry(import.meta.url)) {
3530
+ return;
3531
+ }
3532
+ const { values } = getArgs();
3533
+ if (!values.live) {
3534
+ return;
3535
+ }
3536
+ if (values.entry) {
3537
+ return;
3538
+ }
3539
+ await cli.liveMainService.connect();
3540
+ listenGracefulShutdown$3();
3541
+ };
3542
+ main$c();
3543
+
3544
+ const stopBacktestList$1 = async () => {
3545
+ for (const item of await Backtest.list()) {
3546
+ if (item.status === "fulfilled") {
3547
+ continue;
3548
+ }
3549
+ Backtest.stop(item.symbol, {
3550
+ exchangeName: item.exchangeName,
3551
+ strategyName: item.strategyName,
3552
+ frameName: item.frameName,
3553
+ });
3554
+ }
3555
+ };
3556
+ const stopLiveList$1 = async () => {
3557
+ for (const item of await Live.list()) {
3558
+ if (item.status === "fulfilled") {
3559
+ continue;
3560
+ }
3561
+ Live.stop(item.symbol, {
3562
+ exchangeName: item.exchangeName,
3563
+ strategyName: item.strategyName,
3564
+ });
3565
+ }
3566
+ };
3567
+ const stopWalkerList$1 = async () => {
3568
+ for (const item of await Walker.list()) {
3569
+ if (item.status === "fulfilled") {
3570
+ continue;
3571
+ }
3572
+ Walker.stop(item.symbol, { walkerName: item.walkerName });
3573
+ }
3574
+ };
3575
+ const stopMain$1 = async () => {
3576
+ await stopBacktestList$1();
3577
+ await stopLiveList$1();
3578
+ await stopWalkerList$1();
3579
+ };
3580
+ const listenFinish$1 = singleshot(() => {
3581
+ let disposeRef;
3582
+ const unBacktest = listenDoneBacktest(() => {
3583
+ console.log("Backtest trading finished");
3584
+ disposeRef && disposeRef();
3585
+ });
3586
+ const unLive = listenDoneLive(() => {
3587
+ console.log("Live trading finished");
3588
+ disposeRef && disposeRef();
3589
+ });
3590
+ const unWalker = listenDoneWalker(() => {
3591
+ console.log("Walker comparison finished");
3592
+ disposeRef && disposeRef();
3593
+ });
3594
+ disposeRef = compose(() => unBacktest(), () => unLive(), () => unWalker());
3595
+ shutdown();
3596
+ });
3597
+ const BEFORE_EXIT_FN$2 = singleshot(async () => {
3598
+ process.off("SIGINT", BEFORE_EXIT_FN$2);
3599
+ notifyShutdown();
3600
+ notifyKill();
3601
+ await stopMain$1();
3602
+ });
3521
3603
  const listenGracefulShutdown$2 = singleshot(() => {
3522
3604
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3523
3605
  });
@@ -3526,14 +3608,56 @@ const main$b = async () => {
3526
3608
  return;
3527
3609
  }
3528
3610
  const { values } = getArgs();
3529
- if (!values.live) {
3611
+ if (!values.main) {
3530
3612
  return;
3531
3613
  }
3532
3614
  if (values.entry) {
3533
3615
  return;
3534
3616
  }
3535
- await cli.liveMainService.connect();
3617
+ const [entryPoint = null] = getPositionals();
3618
+ if (!entryPoint) {
3619
+ throw new Error("Entry point is required");
3620
+ }
3621
+ {
3622
+ const cwd = process.cwd();
3623
+ dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
3624
+ }
3625
+ await cli.configConnectionService.loadConfig("setup.config");
3626
+ {
3627
+ const loader = await cli.configConnectionService.loadConfig("loader.config");
3628
+ try {
3629
+ if (typeof loader === "function") {
3630
+ await loader();
3631
+ }
3632
+ if (typeof loader?.loader === "function") {
3633
+ await loader.loader();
3634
+ }
3635
+ }
3636
+ catch (error) {
3637
+ console.error("Module loader failed", error);
3638
+ kill(-1);
3639
+ return;
3640
+ }
3641
+ }
3642
+ {
3643
+ await cli.configService.waitForInit();
3644
+ Setup.enable();
3645
+ }
3646
+ const cwd = process.cwd();
3647
+ {
3648
+ const absolutePath = path.resolve(entryPoint);
3649
+ const moduleRoot = path.dirname(absolutePath);
3650
+ process.chdir(moduleRoot);
3651
+ cwd !== moduleRoot && Setup.update();
3652
+ dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
3653
+ }
3654
+ if (!values.noFlush) {
3655
+ await flush(path.resolve(cwd, entryPoint));
3656
+ }
3657
+ await cli.moduleConnectionService.loadModule("main.module");
3658
+ listenFinish$1();
3536
3659
  listenGracefulShutdown$2();
3660
+ await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
3537
3661
  };
3538
3662
  main$b();
3539
3663
 
@@ -3542,13 +3666,29 @@ const MODE_MODULE = {
3542
3666
  live: "live.module",
3543
3667
  paper: "paper.module",
3544
3668
  walker: "walker.module",
3669
+ main: "main.module",
3545
3670
  };
3671
+ const MODE_LIST = ["backtest", "live", "paper", "walker", "main"];
3546
3672
  const resolveMode = (values) => {
3547
- const enabled = ["backtest", "live", "paper", "walker"].filter((mode) => Boolean(values[mode]));
3548
- if (enabled.length !== 1) {
3673
+ if (MODE_LIST.filter((mode) => values[mode]).length > 1) {
3549
3674
  return null;
3550
3675
  }
3551
- return enabled[0];
3676
+ if (values.main) {
3677
+ return "main";
3678
+ }
3679
+ if (values.backtest) {
3680
+ return "backtest";
3681
+ }
3682
+ if (values.live) {
3683
+ return "live";
3684
+ }
3685
+ if (values.paper) {
3686
+ return "paper";
3687
+ }
3688
+ if (values.walker) {
3689
+ return "walker";
3690
+ }
3691
+ return null;
3552
3692
  };
3553
3693
  const stopBacktestList = async () => {
3554
3694
  for (const item of await Backtest.list()) {
@@ -3581,11 +3721,17 @@ const stopWalkerList = async () => {
3581
3721
  Walker.stop(item.symbol, { walkerName: item.walkerName });
3582
3722
  }
3583
3723
  };
3724
+ const stopMain = async () => {
3725
+ await stopBacktestList();
3726
+ await stopLiveList();
3727
+ await stopWalkerList();
3728
+ };
3584
3729
  const MODE_STOP = {
3585
3730
  backtest: stopBacktestList,
3586
3731
  live: stopLiveList,
3587
3732
  paper: stopLiveList,
3588
3733
  walker: stopWalkerList,
3734
+ main: stopMain,
3589
3735
  };
3590
3736
  const listenFinish = singleshot(() => {
3591
3737
  let disposeRef;
@@ -3626,12 +3772,12 @@ const main$a = async () => {
3626
3772
  }
3627
3773
  const mode = resolveMode(values);
3628
3774
  if (!mode) {
3629
- console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
3775
+ console.error("--entry requires exactly one of --backtest, --live, --paper, --walker, --main");
3630
3776
  kill(1);
3631
3777
  return;
3632
3778
  }
3633
3779
  const entryPoints = getPositionals();
3634
- if (!entryPoints.length) {
3780
+ if (mode !== "main" && !entryPoints.length) {
3635
3781
  throw new Error("At least one entry point is required");
3636
3782
  }
3637
3783
  {
@@ -3678,12 +3824,18 @@ const main$a = async () => {
3678
3824
  }
3679
3825
  await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
3680
3826
  listenFinish();
3681
- createGracefulShutdown(mode)();
3827
+ {
3828
+ const listenShutdown = createGracefulShutdown(mode);
3829
+ listenShutdown();
3830
+ }
3682
3831
  let absolutePath;
3683
3832
  for (const entryPoint of entryPoints) {
3684
3833
  absolutePath = path.resolve(cwd, entryPoint);
3685
3834
  await cli.resolveService.attachEntry(absolutePath);
3686
3835
  }
3836
+ if (mode === "main") {
3837
+ return;
3838
+ }
3687
3839
  await getEntrySubject().next(absolutePath);
3688
3840
  };
3689
3841
  main$a();
@@ -4295,183 +4447,204 @@ const main$2 = async () => {
4295
4447
  main$2();
4296
4448
 
4297
4449
  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
4450
+ const HELP_TEXT = `
4451
+ Usage:
4452
+ node index.mjs --<mode> [flags] [entry-point]
4453
+
4454
+ Modes:
4455
+
4456
+ --backtest <entry> Run strategy against historical candle data
4457
+ --walker <entry...> Run Walker A/B strategy comparison across multiple strategies
4458
+ --paper <entry> Paper trading (live prices, no real orders)
4459
+ --live <entry> Live trading with real orders
4460
+ --main <entry> Run an entry point with prepared environment, no trading harness
4461
+ --pine <entry> Execute a local .pine indicator file
4462
+ --editor Open the Pine Script visual editor in the browser
4463
+ --dump Fetch and save raw OHLCV candles
4464
+ --pnldebug Simulate PnL per minute for a given entry price and direction
4465
+ --brokerdebug Fire a single broker commit against the live broker adapter
4466
+ --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
4467
+ --init Scaffold a new project in the current directory
4468
+ --docker Scaffold a Docker workspace for running strategies in a container
4469
+ --help Print this help message
4470
+
4471
+ Backtest flags:
4472
+
4473
+ --symbol <string> Trading pair (default: BTCUSDT)
4474
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
4475
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
4476
+ --frame <string> Frame name from addFrameSchema (default: first registered)
4477
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4478
+ --noCache Skip candle cache warming before the run
4479
+ --noFlush Skip removing report/log/markdown/agent folders before backtest run
4480
+ --verbose Log every candle fetch to stdout
4481
+ --ui Start web dashboard at http://localhost:60050
4482
+ --telegram Send trade notifications to Telegram
4483
+
4484
+ Walker flags (--walker):
4485
+
4486
+ --symbol <string> Trading pair (default: BTCUSDT)
4487
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
4488
+ --noCache Skip candle cache warming before the run
4489
+ --noFlush Skip removing report/log/markdown/agent folders before walker run
4490
+ --verbose Log every candle fetch to stdout
4491
+ --output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
4492
+ --json Save results as JSON to ./dump/<output>.json
4493
+ --markdown Save report as Markdown to ./dump/<output>.md
4494
+
4495
+ Each positional argument is a strategy entry point. All strategy files are loaded without
4496
+ changing process.cwd() — .env is read from the working directory only.
4497
+ addWalkerSchema is called automatically using the registered exchange and frame.
4498
+ After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4499
+
4500
+ Module file ./modules/walker.module is loaded automatically if it exists.
4501
+
4502
+ Paper / Live flags:
4503
+
4504
+ --symbol <string> Trading pair (default: BTCUSDT)
4505
+ --strategy <string> Strategy name (default: first registered)
4506
+ --exchange <string> Exchange name (default: first registered)
4507
+ --verbose Log every candle fetch to stdout
4508
+ --ui Start web dashboard
4509
+ --telegram Send Telegram notifications
4510
+
4511
+ Main flags (--main):
4512
+
4513
+ --noFlush Skip removing report/log/markdown/agent folders before the run
4514
+
4515
+ Prepares the runtime environment (loads .env, setup.config, loader.config and
4516
+ modules/main.module) and runs the single positional entry point — but does NOT
4517
+ start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
4518
+ Backtest/Live/Walker.background; the entry point decides what to run.
4519
+
4520
+ Exactly one positional entry point is required. process.cwd() is changed to the
4521
+ entry point directory and its local .env is loaded.
4522
+
4523
+ Backtest, Live and Walker runs started from userspace are still tracked: the run
4524
+ finishes automatically when one of them completes, Ctrl+C stops any active run,
4525
+ and a second Ctrl+C force-quits.
4526
+
4527
+ Module file ./modules/main.module is loaded automatically if it exists.
4528
+
4529
+ PineScript flags (--pine):
4530
+
4531
+ --symbol <string> Trading pair (default: BTCUSDT)
4532
+ --timeframe <string> Candle interval (default: 15m)
4533
+ --limit <string> Number of candles to fetch (default: 250)
4534
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4535
+ --exchange <string> Exchange name (default: first registered)
4536
+ --output <string> Output file base name without extension
4537
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
4538
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4539
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4540
+
4541
+ Only plot() calls with display=display.data_window produce output columns.
4542
+ Module file ./modules/pine.module is loaded automatically if it exists.
4543
+
4544
+ Candle dump flags (--dump):
4545
+
4546
+ --symbol <string> Trading pair (default: BTCUSDT)
4547
+ --timeframe <string> Candle interval (default: 15m)
4548
+ --limit <string> Number of candles (default: 250)
4549
+ --when <string> End date ISO 8601 or Unix ms (default: now)
4550
+ --exchange <string> Exchange name (default: first registered)
4551
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4552
+ --json Save as JSON array to ./dump/<output>.json
4553
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4554
+
4555
+ Module file ./modules/dump.module is loaded automatically if it exists.
4556
+
4557
+ PnL debug flags (--pnldebug):
4558
+
4559
+ --symbol <string> Trading pair (default: BTCUSDT)
4560
+ --priceopen <number> Entry price (required)
4561
+ --direction <string> Position direction: long or short (default: long)
4562
+ --when <string> Start timestamp ISO 8601 or Unix ms (default: now)
4563
+ --minutes <string> Number of 1m candles to simulate (default: 60)
4564
+ --exchange <string> Exchange name (default: first registered)
4565
+ --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4566
+ --json Save as JSON array to ./dump/<output>.json
4567
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
4568
+ --markdown Save as Markdown table to ./dump/<output>.md
4569
+
4570
+ Module file ./modules/pnldebug.module is loaded automatically if it exists.
4571
+
4572
+ Broker debug flags (--brokerdebug):
4573
+
4574
+ --symbol <string> Trading pair (default: BTCUSDT)
4575
+ --exchange <string> Exchange name (default: first registered)
4576
+ --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4577
+ partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4578
+ (default: signal-open)
4579
+
4580
+ Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4581
+ the selected broker commit with synthetic payload values derived from current price.
4582
+
4583
+ Flush flags (--flush):
4584
+
4585
+ One or more positional entry points. For each entry point the following
4586
+ subdirectories are removed from <entry-dir>/dump/:
4587
+
4588
+ report log markdown agent
4589
+
4590
+ Init flags (--init):
4591
+
4592
+ --output <string> Target directory name (default: backtest-kit-project)
4593
+
4594
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4595
+
4596
+ Docker flags (--docker):
4597
+
4598
+ --output <string> Target directory name (default: backtest-kit-docker)
4599
+
4600
+ Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4601
+ tsconfig.json, and a sample strategy under content/. Run npm install then
4602
+ docker compose up to start the container.
4603
+
4604
+ Module hooks (loaded automatically by each mode):
4605
+
4606
+ modules/backtest.module --backtest Broker adapter for backtest
4607
+ modules/walker.module --walker Broker adapter for walker comparison
4608
+ modules/paper.module --paper Broker adapter for paper trading
4609
+ modules/live.module --live Broker adapter for live trading
4610
+ modules/main.module --main Environment setup for a custom entry point
4611
+ modules/pine.module --pine Exchange schema for PineScript runs
4612
+ modules/editor.module --editor Exchange schema for the visual Pine editor
4613
+ modules/dump.module --dump Exchange schema for candle dumps
4614
+ modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4615
+ modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4616
+
4617
+ --flush has no associated module. It only removes dump subdirectories.
4618
+
4619
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4620
+
4621
+ Environment variables:
4622
+
4623
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4624
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4625
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4626
+ CC_WWWROOT_PORT UI server port (default: 60050)
4627
+
4628
+ Examples:
4629
+
4630
+ node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4631
+ node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4632
+ node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4633
+ node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4634
+ node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4635
+ node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4636
+ node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
4637
+ node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4638
+ node ${ENTRY_PATH} --editor
4639
+ node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4640
+ node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4641
+ node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4642
+ node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4643
+ node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4644
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4645
+ node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4646
+ node ${ENTRY_PATH} --init --output my-trading-bot
4647
+ node ${ENTRY_PATH} --docker --output my-docker-workspace
4475
4648
  `.trimStart();
4476
4649
  const main$1 = async () => {
4477
4650
  if (!getEntry(import.meta.url)) {
@@ -4481,7 +4654,7 @@ const main$1 = async () => {
4481
4654
  if (!values.help) {
4482
4655
  return;
4483
4656
  }
4484
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n\n`);
4657
+ process.stdout.write(`@backtest-kit/cli ${"12.5.0"}\n\n`);
4485
4658
  process.stdout.write(HELP_TEXT);
4486
4659
  process.exit(0);
4487
4660
  };
@@ -4495,7 +4668,7 @@ const main = async () => {
4495
4668
  if (!values.version) {
4496
4669
  return;
4497
4670
  }
4498
- process.stdout.write(`@backtest-kit/cli ${"12.4.0"}\n`);
4671
+ process.stdout.write(`@backtest-kit/cli ${"12.5.0"}\n`);
4499
4672
  process.exit(0);
4500
4673
  };
4501
4674
  main();
@@ -4517,17 +4690,17 @@ async function run(mode, args) {
4517
4690
  }
4518
4691
  if (mode === "backtest") {
4519
4692
  await cli.backtestMainService.run(args);
4520
- listenGracefulShutdown$5();
4693
+ listenGracefulShutdown$6();
4521
4694
  return;
4522
4695
  }
4523
4696
  if (mode === "paper") {
4524
4697
  await cli.paperMainService.run(args);
4525
- listenGracefulShutdown$3();
4698
+ listenGracefulShutdown$4();
4526
4699
  return;
4527
4700
  }
4528
4701
  if (mode === "live") {
4529
4702
  await cli.liveMainService.run(args);
4530
- listenGracefulShutdown$2();
4703
+ listenGracefulShutdown$3();
4531
4704
  return;
4532
4705
  }
4533
4706
  throw new Error(`Invalid mode: ${mode}`);