@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 +63 -0
- package/build/index.cjs +63 -21
- package/build/index.mjs +63 -21
- package/package.json +14 -13
- package/template/project/package.mustache +6 -6
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$
|
|
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.
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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.
|
|
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$
|
|
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.
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
|
65
|
-
"@backtest-kit/
|
|
66
|
-
"@backtest-kit/
|
|
67
|
-
"@backtest-kit/
|
|
68
|
-
"@backtest-kit/
|
|
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.
|
|
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/
|
|
92
|
-
"@backtest-kit/
|
|
93
|
-
"@backtest-kit/
|
|
94
|
-
"@backtest-kit/
|
|
95
|
-
"@backtest-kit/
|
|
96
|
-
"backtest-kit": "^6.
|
|
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.
|
|
16
|
-
"@backtest-kit/graph": "^6.
|
|
17
|
-
"@backtest-kit/pinets": "^6.
|
|
18
|
-
"@backtest-kit/ui": "^6.
|
|
19
|
-
"agent-swarm-kit": "^2.5.
|
|
20
|
-
"backtest-kit": "^6.
|
|
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",
|