@backtest-kit/cli 6.15.0 → 6.16.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
@@ -40,6 +40,7 @@ Point the CLI at your strategy file, choose a mode, and it handles exchange conn
40
40
  | **UI Dashboard** | `--ui` | Web dashboard at `http://localhost:60050` |
41
41
  | **Telegram** | `--telegram` | Trade notifications with price charts |
42
42
  | **PineScript** | `--pine` | Run a local `.pine` indicator against exchange data |
43
+ | **Pine Editor** | `--editor` | Open the visual Pine Script editor in the browser |
43
44
  | **Candle Dump** | `--dump` | Fetch and save raw OHLCV candles to a file |
44
45
  | **Flush** | `--flush` | Delete report/log/markdown/agent folders from strategy dump dir |
45
46
  | **Init Project** | `--init` | Scaffold a new backtest-kit project |
@@ -739,6 +740,68 @@ Print to stdout (no flag):
739
740
  npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine
740
741
  ```
741
742
 
743
+ ## 🎨 Visual Pine Script Editor
744
+
745
+ `@backtest-kit/cli` ships a browser-based Pine Script editor powered by `@backtest-kit/ui`. It lets you write, run, and iterate on indicators interactively — with a live chart that updates as you hit **▶ Run** — without leaving the terminal.
746
+
747
+ ### Usage
748
+
749
+ ```bash
750
+ npx @backtest-kit/cli --editor
751
+ ```
752
+
753
+ The CLI will:
754
+
755
+ 1. Load `./modules/editor.module` (if it exists) — use it to register your exchange schema, identical to `pine.module`
756
+ 2. Start the `@backtest-kit/ui` server on `http://localhost:60050` (or `CC_WWWROOT_PORT`)
757
+ 3. Open `http://localhost:{CC_WWWROOT_PORT}?pine=1` automatically in your default browser
758
+
759
+ Press **Ctrl+C** to stop the server.
760
+
761
+ ### Exchange via `editor.module`
762
+
763
+ Drop a `modules/editor.module.ts` next to your project to register the exchange that the editor's candle provider will use:
764
+
765
+ ```typescript
766
+ // modules/editor.module.ts
767
+ import { addExchangeSchema } from "backtest-kit";
768
+ import ccxt from "ccxt";
769
+
770
+ addExchangeSchema({
771
+ exchangeName: "my-exchange",
772
+ getCandles: async (symbol, interval, since, limit) => {
773
+ const exchange = new ccxt.bybit({ enableRateLimit: true });
774
+ const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
775
+ return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
776
+ timestamp, open, high, low, close, volume,
777
+ }));
778
+ },
779
+ formatPrice: (symbol, price) => price.toFixed(2),
780
+ formatQuantity: (symbol, quantity) => quantity.toFixed(8),
781
+ });
782
+ ```
783
+
784
+ ### Environment Variables
785
+
786
+ | Variable | Default | Description |
787
+ |-------------------|-----------|----------------------------------|
788
+ | `CC_WWWROOT_HOST` | `0.0.0.0` | UI server bind address |
789
+ | `CC_WWWROOT_PORT` | `60050` | UI server port |
790
+
791
+ ### `package.json` script
792
+
793
+ ```json
794
+ {
795
+ "scripts": {
796
+ "editor": "npx @backtest-kit/cli --editor"
797
+ }
798
+ }
799
+ ```
800
+
801
+ ```bash
802
+ npm run editor
803
+ ```
804
+
742
805
  ## 💾 Dumping Raw Candles
743
806
 
744
807
  `@backtest-kit/cli` can fetch raw OHLCV candles from any registered exchange and save them to a file — no strategy file required.
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 open = require('open');
32
33
  var child_process = require('child_process');
33
34
 
