@backtest-kit/cli 5.11.0 → 6.0.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/README.md CHANGED
@@ -38,6 +38,9 @@ Point the CLI at your strategy file, choose a mode, and it handles exchange conn
38
38
  | **Live** | `--live` | Real trades via exchange API |
39
39
  | **UI Dashboard** | `--ui` | Web dashboard at `http://localhost:60050` |
40
40
  | **Telegram** | `--telegram` | Trade notifications with price charts |
41
+ | **PineScript** | `--pine` | Run a local `.pine` indicator against exchange data |
42
+ | **Candle Dump** | `--dump` | Fetch and save raw OHLCV candles to a file |
43
+ | **Init Project** | `--init` | Scaffold a new backtest-kit project |
41
44
 
42
45
  ## 🚀 Installation
43
46
 
@@ -705,6 +708,93 @@ Or add it to `package.json`:
705
708
  npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
706
709
  ```
707
710
 
711
+ ## 🗂️ Scaffolding a New Project (`--init`)
712
+
713
+ `@backtest-kit/cli` can bootstrap a ready-to-use project directory with a pre-configured layout, example strategy files, and all documentation fetched automatically.
714
+
715
+ ### CLI Flags
716
+
717
+ | Flag | Type | Description |
718
+ |------|------|-------------|
719
+ | `--init` | boolean | Scaffold a new project |
720
+ | `--output` | string | Target directory name (default: `backtest-kit-project`) |
721
+
722
+ ### Usage
723
+
724
+ ```bash
725
+ npx @backtest-kit/cli --init
726
+ ```
727
+
728
+ Creates `./backtest-kit-project/` in the current working directory.
729
+
730
+ Override the directory name with `--output`:
731
+
732
+ ```bash
733
+ npx @backtest-kit/cli --init --output my-trading-bot
734
+ ```
735
+
736
+ Creates `./my-trading-bot/`.
737
+
738
+ The target directory must not exist or must be empty — the command aborts if it contains any files.
739
+
740
+ ### Generated Project Structure
741
+
742
+ ```
743
+ backtest-kit-project/
744
+ ├── package.json # pre-configured with all backtest-kit dependencies
745
+ ├── .gitignore
746
+ ├── CLAUDE.md # AI-agent guide for writing strategies
747
+ ├── content/
748
+ │ └── feb_2026.strategy.ts # example strategy entry point
749
+ ├── docs/
750
+ │ ├── lib/ # fetched automatically (see below)
751
+ │ ├── backtest_actions.md
752
+ │ ├── backtest_graph_pattern.md
753
+ │ ├── backtest_logging_jsonl.md
754
+ │ ├── backtest_pinets_usage.md
755
+ │ ├── backtest_risk_async.md
756
+ │ ├── backtest_strategy_structure.md
757
+ │ ├── pine_debug.md
758
+ │ └── pine_indicator_warmup.md
759
+ ├── math/
760
+ │ └── feb_2026.pine # example PineScript indicator
761
+ ├── modules/
762
+ │ ├── dump.module.ts # exchange schema for --dump mode
763
+ │ └── pine.module.ts # exchange schema for --pine mode
764
+ ├── report/
765
+ │ └── feb_2026.md # example strategy research report
766
+ └── scripts/
767
+ └── fetch_docs.mjs # utility: downloads library READMEs into docs/lib/
768
+ ```
769
+
770
+ ### Automatic Documentation Fetch
771
+
772
+ After scaffolding, the CLI immediately runs `scripts/fetch_docs.mjs` inside the new project, which downloads the latest README files for all bundled libraries into `docs/lib/`:
773
+
774
+ | File | Source |
775
+ |------|--------|
776
+ | `backtest-kit.md` | `backtest-kit` README |
777
+ | `backtest-kit__graph.md` | `@backtest-kit/graph` README |
778
+ | `backtest-kit__pinets.md` | `@backtest-kit/pinets` README |
779
+ | `backtest-kit__cli.md` | `@backtest-kit/cli` README |
780
+ | `garch.md` | `garch` README |
781
+ | `volume-anomaly.md` | `volume-anomaly` README |
782
+ | `agent-swarm-kit.md` | `agent-swarm-kit` README |
783
+ | `functools-kit.md` | `functools-kit` README |
784
+
785
+ You can re-run this script at any time to refresh the docs:
786
+
787
+ ```bash
788
+ cd backtest-kit-project
789
+ node ./scripts/fetch_docs.mjs
790
+ ```
791
+
792
+ Or via the pre-configured npm script:
793
+
794
+ ```bash
795
+ npm run sync:lib
796
+ ```
797
+
708
798
  ## 🌍 Environment Variables
709
799
 
710
800
  Create a `.env` file in your project root:
package/build/index.cjs CHANGED
@@ -29,6 +29,7 @@ var BacktestKitGraph = require('@backtest-kit/graph');
29
29
  var BacktestKitOllama = require('@backtest-kit/ollama');
30
30
  var BacktestKitPinets = require('@backtest-kit/pinets');
31
31
  var BacktestKitSignals = require('@backtest-kit/signals');
32
+ var child_process = require('child_process');
32
33
 
33
34
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
34
35
  function _interopNamespaceDefault(e) {
@@ -284,15 +285,15 @@ const TYPES = {
284
285
 
285
286
  const entrySubject = new functoolsKit.BehaviorSubject();
286
287
 
287
- const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
288
- const __dirname$1 = path.dirname(__filename$1);
288
+ const __filename$2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
289
+ const __dirname$2 = path.dirname(__filename$2);
289
290
  let _is_launched = false;
290
291
  class ResolveService {
291
292
  constructor() {
292
293
  this.loggerService = inject(TYPES.loggerService);
293
294
  this.loaderService = inject(TYPES.loaderService);
294
- this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$1, '..', 'template');
295
- this.DEFAULT_MODULES_DIR = path.resolve(__dirname$1, '..', 'modules');
295
+ this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$2, '..', 'template');
296
+ this.DEFAULT_MODULES_DIR = path.resolve(__dirname$2, '..', 'modules');
296
297
  this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
297
298
  this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
298
299
  this.getIsLaunched = () => {
@@ -454,6 +455,10 @@ const ALLOWED_EXTENSIONS = [
454
455
  `.js`,
455
456
  `.pine`,
456
457
  ];
458
+ const DISALLOWED_PATHS = [
459
+ "node_modules",
460
+ "@backtest-kit",
461
+ ];
457
462
  const getArgs = functoolsKit.singleshot(() => {
458
463
  const { values, positionals } = util.parseArgs({
459
464
  args: process.argv,
@@ -548,6 +553,18 @@ const getArgs = functoolsKit.singleshot(() => {
548
553
  type: "boolean",
549
554
  default: false,
550
555
  },
556
+ init: {
557
+ type: "boolean",
558
+ default: false,
559
+ },
560
+ help: {
561
+ type: "boolean",
562
+ default: false,
563
+ },
564
+ version: {
565
+ type: "boolean",
566
+ default: false,
567
+ },
551
568
  },
552
569
  strict: false,
553
570
  allowPositionals: true,
@@ -559,7 +576,9 @@ const getArgs = functoolsKit.singleshot(() => {
559
576
  });
560
577
  const getPositional = functoolsKit.singleshot(() => {
561
578
  const { positionals = [] } = getArgs();
562
- const result = positionals.find((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
579
+ const result = positionals
580
+ .filter((value) => !DISALLOWED_PATHS.some((path) => value.includes(path)))
581
+ .find((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
563
582
  return result || null;
564
583
  });
565
584
 
@@ -2220,7 +2239,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
2220
2239
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
2221
2240
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2222
2241
  });
2223
- const main$6 = async () => {
2242
+ const main$9 = async () => {
2224
2243
  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)))) {
2225
2244
  return;
2226
2245
  }
@@ -2231,7 +2250,7 @@ const main$6 = async () => {
2231
2250
  await cli.backtestMainService.connect();
2232
2251
  listenGracefulShutdown$4();
2233
2252
  };
2234
- main$6();
2253
+ main$9();
2235
2254
 
2236
2255
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2237
2256
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2252,7 +2271,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2252
2271
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
2253
2272
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2254
2273
  });
2255
- const main$5 = async () => {
2274
+ const main$8 = async () => {
2256
2275
  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)))) {
2257
2276
  return;
2258
2277
  }
@@ -2263,7 +2282,7 @@ const main$5 = async () => {
2263
2282
  cli.paperMainService.connect();
2264
2283
  listenGracefulShutdown$3();
2265
2284
  };
2266
- main$5();
2285
+ main$8();
2267
2286
 
2268
2287
  const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2269
2288
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2284,7 +2303,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2284
2303
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
2285
2304
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2286
2305
  });
2287
- const main$4 = async () => {
2306
+ const main$7 = async () => {
2288
2307
  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)))) {
2289
2308
  return;
2290
2309
  }
@@ -2295,7 +2314,7 @@ const main$4 = async () => {
2295
2314
  await cli.liveMainService.connect();
2296
2315
  listenGracefulShutdown$2();
2297
2316
  };
2298
- main$4();
2317
+ main$7();
2299
2318
 
2300
2319
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2301
2320
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2305,7 +2324,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2305
2324
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
2306
2325
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2307
2326
  });
2308
- const main$3 = async () => {
2327
+ const main$6 = async () => {
2309
2328
  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)))) {
2310
2329
  return;
2311
2330
  }
@@ -2315,7 +2334,7 @@ const main$3 = async () => {
2315
2334
  }
2316
2335
  listenGracefulShutdown$1();
2317
2336
  };
2318
- main$3();
2337
+ main$6();
2319
2338
 
2320
2339
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2321
2340
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2325,7 +2344,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2325
2344
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
2326
2345
  process.on("SIGINT", BEFORE_EXIT_FN);
2327
2346
  });
2328
- const main$2 = async () => {
2347
+ const main$5 = async () => {
2329
2348
  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)))) {
2330
2349
  return;
2331
2350
  }
@@ -2335,7 +2354,7 @@ const main$2 = async () => {
2335
2354
  }
2336
2355
  listenGracefulShutdown();
2337
2356
  };
2338
- main$2();
2357
+ main$5();
2339
2358
 
2340
2359
  const EXTRACT_ROWS_FN = (plots, schema) => {
2341
2360
  const keys = Object.keys(schema);
@@ -2357,7 +2376,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2357
2376
  }
2358
2377
  return rows;
2359
2378
  };
2360
- const main$1 = async () => {
2379
+ const main$4 = async () => {
2361
2380
  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)))) {
2362
2381
  return;
2363
2382
  }
@@ -2410,6 +2429,7 @@ const main$1 = async () => {
2410
2429
  await fs$1.mkdir(dumpDir, { recursive: true });
2411
2430
  await fs$1.writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
2412
2431
  console.log(`Saved: ${filePath}`);
2432
+ process.exit(0);
2413
2433
  return;
2414
2434
  }
2415
2435
  if (values.jsonl) {
@@ -2418,6 +2438,7 @@ const main$1 = async () => {
2418
2438
  await fs$1.mkdir(dumpDir, { recursive: true });
2419
2439
  await fs$1.writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2420
2440
  console.log(`Saved: ${filePath}`);
2441
+ process.exit(0);
2421
2442
  return;
2422
2443
  }
2423
2444
  if (values.markdown) {
@@ -2425,13 +2446,15 @@ const main$1 = async () => {
2425
2446
  await fs$1.mkdir(dumpDir, { recursive: true });
2426
2447
  await fs$1.writeFile(filePath, await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema), "utf-8");
2427
2448
  console.log(`Saved: ${filePath}`);
2449
+ process.exit(0);
2428
2450
  return;
2429
2451
  }
2430
2452
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
2453
+ process.exit(0);
2431
2454
  };
2432
- main$1();
2455
+ main$4();
2433
2456
 
2434
- const main = async () => {
2457
+ const main$3 = async () => {
2435
2458
  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)))) {
2436
2459
  return;
2437
2460
  }
@@ -2465,6 +2488,7 @@ const main = async () => {
2465
2488
  await fs$1.mkdir(dumpDir, { recursive: true });
2466
2489
  await fs$1.writeFile(filePath, JSON.stringify(candles, null, 2), "utf-8");
2467
2490
  console.log(`Saved: ${filePath}`);
2491
+ process.exit(0);
2468
2492
  return;
2469
2493
  }
2470
2494
  if (values.jsonl) {
@@ -2472,9 +2496,221 @@ const main = async () => {
2472
2496
  await fs$1.mkdir(dumpDir, { recursive: true });
2473
2497
  await fs$1.writeFile(filePath, candles.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2474
2498
  console.log(`Saved: ${filePath}`);
2499
+ process.exit(0);
2475
2500
  return;
2476
2501
  }
2477
2502
  console.log(JSON.stringify(candles, null, 2));
2503
+ process.exit(0);
2504
+ };
2505
+ main$3();
2506
+
2507
+ const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
2508
+ const __dirname$1 = path.dirname(__filename$1);
2509
+ const MUSTACHE_EXT = ".mustache";
2510
+ const MUSTACHE_RENAME = {
2511
+ "gitignore": ".gitignore",
2512
+ "package": "package.json",
2513
+ };
2514
+ async function isDirEmpty(dirPath) {
2515
+ try {
2516
+ const files = await fs$1.readdir(dirPath);
2517
+ return files.length === 0;
2518
+ }
2519
+ catch (error) {
2520
+ if (error.code === "ENOENT") {
2521
+ return true;
2522
+ }
2523
+ throw error;
2524
+ }
2525
+ }
2526
+ async function copyDir(srcDir, destDir, data) {
2527
+ await fs$1.mkdir(destDir, { recursive: true });
2528
+ const entries = await fs$1.readdir(srcDir, { withFileTypes: true });
2529
+ for (const entry of entries) {
2530
+ const srcPath = path.join(srcDir, entry.name);
2531
+ if (entry.isDirectory()) {
2532
+ await copyDir(srcPath, path.join(destDir, entry.name), data);
2533
+ continue;
2534
+ }
2535
+ if (entry.name === ".gitkeep") {
2536
+ continue;
2537
+ }
2538
+ if (entry.name.endsWith(MUSTACHE_EXT)) {
2539
+ const baseName = entry.name.slice(0, -MUSTACHE_EXT.length);
2540
+ const destName = MUSTACHE_RENAME[baseName] ?? baseName;
2541
+ const destPath = path.join(destDir, destName);
2542
+ const template = await fs$1.readFile(srcPath, "utf-8");
2543
+ const rendered = Mustache.render(template, data);
2544
+ await fs$1.writeFile(destPath, rendered, "utf-8");
2545
+ console.log(` -> ${destPath}`);
2546
+ }
2547
+ else {
2548
+ const destPath = path.join(destDir, entry.name);
2549
+ await fs$1.copyFile(srcPath, destPath);
2550
+ console.log(` -> ${destPath}`);
2551
+ }
2552
+ }
2553
+ }
2554
+ function runScript(scriptPath, cwd) {
2555
+ return new Promise((resolve, reject) => {
2556
+ const node = process.execPath;
2557
+ const child = child_process.spawn(node, [scriptPath], { cwd, stdio: "inherit" });
2558
+ child.on("close", (code) => {
2559
+ if (code !== 0) {
2560
+ reject(new Error(`Script exited with code ${code}`));
2561
+ return;
2562
+ }
2563
+ resolve();
2564
+ });
2565
+ child.on("error", reject);
2566
+ });
2567
+ }
2568
+ const main$2 = async () => {
2569
+ 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)))) {
2570
+ return;
2571
+ }
2572
+ const { values } = getArgs();
2573
+ if (!values.init) {
2574
+ return;
2575
+ }
2576
+ const projectName = values.output || "backtest-kit-project";
2577
+ const projectPath = path.join(process.cwd(), projectName);
2578
+ const templatePath = path.join(__dirname$1, "../template/project");
2579
+ const isEmpty = await isDirEmpty(projectPath);
2580
+ if (!isEmpty) {
2581
+ console.error(`Directory "${projectName}" already exists and is not empty.`);
2582
+ process.exit(1);
2583
+ }
2584
+ console.log(`Creating project in ${projectPath}`);
2585
+ await copyDir(templatePath, projectPath, { PROJECT_NAME: projectName });
2586
+ console.log(`Fetching docs...`);
2587
+ await runScript(path.join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
2588
+ console.log(`Done! Project created at ${projectPath}`);
2589
+ process.exit(0);
2590
+ };
2591
+ main$2();
2592
+
2593
+ const HELP_TEXT = `
2594
+ @backtest-kit/cli
2595
+
2596
+ Usage:
2597
+ node index.mjs --<mode> [flags] [entry-point]
2598
+
2599
+ Modes:
2600
+
2601
+ --backtest <entry> Run strategy against historical candle data
2602
+ --paper <entry> Paper trading (live prices, no real orders)
2603
+ --live <entry> Live trading with real orders
2604
+ --pine <entry> Execute a local .pine indicator file
2605
+ --dump Fetch and save raw OHLCV candles
2606
+ --init Scaffold a new project in the current directory
2607
+ --help Print this help message
2608
+
2609
+ Backtest flags:
2610
+
2611
+ --symbol <string> Trading pair (default: BTCUSDT)
2612
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
2613
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
2614
+ --frame <string> Frame name from addFrameSchema (default: first registered)
2615
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
2616
+ --noCache Skip candle cache warming before the run
2617
+ --verbose Log every candle fetch to stdout
2618
+ --ui Start web dashboard at http://localhost:60050
2619
+ --telegram Send trade notifications to Telegram
2620
+
2621
+ Paper / Live flags:
2622
+
2623
+ --symbol <string> Trading pair (default: BTCUSDT)
2624
+ --strategy <string> Strategy name (default: first registered)
2625
+ --exchange <string> Exchange name (default: first registered)
2626
+ --verbose Log every candle fetch to stdout
2627
+ --ui Start web dashboard
2628
+ --telegram Send Telegram notifications
2629
+
2630
+ PineScript flags (--pine):
2631
+
2632
+ --symbol <string> Trading pair (default: BTCUSDT)
2633
+ --timeframe <string> Candle interval (default: 15m)
2634
+ --limit <string> Number of candles to fetch (default: 250)
2635
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
2636
+ --exchange <string> Exchange name (default: first registered)
2637
+ --output <string> Output file base name without extension
2638
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
2639
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
2640
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
2641
+
2642
+ Only plot() calls with display=display.data_window produce output columns.
2643
+ Module file ./modules/pine.module is loaded automatically if it exists.
2644
+
2645
+ Candle dump flags (--dump):
2646
+
2647
+ --symbol <string> Trading pair (default: BTCUSDT)
2648
+ --timeframe <string> Candle interval (default: 15m)
2649
+ --limit <string> Number of candles (default: 250)
2650
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
2651
+ --exchange <string> Exchange name (default: first registered)
2652
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
2653
+ --json Save as JSON array to ./dump/<output>.json
2654
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
2655
+
2656
+ Module file ./modules/dump.module is loaded automatically if it exists.
2657
+
2658
+ Init flags (--init):
2659
+
2660
+ --output <string> Target directory name (default: backtest-kit-project)
2661
+
2662
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
2663
+
2664
+ Module hooks (loaded automatically by each mode):
2665
+
2666
+ modules/backtest.module --backtest Broker adapter for backtest
2667
+ modules/paper.module --paper Broker adapter for paper trading
2668
+ modules/live.module --live Broker adapter for live trading
2669
+ modules/pine.module --pine Exchange schema for PineScript runs
2670
+ modules/dump.module --dump Exchange schema for candle dumps
2671
+
2672
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
2673
+
2674
+ Environment variables:
2675
+
2676
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
2677
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
2678
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
2679
+ CC_WWWROOT_PORT UI server port (default: 60050)
2680
+
2681
+ Examples:
2682
+
2683
+ node index.mjs --backtest ./content/feb_2026.strategy.ts
2684
+ node index.mjs --backtest --symbol BTCUSDT --noCache --ui ./content/feb_2026.strategy.ts
2685
+ node index.mjs --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
2686
+ node index.mjs --live --ui --telegram ./content/feb_2026.strategy.ts
2687
+ node index.mjs --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
2688
+ node index.mjs --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
2689
+ node index.mjs --init --output my-trading-bot
2690
+ `.trimStart();
2691
+ const main$1 = async () => {
2692
+ 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)))) {
2693
+ return;
2694
+ }
2695
+ const { values } = getArgs();
2696
+ if (!values.help) {
2697
+ return;
2698
+ }
2699
+ process.stdout.write(HELP_TEXT);
2700
+ process.exit(0);
2701
+ };
2702
+ main$1();
2703
+
2704
+ const main = async () => {
2705
+ 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)))) {
2706
+ return;
2707
+ }
2708
+ const { values } = getArgs();
2709
+ if (!values.version) {
2710
+ return;
2711
+ }
2712
+ process.stdout.write(`@backtest-kit/cli ${"6.0.0"}\n`);
2713
+ process.exit(0);
2478
2714
  };
2479
2715
  main();
2480
2716