@backtest-kit/cli 5.11.1 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.cjs CHANGED
@@ -557,6 +557,14 @@ const getArgs = functoolsKit.singleshot(() => {
557
557
  type: "boolean",
558
558
  default: false,
559
559
  },
560
+ help: {
561
+ type: "boolean",
562
+ default: false,
563
+ },
564
+ version: {
565
+ type: "boolean",
566
+ default: false,
567
+ },
560
568
  },
561
569
  strict: false,
562
570
  allowPositionals: true,
@@ -2231,7 +2239,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
2231
2239
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
2232
2240
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2233
2241
  });
2234
- const main$7 = async () => {
2242
+ const main$9 = async () => {
2235
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)))) {
2236
2244
  return;
2237
2245
  }
@@ -2242,7 +2250,7 @@ const main$7 = async () => {
2242
2250
  await cli.backtestMainService.connect();
2243
2251
  listenGracefulShutdown$4();
2244
2252
  };
2245
- main$7();
2253
+ main$9();
2246
2254
 
2247
2255
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2248
2256
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2263,7 +2271,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2263
2271
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
2264
2272
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2265
2273
  });
2266
- const main$6 = async () => {
2274
+ const main$8 = async () => {
2267
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)))) {
2268
2276
  return;
2269
2277
  }
@@ -2274,7 +2282,7 @@ const main$6 = async () => {
2274
2282
  cli.paperMainService.connect();
2275
2283
  listenGracefulShutdown$3();
2276
2284
  };
2277
- main$6();
2285
+ main$8();
2278
2286
 
2279
2287
  const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2280
2288
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2295,7 +2303,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2295
2303
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
2296
2304
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2297
2305
  });
2298
- const main$5 = async () => {
2306
+ const main$7 = async () => {
2299
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)))) {
2300
2308
  return;
2301
2309
  }
@@ -2306,7 +2314,7 @@ const main$5 = async () => {
2306
2314
  await cli.liveMainService.connect();
2307
2315
  listenGracefulShutdown$2();
2308
2316
  };
2309
- main$5();
2317
+ main$7();
2310
2318
 
2311
2319
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2312
2320
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2316,7 +2324,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2316
2324
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
2317
2325
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2318
2326
  });
2319
- const main$4 = async () => {
2327
+ const main$6 = async () => {
2320
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)))) {
2321
2329
  return;
2322
2330
  }
@@ -2326,7 +2334,7 @@ const main$4 = async () => {
2326
2334
  }
2327
2335
  listenGracefulShutdown$1();
2328
2336
  };
2329
- main$4();
2337
+ main$6();
2330
2338
 
2331
2339
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2332
2340
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2336,7 +2344,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2336
2344
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
2337
2345
  process.on("SIGINT", BEFORE_EXIT_FN);
2338
2346
  });
2339
- const main$3 = async () => {
2347
+ const main$5 = async () => {
2340
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)))) {
2341
2349
  return;
2342
2350
  }
@@ -2346,7 +2354,7 @@ const main$3 = async () => {
2346
2354
  }
2347
2355
  listenGracefulShutdown();
2348
2356
  };
2349
- main$3();
2357
+ main$5();
2350
2358
 
2351
2359
  const EXTRACT_ROWS_FN = (plots, schema) => {
2352
2360
  const keys = Object.keys(schema);
@@ -2368,7 +2376,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2368
2376
  }
2369
2377
  return rows;
2370
2378
  };
2371
- const main$2 = async () => {
2379
+ const main$4 = async () => {
2372
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)))) {
2373
2381
  return;
2374
2382
  }
@@ -2421,6 +2429,7 @@ const main$2 = async () => {
2421
2429
  await fs$1.mkdir(dumpDir, { recursive: true });
2422
2430
  await fs$1.writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
2423
2431
  console.log(`Saved: ${filePath}`);
2432
+ process.exit(0);
2424
2433
  return;
2425
2434
  }
2426
2435
  if (values.jsonl) {
@@ -2429,6 +2438,7 @@ const main$2 = async () => {
2429
2438
  await fs$1.mkdir(dumpDir, { recursive: true });
2430
2439
  await fs$1.writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2431
2440
  console.log(`Saved: ${filePath}`);
2441
+ process.exit(0);
2432
2442
  return;
2433
2443
  }
2434
2444
  if (values.markdown) {
@@ -2436,13 +2446,15 @@ const main$2 = async () => {
2436
2446
  await fs$1.mkdir(dumpDir, { recursive: true });
2437
2447
  await fs$1.writeFile(filePath, await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema), "utf-8");
2438
2448
  console.log(`Saved: ${filePath}`);
2449
+ process.exit(0);
2439
2450
  return;
2440
2451
  }
2441
2452
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
2453
+ process.exit(0);
2442
2454
  };
2443
- main$2();
2455
+ main$4();
2444
2456
 
2445
- const main$1 = async () => {
2457
+ const main$3 = async () => {
2446
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)))) {
2447
2459
  return;
2448
2460
  }
@@ -2476,6 +2488,7 @@ const main$1 = async () => {
2476
2488
  await fs$1.mkdir(dumpDir, { recursive: true });
2477
2489
  await fs$1.writeFile(filePath, JSON.stringify(candles, null, 2), "utf-8");
2478
2490
  console.log(`Saved: ${filePath}`);
2491
+ process.exit(0);
2479
2492
  return;
2480
2493
  }