34
35
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -482,6 +483,10 @@ const getArgs = functoolsKit.singleshot(() => {
482
483
  default: "1m, 15m, 30m, 4h",
483
484
  },
484
485
  // pinescript entry
486
+ editor: {
487
+ type: "boolean",
488
+ default: false,
489
+ },
485
490
  pine: {
486
491
  type: "boolean",
487
492
  default: false,
@@ -2611,14 +2616,14 @@ BacktestKit.setConfig({
2611
2616
  CC_WALKER_MARKDOWN_TOP_N: 10,
2612
2617
  });
2613
2618
 
2614
- const MODES = ["backtest", "walker", "paper", "live", "pine", "dump", "flush", "init", "help", "version"];
2619
+ const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "flush", "init", "help", "version"];
2615
2620
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
2616
2621
  const HELP_TEXT$1 = `
2617
2622
  Example:
2618
2623
 
2619
2624
  node ${ENTRY_PATH$1} --help
2620
2625
  `.trimStart();
2621
- const main$c = async () => {
2626
+ const main$d = async () => {
2622
2627
  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)))) {
2623
2628
  return;
2624
2629
  }
@@ -2626,14 +2631,14 @@ const main$c = async () => {
2626
2631
  if (MODES.some((mode) => values[mode])) {
2627
2632
  return;
2628
2633
  }
2629
- process.stdout.write(`@backtest-kit/cli ${"6.15.0"}\n`);
2634
+ process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
2630
2635
  process.stdout.write("\n");
2631
2636
  process.stdout.write(`Run with --help to see available commands.\n`);
2632
2637
  process.stdout.write("\n");
2633
2638
  process.stdout.write(HELP_TEXT$1);
2634
2639
  process.exit(0);
2635
2640
  };
2636
- main$c();
2641
+ main$d();
2637
2642
 
2638
2643
  const notifyShutdown = functoolsKit.singleshot(async () => {
2639
2644
  console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
@@ -2649,7 +2654,7 @@ const flush = async (entryPoint) => {
2649
2654
  console.log(`Removed: ${target}`);
2650
2655
  }
2651
2656
  };
2652
- const main$b = async () => {
2657
+ const main$c = async () => {
2653
2658
  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)))) {
2654
2659
  return;
2655
2660
  }
@@ -2666,7 +2671,7 @@ const main$b = async () => {
2666
2671
  }
2667
2672
  process.exit(0);
2668
2673
  };
2669
- main$b();
2674
+ main$c();
2670
2675
 
2671
2676
  const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
2672
2677
  process.off("SIGINT", BEFORE_EXIT_FN$5);
@@ -2688,7 +2693,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
2688
2693
  const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
2689
2694
  process.on("SIGINT", BEFORE_EXIT_FN$5);
2690
2695
  });
2691
- const main$a = async () => {
2696
+ const main$b = async () => {
2692
2697
  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
2698
  return;
2694
2699
  }
@@ -2703,7 +2708,7 @@ const main$a = async () => {
2703
2708
  await cli.backtestMainService.connect();
2704
2709
  listenGracefulShutdown$5();
2705
2710
  };
2706
- main$a();
2711
+ main$b();
2707
2712
 
2708
2713
  const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
2709
2714
  process.off("SIGINT", BEFORE_EXIT_FN$4);
@@ -2721,7 +2726,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
2721
2726
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
2722
2727
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2723
2728
  });
2724
- const main$9 = async () => {
2729
+ const main$a = async () => {
2725
2730
  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)))) {
2726
2731
  return;
2727
2732
  }
@@ -2737,7 +2742,7 @@ const main$9 = async () => {
2737
2742
  listenGracefulShutdown$4();
2738
2743
  await cli.walkerMainService.connect();
2739
2744
  };
2740
- main$9();
2745
+ main$a();
2741
2746
 
2742
2747
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2743
2748
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2758,7 +2763,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
2758
2763
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
2759
2764
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2760
2765
  });
2761
- const main$8 = async () => {
2766
+ const main$9 = async () => {
2762
2767
  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)))) {
2763
2768
  return;
2764
2769
  }
@@ -2769,7 +2774,7 @@ const main$8 = async () => {
2769
2774
  cli.paperMainService.connect();
2770
2775
  listenGracefulShutdown$3();
2771
2776
  };
2772
- main$8();
2777
+ main$9();
2773
2778
 
2774
2779
  const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2775
2780
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2790,7 +2795,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
2790
2795
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
2791
2796
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2792
2797
  });
2793
- const main$7 = async () => {
2798
+ const main$8 = async () => {
2794
2799
  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)))) {
2795
2800
  return;
2796
2801
  }
