@backtest-kit/cli 6.14.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 +120 -2
- package/build/index.cjs +137 -22
- package/build/index.mjs +140 -25
- package/package.json +14 -13
- package/template/project/package.mustache +6 -6
package/README.md
CHANGED
|
@@ -40,7 +40,9 @@ 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 |
|
|
45
|
+
| **Flush** | `--flush` | Delete report/log/markdown/agent folders from strategy dump dir |
|
|
44
46
|
| **Init Project** | `--init` | Scaffold a new backtest-kit project |
|
|
45
47
|
|
|
46
48
|
## 🚀 Installation
|
|
@@ -144,6 +146,7 @@ npm start -- --symbol BTCUSDT --ui
|
|
|
144
146
|
| `--telegram` | boolean | Enable Telegram notifications (default: `false`) |
|
|
145
147
|
| `--verbose` | boolean | Log each candle fetch (default: `false`) |
|
|
146
148
|
| `--noCache` | boolean | Skip candle cache warming before backtest (default: `false`) |
|
|
149
|
+
| `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before backtest run (default: `false`) |
|
|
147
150
|
| `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
|
|
148
151
|
| `--strategy` | string | Strategy name (default: first registered) |
|
|
149
152
|
| `--exchange` | string | Exchange name (default: first registered) |
|
|
@@ -178,7 +181,7 @@ Runs the strategy against historical candle data using a registered `FrameSchema
|
|
|
178
181
|
npm run backtest
|
|
179
182
|
```
|
|
180
183
|
|
|
181
|
-
Before running, the CLI warms the candle cache for every interval in `--cacheInterval`. On the next run, cached data is used directly — no API calls needed. Pass `--noCache` to skip
|
|
184
|
+
Before running, the CLI removes the `report`, `log`, `markdown`, and `agent` folders from the strategy's `dump/` directory, then warms the candle cache for every interval in `--cacheInterval`. On the next run, cached data is used directly — no API calls needed. Pass `--noCache` to skip cache warming, `--noFlush` to keep existing output folders.
|
|
182
185
|
|
|
183
186
|
### Paper Trading
|
|
184
187
|
|
|
@@ -228,7 +231,7 @@ Runs the same historical period against multiple strategy files and prints a ran
|
|
|
228
231
|
npm run walker
|
|
229
232
|
```
|
|
230
233
|
|
|
231
|
-
Each positional argument is a separate strategy entry point. All files are loaded without changing `process.cwd()` — `.env` is read from the working directory only. After loading, `addWalkerSchema` is called automatically using the exchange and frame registered by the strategy files.
|
|
234
|
+
Each positional argument is a separate strategy entry point. Before loading them, the CLI removes the `report`, `log`, `markdown`, and `agent` folders from each entry point's `dump/` directory. Pass `--noFlush` to keep existing output. All files are loaded without changing `process.cwd()` — `.env` is read from the working directory only. After loading, `addWalkerSchema` is called automatically using the exchange and frame registered by the strategy files.
|
|
232
235
|
|
|
233
236
|
If no frame is registered, the CLI falls back to the last 31 days from `Date.now()` with a console warning.
|
|
234
237
|
|
|
@@ -240,6 +243,7 @@ If no frame is registered, the CLI falls back to the last 31 days from `Date.now
|
|
|
240
243
|
| `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
|
|
241
244
|
| `--cacheInterval` | string | Intervals to pre-cache (default: `"1m, 15m, 30m, 4h"`) |
|
|
242
245
|
| `--noCache` | boolean | Skip candle cache warming (default: `false`) |
|
|
246
|
+
| `--noFlush` | boolean | Skip removing report/log/markdown/agent folders before walker run (default: `false`) |
|
|
243
247
|
| `--verbose` | boolean | Log each candle fetch and strategy progress (default: `false`) |
|
|
244
248
|
| `--output` | string | Output file base name (default: `walker_{SYMBOL}_{TIMESTAMP}`) |
|
|
245
249
|
| `--json` | boolean | Save results as JSON to `./dump/<output>.json` |
|
|
@@ -736,6 +740,68 @@ Print to stdout (no flag):
|
|
|
736
740
|
npx @backtest-kit/cli --pine ./math/impulse_trend_15m.pine
|
|
737
741
|
```
|
|
738
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
|
+
|
|
739
805
|
## 💾 Dumping Raw Candles
|
|
740
806
|
|
|
741
807
|
`@backtest-kit/cli` can fetch raw OHLCV candles from any registered exchange and save them to a file — no strategy file required.
|
|
@@ -828,6 +894,57 @@ Or add it to `package.json`:
|
|
|
828
894
|
npx @backtest-kit/cli --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
829
895
|
```
|
|
830
896
|
|
|
897
|
+
## 🗑️ Flushing Strategy Output (`--flush`)
|
|
898
|
+
|
|
899
|
+
`@backtest-kit/cli` can delete generated output folders from one or more strategy dump directories without touching cached candle data.
|
|
900
|
+
|
|
901
|
+
### CLI Flags
|
|
902
|
+
|
|
903
|
+
| Flag | Type | Description |
|
|
904
|
+
|------|------|-------------|
|
|
905
|
+
| `--flush` | boolean | Enable flush mode |
|
|
906
|
+
|
|
907
|
+
**Positional arguments (required):** one or more strategy entry point files. For each entry point the CLI resolves its directory and removes the following subdirectories from `<entry-dir>/dump/`:
|
|
908
|
+
|
|
909
|
+
| Folder | Contents |
|
|
910
|
+
|--------|----------|
|
|
911
|
+
| `report` | Backtest report files (`.jsonl`) |
|
|
912
|
+
| `log` | Run logs (`log.jsonl`) |
|
|
913
|
+
| `markdown` | Exported Markdown reports |
|
|
914
|
+
| `agent` | Agent outline files |
|
|
915
|
+
|
|
916
|
+
Candle cache (`dump/data/`) and AI forecast outlines (`dump/outline/`) are **not** removed.
|
|
917
|
+
|
|
918
|
+
### Usage
|
|
919
|
+
|
|
920
|
+
Flush a single strategy:
|
|
921
|
+
|
|
922
|
+
```bash
|
|
923
|
+
npx @backtest-kit/cli --flush ./content/feb_2026.strategy/modules/backtest.module.ts
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
Flush multiple strategies at once:
|
|
927
|
+
|
|
928
|
+
```bash
|
|
929
|
+
npx @backtest-kit/cli --flush \
|
|
930
|
+
./content/feb_2026.strategy/modules/backtest.module.ts \
|
|
931
|
+
./content/mar_2026.strategy/modules/backtest.module.ts
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
Or add it to `package.json`:
|
|
935
|
+
|
|
936
|
+
```json
|
|
937
|
+
{
|
|
938
|
+
"scripts": {
|
|
939
|
+
"flush": "npx @backtest-kit/cli --flush ./content/feb_2026.strategy/modules/backtest.module.ts"
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
```bash
|
|
945
|
+
npm run flush
|
|
946
|
+
```
|
|
947
|
+
|
|
831
948
|
## 🗂️ Scaffolding a New Project (`--init`)
|
|
832
949
|
|
|
833
950
|
`@backtest-kit/cli` can bootstrap a ready-to-use project directory with a pre-configured layout, example strategy files, and all documentation fetched automatically.
|
|
@@ -985,6 +1102,7 @@ await run(mode, args);
|
|
|
985
1102
|
| `frame` | `string` | Frame name (default: first registered) |
|
|
986
1103
|
| `cacheInterval` | `CandleInterval[]` | Intervals to pre-cache (default: `["1m","15m","30m","1h","4h"]`) |
|
|
987
1104
|
| `noCache` | `boolean` | Skip candle cache warming (default: `false`) |
|
|
1105
|
+
| `noFlush` | `boolean` | Skip removing report/log/markdown/agent folders before the run (default: `false`) |
|
|
988
1106
|
| `verbose` | `boolean` | Log each candle fetch (default: `false`) |
|
|
989
1107
|
|
|
990
1108
|
**Paper** and **Live** (`mode: "paper"` / `mode: "live"`):
|
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;
|
|
@@ -473,11 +474,19 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
473
474
|
type: "boolean",
|
|
474
475
|
default: false,
|
|
475
476
|
},
|
|
477
|
+
noFlush: {
|
|
478
|
+
type: "boolean",
|
|
479
|
+
default: false,
|
|
480
|
+
},
|
|
476
481
|
cacheInterval: {
|
|
477
482
|
type: "string",
|
|
478
483
|
default: "1m, 15m, 30m, 4h",
|
|
479
484
|
},
|
|
480
485
|
// pinescript entry
|
|
486
|
+
editor: {
|
|
487
|
+
type: "boolean",
|
|
488
|
+
default: false,
|
|
489
|
+
},
|
|
481
490
|
pine: {
|
|
482
491
|
type: "boolean",
|
|
483
492
|
default: false,
|
|
@@ -486,6 +495,10 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
486
495
|
type: "boolean",
|
|
487
496
|
default: false,
|
|
488
497
|
},
|
|
498
|
+
flush: {
|
|
499
|
+
type: "boolean",
|
|
500
|
+
default: false,
|
|
501
|
+
},
|
|
489
502
|
timeframe: {
|
|
490
503
|
type: "string",
|
|
491
504
|
default: "",
|
|
@@ -2461,14 +2474,27 @@ const cli = {
|
|
|
2461
2474
|
};
|
|
2462
2475
|
init();
|
|
2463
2476
|
|
|
2477
|
+
const NOTIFICATION_CONFIG = {
|
|
2478
|
+
signal: true,
|
|
2479
|
+
risk: true,
|
|
2480
|
+
info: true,
|
|
2481
|
+
breakeven: true,
|
|
2482
|
+
common_error: true,
|
|
2483
|
+
critical_error: true,
|
|
2484
|
+
validation_error: true,
|
|
2485
|
+
partial_loss: false,
|
|
2486
|
+
partial_profit: false,
|
|
2487
|
+
signal_sync: false,
|
|
2488
|
+
strategy_commit: true,
|
|
2489
|
+
};
|
|
2464
2490
|
class SetupUtils {
|
|
2465
2491
|
constructor() {
|
|
2466
2492
|
this.enable = functoolsKit.singleshot(() => {
|
|
2467
2493
|
cli.loggerService.debug("SetupUtils enable");
|
|
2494
|
+
BacktestKit.Notification.enable(NOTIFICATION_CONFIG);
|
|
2468
2495
|
{
|
|
2469
2496
|
BacktestKit.Recent.enable();
|
|
2470
2497
|
BacktestKit.Storage.enable();
|
|
2471
|
-
BacktestKit.Notification.enable();
|
|
2472
2498
|
}
|
|
2473
2499
|
{
|
|
2474
2500
|
BacktestKit.Markdown.enable();
|
|
@@ -2590,14 +2616,14 @@ BacktestKit.setConfig({
|
|
|
2590
2616
|
CC_WALKER_MARKDOWN_TOP_N: 10,
|
|
2591
2617
|
});
|
|
2592
2618
|
|
|
2593
|
-
const MODES = ["backtest", "walker", "paper", "live", "pine", "dump", "init", "help", "version"];
|
|
2619
|
+
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "flush", "init", "help", "version"];
|
|
2594
2620
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2595
2621
|
const HELP_TEXT$1 = `
|
|
2596
2622
|
Example:
|
|
2597
2623
|
|
|
2598
2624
|
node ${ENTRY_PATH$1} --help
|
|
2599
2625
|
`.trimStart();
|
|
2600
|
-
const main$
|
|
2626
|
+
const main$d = async () => {
|
|
2601
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)))) {
|
|
2602
2628
|
return;
|
|
2603
2629
|
}
|
|
@@ -2605,19 +2631,48 @@ const main$b = async () => {
|
|
|
2605
2631
|
if (MODES.some((mode) => values[mode])) {
|
|
2606
2632
|
return;
|
|
2607
2633
|
}
|
|
2608
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
2634
|
+
process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
|
|
2609
2635
|
process.stdout.write("\n");
|
|
2610
2636
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2611
2637
|
process.stdout.write("\n");
|
|
2612
2638
|
process.stdout.write(HELP_TEXT$1);
|
|
2613
2639
|
process.exit(0);
|
|
2614
2640
|
};
|
|
2615
|
-
main$
|
|
2641
|
+
main$d();
|
|
2616
2642
|
|
|
2617
2643
|
const notifyShutdown = functoolsKit.singleshot(async () => {
|
|
2618
2644
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
2619
2645
|
});
|
|
2620
2646
|
|
|
2647
|
+
const FLUSH_DIRS = ["report", "log", "markdown", "agent"];
|
|
2648
|
+
const flush = async (entryPoint) => {
|
|
2649
|
+
const moduleRoot = path.dirname(path.resolve(process.cwd(), entryPoint));
|
|
2650
|
+
const dumpDir = path.join(moduleRoot, "dump");
|
|
2651
|
+
for (const dir of FLUSH_DIRS) {
|
|
2652
|
+
const target = path.join(dumpDir, dir);
|
|
2653
|
+
await fs$1.rm(target, { recursive: true, force: true });
|
|
2654
|
+
console.log(`Removed: ${target}`);
|
|
2655
|
+
}
|
|
2656
|
+
};
|
|
2657
|
+
const main$c = async () => {
|
|
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)))) {
|
|
2659
|
+
return;
|
|
2660
|
+
}
|
|
2661
|
+
const { values } = getArgs();
|
|
2662
|
+
if (!values.flush) {
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
const entryPoints = getPositionals();
|
|
2666
|
+
if (!entryPoints.length) {
|
|
2667
|
+
throw new Error("Entry point is required");
|
|
2668
|
+
}
|
|
2669
|
+
for (const entryPoint of entryPoints) {
|
|
2670
|
+
await flush(entryPoint);
|
|
2671
|
+
}
|
|
2672
|
+
process.exit(0);
|
|
2673
|
+
};
|
|
2674
|
+
main$c();
|
|
2675
|
+
|
|
2621
2676
|
const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
2622
2677
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
2623
2678
|
const [running = null] = await BacktestKit.Backtest.list();
|
|
@@ -2638,7 +2693,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
|
2638
2693
|
const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
|
|
2639
2694
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
2640
2695
|
});
|
|
2641
|
-
const main$
|
|
2696
|
+
const main$b = async () => {
|
|
2642
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)))) {
|
|
2643
2698
|
return;
|
|
2644
2699
|
}
|
|
@@ -2646,10 +2701,14 @@ const main$a = async () => {
|
|
|
2646
2701
|
if (!values.backtest) {
|
|
2647
2702
|
return;
|
|
2648
2703
|
}
|
|
2704
|
+
if (!values.noFlush) {
|
|
2705
|
+
const [entryPoint = null] = getPositionals();
|
|
2706
|
+
entryPoint && await flush(entryPoint);
|
|
2707
|
+
}
|
|
2649
2708
|
await cli.backtestMainService.connect();
|
|
2650
2709
|
listenGracefulShutdown$5();
|
|
2651
2710
|
};
|
|
2652
|
-
main$
|
|
2711
|
+
main$b();
|
|
2653
2712
|
|
|
2654
2713
|
const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
2655
2714
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -2667,7 +2726,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
2667
2726
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
2668
2727
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2669
2728
|
});
|
|
2670
|
-
const main$
|
|
2729
|
+
const main$a = async () => {
|
|
2671
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)))) {
|
|
2672
2731
|
return;
|
|
2673
2732
|
}
|
|
@@ -2675,10 +2734,15 @@ const main$9 = async () => {
|
|
|
2675
2734
|
if (!values.walker) {
|
|
2676
2735
|
return;
|
|
2677
2736
|
}
|
|
2737
|
+
if (!values.noFlush) {
|
|
2738
|
+
for (const entryPoint of getPositionals()) {
|
|
2739
|
+
await flush(entryPoint);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2678
2742
|
listenGracefulShutdown$4();
|
|
2679
2743
|
await cli.walkerMainService.connect();
|
|
2680
2744
|
};
|
|
2681
|
-
main$
|
|
2745
|
+
main$a();
|
|
2682
2746
|
|
|
2683
2747
|
const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
2684
2748
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -2699,7 +2763,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
2699
2763
|
const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
|
|
2700
2764
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
2701
2765
|
});
|
|
2702
|
-
const main$
|
|
2766
|
+
const main$9 = async () => {
|
|
2703
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)))) {
|
|
2704
2768
|
return;
|
|
2705
2769
|
}
|
|
@@ -2710,7 +2774,7 @@ const main$8 = async () => {
|
|
|
2710
2774
|
cli.paperMainService.connect();
|
|
2711
2775
|
listenGracefulShutdown$3();
|
|
2712
2776
|
};
|
|
2713
|
-
main$
|
|
2777
|
+
main$9();
|
|
2714
2778
|
|
|
2715
2779
|
const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
2716
2780
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -2731,7 +2795,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
2731
2795
|
const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
|
|
2732
2796
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
2733
2797
|
});
|
|
2734
|
-
const main$
|
|
2798
|
+
const main$8 = async () => {
|
|
2735
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)))) {
|
|
2736
2800
|
return;
|
|
2737
2801
|
}
|
|
@@ -2742,7 +2806,7 @@ const main$7 = async () => {
|
|
|
2742
2806
|
await cli.liveMainService.connect();
|
|
2743
2807
|
listenGracefulShutdown$2();
|
|
2744
2808
|
};
|
|
2745
|
-
main$
|
|
2809
|
+
main$8();
|
|
2746
2810
|
|
|
2747
2811
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
2748
2812
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -2752,7 +2816,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
|
2752
2816
|
const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
|
|
2753
2817
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
2754
2818
|
});
|
|
2755
|
-
const main$
|
|
2819
|
+
const main$7 = async () => {
|
|
2756
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)))) {
|
|
2757
2821
|
return;
|
|
2758
2822
|
}
|
|
@@ -2762,7 +2826,7 @@ const main$6 = async () => {
|
|
|
2762
2826
|
}
|
|
2763
2827
|
listenGracefulShutdown$1();
|
|
2764
2828
|
};
|
|
2765
|
-
main$
|
|
2829
|
+
main$7();
|
|
2766
2830
|
|
|
2767
2831
|
const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
2768
2832
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -2772,7 +2836,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
|
2772
2836
|
const listenGracefulShutdown = functoolsKit.singleshot(() => {
|
|
2773
2837
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
2774
2838
|
});
|
|
2775
|
-
const main$
|
|
2839
|
+
const main$6 = async () => {
|
|
2776
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)))) {
|
|
2777
2841
|
return;
|
|
2778
2842
|
}
|
|
@@ -2782,7 +2846,7 @@ const main$5 = async () => {
|
|
|
2782
2846
|
}
|
|
2783
2847
|
listenGracefulShutdown();
|
|
2784
2848
|
};
|
|
2785
|
-
main$
|
|
2849
|
+
main$6();
|
|
2786
2850
|
|
|
2787
2851
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
2788
2852
|
const keys = Object.keys(schema);
|
|
@@ -2804,7 +2868,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
2804
2868
|
}
|
|
2805
2869
|
return rows;
|
|
2806
2870
|
};
|
|
2807
|
-
const main$
|
|
2871
|
+
const main$5 = async () => {
|
|
2808
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)))) {
|
|
2809
2873
|
return;
|
|
2810
2874
|
}
|
|
@@ -2812,6 +2876,10 @@ const main$4 = async () => {
|
|
|
2812
2876
|
if (!values.pine) {
|
|
2813
2877
|
return;
|
|
2814
2878
|
}
|
|
2879
|
+
if (values.editor) {
|
|
2880
|
+
console.warn("--pine and --editor are mutually exclusive. Use one at a time.");
|
|
2881
|
+
process.exit(1);
|
|
2882
|
+
}
|
|
2815
2883
|
const [entryPoint = null] = getPositionals();
|
|
2816
2884
|
if (!entryPoint) {
|
|
2817
2885
|
return;
|
|
@@ -2880,6 +2948,36 @@ const main$4 = async () => {
|
|
|
2880
2948
|
console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
|
|
2881
2949
|
process.exit(0);
|
|
2882
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
|
+
};
|
|
2883
2981
|
main$4();
|
|
2884
2982
|
|
|
2885
2983
|
const main$3 = async () => {
|
|
@@ -3059,7 +3157,9 @@ Modes:
|
|
|
3059
3157
|
--paper <entry> Paper trading (live prices, no real orders)
|
|
3060
3158
|
--live <entry> Live trading with real orders
|
|
3061
3159
|
--pine <entry> Execute a local .pine indicator file
|
|
3160
|
+
--editor Open the Pine Script visual editor in the browser
|
|
3062
3161
|
--dump Fetch and save raw OHLCV candles
|
|
3162
|
+
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
3063
3163
|
--init Scaffold a new project in the current directory
|
|
3064
3164
|
--help Print this help message
|
|
3065
3165
|
|
|
@@ -3071,6 +3171,7 @@ Backtest flags:
|
|
|
3071
3171
|
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
3072
3172
|
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
3073
3173
|
--noCache Skip candle cache warming before the run
|
|
3174
|
+
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
3074
3175
|
--verbose Log every candle fetch to stdout
|
|
3075
3176
|
--ui Start web dashboard at http://localhost:60050
|
|
3076
3177
|
--telegram Send trade notifications to Telegram
|
|
@@ -3080,6 +3181,7 @@ Walker flags (--walker):
|
|
|
3080
3181
|
--symbol <string> Trading pair (default: BTCUSDT)
|
|
3081
3182
|
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
3082
3183
|
--noCache Skip candle cache warming before the run
|
|
3184
|
+
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
3083
3185
|
--verbose Log every candle fetch to stdout
|
|
3084
3186
|
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
3085
3187
|
--json Save results as JSON to ./dump/<output>.json
|
|
@@ -3129,6 +3231,13 @@ Candle dump flags (--dump):
|
|
|
3129
3231
|
|
|
3130
3232
|
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
3131
3233
|
|
|
3234
|
+
Flush flags (--flush):
|
|
3235
|
+
|
|
3236
|
+
One or more positional entry points. For each entry point the following
|
|
3237
|
+
subdirectories are removed from <entry-dir>/dump/:
|
|
3238
|
+
|
|
3239
|
+
report log markdown agent
|
|
3240
|
+
|
|
3132
3241
|
Init flags (--init):
|
|
3133
3242
|
|
|
3134
3243
|
--output <string> Target directory name (default: backtest-kit-project)
|
|
@@ -3142,8 +3251,11 @@ Module hooks (loaded automatically by each mode):
|
|
|
3142
3251
|
modules/paper.module --paper Broker adapter for paper trading
|
|
3143
3252
|
modules/live.module --live Broker adapter for live trading
|
|
3144
3253
|
modules/pine.module --pine Exchange schema for PineScript runs
|
|
3254
|
+
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
3145
3255
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
3146
3256
|
|
|
3257
|
+
--flush has no associated module. It only removes dump subdirectories.
|
|
3258
|
+
|
|
3147
3259
|
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
3148
3260
|
|
|
3149
3261
|
Environment variables:
|
|
@@ -3156,13 +3268,16 @@ Environment variables:
|
|
|
3156
3268
|
Examples:
|
|
3157
3269
|
|
|
3158
3270
|
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
3159
|
-
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --ui ./content/feb_2026.strategy.ts
|
|
3271
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
3160
3272
|
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
3161
|
-
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
3273
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
3162
3274
|
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
3163
3275
|
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
3164
3276
|
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
3277
|
+
node ${ENTRY_PATH} --editor
|
|
3165
3278
|
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
3279
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
3280
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
3166
3281
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
3167
3282
|
`.trimStart();
|
|
3168
3283
|
const main$1 = async () => {
|
|
@@ -3173,7 +3288,7 @@ const main$1 = async () => {
|
|
|
3173
3288
|
if (!values.help) {
|
|
3174
3289
|
return;
|
|
3175
3290
|
}
|
|
3176
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
3291
|
+
process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n\n`);
|
|
3177
3292
|
process.stdout.write(HELP_TEXT);
|
|
3178
3293
|
process.exit(0);
|
|
3179
3294
|
};
|
|
@@ -3187,7 +3302,7 @@ const main = async () => {
|
|
|
3187
3302
|
if (!values.version) {
|
|
3188
3303
|
return;
|
|
3189
3304
|
}
|
|
3190
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
3305
|
+
process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
|
|
3191
3306
|
process.exit(0);
|
|
3192
3307
|
};
|
|
3193
3308
|
main();
|
package/build/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, Session, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, Recent, Storage,
|
|
3
|
+
import { Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, Session, Cache, Interval, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, Notification, Recent, Storage, Markdown, Report, Dump, Memory, StorageLive, StorageBacktest, RecentLive, RecentBacktest, NotificationLive, NotificationBacktest, MarkdownWriter, ReportWriter, PersistSignalAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistPartialAdapter, PersistBreakevenAdapter, PersistCandleAdapter, PersistStorageAdapter, PersistNotificationAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistIntervalAdapter, PersistMemoryAdapter, PersistRecentAdapter, setConfig, Exchange } from 'backtest-kit';
|
|
4
4
|
import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, createAwaiter, execpool, queued, sleep, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize } from 'functools-kit';
|
|
5
5
|
import fs, { constants } from 'fs';
|
|
6
6
|
import * as stackTrace from 'stack-trace';
|
|
7
|
-
import path, { join, resolve, basename, extname
|
|
8
|
-
import fs$1, { access, readFile, mkdir, writeFile, readdir, copyFile } from 'fs/promises';
|
|
7
|
+
import path, { join, resolve, dirname, basename, extname } from 'path';
|
|
8
|
+
import fs$1, { access, readFile, mkdir, writeFile, rm, readdir, copyFile } from 'fs/promises';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
import { createActivator } from 'di-kit';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
@@ -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
|
/**
|
|
@@ -448,11 +449,19 @@ const getArgs = singleshot(() => {
|
|
|
448
449
|
type: "boolean",
|
|
449
450
|
default: false,
|
|
450
451
|
},
|
|
452
|
+
noFlush: {
|
|
453
|
+
type: "boolean",
|
|
454
|
+
default: false,
|
|
455
|
+
},
|
|
451
456
|
cacheInterval: {
|
|
452
457
|
type: "string",
|
|
453
458
|
default: "1m, 15m, 30m, 4h",
|
|
454
459
|
},
|
|
455
460
|
// pinescript entry
|
|
461
|
+
editor: {
|
|
462
|
+
type: "boolean",
|
|
463
|
+
default: false,
|
|
464
|
+
},
|
|
456
465
|
pine: {
|
|
457
466
|
type: "boolean",
|
|
458
467
|
default: false,
|
|
@@ -461,6 +470,10 @@ const getArgs = singleshot(() => {
|
|
|
461
470
|
type: "boolean",
|
|
462
471
|
default: false,
|
|
463
472
|
},
|
|
473
|
+
flush: {
|
|
474
|
+
type: "boolean",
|
|
475
|
+
default: false,
|
|
476
|
+
},
|
|
464
477
|
timeframe: {
|
|
465
478
|
type: "string",
|
|
466
479
|
default: "",
|
|
@@ -2432,14 +2445,27 @@ const cli = {
|
|
|
2432
2445
|
};
|
|
2433
2446
|
init();
|
|
2434
2447
|
|
|
2448
|
+
const NOTIFICATION_CONFIG = {
|
|
2449
|
+
signal: true,
|
|
2450
|
+
risk: true,
|
|
2451
|
+
info: true,
|
|
2452
|
+
breakeven: true,
|
|
2453
|
+
common_error: true,
|
|
2454
|
+
critical_error: true,
|
|
2455
|
+
validation_error: true,
|
|
2456
|
+
partial_loss: false,
|
|
2457
|
+
partial_profit: false,
|
|
2458
|
+
signal_sync: false,
|
|
2459
|
+
strategy_commit: true,
|
|
2460
|
+
};
|
|
2435
2461
|
class SetupUtils {
|
|
2436
2462
|
constructor() {
|
|
2437
2463
|
this.enable = singleshot(() => {
|
|
2438
2464
|
cli.loggerService.debug("SetupUtils enable");
|
|
2465
|
+
Notification.enable(NOTIFICATION_CONFIG);
|
|
2439
2466
|
{
|
|
2440
2467
|
Recent.enable();
|
|
2441
2468
|
Storage.enable();
|
|
2442
|
-
Notification.enable();
|
|
2443
2469
|
}
|
|
2444
2470
|
{
|
|
2445
2471
|
Markdown.enable();
|
|
@@ -2561,14 +2587,14 @@ setConfig({
|
|
|
2561
2587
|
CC_WALKER_MARKDOWN_TOP_N: 10,
|
|
2562
2588
|
});
|
|
2563
2589
|
|
|
2564
|
-
const MODES = ["backtest", "walker", "paper", "live", "pine", "dump", "init", "help", "version"];
|
|
2590
|
+
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "flush", "init", "help", "version"];
|
|
2565
2591
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2566
2592
|
const HELP_TEXT$1 = `
|
|
2567
2593
|
Example:
|
|
2568
2594
|
|
|
2569
2595
|
node ${ENTRY_PATH$1} --help
|
|
2570
2596
|
`.trimStart();
|
|
2571
|
-
const main$
|
|
2597
|
+
const main$d = async () => {
|
|
2572
2598
|
if (!getEntry(import.meta.url)) {
|
|
2573
2599
|
return;
|
|
2574
2600
|
}
|
|
@@ -2576,19 +2602,48 @@ const main$b = async () => {
|
|
|
2576
2602
|
if (MODES.some((mode) => values[mode])) {
|
|
2577
2603
|
return;
|
|
2578
2604
|
}
|
|
2579
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
2605
|
+
process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
|
|
2580
2606
|
process.stdout.write("\n");
|
|
2581
2607
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2582
2608
|
process.stdout.write("\n");
|
|
2583
2609
|
process.stdout.write(HELP_TEXT$1);
|
|
2584
2610
|
process.exit(0);
|
|
2585
2611
|
};
|
|
2586
|
-
main$
|
|
2612
|
+
main$d();
|
|
2587
2613
|
|
|
2588
2614
|
const notifyShutdown = singleshot(async () => {
|
|
2589
2615
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
2590
2616
|
});
|
|
2591
2617
|
|
|
2618
|
+
const FLUSH_DIRS = ["report", "log", "markdown", "agent"];
|
|
2619
|
+
const flush = async (entryPoint) => {
|
|
2620
|
+
const moduleRoot = dirname(resolve(process.cwd(), entryPoint));
|
|
2621
|
+
const dumpDir = join(moduleRoot, "dump");
|
|
2622
|
+
for (const dir of FLUSH_DIRS) {
|
|
2623
|
+
const target = join(dumpDir, dir);
|
|
2624
|
+
await rm(target, { recursive: true, force: true });
|
|
2625
|
+
console.log(`Removed: ${target}`);
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2628
|
+
const main$c = async () => {
|
|
2629
|
+
if (!getEntry(import.meta.url)) {
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
const { values } = getArgs();
|
|
2633
|
+
if (!values.flush) {
|
|
2634
|
+
return;
|
|
2635
|
+
}
|
|
2636
|
+
const entryPoints = getPositionals();
|
|
2637
|
+
if (!entryPoints.length) {
|
|
2638
|
+
throw new Error("Entry point is required");
|
|
2639
|
+
}
|
|
2640
|
+
for (const entryPoint of entryPoints) {
|
|
2641
|
+
await flush(entryPoint);
|
|
2642
|
+
}
|
|
2643
|
+
process.exit(0);
|
|
2644
|
+
};
|
|
2645
|
+
main$c();
|
|
2646
|
+
|
|
2592
2647
|
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
2593
2648
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
2594
2649
|
const [running = null] = await Backtest.list();
|
|
@@ -2609,7 +2664,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
2609
2664
|
const listenGracefulShutdown$5 = singleshot(() => {
|
|
2610
2665
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
2611
2666
|
});
|
|
2612
|
-
const main$
|
|
2667
|
+
const main$b = async () => {
|
|
2613
2668
|
if (!getEntry(import.meta.url)) {
|
|
2614
2669
|
return;
|
|
2615
2670
|
}
|
|
@@ -2617,10 +2672,14 @@ const main$a = async () => {
|
|
|
2617
2672
|
if (!values.backtest) {
|
|
2618
2673
|
return;
|
|
2619
2674
|
}
|
|
2675
|
+
if (!values.noFlush) {
|
|
2676
|
+
const [entryPoint = null] = getPositionals();
|
|
2677
|
+
entryPoint && await flush(entryPoint);
|
|
2678
|
+
}
|
|
2620
2679
|
await cli.backtestMainService.connect();
|
|
2621
2680
|
listenGracefulShutdown$5();
|
|
2622
2681
|
};
|
|
2623
|
-
main$
|
|
2682
|
+
main$b();
|
|
2624
2683
|
|
|
2625
2684
|
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
2626
2685
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -2638,7 +2697,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
2638
2697
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
2639
2698
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2640
2699
|
});
|
|
2641
|
-
const main$
|
|
2700
|
+
const main$a = async () => {
|
|
2642
2701
|
if (!getEntry(import.meta.url)) {
|
|
2643
2702
|
return;
|
|
2644
2703
|
}
|
|
@@ -2646,10 +2705,15 @@ const main$9 = async () => {
|
|
|
2646
2705
|
if (!values.walker) {
|
|
2647
2706
|
return;
|
|
2648
2707
|
}
|
|
2708
|
+
if (!values.noFlush) {
|
|
2709
|
+
for (const entryPoint of getPositionals()) {
|
|
2710
|
+
await flush(entryPoint);
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2649
2713
|
listenGracefulShutdown$4();
|
|
2650
2714
|
await cli.walkerMainService.connect();
|
|
2651
2715
|
};
|
|
2652
|
-
main$
|
|
2716
|
+
main$a();
|
|
2653
2717
|
|
|
2654
2718
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
2655
2719
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -2670,7 +2734,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
2670
2734
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
2671
2735
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
2672
2736
|
});
|
|
2673
|
-
const main$
|
|
2737
|
+
const main$9 = async () => {
|
|
2674
2738
|
if (!getEntry(import.meta.url)) {
|
|
2675
2739
|
return;
|
|
2676
2740
|
}
|
|
@@ -2681,7 +2745,7 @@ const main$8 = async () => {
|
|
|
2681
2745
|
cli.paperMainService.connect();
|
|
2682
2746
|
listenGracefulShutdown$3();
|
|
2683
2747
|
};
|
|
2684
|
-
main$
|
|
2748
|
+
main$9();
|
|
2685
2749
|
|
|
2686
2750
|
const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
2687
2751
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -2702,7 +2766,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
2702
2766
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
2703
2767
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
2704
2768
|
});
|
|
2705
|
-
const main$
|
|
2769
|
+
const main$8 = async () => {
|
|
2706
2770
|
if (!getEntry(import.meta.url)) {
|
|
2707
2771
|
return;
|
|
2708
2772
|
}
|
|
@@ -2713,7 +2777,7 @@ const main$7 = async () => {
|
|
|
2713
2777
|
await cli.liveMainService.connect();
|
|
2714
2778
|
listenGracefulShutdown$2();
|
|
2715
2779
|
};
|
|
2716
|
-
main$
|
|
2780
|
+
main$8();
|
|
2717
2781
|
|
|
2718
2782
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
2719
2783
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -2723,7 +2787,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
|
2723
2787
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
2724
2788
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
2725
2789
|
});
|
|
2726
|
-
const main$
|
|
2790
|
+
const main$7 = async () => {
|
|
2727
2791
|
if (!getEntry(import.meta.url)) {
|
|
2728
2792
|
return;
|
|
2729
2793
|
}
|
|
@@ -2733,7 +2797,7 @@ const main$6 = async () => {
|
|
|
2733
2797
|
}
|
|
2734
2798
|
listenGracefulShutdown$1();
|
|
2735
2799
|
};
|
|
2736
|
-
main$
|
|
2800
|
+
main$7();
|
|
2737
2801
|
|
|
2738
2802
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
2739
2803
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -2743,7 +2807,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
|
|
|
2743
2807
|
const listenGracefulShutdown = singleshot(() => {
|
|
2744
2808
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
2745
2809
|
});
|
|
2746
|
-
const main$
|
|
2810
|
+
const main$6 = async () => {
|
|
2747
2811
|
if (!getEntry(import.meta.url)) {
|
|
2748
2812
|
return;
|
|
2749
2813
|
}
|
|
@@ -2753,7 +2817,7 @@ const main$5 = async () => {
|
|
|
2753
2817
|
}
|
|
2754
2818
|
listenGracefulShutdown();
|
|
2755
2819
|
};
|
|
2756
|
-
main$
|
|
2820
|
+
main$6();
|
|
2757
2821
|
|
|
2758
2822
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
2759
2823
|
const keys = Object.keys(schema);
|
|
@@ -2775,7 +2839,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
2775
2839
|
}
|
|
2776
2840
|
return rows;
|
|
2777
2841
|
};
|
|
2778
|
-
const main$
|
|
2842
|
+
const main$5 = async () => {
|
|
2779
2843
|
if (!getEntry(import.meta.url)) {
|
|
2780
2844
|
return;
|
|
2781
2845
|
}
|
|
@@ -2783,6 +2847,10 @@ const main$4 = async () => {
|
|
|
2783
2847
|
if (!values.pine) {
|
|
2784
2848
|
return;
|
|
2785
2849
|
}
|
|
2850
|
+
if (values.editor) {
|
|
2851
|
+
console.warn("--pine and --editor are mutually exclusive. Use one at a time.");
|
|
2852
|
+
process.exit(1);
|
|
2853
|
+
}
|
|
2786
2854
|
const [entryPoint = null] = getPositionals();
|
|
2787
2855
|
if (!entryPoint) {
|
|
2788
2856
|
return;
|
|
@@ -2851,6 +2919,36 @@ const main$4 = async () => {
|
|
|
2851
2919
|
console.log(await toMarkdown(signalId, plots, signalSchema));
|
|
2852
2920
|
process.exit(0);
|
|
2853
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
|
+
};
|
|
2854
2952
|
main$4();
|
|
2855
2953
|
|
|
2856
2954
|
const main$3 = async () => {
|
|
@@ -3030,7 +3128,9 @@ Modes:
|
|
|
3030
3128
|
--paper <entry> Paper trading (live prices, no real orders)
|
|
3031
3129
|
--live <entry> Live trading with real orders
|
|
3032
3130
|
--pine <entry> Execute a local .pine indicator file
|
|
3131
|
+
--editor Open the Pine Script visual editor in the browser
|
|
3033
3132
|
--dump Fetch and save raw OHLCV candles
|
|
3133
|
+
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
3034
3134
|
--init Scaffold a new project in the current directory
|
|
3035
3135
|
--help Print this help message
|
|
3036
3136
|
|
|
@@ -3042,6 +3142,7 @@ Backtest flags:
|
|
|
3042
3142
|
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
3043
3143
|
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
3044
3144
|
--noCache Skip candle cache warming before the run
|
|
3145
|
+
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
3045
3146
|
--verbose Log every candle fetch to stdout
|
|
3046
3147
|
--ui Start web dashboard at http://localhost:60050
|
|
3047
3148
|
--telegram Send trade notifications to Telegram
|
|
@@ -3051,6 +3152,7 @@ Walker flags (--walker):
|
|
|
3051
3152
|
--symbol <string> Trading pair (default: BTCUSDT)
|
|
3052
3153
|
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
3053
3154
|
--noCache Skip candle cache warming before the run
|
|
3155
|
+
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
3054
3156
|
--verbose Log every candle fetch to stdout
|
|
3055
3157
|
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
3056
3158
|
--json Save results as JSON to ./dump/<output>.json
|
|
@@ -3100,6 +3202,13 @@ Candle dump flags (--dump):
|
|
|
3100
3202
|
|
|
3101
3203
|
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
3102
3204
|
|
|
3205
|
+
Flush flags (--flush):
|
|
3206
|
+
|
|
3207
|
+
One or more positional entry points. For each entry point the following
|
|
3208
|
+
subdirectories are removed from <entry-dir>/dump/:
|
|
3209
|
+
|
|
3210
|
+
report log markdown agent
|
|
3211
|
+
|
|
3103
3212
|
Init flags (--init):
|
|
3104
3213
|
|
|
3105
3214
|
--output <string> Target directory name (default: backtest-kit-project)
|
|
@@ -3113,8 +3222,11 @@ Module hooks (loaded automatically by each mode):
|
|
|
3113
3222
|
modules/paper.module --paper Broker adapter for paper trading
|
|
3114
3223
|
modules/live.module --live Broker adapter for live trading
|
|
3115
3224
|
modules/pine.module --pine Exchange schema for PineScript runs
|
|
3225
|
+
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
3116
3226
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
3117
3227
|
|
|
3228
|
+
--flush has no associated module. It only removes dump subdirectories.
|
|
3229
|
+
|
|
3118
3230
|
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
3119
3231
|
|
|
3120
3232
|
Environment variables:
|
|
@@ -3127,13 +3239,16 @@ Environment variables:
|
|
|
3127
3239
|
Examples:
|
|
3128
3240
|
|
|
3129
3241
|
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
3130
|
-
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --ui ./content/feb_2026.strategy.ts
|
|
3242
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
3131
3243
|
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
3132
|
-
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
3244
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
3133
3245
|
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
3134
3246
|
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
3135
3247
|
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
3248
|
+
node ${ENTRY_PATH} --editor
|
|
3136
3249
|
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
3250
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
3251
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
3137
3252
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
3138
3253
|
`.trimStart();
|
|
3139
3254
|
const main$1 = async () => {
|
|
@@ -3144,7 +3259,7 @@ const main$1 = async () => {
|
|
|
3144
3259
|
if (!values.help) {
|
|
3145
3260
|
return;
|
|
3146
3261
|
}
|
|
3147
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
3262
|
+
process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n\n`);
|
|
3148
3263
|
process.stdout.write(HELP_TEXT);
|
|
3149
3264
|
process.exit(0);
|
|
3150
3265
|
};
|
|
@@ -3158,7 +3273,7 @@ const main = async () => {
|
|
|
3158
3273
|
if (!values.version) {
|
|
3159
3274
|
return;
|
|
3160
3275
|
}
|
|
3161
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
3276
|
+
process.stdout.write(`@backtest-kit/cli ${"6.16.0"}\n`);
|
|
3162
3277
|
process.exit(0);
|
|
3163
3278
|
};
|
|
3164
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",
|