2481
2494
  if (values.jsonl) {
@@ -2483,15 +2496,34 @@ const main$1 = async () => {
2483
2496
  await fs$1.mkdir(dumpDir, { recursive: true });
2484
2497
  await fs$1.writeFile(filePath, candles.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2485
2498
  console.log(`Saved: ${filePath}`);
2499
+ process.exit(0);
2500
+ return;
2501
+ }
2502
+ if (values.markdown) {
2503
+ const header = `| open | high | low | close | volume | timestamp |\n| --- | --- | --- | --- | --- | --- |`;
2504
+ const rows = candles.map((c) => `| ${c.open} | ${c.high} | ${c.low} | ${c.close} | ${c.volume} | ${new Date(c.timestamp).toISOString()} |`);
2505
+ const md = [header, ...rows].join("\n");
2506
+ {
2507
+ const filePath = path.resolve(dumpDir, `${dumpName}.md`);
2508
+ await fs$1.mkdir(dumpDir, { recursive: true });
2509
+ await fs$1.writeFile(filePath, md, "utf-8");
2510
+ console.log(`Saved: ${filePath}`);
2511
+ }
2512
+ process.exit(0);
2486
2513
  return;
2487
2514
  }
2488
2515
  console.log(JSON.stringify(candles, null, 2));
2516
+ process.exit(0);
2489
2517
  };
2490
- main$1();
2518
+ main$3();
2491
2519
 
2492
2520
  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)));
2493
2521
  const __dirname$1 = path.dirname(__filename$1);
2494
2522
  const MUSTACHE_EXT = ".mustache";
2523
+ const MUSTACHE_RENAME = {
2524
+ "gitignore": ".gitignore",
2525
+ "package": "package.json",
2526
+ };
2495
2527
  async function isDirEmpty(dirPath) {
2496
2528
  try {
2497
2529
  const files = await fs$1.readdir(dirPath);
@@ -2513,8 +2545,12 @@ async function copyDir(srcDir, destDir, data) {
2513
2545
  await copyDir(srcPath, path.join(destDir, entry.name), data);
2514
2546
  continue;
2515
2547
  }
2548
+ if (entry.name === ".gitkeep") {
2549
+ continue;
2550
+ }
2516
2551
  if (entry.name.endsWith(MUSTACHE_EXT)) {
2517
- const destName = entry.name.slice(0, -MUSTACHE_EXT.length);
2552
+ const baseName = entry.name.slice(0, -MUSTACHE_EXT.length);
2553
+ const destName = MUSTACHE_RENAME[baseName] ?? baseName;
2518
2554
  const destPath = path.join(destDir, destName);
2519
2555
  const template = await fs$1.readFile(srcPath, "utf-8");
2520
2556
  const rendered = Mustache.render(template, data);
@@ -2542,7 +2578,7 @@ function runScript(scriptPath, cwd) {
2542
2578
  child.on("error", reject);
2543
2579
  });
2544
2580
  }
2545
- const main = async () => {
2581
+ const main$2 = async () => {
2546
2582
  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)))) {
2547
2583
  return;
2548
2584
  }
@@ -2552,7 +2588,7 @@ const main = async () => {
2552
2588
  }
2553
2589
  const projectName = values.output || "backtest-kit-project";
2554
2590
  const projectPath = path.join(process.cwd(), projectName);
2555
- const templatePath = path.join(__dirname$1, "../../template/project");
2591
+ const templatePath = path.join(__dirname$1, "../template/project");
2556
2592
  const isEmpty = await isDirEmpty(projectPath);
2557
2593
  if (!isEmpty) {
2558
2594
  console.error(`Directory "${projectName}" already exists and is not empty.`);
@@ -2563,6 +2599,131 @@ const main = async () => {
2563
2599
  console.log(`Fetching docs...`);
2564
2600
  await runScript(path.join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
2565
2601
  console.log(`Done! Project created at ${projectPath}`);
2602
+ process.exit(0);
2603
+ };
2604
+ main$2();
2605
+
2606
+ const HELP_TEXT = `
2607
+ @backtest-kit/cli
2608
+
2609
+ Usage:
2610
+ node index.mjs --<mode> [flags] [entry-point]
2611
+
2612
+ Modes:
2613
+
2614
+ --backtest <entry> Run strategy against historical candle data
2615
+ --paper <entry> Paper trading (live prices, no real orders)
2616
+ --live <entry> Live trading with real orders
2617
+ --pine <entry> Execute a local .pine indicator file
2618
+ --dump Fetch and save raw OHLCV candles
2619
+ --init Scaffold a new project in the current directory
2620
+ --help Print this help message
2621
+
2622
+ Backtest flags:
2623
+
2624
+ --symbol <string> Trading pair (default: BTCUSDT)
2625
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
2626
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
2627
+ --frame <string> Frame name from addFrameSchema (default: first registered)
2628
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
2629
+ --noCache Skip candle cache warming before the run
2630
+ --verbose Log every candle fetch to stdout
2631
+ --ui Start web dashboard at http://localhost:60050
2632
+ --telegram Send trade notifications to Telegram
2633
+
2634
+ Paper / Live flags:
2635
+
2636
+ --symbol <string> Trading pair (default: BTCUSDT)
2637
+ --strategy <string> Strategy name (default: first registered)
2638
+ --exchange <string> Exchange name (default: first registered)
2639
+ --verbose Log every candle fetch to stdout
2640
+ --ui Start web dashboard
2641
+ --telegram Send Telegram notifications
2642
+
2643
+ PineScript flags (--pine):
2644
+
2645
+ --symbol <string> Trading pair (default: BTCUSDT)
2646
+ --timeframe <string> Candle interval (default: 15m)
2647
+ --limit <string> Number of candles to fetch (default: 250)
2648
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
2649
+ --exchange <string> Exchange name (default: first registered)
2650
+ --output <string> Output file base name without extension
2651
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
2652
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
2653
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
2654
+
2655
+ Only plot() calls with display=display.data_window produce output columns.
2656
+ Module file ./modules/pine.module is loaded automatically if it exists.
2657
+
2658
+ Candle dump flags (--dump):
2659
+
2660
+ --symbol <string> Trading pair (default: BTCUSDT)
2661
+ --timeframe <string> Candle interval (default: 15m)
2662
+ --limit <string> Number of candles (default: 250)
2663
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
2664
+ --exchange <string> Exchange name (default: first registered)
2665
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
2666
+ --json Save as JSON array to ./dump/<output>.json
2667
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
2668
+
2669
+ Module file ./modules/dump.module is loaded automatically if it exists.
2670
+
2671
+ Init flags (--init):
2672
+
2673
+ --output <string> Target directory name (default: backtest-kit-project)
2674
+
2675
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
2676
+
2677
+ Module hooks (loaded automatically by each mode):
2678
+
2679
+ modules/backtest.module --backtest Broker adapter for backtest
2680
+ modules/paper.module --paper Broker adapter for paper trading
2681
+ modules/live.module --live Broker adapter for live trading
2682
+ modules/pine.module --pine Exchange schema for PineScript runs
2683
+ modules/dump.module --dump Exchange schema for candle dumps
2684
+
2685
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
2686
+
2687
+ Environment variables:
2688
+
2689
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
2690
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
2691
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
2692
+ CC_WWWROOT_PORT UI server port (default: 60050)
2693
+
2694
+ Examples:
2695
+
2696
+ node index.mjs --backtest ./content/feb_2026.strategy.ts
2697
+ node index.mjs --backtest --symbol BTCUSDT --noCache --ui ./content/feb_2026.strategy.ts
2698
+ node index.mjs --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
2699
+ node index.mjs --live --ui --telegram ./content/feb_2026.strategy.ts
2700
+ node index.mjs --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
2701
+ node index.mjs --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
2702
+ node index.mjs --init --output my-trading-bot
2703
+ `.trimStart();
2704
+ const main$1 = 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.help) {
2710
+ return;
2711
+ }
2712
+ process.stdout.write(HELP_TEXT);
2713
+ process.exit(0);
2714
+ };
2715
+ main$1();
2716
+
2717
+ const main = async () => {
2718
+ 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)))) {
2719
+ return;
2720
+ }
2721
+ const { values } = getArgs();
2722
+ if (!values.version) {
2723
+ return;
2724
+ }
2725
+ process.stdout.write(`@backtest-kit/cli ${"6.0.0"}\n`);
2726
+ process.exit(0);
2566
2727
  };
