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