@@ -2801,7 +2806,7 @@ const main$7 = async () => {
2801
2806
  await cli.liveMainService.connect();
2802
2807
  listenGracefulShutdown$2();
2803
2808
  };
2804
- main$7();
2809
+ main$8();
2805
2810
 
2806
2811
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2807
2812
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2811,7 +2816,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
2811
2816
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
2812
2817
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2813
2818
  });
2814
- const main$6 = async () => {
2819
+ const main$7 = async () => {
2815
2820
  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)))) {
2816
2821
  return;
2817
2822
  }
@@ -2821,7 +2826,7 @@ const main$6 = async () => {
2821
2826
  }
2822
2827
  listenGracefulShutdown$1();
2823
2828
  };
2824
- main$6();
2829
+ main$7();
2825
2830
 
2826
2831
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2827
2832
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2831,7 +2836,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
2831
2836
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
2832
2837
  process.on("SIGINT", BEFORE_EXIT_FN);
2833
2838
  });
2834
- const main$5 = async () => {
2839
+ const main$6 = async () => {
2835
2840
  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)))) {
2836
2841
  return;
2837
2842
  }
@@ -2841,7 +2846,7 @@ const main$5 = async () => {
2841
2846
  }
2842
2847
  listenGracefulShutdown();
2843
2848
  };
2844
- main$5();
2849
+ main$6();
2845
2850
 
2846
2851
  const EXTRACT_ROWS_FN = (plots, schema) => {
2847
2852
  const keys = Object.keys(schema);
@@ -2863,7 +2868,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2863
2868
  }
2864
2869
  return rows;
2865
2870
  };
2866
- const main$4 = async () => {
2871
+ const main$5 = async () => {
2867
2872
  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)))) {
2868
2873
  return;
2869
2874
  }
@@ -2871,6 +2876,10 @@ const main$4 = async () => {
2871
2876
  if (!values.pine) {
2872
2877
  return;
2873
2878
  }
2879
+ if (values.editor) {
2880
+ console.warn("--pine and --editor are mutually exclusive. Use one at a time.");
2881
+ process.exit(1);
2882
+ }
2874
2883
  const [entryPoint = null] = getPositionals();
2875
2884
  if (!entryPoint) {
2876
2885
  return;
@@ -2939,6 +2948,36 @@ const main$4 = async () => {
2939
2948
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
2940
2949
  process.exit(0);
2941
2950
  };
2951
+ main$5();
2952
+
2953
+ const main$4 = async () => {
2954
+ 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)))) {
2955
+ return;
2956
+ }
2957
+ const { values } = getArgs();
2958
+ if (!values.editor) {
2959
+ return;
2960
+ }
2961
+ if (values.pine) {
2962
+ console.warn("--editor and --pine are mutually exclusive. Use one at a time.");
2963
+ process.exit(1);
2964
+ }
2965
+ await cli.moduleConnectionService.loadModule("./editor.module");
2966
+ const { CC_WWWROOT_HOST, CC_WWWROOT_PORT } = getEnv();
2967
+ const unServer = BacktestKitUi.serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, cli.resolveService.PROJECT_ROOT_DIR);
2968
+ try {
2969
+ await open(`http://localhost:${CC_WWWROOT_PORT}?pine=1`);
2970
+ }
2971
+ catch {
2972
+ console.log(`Editor launched: http://localhost:${CC_WWWROOT_PORT}?pine=1`);
2973
+ }
2974
+ const beforeExit = () => {
2975
+ process.off("SIGINT", beforeExit);
2976
+ unServer();
2977
+ process.exit(0);
2978
+ };
2979
+ process.on("SIGINT", beforeExit);
2980
+ };
2942
2981
  main$4();
2943
2982
 