2567
2728
  main();
2568
2729
 
package/build/index.mjs CHANGED
@@ -532,6 +532,14 @@ const getArgs = singleshot(() => {
532
532
  type: "boolean",
533
533
  default: false,
534
534
  },
535
+ help: {
536
+ type: "boolean",
537
+ default: false,
538
+ },
539
+ version: {
540
+ type: "boolean",
541
+ default: false,
542
+ },
535
543
  },
536
544
  strict: false,
537
545
  allowPositionals: true,
@@ -2202,7 +2210,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
2202
2210
  const listenGracefulShutdown$4 = singleshot(() => {
2203
2211
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2204
2212
  });
2205
- const main$7 = async () => {
2213
+ const main$9 = async () => {
2206
2214
  if (!getEntry(import.meta.url)) {
2207
2215
  return;
2208
2216
  }
@@ -2213,7 +2221,7 @@ const main$7 = async () => {
2213
2221
  await cli.backtestMainService.connect();
2214
2222
  listenGracefulShutdown$4();
2215
2223
  };
2216
- main$7();
2224
+ main$9();
2217
2225
 
2218
2226
  const BEFORE_EXIT_FN$3 = singleshot(async () => {
2219
2227
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2234,7 +2242,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
2234
2242
  const listenGracefulShutdown$3 = singleshot(() => {
2235
2243
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2236
2244
  });
2237
- const main$6 = async () => {
2245
+ const main$8 = async () => {
2238
2246
  if (!getEntry(import.meta.url)) {
2239
2247
  return;
2240
2248
  }
@@ -2245,7 +2253,7 @@ const main$6 = async () => {
2245
2253
  cli.paperMainService.connect();
2246
2254
  listenGracefulShutdown$3();
2247
2255
  };
2248
- main$6();
2256
+ main$8();
2249
2257
 
2250
2258
  const BEFORE_EXIT_FN$2 = singleshot(async () => {
2251
2259
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2266,7 +2274,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
2266
2274
  const listenGracefulShutdown$2 = singleshot(() => {
2267
2275
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2268
2276
  });
2269
- const main$5 = async () => {
2277
+ const main$7 = async () => {
2270
2278
  if (!getEntry(import.meta.url)) {
2271
2279
  return;
2272
2280
  }
@@ -2277,7 +2285,7 @@ const main$5 = async () => {
2277
2285
  await cli.liveMainService.connect();
2278
2286
  listenGracefulShutdown$2();
2279
2287
  };
2280
- main$5();
2288
+ main$7();
2281
2289
 
2282
2290
  const BEFORE_EXIT_FN$1 = singleshot(async () => {
2283
2291
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2287,7 +2295,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
2287
2295
  const listenGracefulShutdown$1 = singleshot(() => {
2288
2296
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2289
2297
  });
2290
- const main$4 = async () => {
2298
+ const main$6 = async () => {
2291
2299
  if (!getEntry(import.meta.url)) {
2292
2300
  return;
2293
2301
  }
@@ -2297,7 +2305,7 @@ const main$4 = async () => {
2297
2305
  }
2298
2306
  listenGracefulShutdown$1();
2299
2307
  };
2300
- main$4();
2308
+ main$6();
2301
2309
 
2302
2310
  const BEFORE_EXIT_FN = singleshot(async () => {
2303
2311
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2307,7 +2315,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
2307
2315
  const listenGracefulShutdown = singleshot(() => {
2308
2316
  process.on("SIGINT", BEFORE_EXIT_FN);
2309
2317
  });
2310
- const main$3 = async () => {
2318
+ const main$5 = async () => {
2311
2319
  if (!getEntry(import.meta.url)) {
2312
2320
  return;
2313
2321
  }
@@ -2317,7 +2325,7 @@ const main$3 = async () => {
2317
2325
  }
2318
2326
  listenGracefulShutdown();
2319
2327
  };
2320
- main$3();
2328
+ main$5();
2321
2329
 
2322
2330
  const EXTRACT_ROWS_FN = (plots, schema) => {
2323
2331
  const keys = Object.keys(schema);
@@ -2339,7 +2347,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2339
2347
  }
2340
2348
  return rows;
2341
2349
  };
2342
- const main$2 = async () => {
2350
+ const main$4 = async () => {
2343
2351
  if (!getEntry(import.meta.url)) {
2344
2352
  return;
2345
2353
  }
@@ -2392,6 +2400,7 @@ const main$2 = async () => {
2392
2400
  await mkdir(dumpDir, { recursive: true });
2393
2401
  await writeFile(filePath, JSON.stringify(rows, null, 2), "utf-8");
2394
2402
  console.log(`Saved: ${filePath}`);
2403
+ process.exit(0);
2395
2404
  return;
2396
2405
  }
2397
2406
  if (values.jsonl) {
@@ -2400,6 +2409,7 @@ const main$2 = async () => {
2400
2409
  await mkdir(dumpDir, { recursive: true });
2401
2410
  await writeFile(filePath, rows.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2402
2411
  console.log(`Saved: ${filePath}`);
2412
+ process.exit(0);
2403
2413
  return;
2404
2414
  }
2405
2415
  if (values.markdown) {
@@ -2407,13 +2417,15 @@ const main$2 = async () => {
2407
2417
  await mkdir(dumpDir, { recursive: true });
2408
2418
  await writeFile(filePath, await toMarkdown(signalId, plots, signalSchema), "utf-8");
2409
2419
  console.log(`Saved: ${filePath}`);
2420
+ process.exit(0);
2410
2421
  return;
2411
2422
  }
2412
2423
  console.log(await toMarkdown(signalId, plots, signalSchema));
2424
+ process.exit(0);
2413
2425
  };
2414
- main$2();
2426
+ main$4();
2415
2427
 
2416
- const main$1 = async () => {
2428
+ const main$3 = async () => {
2417
2429
  if (!getEntry(import.meta.url)) {
2418
2430
  return;
2419
2431
  }
@@ -2447,6 +2459,7 @@ const main$1 = async () => {
2447
2459
  await mkdir(dumpDir, { recursive: true });
2448
2460
  await writeFile(filePath, JSON.stringify(candles, null, 2), "utf-8");
2449
2461
  console.log(`Saved: ${filePath}`);
2462
+ process.exit(0);
2450
2463
  return;
2451
2464
  }
2452
2465
  if (values.jsonl) {
@@ -2454,15 +2467,34 @@ const main$1 = async () => {
2454
2467
  await mkdir(dumpDir, { recursive: true });
2455
2468
  await writeFile(filePath, candles.map((r) => JSON.stringify(r)).join("\n"), "utf-8");
2456
2469
  console.log(`Saved: ${filePath}`);
2470
+ process.exit(0);
2471
+ return;
2472
+ }
2473
+ if (values.markdown) {
2474
+ const header = `| open | high | low | close | volume | timestamp |\n| --- | --- | --- | --- | --- | --- |`;
2475
+ const rows = candles.map((c) => `| ${c.open} | ${c.high} | ${c.low} | ${c.close} | ${c.volume} | ${new Date(c.timestamp).toISOString()} |`);
2476
+ const md = [header, ...rows].join("\n");
2477
+ {
2478
+ const filePath = resolve(dumpDir, `${dumpName}.md`);
2479
+ await mkdir(dumpDir, { recursive: true });
2480
+ await writeFile(filePath, md, "utf-8");
2481
+ console.log(`Saved: ${filePath}`);
2482
+ }
2483
+ process.exit(0);
2457
2484
  return;
2458
2485
  }
2459
2486
  console.log(JSON.stringify(candles, null, 2));
2487
+ process.exit(0);
2460
2488
  };
2461
- main$1();
2489
+ main$3();
2462
2490
 
2463
2491
  const __filename = fileURLToPath(import.meta.url);
2464
2492
  const __dirname = dirname(__filename);
2465
2493
  const MUSTACHE_EXT = ".mustache";
2494
+ const MUSTACHE_RENAME = {
2495
+ "gitignore": ".gitignore",
2496
+ "package": "package.json",
2497
+ };
2466
2498
  async function isDirEmpty(dirPath) {
2467
2499
  try {
2468
2500
  const files = await readdir(dirPath);
@@ -2484,8 +2516,12 @@ async function copyDir(srcDir, destDir, data) {
2484
2516
  await copyDir(srcPath, join(destDir, entry.name), data);
2485
2517
  continue;
2486
2518
  }
2519
+ if (entry.name === ".gitkeep") {
2520
+ continue;
2521
+ }
2487
2522
  if (entry.name.endsWith(MUSTACHE_EXT)) {
2488
- const destName = entry.name.slice(0, -MUSTACHE_EXT.length);
2523
+ const baseName = entry.name.slice(0, -MUSTACHE_EXT.length);
2524
+ const destName = MUSTACHE_RENAME[baseName] ?? baseName;
2489
2525
  const destPath = join(destDir, destName);
2490
2526
  const template = await readFile(srcPath, "utf-8");
2491
2527
  const rendered = Mustache.render(template, data);
@@ -2513,7 +2549,7 @@ function runScript(scriptPath, cwd) {
2513
2549
  child.on("error", reject);
2514
2550
  });
2515
2551
  }
2516
- const main = async () => {
2552
+ const main$2 = async () => {
2517
2553
  if (!getEntry(import.meta.url)) {
2518
2554
  return;
2519
2555
  }
@@ -2523,7 +2559,7 @@ const main = async () => {
2523
2559
  }
2524
2560
  const projectName = values.output || "backtest-kit-project";
2525
2561
  const projectPath = join(process.cwd(), projectName);
2526
- const templatePath = join(__dirname, "../../template/project");
2562
+ const templatePath = join(__dirname, "../template/project");
2527
2563
  const isEmpty = await isDirEmpty(projectPath);
2528
2564
  if (!isEmpty) {
2529
2565
  console.error(`Directory "${projectName}" already exists and is not empty.`);
@@ -2534,6 +2570,131 @@ const main = async () => {
2534
2570
  console.log(`Fetching docs...`);
2535
2571
  await runScript(join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
2536
2572
  console.log(`Done! Project created at ${projectPath}`);
2573
+ process.exit(0);
2574
+ };
2575
+ main$2();
2576
+
2577
+ const HELP_TEXT = `
2578
+ @backtest-kit/cli
2579
+
2580
+ Usage:
2581
+ node index.mjs --<mode> [flags] [entry-point]
2582
+
2583
+ Modes:
2584
+
2585
+ --backtest <entry> Run strategy against historical candle data
2586
+ --paper <entry> Paper trading (live prices, no real orders)
2587
+ --live <entry> Live trading with real orders
2588
+ --pine <entry> Execute a local .pine indicator file
2589
+ --dump Fetch and save raw OHLCV candles
2590
+ --init Scaffold a new project in the current directory
2591
+ --help Print this help message
2592
+
2593
+ Backtest flags:
2594
+
2595
+ --symbol <string> Trading pair (default: BTCUSDT)
2596
+ --strategy <string> Strategy name from addStrategySchema (default: first registered)
2597
+ --exchange <string> Exchange name from addExchangeSchema (default: first registered)
2598
+ --frame <string> Frame name from addFrameSchema (default: first registered)
2599
+ --cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
2600
+ --noCache Skip candle cache warming before the run
2601
+ --verbose Log every candle fetch to stdout
2602
+ --ui Start web dashboard at http://localhost:60050
2603
+ --telegram Send trade notifications to Telegram
2604
+
2605
+ Paper / Live flags:
2606
+
2607
+ --symbol <string> Trading pair (default: BTCUSDT)
2608
+ --strategy <string> Strategy name (default: first registered)
2609
+ --exchange <string> Exchange name (default: first registered)
2610
+ --verbose Log every candle fetch to stdout
2611
+ --ui Start web dashboard
2612
+ --telegram Send Telegram notifications
2613
+
2614
+ PineScript flags (--pine):
2615
+
2616
+ --symbol <string> Trading pair (default: BTCUSDT)
2617
+ --timeframe <string> Candle interval (default: 15m)
2618
+ --limit <string> Number of candles to fetch (default: 250)
2619
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
2620
+ --exchange <string> Exchange name (default: first registered)
2621
+ --output <string> Output file base name without extension
2622
+ --json Save output as JSON array to <pine-dir>/dump/<output>.json
2623
+ --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
2624
+ --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
2625
+
2626
+ Only plot() calls with display=display.data_window produce output columns.
2627
+ Module file ./modules/pine.module is loaded automatically if it exists.
2628
+
2629
+ Candle dump flags (--dump):
2630
+
2631
+ --symbol <string> Trading pair (default: BTCUSDT)
2632
+ --timeframe <string> Candle interval (default: 15m)
2633
+ --limit <string> Number of candles (default: 250)
2634
+ --when <string> End date — ISO 8601 or Unix ms (default: now)
2635
+ --exchange <string> Exchange name (default: first registered)
2636
+ --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
2637
+ --json Save as JSON array to ./dump/<output>.json
2638
+ --jsonl Save as JSONL to ./dump/<output>.jsonl
2639
+
2640
+ Module file ./modules/dump.module is loaded automatically if it exists.
2641
+
2642
+ Init flags (--init):
2643
+
2644
+ --output <string> Target directory name (default: backtest-kit-project)
2645
+
2646
+ Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
2647
+
2648
+ Module hooks (loaded automatically by each mode):
2649
+
2650
+ modules/backtest.module --backtest Broker adapter for backtest
2651
+ modules/paper.module --paper Broker adapter for paper trading
2652
+ modules/live.module --live Broker adapter for live trading
2653
+ modules/pine.module --pine Exchange schema for PineScript runs
2654
+ modules/dump.module --dump Exchange schema for candle dumps
2655
+
2656
+ Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
2657
+
2658
+ Environment variables:
2659
+
2660
+ CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
2661
+ CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
2662
+ CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
2663
+ CC_WWWROOT_PORT UI server port (default: 60050)
2664
+
2665
+ Examples:
2666
+
2667
+ node index.mjs --backtest ./content/feb_2026.strategy.ts
2668
+ node index.mjs --backtest --symbol BTCUSDT --noCache --ui ./content/feb_2026.strategy.ts
2669
+ node index.mjs --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
2670
+ node index.mjs --live --ui --telegram ./content/feb_2026.strategy.ts
2671
+ node index.mjs --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
2672
+ node index.mjs --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
2673
+ node index.mjs --init --output my-trading-bot
2674
+ `.trimStart();
2675
+ const main$1 = async () => {
2676
+ if (!getEntry(import.meta.url)) {
2677
+ return;
2678
+ }
2679
+ const { values } = getArgs();
2680
+ if (!values.help) {
2681
+ return;
2682
+ }
2683
+ process.stdout.write(HELP_TEXT);
2684
+ process.exit(0);
2685
+ };
2686
+ main$1();
2687
+
2688
+ const main = async () => {
2689
+ if (!getEntry(import.meta.url)) {
2690
+ return;
2691
+ }
2692
+ const { values } = getArgs();
2693
+ if (!values.version) {
2694
+ return;
2695
+ }
2696
+ process.stdout.write(`@backtest-kit/cli ${"6.0.0"}\n`);
2697
+ process.exit(0);
2537
2698
  };
2538
2699
  main();
2539
2700
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "5.11.1",
3
+ "version": "6.0.1",
4
4
  "description": "Zero-boilerplate CLI runner for backtest-kit strategies. Run backtests, paper trading, and live bots with candle cache warming, web dashboard, and Telegram notifications — no setup code required.",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
@@ -61,11 +61,11 @@
61
61
  "devDependencies": {
62
62
  "@babel/plugin-transform-modules-umd": "7.27.1",
63
63
  "@babel/standalone": "7.29.1",
64
- "@backtest-kit/ui": "5.11.0",
65
- "@backtest-kit/graph": "5.11.0",
66
- "@backtest-kit/ollama": "5.11.0",
67
- "@backtest-kit/pinets": "5.11.0",
68
- "@backtest-kit/signals": "5.11.0",
64
+ "@backtest-kit/ui": "6.0.0",
65
+ "@backtest-kit/graph": "6.0.0",
66
+ "@backtest-kit/ollama": "6.0.0",
67
+ "@backtest-kit/pinets": "6.0.0",
68
+ "@backtest-kit/signals": "6.0.0",
69
69
  "@rollup/plugin-replace": "6.0.3",
70
70
  "@rollup/plugin-typescript": "11.1.6",
71
71
  "@types/image-size": "0.7.0",
@@ -73,7 +73,7 @@
73
73
  "@types/mustache": "4.2.6",
74
74
  "@types/node": "22.9.0",
75
75
  "@types/stack-trace": "0.0.33",
76
- "backtest-kit": "5.11.0",
76
+ "backtest-kit": "6.0.0",
77
77
  "glob": "11.0.1",
78
78
  "markdown-it": "14.1.1",
79
79
  "rimraf": "6.0.1",
@@ -88,12 +88,12 @@
88
88
  "peerDependencies": {
89
89
  "@babel/plugin-transform-modules-umd": "^7.27.1",
90
90
  "@babel/standalone": "^7.29.1",
91
- "@backtest-kit/ui": "^5.11.0",
92
- "@backtest-kit/graph": "^5.11.0",
93
- "@backtest-kit/ollama": "^5.11.0",
94
- "@backtest-kit/pinets": "^5.11.0",
95
- "@backtest-kit/signals": "^5.11.0",
96
- "backtest-kit": "^5.11.0",
91
+ "@backtest-kit/ui": "^6.0.0",
92
+ "@backtest-kit/graph": "^6.0.0",
93
+ "@backtest-kit/ollama": "^6.0.0",
94
+ "@backtest-kit/pinets": "^6.0.0",
95
+ "@backtest-kit/signals": "^6.0.0",
96
+ "backtest-kit": "^6.0.0",
97
97
  "markdown-it": "^14.1.1",
98
98
  "typescript": "^5.0.0"
99
99
  },
@@ -0,0 +1,207 @@
1
+ # 🧿 Backtest Kit Project
2
+
3
+ > A TypeScript framework for backtesting and live trading strategies on multi-asset, crypto, forex or [DEX (peer-to-peer marketplace)](https://en.wikipedia.org/wiki/Decentralized_finance#Decentralized_exchanges), spot, futures with crash-safe persistence, signal validation, and AI optimization.
4
+
5
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot16.png)
6
+
7
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/tripolskypetr/backtest-kit)
8
+ [![npm](https://img.shields.io/npm/v/backtest-kit.svg?style=flat-square)](https://npmjs.org/package/backtest-kit)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
10
+ [![Build](https://github.com/tripolskypetr/backtest-kit/actions/workflows/webpack.yml/badge.svg)](https://github.com/tripolskypetr/backtest-kit/actions/workflows/webpack.yml)
11
+
12
+ A minimal project scaffold for [backtest-kit](https://github.com/tripolskypetr/backtest-kit). All infrastructure (exchange registration, candle caching, runner, UI, Telegram) is handled by `@backtest-kit/cli` — this project contains only your strategy files.
13
+
14
+ ## 📋 Quick Start
15
+
16
+ ```bash
17
+ npm start # Run the CLI (append flags below)
18
+ npm run sync:lib # Refresh library docs in docs/lib/
19
+ ```
20
+
21
+ ## 🏃 Running Modes
22
+
23
+ All modes are invoked via `npm start -- <flags> <entry-point>`.
24
+
25
+ ### 🧪 Backtest
26
+
27
+ Runs the strategy against historical candle data defined by a `FrameSchema`.
28
+
29
+ ```bash
30
+ npm start -- --backtest --symbol BTCUSDT --strategy feb_2026_strategy --exchange ccxt-exchange --frame feb_2026_frame ./content/feb_2026.strategy.ts
31
+ ```
32
+
33
+ | Flag | Type | Default | Description |
34
+ |------|------|---------|-------------|
35
+ | `--backtest` | boolean | — | Enable backtest mode |
36
+ | `--symbol` | string | `BTCUSDT` | Trading pair |
37
+ | `--strategy` | string | first registered | Strategy name from `addStrategySchema` |
38
+ | `--exchange` | string | first registered | Exchange name from `addExchangeSchema` |
39
+ | `--frame` | string | first registered | Frame name from `addFrameSchema` |
40
+ | `--cacheInterval` | string | `1m, 15m, 30m, 1h, 4h` | Comma-separated intervals to pre-cache before the run |
41
+ | `--noCache` | boolean | `false` | Skip candle cache warming |
42
+ | `--verbose` | boolean | `false` | Log every candle fetch to stdout |
43
+ | `--ui` | boolean | `false` | Start web dashboard at `http://localhost:60050` |
44
+ | `--telegram` | boolean | `false` | Send trade notifications to Telegram |
45
+
46
+ Before the backtest starts, the CLI warms the candle cache for every interval in `--cacheInterval`. On subsequent runs the cache is used directly — no extra API calls. Pass `--noCache` to skip this step.
47
+
48
+ Module file `./modules/backtest.module.ts` (or `.mjs`) is loaded automatically if it exists.
49
+
50
+ ### 📄 Paper Trading
51
+
52
+ Connects to the live exchange but places no real orders. Identical code path to `--live` — safe for strategy validation.
53
+
54
+ ```bash
55
+ npm start -- --paper --symbol BTCUSDT --strategy feb_2026_strategy --exchange ccxt-exchange ./content/feb_2026.strategy.ts
56
+ ```
57
+
58
+ | Flag | Type | Default | Description |
59
+ |------|------|---------|-------------|
60
+ | `--paper` | boolean | — | Enable paper trading mode |
61
+ | `--symbol` | string | `BTCUSDT` | Trading pair |
62
+ | `--strategy` | string | first registered | Strategy name |
63
+ | `--exchange` | string | first registered | Exchange name |
64
+ | `--verbose` | boolean | `false` | Log every candle fetch to stdout |
65
+ | `--ui` | boolean | `false` | Start web dashboard |
66
+ | `--telegram` | boolean | `false` | Enable Telegram notifications |
67
+
68
+ Module file `./modules/paper.module.ts` is loaded automatically if it exists.
69
+
70
+ ### 📈 Live Trading
71
+
72
+ Deploys a real trading bot. Requires exchange API keys in `.env`.
73
+
74
+ ```bash
75
+ npm start -- --live --symbol BTCUSDT --ui --telegram ./content/feb_2026.strategy.ts
76
+ ```
77
+
78
+ | Flag | Type | Default | Description |
79
+ |------|------|---------|-------------|
80
+ | `--live` | boolean | — | Enable live trading mode |
81
+ | `--symbol` | string | `BTCUSDT` | Trading pair |
82
+ | `--strategy` | string | first registered | Strategy name |
83
+ | `--exchange` | string | first registered | Exchange name |
84
+ | `--verbose` | boolean | `false` | Log every candle fetch to stdout |
85
+ | `--ui` | boolean | `false` | Start web dashboard |
86
+ | `--telegram` | boolean | `false` | Enable Telegram notifications |
87
+
88
+ Module file `./modules/live.module.ts` is loaded automatically if it exists. Use it to register a `Broker` adapter that intercepts every trade mutation before internal state changes — exchange rejection rolls back the operation atomically.
89
+
90
+ ## 🌲 Running PineScript Indicators (`--pine`)
91
+
92
+ Executes a local `.pine` file against a real exchange and prints the output as a Markdown table or saves it to a file.
93
+
94
+ ```bash
95
+ npm start -- --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --when "2026-02-28T00:00:00.000Z" --jsonl
96
+ ```
97
+
98
+ Output file is created at `./math/dump/<name>.jsonl` (next to the `.pine` file).
99
+
100
+ | Flag | Type | Default | Description |
101
+ |------|------|---------|-------------|
102
+ | `--pine` | boolean | — | Enable PineScript execution mode |
103
+ | `--symbol` | string | `BTCUSDT` | Trading pair |
104
+ | `--timeframe` | string | `15m` | Candle interval |
105
+ | `--limit` | string | `250` | Number of candles to fetch |
106
+ | `--when` | string | now | End date — ISO 8601 or Unix ms |
107
+ | `--exchange` | string | first registered | Exchange name |
108
+ | `--output` | string | `.pine` file name | Output file base name (no extension) |
109
+ | `--json` | boolean | `false` | Save output as JSON array |
110
+ | `--jsonl` | boolean | `false` | Save output as JSONL (one row per line) |
111
+ | `--markdown` | boolean | `false` | Save output as Markdown table |
112
+
113
+ Module file `./modules/pine.module.ts` is loaded automatically. The project includes it pre-configured with CCXT Binance. Override it to use a different exchange.
114
+
115
+ Only `plot()` calls with `display=display.data_window` produce output columns:
116
+
117
+ ```pine
118
+ plot(close, "Close", display=display.data_window)
119
+ plot(position, "Position", display=display.data_window)
120
+ ```
121
+
122
+ ## 💾 Dumping Raw Candles (`--dump`)
123
+
124
+ Fetches raw OHLCV candles from an exchange and saves them to a file.
125
+
126
+ ```bash
127
+ npm start -- --dump --timeframe 15m --limit 500 --when "2026-02-28T00:00:00.000Z" --jsonl
128
+ ```
129
+
130
+ Output file is created at `./dump/<name>.jsonl`.
131
+
132
+ | Flag | Type | Default | Description |
133
+ |------|------|---------|-------------|
134
+ | `--dump` | boolean | — | Enable candle dump mode |
135
+ | `--symbol` | string | `BTCUSDT` | Trading pair |
136
+ | `--timeframe` | string | `15m` | Candle interval |
137
+ | `--limit` | string | `250` | Number of candles |
138
+ | `--when` | string | now | End date — ISO 8601 or Unix ms |
139
+ | `--exchange` | string | first registered | Exchange name |
140
+ | `--output` | string | `{SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP}` | Output file base name |
141
+ | `--json` | boolean | `false` | Save as JSON array |
142
+ | `--jsonl` | boolean | `false` | Save as JSONL |
143
+
144
+ Module file `./modules/dump.module.ts` is loaded automatically. The project includes it pre-configured with CCXT Binance.
145
+
146
+ ## 🧩 Module Hooks
147
+
148
+ | File | Loaded by mode | Purpose |
149
+ |------|----------------|---------|
150
+ | `modules/backtest.module.ts` | `--backtest` | Register a `Broker` adapter for backtest |
151
+ | `modules/paper.module.ts` | `--paper` | Register a `Broker` adapter for paper trading |
152
+ | `modules/live.module.ts` | `--live` | Register a `Broker` adapter for live trading |
153
+ | `modules/pine.module.ts` | `--pine` | Register an exchange schema for PineScript runs |
154
+ | `modules/dump.module.ts` | `--dump` | Register an exchange schema for candle dumps |
155
+
156
+ All files are optional — a missing module is a soft warning, not an error. Extensions `.ts`, `.mjs`, `.cjs` are tried automatically.
157
+
158
+ ## 🌍 Environment Variables
159
+
160
+ Create a `.env` file in the project root:
161
+
162
+ ```env
163
+ # Telegram notifications (required for --telegram)
164
+ CC_TELEGRAM_TOKEN=your_bot_token_here
165
+ CC_TELEGRAM_CHANNEL=-100123456789
166
+
167
+ # Web UI server (optional, defaults shown)
168
+ CC_WWWROOT_HOST=0.0.0.0
169
+ CC_WWWROOT_PORT=60050
170
+ ```
171
+
172
+ | Variable | Default | Description |
173
+ |----------|---------|-------------|
174
+ | `CC_TELEGRAM_TOKEN` | — | Telegram bot token (from @BotFather) |
175
+ | `CC_TELEGRAM_CHANNEL` | — | Telegram channel or chat ID |
176
+ | `CC_WWWROOT_HOST` | `0.0.0.0` | UI server bind address |
177
+ | `CC_WWWROOT_PORT` | `60050` | UI server port |
178
+
179
+
180
+ ## 🗂️ Project Structure
181
+
182
+ ```
183
+ ├── content/ # Strategy entry points (.ts)
184
+ │ └── feb_2026.strategy.ts
185
+ ├── docs/ # Documentation
186
+ │ ├── lib/ # Auto-fetched library READMEs (via sync:lib)
187
+ │ └── *.md # Backtest Kit how-to guides
188
+ ├── math/ # PineScript indicator files (.pine)
189
+ │ └── feb_2026.pine
190
+ ├── modules/ # Side-effect module hooks (loaded automatically)
191
+ │ ├── dump.module.ts # Exchange schema for --dump mode
192
+ │ └── pine.module.ts # Exchange schema for --pine mode
193
+ ├── report/ # Strategy research reports (.md)
194
+ │ └── feb_2026.md
195
+ ├── scripts/
196
+ │ └── fetch_docs.mjs # Downloads library READMEs into docs/lib/
197
+ ├── CLAUDE.md # AI-agent guide for writing strategies
198
+ └── package.json
199
+ ```
200
+
201
+ ## 📚 Updating Library Documentation
202
+
203
+ ```bash
204
+ npm run sync:lib
205
+ ```
206
+
207
+ Downloads the latest README files for all bundled libraries into `docs/lib/`. Run this after updating package versions or when you want fresh documentation available to the AI agent.
@@ -12,12 +12,12 @@
12
12
  "license": "ISC",
13
13
  "type": "commonjs",
14
14
  "dependencies": {
15
- "@backtest-kit/cli": "^5.11.1",
16
- "@backtest-kit/graph": "^5.11.0",
17
- "@backtest-kit/pinets": "^5.11.0",
18
- "@backtest-kit/ui": "^5.11.0",
15
+ "@backtest-kit/cli": "^6.0.0",
16
+ "@backtest-kit/graph": "^6.0.0",
17
+ "@backtest-kit/pinets": "^6.0.0",
18
+ "@backtest-kit/ui": "^6.0.0",
19
19
  "agent-swarm-kit": "^1.3.0",
20
- "backtest-kit": "^5.11.0",
20
+ "backtest-kit": "^6.0.0",
21
21
  "functools-kit": "^1.0.95",
22
22
  "garch": "^1.2.3",
23
23
  "get-moment-stamp": "^1.1.2",
@@ -44,15 +44,24 @@ const OUT_DIR = path.resolve("./docs/lib");
44
44
 
45
45
  fs.mkdirSync(OUT_DIR, { recursive: true });
46
46
 
47
- for (const lib of LIBRARY_LIST) {
48
- const res = await fetch(lib.readme);
49
- if (!res.ok) {
50
- console.error(`Failed to fetch ${lib.name}: ${res.status} ${res.statusText}`);
51
- continue;
47
+ const main = async () => {
48
+ for (const lib of LIBRARY_LIST) {
49
+ const res = await fetch(lib.readme);
50
+ if (!res.ok) {
51
+ console.error(`Failed to fetch ${lib.name}: ${res.status} ${res.statusText}`);
52
+ continue;
53
+ }
54
+ const text = await res.text();
55
+ const fileName = lib.name.replace(/\//g, "__") + ".md";
56
+ const outPath = path.join(OUT_DIR, fileName);
57
+ fs.writeFileSync(outPath, text, "utf-8");
58
+ console.log(`Saved ${lib.name} -> ${outPath}`);
52
59
  }
53
- const text = await res.text();
54
- const fileName = lib.name.replace(/\//g, "__") + ".md";
55
- const outPath = path.join(OUT_DIR, fileName);
56
- fs.writeFileSync(outPath, text, "utf-8");
57
- console.log(`Saved ${lib.name} -> ${outPath}`);
60
+ };
61
+
62
+ try {
63
+ await main();
64
+ } catch {
65
+ console.log("Failed to fetch docs")
58
66
  }
67
+