2944
2983
  const main$3 = async () => {
@@ -3118,6 +3157,7 @@ Modes:
3118
3157
  --paper <entry> Paper trading (live prices, no real orders)
3119
3158
  --live <entry> Live trading with real orders
3120
3159
  --pine <entry> Execute a local .pine indicator file
3160
+ --editor Open the Pine Script visual editor in the browser
3121
3161
  --dump Fetch and save raw OHLCV candles
3122
3162
  --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
3123
3163
  --init Scaffold a new project in the current directory
@@ -3211,6 +3251,7 @@ Module hooks (loaded automatically by each mode):
3211
3251
  modules/paper.module --paper Broker adapter for paper trading
3212
3252
  modules/live.module --live Broker adapter for live trading
3213
3253
  modules/pine.module --pine Exchange schema for PineScript runs
3254
+ modules/editor.module --editor Exchange schema for the visual Pine editor
3214
3255
  modules/dump.module --dump Exchange schema for candle dumps
3215
3256
 
3216
3257
  --flush has no associated module. It only removes dump subdirectories.
@@ -3233,6 +3274,7 @@ Examples:
3233
3274
  node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
3234
3275
  node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
3235
3276
  node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
3277
+ node ${ENTRY_PATH} --editor
3236
3278
  node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
3237
3279
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
3238
3280
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
@@ -3246,7 +3288,7 @@ const main$1 = async () => {
3246
3288
  if (!values.help) {
3247
3289
  return;
3248
3290
  }
3249
- process.stdout.write(`@backtest-kit/cli ${"6.15.0"}\n\n`);
3291
+ process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n\n`);
3250
3292
  process.stdout.write(HELP_TEXT);
3251
3293
  process.exit(0);
3252
3294
  };
@@ -3260,7 +3302,7 @@ const main = async () => {
3260
3302
  if (!values.version) {
3261
3303
  return;
3262
3304
  }
3263
- process.stdout.write(`@backtest-kit/cli ${"6.15.0"}\n`);
3305
+ process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
3264
3306
  process.exit(0);
3265
3307
  };
3266
3308
  main();
package/build/index.mjs CHANGED
@@ -30,6 +30,7 @@ import * as BacktestKitOllama from '@backtest-kit/ollama';
30
30
  import * as BacktestKitPinets from '@backtest-kit/pinets';
31
31
  import { run as run$1, Code, toMarkdown } from '@backtest-kit/pinets';
32
32
  import * as BacktestKitSignals from '@backtest-kit/signals';
33
+ import open from 'open';
33
34
  import { spawn } from 'child_process';
34
35
 
35
36
  /**
@@ -457,6 +458,10 @@ const getArgs = singleshot(() => {
457
458
  default: "1m, 15m, 30m, 4h",
458
459
  },
459
460
  // pinescript entry
461
+ editor: {
462
+ type: "boolean",
463
+ default: false,
464
+ },
460
465
  pine: {
461
466
  type: "boolean",
462
467
  default: false,
@@ -2582,14 +2587,14 @@ setConfig({
2582
2587
  CC_WALKER_MARKDOWN_TOP_N: 10,
2583
2588
  });
2584
2589
 
2585
- const MODES = ["backtest", "walker", "paper", "live", "pine", "dump", "flush", "init", "help", "version"];
2590
+ const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "flush", "init", "help", "version"];
2586
2591
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
2587
2592
  const HELP_TEXT$1 = `
2588
2593
  Example:
2589
2594
 
2590
2595
  node ${ENTRY_PATH$1} --help
2591
2596
  `.trimStart();
2592
- const main$c = async () => {
2597
+ const main$d = async () => {
2593
2598
  if (!getEntry(import.meta.url)) {
2594
2599
  return;
2595
2600
  }
@@ -2597,14 +2602,14 @@ const main$c = async () => {
2597
2602
  if (MODES.some((mode) => values[mode])) {
2598
2603
  return;
2599
2604
  }
2600
- process.stdout.write(`@backtest-kit/cli ${"6.15.0"}\n`);
2605
+ process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
2601
2606
  process.stdout.write("\n");
2602
2607
  process.stdout.write(`Run with --help to see available commands.\n`);
2603
2608
  process.stdout.write("\n");
2604
2609
  process.stdout.write(HELP_TEXT$1);
2605
2610
  process.exit(0);
2606
2611
  };
2607
- main$c();
2612
+ main$d();
2608
2613
 
2609
2614
  const notifyShutdown = singleshot(async () => {
2610
2615
  console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
@@ -2620,7 +2625,7 @@ const flush = async (entryPoint) => {
2620
2625
  console.log(`Removed: ${target}`);
2621
2626
  }
2622
2627
  };
2623
- const main$b = async () => {
2628
+ const main$c = async () => {
2624
2629
  if (!getEntry(import.meta.url)) {
2625
2630
  return;
2626
2631
  }
@@ -2637,7 +2642,7 @@ const main$b = async () => {
2637
2642
  }
2638
2643
  process.exit(0);
2639
2644
  };
2640
- main$b();
2645
+ main$c();
2641
2646
 
2642
2647
  const BEFORE_EXIT_FN$5 = singleshot(async () => {
2643
2648
  process.off("SIGINT", BEFORE_EXIT_FN$5);
@@ -2659,7 +2664,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
2659
2664
  const listenGracefulShutdown$5 = singleshot(() => {
2660
2665
  process.on("SIGINT", BEFORE_EXIT_FN$5);
2661
2666
  });
2662
- const main$a = async () => {
2667
+ const main$b = async () => {
2663
2668
  if (!getEntry(import.meta.url)) {
2664
2669
  return;
2665
2670
  }
@@ -2674,7 +2679,7 @@ const main$a = async () => {
2674
2679
  await cli.backtestMainService.connect();
2675
2680
  listenGracefulShutdown$5();
2676
2681
  };
2677
- main$a();
2682
+ main$b();
2678
2683
 
2679
2684
  const BEFORE_EXIT_FN$4 = singleshot(async () => {
2680
2685
  process.off("SIGINT", BEFORE_EXIT_FN$4);
@@ -2692,7 +2697,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
2692
2697
  const listenGracefulShutdown$4 = singleshot(() => {
2693
2698
  process.on("SIGINT", BEFORE_EXIT_FN$4);
2694
2699
  });
2695
- const main$9 = async () => {
2700
+ const main$a = async () => {
2696
2701
  if (!getEntry(import.meta.url)) {
2697
2702
  return;
2698
2703
  }
@@ -2708,7 +2713,7 @@ const main$9 = async () => {
2708
2713
  listenGracefulShutdown$4();
2709
2714
  await cli.walkerMainService.connect();
2710
2715
  };
2711
- main$9();
2716
+ main$a();
2712
2717
 
2713
2718
  const BEFORE_EXIT_FN$3 = singleshot(async () => {
2714
2719
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -2729,7 +2734,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
2729
2734
  const listenGracefulShutdown$3 = singleshot(() => {
2730
2735
  process.on("SIGINT", BEFORE_EXIT_FN$3);
2731
2736
  });
2732
- const main$8 = async () => {
2737
+ const main$9 = async () => {
2733
2738
  if (!getEntry(import.meta.url)) {
2734
2739
  return;
2735
2740
  }
@@ -2740,7 +2745,7 @@ const main$8 = async () => {
2740
2745
  cli.paperMainService.connect();
2741
2746
  listenGracefulShutdown$3();
2742
2747
  };
2743
- main$8();
2748
+ main$9();
2744
2749
 
2745
2750
  const BEFORE_EXIT_FN$2 = singleshot(async () => {
2746
2751
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -2761,7 +2766,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
2761
2766
  const listenGracefulShutdown$2 = singleshot(() => {
2762
2767
  process.on("SIGINT", BEFORE_EXIT_FN$2);
2763
2768
  });
2764
- const main$7 = async () => {
2769
+ const main$8 = async () => {
2765
2770
  if (!getEntry(import.meta.url)) {
2766
2771
  return;
2767
2772
  }
@@ -2772,7 +2777,7 @@ const main$7 = async () => {
2772
2777
  await cli.liveMainService.connect();
2773
2778
  listenGracefulShutdown$2();
2774
2779
  };
2775
- main$7();
2780
+ main$8();
2776
2781
 
2777
2782
  const BEFORE_EXIT_FN$1 = singleshot(async () => {
2778
2783
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -2782,7 +2787,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
2782
2787
  const listenGracefulShutdown$1 = singleshot(() => {
2783
2788
  process.on("SIGINT", BEFORE_EXIT_FN$1);
2784
2789
  });
2785
- const main$6 = async () => {
2790
+ const main$7 = async () => {
2786
2791
  if (!getEntry(import.meta.url)) {
2787
2792
  return;
2788
2793
  }
@@ -2792,7 +2797,7 @@ const main$6 = async () => {
2792
2797
  }
2793
2798
  listenGracefulShutdown$1();
2794
2799
  };
2795
- main$6();
2800
+ main$7();
2796
2801
 
2797
2802
  const BEFORE_EXIT_FN = singleshot(async () => {
2798
2803
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -2802,7 +2807,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
2802
2807
  const listenGracefulShutdown = singleshot(() => {
2803
2808
  process.on("SIGINT", BEFORE_EXIT_FN);
2804
2809
  });
2805
- const main$5 = async () => {
2810
+ const main$6 = async () => {
2806
2811
  if (!getEntry(import.meta.url)) {
2807
2812
  return;
2808
2813
  }
@@ -2812,7 +2817,7 @@ const main$5 = async () => {
2812
2817
  }
2813
2818
  listenGracefulShutdown();
2814
2819
  };
2815
- main$5();
2820
+ main$6();
2816
2821
 
2817
2822
  const EXTRACT_ROWS_FN = (plots, schema) => {
2818
2823
  const keys = Object.keys(schema);
@@ -2834,7 +2839,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
2834
2839
  }
2835
2840
  return rows;
2836
2841
  };
2837
- const main$4 = async () => {
2842
+ const main$5 = async () => {
2838
2843
  if (!getEntry(import.meta.url)) {
2839
2844
  return;
2840
2845
  }
@@ -2842,6 +2847,10 @@ const main$4 = async () => {
2842
2847
  if (!values.pine) {
2843
2848
  return;
2844
2849
  }
2850
+ if (values.editor) {
2851
+ console.warn("--pine and --editor are mutually exclusive. Use one at a time.");
2852
+ process.exit(1);
2853
+ }
2845
2854
  const [entryPoint = null] = getPositionals();
2846
2855
  if (!entryPoint) {
2847
2856
  return;
@@ -2910,6 +2919,36 @@ const main$4 = async () => {
2910
2919
  console.log(await toMarkdown(signalId, plots, signalSchema));
2911
2920
  process.exit(0);
2912
2921
  };
2922
+ main$5();
2923
+
2924
+ const main$4 = async () => {
2925
+ if (!getEntry(import.meta.url)) {
2926
+ return;
2927
+ }
2928
+ const { values } = getArgs();
2929
+ if (!values.editor) {
2930
+ return;
2931
+ }
2932
+ if (values.pine) {
2933
+ console.warn("--editor and --pine are mutually exclusive. Use one at a time.");
2934
+ process.exit(1);
2935
+ }
2936
+ await cli.moduleConnectionService.loadModule("./editor.module");
2937
+ const { CC_WWWROOT_HOST, CC_WWWROOT_PORT } = getEnv();
2938
+ const unServer = serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, cli.resolveService.PROJECT_ROOT_DIR);
2939
+ try {
2940
+ await open(`http://localhost:${CC_WWWROOT_PORT}?pine=1`);
2941
+ }
2942
+ catch {
2943
+ console.log(`Editor launched: http://localhost:${CC_WWWROOT_PORT}?pine=1`);
2944
+ }
2945
+ const beforeExit = () => {
2946
+ process.off("SIGINT", beforeExit);
2947
+ unServer();
2948
+ process.exit(0);
2949
+ };
2950
+ process.on("SIGINT", beforeExit);
2951
+ };
2913
2952
  main$4();
2914
2953
 
2915
2954
  const main$3 = async () => {
@@ -3089,6 +3128,7 @@ Modes:
3089
3128
  --paper <entry> Paper trading (live prices, no real orders)
3090
3129
  --live <entry> Live trading with real orders
3091
3130
  --pine <entry> Execute a local .pine indicator file
3131
+ --editor Open the Pine Script visual editor in the browser
3092
3132
  --dump Fetch and save raw OHLCV candles
3093
3133
  --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
3094
3134
  --init Scaffold a new project in the current directory
@@ -3182,6 +3222,7 @@ Module hooks (loaded automatically by each mode):
3182
3222
  modules/paper.module --paper Broker adapter for paper trading
3183
3223
  modules/live.module --live Broker adapter for live trading
3184
3224
  modules/pine.module --pine Exchange schema for PineScript runs
3225
+ modules/editor.module --editor Exchange schema for the visual Pine editor
3185
3226
  modules/dump.module --dump Exchange schema for candle dumps
3186
3227
 
3187
3228
  --flush has no associated module. It only removes dump subdirectories.
@@ -3204,6 +3245,7 @@ Examples:
3204
3245
  node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
3205
3246
  node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
3206
3247
  node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
3248
+ node ${ENTRY_PATH} --editor
3207
3249
  node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
3208
3250
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
3209
3251
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
@@ -3217,7 +3259,7 @@ const main$1 = async () => {
3217
3259
  if (!values.help) {
3218
3260
  return;
3219
3261
  }
3220
- process.stdout.write(`@backtest-kit/cli ${"6.15.0"}\n\n`);
3262
+ process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n\n`);
3221
3263
  process.stdout.write(HELP_TEXT);
3222
3264
  process.exit(0);
3223
3265
  };
@@ -3231,7 +3273,7 @@ const main = async () => {
3231
3273
  if (!values.version) {
3232
3274
  return;
3233
3275
  }
3234
- process.stdout.write(`@backtest-kit/cli ${"6.15.0"}\n`);
3276
+ process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
3235
3277
  process.exit(0);
3236
3278
  };
3237
3279
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/cli",
3
- "version": "6.15.0",
3
+ "version": "6.16.0",
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": "6.15.0",
65
- "@backtest-kit/graph": "6.15.0",
66
- "@backtest-kit/ollama": "6.15.0",
67
- "@backtest-kit/pinets": "6.15.0",
68
- "@backtest-kit/signals": "6.15.0",
64
+ "@backtest-kit/graph": "6.16.0",
65
+ "@backtest-kit/ollama": "6.16.0",
66
+ "@backtest-kit/pinets": "6.16.0",
67
+ "@backtest-kit/signals": "6.16.0",
68
+ "@backtest-kit/ui": "6.16.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": "6.15.0",
76
+ "backtest-kit": "6.16.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": "^6.15.0",
92
- "@backtest-kit/graph": "^6.15.0",
93
- "@backtest-kit/ollama": "^6.15.0",
94
- "@backtest-kit/pinets": "^6.15.0",
95
- "@backtest-kit/signals": "^6.15.0",
96
- "backtest-kit": "^6.15.0",
91
+ "@backtest-kit/graph": "^6.16.0",
92
+ "@backtest-kit/ollama": "^6.16.0",
93
+ "@backtest-kit/pinets": "^6.16.0",
94
+ "@backtest-kit/signals": "^6.16.0",
95
+ "@backtest-kit/ui": "^6.16.0",
96
+ "backtest-kit": "^6.16.0",
97
97
  "markdown-it": "^14.1.1",
98
98
  "typescript": "^5.0.0"
99
99
  },
@@ -108,6 +108,7 @@
108
108
  "jsdom": "26.1.0",
109
109
  "markdownlint": "0.38.0",
110
110
  "mustache": "4.2.0",
111
+ "open": "11.0.0",
111
112
  "quickchart-js": "3.1.3",
112
113
  "resize-image-buffer": "1.0.0",
113
114
  "sanitize-html": "2.17.0",
@@ -12,12 +12,12 @@
12
12
  "license": "ISC",
13
13
  "type": "commonjs",
14
14
  "dependencies": {
15
- "@backtest-kit/cli": "^6.15.0",
16
- "@backtest-kit/graph": "^6.15.0",
17
- "@backtest-kit/pinets": "^6.15.0",
18
- "@backtest-kit/ui": "^6.15.0",
19
- "agent-swarm-kit": "^2.5.0",
20
- "backtest-kit": "^6.15.0",
15
+ "@backtest-kit/cli": "^6.16.0",
16
+ "@backtest-kit/graph": "^6.16.0",
17
+ "@backtest-kit/pinets": "^6.16.0",
18
+ "@backtest-kit/ui": "^6.16.0",
19
+ "agent-swarm-kit": "^2.5.1",
20
+ "backtest-kit": "^6.16.0",
21
21
  "functools-kit": "^2.2.0",
22
22
  "garch": "^1.2.3",
23
23
  "get-moment-stamp": "^1.1.2",